Repository: metamodel Updated Branches: refs/heads/master 67ef25f38 -> 272461944
METAMODEL-195: Fixed Fixes #70 Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/27246194 Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/27246194 Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/27246194 Branch: refs/heads/master Commit: 272461944aba90ea4b6fd5538dbd6c93a9c28bb7 Parents: 67ef25f Author: Kasper Sørensen <i.am.kasper.soren...@gmail.com> Authored: Wed Dec 9 11:44:41 2015 +0100 Committer: Kasper Sørensen <i.am.kasper.soren...@gmail.com> Committed: Wed Dec 9 11:44:41 2015 +0100 ---------------------------------------------------------------------- CHANGES.md | 1 + .../cassandra/CassandraDataContextTest.java | 3 +- .../metamodel/data/ScalarFunctionRow.java | 2 +- .../metamodel/query/AbstractQueryClause.java | 5 + .../org/apache/metamodel/query/FromClause.java | 8 +- .../apache/metamodel/query/FunctionType.java | 1 + .../metamodel/query/FunctionTypeFactory.java | 2 + .../metamodel/query/MapValueFunction.java | 57 ++++++++ .../java/org/apache/metamodel/query/Query.java | 24 +++- .../org/apache/metamodel/query/QueryClause.java | 2 + .../apache/metamodel/query/ScalarFunction.java | 4 +- .../org/apache/metamodel/query/SelectItem.java | 137 ++++++++++++++++--- .../metamodel/query/ToBooleanFunction.java | 2 +- .../apache/metamodel/query/ToDateFunction.java | 2 +- .../metamodel/query/ToNumberFunction.java | 2 +- .../metamodel/query/ToStringFunction.java | 2 +- .../builder/FunctionSelectBuilderImpl.java | 56 ++++---- .../query/builder/GroupedQueryBuilderImpl.java | 8 +- .../query/builder/SatisfiedFromBuilder.java | 10 +- .../builder/SatisfiedFromBuilderCallback.java | 46 +++++-- .../query/parser/SelectItemParser.java | 38 +++-- .../QueryPostprocessDataContextTest.java | 2 +- .../ConvertedDataSetInterceptorTest.java | 111 +++++++-------- .../metamodel/query/MapValueFunctionTest.java | 57 ++++++++ .../metamodel/query/parser/QueryParserTest.java | 42 ++++-- .../ElasticSearchDataContextTest.java | 14 +- .../rest/JestElasticSearchDataContextTest.java | 3 +- .../org/apache/metamodel/jdbc/DerbyTest.java | 2 +- .../metamodel/json/JsonDataContextTest.java | 44 +++++- 29 files changed, 520 insertions(+), 167 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/CHANGES.md ---------------------------------------------------------------------- diff --git a/CHANGES.md b/CHANGES.md index d21df5a..2baa60f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ * [METAMODEL-207] - Ensured the serializability of the SingleLineCsvRow class. * [METAMODEL-211] - Fixed a bug related to lookup by primary key (_id) on MongoDB. * [METAMODEL-216] - Added new aggregate functions: FIRST, LAST and RANDOM. + * [METAMODEL-195] - Added new function MAP_VALUE which allows extracting a nested value from within a key/value map field. * [METAMODEL-15] - Query parser support for table names with space. Delimitters can be double quote or square brackets. * [METAMODEL-215] - Improved the capability of NumberComparator to support Integer, Long, Double, BigInteger and other built-in Number classes. http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/cassandra/src/test/java/org/apache/metamodel/cassandra/CassandraDataContextTest.java ---------------------------------------------------------------------- diff --git a/cassandra/src/test/java/org/apache/metamodel/cassandra/CassandraDataContextTest.java b/cassandra/src/test/java/org/apache/metamodel/cassandra/CassandraDataContextTest.java index a52d171..4921639 100644 --- a/cassandra/src/test/java/org/apache/metamodel/cassandra/CassandraDataContextTest.java +++ b/cassandra/src/test/java/org/apache/metamodel/cassandra/CassandraDataContextTest.java @@ -33,6 +33,7 @@ import org.apache.metamodel.data.DataSet; import org.apache.metamodel.data.DataSetTableModel; import org.apache.metamodel.data.FilteredDataSet; import org.apache.metamodel.query.Query; +import org.apache.metamodel.query.parser.QueryParserException; import org.apache.metamodel.schema.ColumnType; import org.apache.metamodel.schema.Table; import org.cassandraunit.utils.EmbeddedCassandraServerHelper; @@ -178,7 +179,7 @@ public class CassandraDataContextTest { boolean thrown = false; try { dc.query().from(testTableName).select("nonExistingField").execute(); - } catch (IllegalArgumentException IAex) { + } catch (QueryParserException ex) { thrown = true; } assertTrue(thrown); http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/data/ScalarFunctionRow.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/data/ScalarFunctionRow.java b/core/src/main/java/org/apache/metamodel/data/ScalarFunctionRow.java index 7ba8c7c..1fe86cb 100644 --- a/core/src/main/java/org/apache/metamodel/data/ScalarFunctionRow.java +++ b/core/src/main/java/org/apache/metamodel/data/ScalarFunctionRow.java @@ -50,7 +50,7 @@ final class ScalarFunctionRow extends AbstractRow { } final SelectItem selectItem = scalarFunctionSelectItems.get(index); final SelectItem selectItemWithoutFunction = selectItem.replaceFunction(null); - return selectItem.getScalarFunction().evaluate(_row, selectItemWithoutFunction); + return selectItem.getScalarFunction().evaluate(_row, selectItem.getFunctionParameters(), selectItemWithoutFunction); } @Override http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/AbstractQueryClause.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/AbstractQueryClause.java b/core/src/main/java/org/apache/metamodel/query/AbstractQueryClause.java index 13d8ec5..572d69f 100644 --- a/core/src/main/java/org/apache/metamodel/query/AbstractQueryClause.java +++ b/core/src/main/java/org/apache/metamodel/query/AbstractQueryClause.java @@ -95,6 +95,11 @@ public abstract class AbstractQueryClause<E extends QueryItem> extends BaseObjec public int getItemCount() { return _items.size(); } + + @Override + public int indexOf(E item) { + return _items.indexOf(item); + } @Override public boolean isEmpty() { http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/FromClause.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/FromClause.java b/core/src/main/java/org/apache/metamodel/query/FromClause.java index 808886d..e86b89d 100644 --- a/core/src/main/java/org/apache/metamodel/query/FromClause.java +++ b/core/src/main/java/org/apache/metamodel/query/FromClause.java @@ -75,7 +75,13 @@ public class FromClause extends AbstractQueryClause<FromItem> { return null; } - private FromItem getItemByReference(FromItem item, String reference) { + private FromItem getItemByReference(final FromItem item, final String reference) { + if (reference.equals(item.toStringNoAlias(false))) { + return item; + } + if (reference.equals(item.toStringNoAlias(true))) { + return item; + } final String alias = item.getAlias(); if (reference.equals(alias)) { return item; http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/FunctionType.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/FunctionType.java b/core/src/main/java/org/apache/metamodel/query/FunctionType.java index bb4a11b..8d06dcc 100644 --- a/core/src/main/java/org/apache/metamodel/query/FunctionType.java +++ b/core/src/main/java/org/apache/metamodel/query/FunctionType.java @@ -39,6 +39,7 @@ public interface FunctionType { public static final ScalarFunction TO_NUMBER = new ToNumberFunction(); public static final ScalarFunction TO_DATE = new ToDateFunction(); public static final ScalarFunction TO_BOOLEAN = new ToBooleanFunction(); + public static final ScalarFunction MAP_VALUE = new MapValueFunction(); public ColumnType getExpectedColumnType(ColumnType type); http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/FunctionTypeFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/FunctionTypeFactory.java b/core/src/main/java/org/apache/metamodel/query/FunctionTypeFactory.java index be98f07..95e25ce 100644 --- a/core/src/main/java/org/apache/metamodel/query/FunctionTypeFactory.java +++ b/core/src/main/java/org/apache/metamodel/query/FunctionTypeFactory.java @@ -67,6 +67,8 @@ public class FunctionTypeFactory { case "TO_DATE": case "DATE": return FunctionType.TO_DATE; + case "MAP_VALUE": + return FunctionType.MAP_VALUE; default: return null; } http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/MapValueFunction.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/MapValueFunction.java b/core/src/main/java/org/apache/metamodel/query/MapValueFunction.java new file mode 100644 index 0000000..5cb743a --- /dev/null +++ b/core/src/main/java/org/apache/metamodel/query/MapValueFunction.java @@ -0,0 +1,57 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.query; + +import java.util.Map; + +import org.apache.metamodel.data.Row; +import org.apache.metamodel.schema.ColumnType; +import org.apache.metamodel.util.CollectionUtils; + +/** + * Represents a function that retrieves a value from within a column of type + * {@link ColumnType#MAP} or similar. + */ +public final class MapValueFunction extends DefaultScalarFunction { + + @Override + public Object evaluate(Row row, Object[] parameters, SelectItem operandItem) { + if (parameters.length == 0) { + throw new IllegalArgumentException("Expecting path parameter to MAP_VALUE function"); + } + Object value = row.getValue(operandItem); + if (value instanceof Map) { + Map<?, ?> map = (Map<?, ?>) value; + return CollectionUtils.find(map, (String) parameters[0]); + } + return null; + } + + @Override + public ColumnType getExpectedColumnType(ColumnType type) { + // the column type cannot be inferred so null is returned + return null; + } + + @Override + public String getFunctionName() { + return "MAP_VALUE"; + } + +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/Query.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/Query.java b/core/src/main/java/org/apache/metamodel/query/Query.java index 209e71f..f8194a7 100644 --- a/core/src/main/java/org/apache/metamodel/query/Query.java +++ b/core/src/main/java/org/apache/metamodel/query/Query.java @@ -122,13 +122,29 @@ public final class Query extends BaseObject implements Cloneable, Serializable { * @return */ public Query select(String expression) { - QueryPartParser clauseParser = new QueryPartParser(new SelectItemParser(this, true), expression, ","); + return select(expression, false); + } + + /** + * Adds a selection to this query. + * + * @param expression + * a textual representation of the select item, e.g. "MAX(foo)" + * or just "foo", where "foo" is a column name. + * @param allowExpressionBasedSelectItem + * whether or not expression-based select items are allowed or + * not (see {@link SelectItem#getExpression()}. + * @return + */ + public Query select(String expression, boolean allowExpressionBasedSelectItem) { + final QueryPartParser clauseParser = new QueryPartParser(new SelectItemParser(this, + allowExpressionBasedSelectItem), expression, ","); clauseParser.parse(); return this; } private SelectItem findSelectItem(String expression, boolean allowExpressionBasedSelectItem) { - SelectItemParser parser = new SelectItemParser(this, allowExpressionBasedSelectItem); + final SelectItemParser parser = new SelectItemParser(this, allowExpressionBasedSelectItem); return parser.findSelectItem(expression); } @@ -579,7 +595,7 @@ public final class Query extends BaseObject implements Cloneable, Serializable { @Override public Query clone() { - Query q = new Query(); + final Query q = new Query(); q.setMaxRows(_maxRows); q.setFirstRow(_firstRow); q.getSelectClause().setDistinct(_selectClause.isDistinct()); @@ -587,7 +603,7 @@ public final class Query extends BaseObject implements Cloneable, Serializable { q.from(item.clone()); } for (SelectItem item : _selectClause.getItems()) { - q.select(item.clone()); + q.select(item.clone(q)); } for (FilterItem item : _whereClause.getItems()) { q.where(item.clone()); http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/QueryClause.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/QueryClause.java b/core/src/main/java/org/apache/metamodel/query/QueryClause.java index 33b09f3..19a9472 100644 --- a/core/src/main/java/org/apache/metamodel/query/QueryClause.java +++ b/core/src/main/java/org/apache/metamodel/query/QueryClause.java @@ -50,4 +50,6 @@ public interface QueryClause<E> extends Serializable { public String toSql(boolean includeSchemaInColumnPaths); public String toSql(); + + public int indexOf(E item); } http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/ScalarFunction.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/ScalarFunction.java b/core/src/main/java/org/apache/metamodel/query/ScalarFunction.java index 8262c2b..dcdfd7b 100644 --- a/core/src/main/java/org/apache/metamodel/query/ScalarFunction.java +++ b/core/src/main/java/org/apache/metamodel/query/ScalarFunction.java @@ -33,11 +33,13 @@ public interface ScalarFunction extends FunctionType { * * @param row * the row containing data + * @param parameters + * any parameters associated with the function call * @param operandItem * the select item which is the argument to this function. If a * function takes multiple select items, this will be the primary * one. * @return */ - public Object evaluate(Row row, SelectItem operandItem); + public Object evaluate(Row row, Object[] parameters, SelectItem operandItem); } http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/SelectItem.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/SelectItem.java b/core/src/main/java/org/apache/metamodel/query/SelectItem.java index cbbe919..bc872d1 100644 --- a/core/src/main/java/org/apache/metamodel/query/SelectItem.java +++ b/core/src/main/java/org/apache/metamodel/query/SelectItem.java @@ -20,6 +20,7 @@ package org.apache.metamodel.query; import java.util.List; +import org.apache.metamodel.DataContext; import org.apache.metamodel.schema.Column; import org.apache.metamodel.schema.ColumnType; import org.apache.metamodel.schema.Schema; @@ -39,8 +40,7 @@ import org.slf4j.LoggerFactory; * <li>expression function SELECTs (retrieves databased on a function and an * expression, only COUNT(*) is supported for non-JDBC datastores))</li> * <li>SELECTs from subqueries (works just like column selects, but in stead of - * pointing to a column, it retrieves data from the select item of a subquery) - * </li> + * pointing to a column, it retrieves data from the select item of a subquery)</li> * </ul> * * @see SelectClause @@ -55,6 +55,7 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable { // immutable fields (essense) private final Column _column; private final FunctionType _function; + private final Object[] _functionParameters; private final String _expression; private final SelectItem _subQuerySelectItem; private final FromItem _fromItem; @@ -70,17 +71,19 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable { * @param column * @param fromItem * @param function + * @param functionParameters * @param expression * @param subQuerySelectItem * @param alias * @param functionApproximationAllowed */ - private SelectItem(Column column, FromItem fromItem, FunctionType function, String expression, - SelectItem subQuerySelectItem, String alias, boolean functionApproximationAllowed) { + private SelectItem(Column column, FromItem fromItem, FunctionType function, Object[] functionParameters, + String expression, SelectItem subQuerySelectItem, String alias, boolean functionApproximationAllowed) { super(); _column = column; _fromItem = fromItem; _function = function; + _functionParameters = functionParameters; _expression = expression; _subQuerySelectItem = subQuerySelectItem; _alias = alias; @@ -123,6 +126,17 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable { } /** + * Create a SelectItem that uses a function with parameters on a column. + * + * @param function + * @param functionParameters + * @param column + */ + public SelectItem(FunctionType function, Object[] functionParameters, Column column) { + this(function, functionParameters, column, null); + } + + /** * Creates a SelectItem that references a column from a particular * {@link FromItem}, for example a.price or p.age * @@ -152,7 +166,24 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable { * @param fromItem */ public SelectItem(FunctionType function, Column column, FromItem fromItem) { - this(column, fromItem, function, null, null, null, false); + this(column, fromItem, function, null, null, null, null, false); + if (column == null) { + throw new IllegalArgumentException("column=null"); + } + } + + /** + * Creates a SelectItem that uses a function with parameters on a column + * from a particular {@link FromItem}, for example + * MAP_VALUE('path.to.value', doc) + * + * @param function + * @param functionParameters + * @param column + * @param fromItem + */ + public SelectItem(FunctionType function, Object[] functionParameters, Column column, FromItem fromItem) { + this(column, fromItem, function, functionParameters, null, null, null, false); if (column == null) { throw new IllegalArgumentException("column=null"); } @@ -178,7 +209,7 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable { * @param alias */ public SelectItem(FunctionType function, String expression, String alias) { - this(null, null, function, expression, null, alias, false); + this(null, null, function, null, expression, null, alias, false); if (expression == null) { throw new IllegalArgumentException("expression=null"); } @@ -192,7 +223,7 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable { * the FromItem that holds the sub-query */ public SelectItem(SelectItem subQuerySelectItem, FromItem subQueryFromItem) { - this(null, subQueryFromItem, null, null, subQuerySelectItem, null, false); + this(null, subQueryFromItem, null, null, null, subQuerySelectItem, null, false); if (subQueryFromItem.getSubQuery() == null) { throw new IllegalArgumentException("Only sub-query based FromItems allowed."); } @@ -243,6 +274,15 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable { } /** + * Gets any parameters to the {@link #getFunction()} used. + * + * @return + */ + public Object[] getFunctionParameters() { + return _functionParameters; + } + + /** * @return if this is a function based SelectItem where function calculation * is allowed to be approximated (if the datastore type has an * approximate calculation method). Approximated function results @@ -285,6 +325,13 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable { return null; } + /** + * Returns an "expression" that this select item represents. Expressions are + * not necesarily portable across {@link DataContext} implementations, but + * may be useful for utilizing database-specific behaviour in certain cases. + * + * @return + */ public String getExpression() { return _expression; } @@ -399,7 +446,7 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable { @Override public String toSql(boolean includeSchemaInColumnPath) { - StringBuilder sb = toStringNoAlias(includeSchemaInColumnPath); + final StringBuilder sb = toStringNoAlias(includeSchemaInColumnPath); if (_alias != null) { sb.append(" AS "); sb.append(_alias); @@ -412,7 +459,7 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable { } public StringBuilder toStringNoAlias(boolean includeSchemaInColumnPath) { - StringBuilder sb = new StringBuilder(); + final StringBuilder sb = new StringBuilder(); if (_column != null) { sb.append(getToStringColumnPrefix(includeSchemaInColumnPath)); sb.append(_column.getQuotedName()); @@ -427,18 +474,30 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable { sb.append(_subQuerySelectItem.getSuperQueryAlias()); } if (_function != null) { + final StringBuilder functionBeginning = new StringBuilder(); if (_functionApproximationAllowed) { - sb.insert(0, FUNCTION_APPROXIMATION_PREFIX + _function.getFunctionName() + "("); - } else { - sb.insert(0, _function.getFunctionName() + "("); + functionBeginning.append(FUNCTION_APPROXIMATION_PREFIX); } + + functionBeginning.append(_function.getFunctionName()); + functionBeginning.append('('); + final Object[] functionParameters = getFunctionParameters(); + if (functionParameters != null && functionParameters.length != 0) { + for (int i = 0; i < functionParameters.length; i++) { + functionBeginning.append('\''); + functionBeginning.append(functionParameters[i]); + functionBeginning.append('\''); + functionBeginning.append(','); + } + } + sb.insert(0, functionBeginning.toString()); sb.append(")"); } return sb; } private String getToStringColumnPrefix(boolean includeSchemaInColumnPath) { - StringBuilder sb = new StringBuilder(); + final StringBuilder sb = new StringBuilder(); if (_fromItem != null && _fromItem.getAlias() != null) { sb.append(_fromItem.getAlias()); sb.append('.'); @@ -506,16 +565,50 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable { identifiers.add(_column); identifiers.add(_function); identifiers.add(_functionApproximationAllowed); - identifiers.add(_fromItem); + if (_fromItem == null && _column != null && _column.getTable() != null) { + // add a FromItem representing the column's table - this makes equal + // comparison work when the only difference is whether or not + // FromItem is specified + identifiers.add(new FromItem(_column.getTable())); + } else { + identifiers.add(_fromItem); + } identifiers.add(_subQuerySelectItem); } @Override protected SelectItem clone() { + return clone(null); + } + + /** + * Creates a clone of the {@link SelectItem} for use within a cloned + * {@link Query}. + * + * @param clonedQuery + * a new {@link Query} object that represents the clone-to-be of + * a query. It is expected that {@link FromItem}s have already + * been cloned in this {@link Query}. + * @return + */ + protected SelectItem clone(Query clonedQuery) { final SelectItem subQuerySelectItem = (_subQuerySelectItem == null ? null : _subQuerySelectItem.clone()); - final FromItem fromItem = (_fromItem == null ? null : _fromItem.clone()); - final SelectItem s = new SelectItem(_column, fromItem, _function, _expression, subQuerySelectItem, _alias, - _functionApproximationAllowed); + final FromItem fromItem; + if (_fromItem == null) { + fromItem = null; + } else if (clonedQuery != null && _query != null) { + final int indexOfFromItem = _query.getFromClause().indexOf(_fromItem); + if (indexOfFromItem != -1) { + fromItem = clonedQuery.getFromClause().getItem(indexOfFromItem); + } else { + fromItem = _fromItem.clone(); + } + } else { + fromItem = _fromItem.clone(); + } + + final SelectItem s = new SelectItem(_column, fromItem, _function, _functionParameters, _expression, + subQuerySelectItem, _alias, _functionApproximationAllowed); return s; } @@ -527,8 +620,8 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable { * @return */ public SelectItem replaceFunction(FunctionType function) { - return new SelectItem(_column, _fromItem, function, _expression, _subQuerySelectItem, _alias, - _functionApproximationAllowed); + return new SelectItem(_column, _fromItem, function, _functionParameters, _expression, _subQuerySelectItem, + _alias, _functionApproximationAllowed); } /** @@ -539,8 +632,8 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable { * @return */ public SelectItem replaceFunctionApproximationAllowed(boolean functionApproximationAllowed) { - return new SelectItem(_column, _fromItem, _function, _expression, _subQuerySelectItem, _alias, - functionApproximationAllowed); + return new SelectItem(_column, _fromItem, _function, _functionParameters, _expression, _subQuerySelectItem, + _alias, functionApproximationAllowed); } /** @@ -568,4 +661,4 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable { public String toString() { return toSql(); } -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/ToBooleanFunction.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/ToBooleanFunction.java b/core/src/main/java/org/apache/metamodel/query/ToBooleanFunction.java index 829f251..c8684cf 100644 --- a/core/src/main/java/org/apache/metamodel/query/ToBooleanFunction.java +++ b/core/src/main/java/org/apache/metamodel/query/ToBooleanFunction.java @@ -38,7 +38,7 @@ public class ToBooleanFunction extends DefaultScalarFunction { } @Override - public Object evaluate(Row row, SelectItem item) { + public Object evaluate(Row row, Object[] parameters, SelectItem item) { final Object value = row.getValue(item); if (value == null || value instanceof Boolean) { return value; http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/ToDateFunction.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/ToDateFunction.java b/core/src/main/java/org/apache/metamodel/query/ToDateFunction.java index 5d66787..e6f932b 100644 --- a/core/src/main/java/org/apache/metamodel/query/ToDateFunction.java +++ b/core/src/main/java/org/apache/metamodel/query/ToDateFunction.java @@ -40,7 +40,7 @@ public class ToDateFunction extends DefaultScalarFunction { } @Override - public Object evaluate(Row row, SelectItem item) { + public Object evaluate(Row row, Object[] parameters, SelectItem item) { final Object value = row.getValue(item); if (value == null || value instanceof Date) { return value; http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/ToNumberFunction.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/ToNumberFunction.java b/core/src/main/java/org/apache/metamodel/query/ToNumberFunction.java index 0d30e13..a97a84a 100644 --- a/core/src/main/java/org/apache/metamodel/query/ToNumberFunction.java +++ b/core/src/main/java/org/apache/metamodel/query/ToNumberFunction.java @@ -38,7 +38,7 @@ public class ToNumberFunction extends DefaultScalarFunction { } @Override - public Object evaluate(Row row, SelectItem item) { + public Object evaluate(Row row, Object[] parameters, SelectItem item) { final Object value = row.getValue(item); if (value == null || value instanceof Number) { return value; http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/ToStringFunction.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/ToStringFunction.java b/core/src/main/java/org/apache/metamodel/query/ToStringFunction.java index 6eaec39..ce85f0e 100644 --- a/core/src/main/java/org/apache/metamodel/query/ToStringFunction.java +++ b/core/src/main/java/org/apache/metamodel/query/ToStringFunction.java @@ -37,7 +37,7 @@ public class ToStringFunction extends DefaultScalarFunction { } @Override - public Object evaluate(Row row, SelectItem item) { + public Object evaluate(Row row, Object[] parameters, SelectItem item) { final Object value = row.getValue(item); if (value == null || value instanceof String) { return value; http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/builder/FunctionSelectBuilderImpl.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/builder/FunctionSelectBuilderImpl.java b/core/src/main/java/org/apache/metamodel/query/builder/FunctionSelectBuilderImpl.java index 6e1ced1..0b55f1c 100644 --- a/core/src/main/java/org/apache/metamodel/query/builder/FunctionSelectBuilderImpl.java +++ b/core/src/main/java/org/apache/metamodel/query/builder/FunctionSelectBuilderImpl.java @@ -25,32 +25,32 @@ import org.apache.metamodel.query.Query; import org.apache.metamodel.query.SelectItem; import org.apache.metamodel.schema.Column; -final class FunctionSelectBuilderImpl extends SatisfiedSelectBuilderImpl - implements FunctionSelectBuilder<GroupedQueryBuilder> { - - private SelectItem selectItem; - - public FunctionSelectBuilderImpl(FunctionType functionType, Column column, - Query query, GroupedQueryBuilder queryBuilder) { - super(queryBuilder); - - this.selectItem = new SelectItem(functionType, column); - - query.select(selectItem); - } - - @Override - public GroupedQueryBuilder as(String alias) { - if (alias == null) { - throw new IllegalArgumentException("alias cannot be null"); - } - selectItem.setAlias(alias); - return getQueryBuilder(); - } - - @Override - protected void decorateIdentity(List<Object> identifiers) { - super.decorateIdentity(identifiers); - identifiers.add(selectItem); - } +final class FunctionSelectBuilderImpl extends SatisfiedSelectBuilderImpl implements + FunctionSelectBuilder<GroupedQueryBuilder> { + + private SelectItem selectItem; + + public FunctionSelectBuilderImpl(FunctionType functionType, Column column, Object[] functionParameters, + Query query, GroupedQueryBuilder queryBuilder) { + super(queryBuilder); + + this.selectItem = new SelectItem(functionType, functionParameters, column); + + query.select(selectItem); + } + + @Override + public GroupedQueryBuilder as(String alias) { + if (alias == null) { + throw new IllegalArgumentException("alias cannot be null"); + } + selectItem.setAlias(alias); + return getQueryBuilder(); + } + + @Override + protected void decorateIdentity(List<Object> identifiers) { + super.decorateIdentity(identifiers); + identifiers.add(selectItem); + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/builder/GroupedQueryBuilderImpl.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/builder/GroupedQueryBuilderImpl.java b/core/src/main/java/org/apache/metamodel/query/builder/GroupedQueryBuilderImpl.java index 4c05bfe..0ea0098 100644 --- a/core/src/main/java/org/apache/metamodel/query/builder/GroupedQueryBuilderImpl.java +++ b/core/src/main/java/org/apache/metamodel/query/builder/GroupedQueryBuilderImpl.java @@ -68,7 +68,7 @@ final class GroupedQueryBuilderImpl extends BaseObject implements GroupedQueryBu throw new IllegalArgumentException("function cannot be null"); } final Column column = findColumn(columnName); - return new FunctionSelectBuilderImpl(function, column, _query, this); + return new FunctionSelectBuilderImpl(function, column, null, _query, this); } @Override @@ -79,7 +79,7 @@ final class GroupedQueryBuilderImpl extends BaseObject implements GroupedQueryBu if (column == null) { throw new IllegalArgumentException("column cannot be null"); } - return new FunctionSelectBuilderImpl(function, column, _query, this); + return new FunctionSelectBuilderImpl(function, column, null, _query, this); } @Override @@ -127,13 +127,13 @@ final class GroupedQueryBuilderImpl extends BaseObject implements GroupedQueryBu final Column column = findColumn(columnName); return where(column); } - + @Override public WhereBuilder<GroupedQueryBuilder> where(ScalarFunction function, Column column) { final SelectItem selectItem = new SelectItem(function, column); return new WhereBuilderImpl(selectItem, _query, this); } - + @Override public WhereBuilder<GroupedQueryBuilder> where(ScalarFunction function, String columnName) { final Column column = findColumn(columnName); http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/builder/SatisfiedFromBuilder.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/builder/SatisfiedFromBuilder.java b/core/src/main/java/org/apache/metamodel/query/builder/SatisfiedFromBuilder.java index c01bef3..f1a706c 100644 --- a/core/src/main/java/org/apache/metamodel/query/builder/SatisfiedFromBuilder.java +++ b/core/src/main/java/org/apache/metamodel/query/builder/SatisfiedFromBuilder.java @@ -36,9 +36,13 @@ public interface SatisfiedFromBuilder { public ColumnSelectBuilder<?> select(Column column); - public SatisfiedQueryBuilder<?> select(FunctionType function, String columnName); + public FunctionSelectBuilder<?> select(FunctionType function, String columnName); public FunctionSelectBuilder<?> select(FunctionType function, Column column); + + public FunctionSelectBuilder<?> select(FunctionType function, String columnName, Object[] functionParameters); + + public FunctionSelectBuilder<?> select(FunctionType function, Column column, Object[] functionParameters); public CountSelectBuilder<?> selectCount(); @@ -46,7 +50,9 @@ public interface SatisfiedFromBuilder { public SatisfiedSelectBuilder<?> selectAll(); - public ColumnSelectBuilder<?> select(String columnName); + public SatisfiedSelectBuilder<?> select(String selectExpression); + + public SatisfiedSelectBuilder<?> select(String selectExpression, boolean allowExpressionBasedSelectItem); public SatisfiedSelectBuilder<?> select(String... columnNames); } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/builder/SatisfiedFromBuilderCallback.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/builder/SatisfiedFromBuilderCallback.java b/core/src/main/java/org/apache/metamodel/query/builder/SatisfiedFromBuilderCallback.java index e142a79..74831e9 100644 --- a/core/src/main/java/org/apache/metamodel/query/builder/SatisfiedFromBuilderCallback.java +++ b/core/src/main/java/org/apache/metamodel/query/builder/SatisfiedFromBuilderCallback.java @@ -71,25 +71,37 @@ abstract class SatisfiedFromBuilderCallback extends BaseObject implements Satisf GroupedQueryBuilder queryBuilder = new GroupedQueryBuilderImpl(dataContext, query); return new SatisfiedSelectBuilderImpl(queryBuilder); } - + + @Override + public FunctionSelectBuilder<?> select(FunctionType function, String columnName) { + GroupedQueryBuilderImpl queryBuilder = new GroupedQueryBuilderImpl(dataContext, query); + Column column = queryBuilder.findColumn(columnName); + return select(function, column); + } + @Override - public SatisfiedQueryBuilder<?> select(FunctionType functionType, String columnName) { + public FunctionSelectBuilder<?> select(FunctionType function, String columnName, Object[] functionParameters) { GroupedQueryBuilderImpl queryBuilder = new GroupedQueryBuilderImpl(dataContext, query); Column column = queryBuilder.findColumn(columnName); - return select(functionType, column); + return select(function, column, functionParameters); + } + + @Override + public FunctionSelectBuilder<?> select(FunctionType function, Column column) { + return select(function, column, new Object[0]); } @Override - public FunctionSelectBuilder<?> select(FunctionType functionType, Column column) { - if (functionType == null) { + public FunctionSelectBuilder<?> select(FunctionType function, Column column, Object[] functionParameters) { + if (function == null) { throw new IllegalArgumentException("functionType cannot be null"); } if (column == null) { throw new IllegalArgumentException("column cannot be null"); } - GroupedQueryBuilder queryBuilder = new GroupedQueryBuilderImpl(dataContext, query); - return new FunctionSelectBuilderImpl(functionType, column, query, queryBuilder); + final GroupedQueryBuilder queryBuilder = new GroupedQueryBuilderImpl(dataContext, query); + return new FunctionSelectBuilderImpl(function, column, functionParameters, query, queryBuilder); } @Override @@ -148,16 +160,22 @@ abstract class SatisfiedFromBuilderCallback extends BaseObject implements Satisf GroupedQueryBuilder queryBuilder = new GroupedQueryBuilderImpl(dataContext, query); return new SatisfiedSelectBuilderImpl(queryBuilder); } - + @Override - public ColumnSelectBuilder<?> select(String columnName) { - if (columnName == null) { - throw new IllegalArgumentException("columnName cannot be null"); + public SatisfiedSelectBuilder<?> select(String selectExpression, boolean allowExpressionBasedSelectItem) { + if (selectExpression == null) { + throw new IllegalArgumentException("selectExpression cannot be null"); } - GroupedQueryBuilderImpl queryBuilder = new GroupedQueryBuilderImpl(dataContext, query); - Column column = queryBuilder.findColumn(columnName); - return select(column); + query.select(selectExpression, allowExpressionBasedSelectItem); + + final GroupedQueryBuilder queryBuilder = new GroupedQueryBuilderImpl(dataContext, query); + return new SatisfiedSelectBuilderImpl(queryBuilder); + } + + @Override + public SatisfiedSelectBuilder<?> select(String selectExpression) { + return select(selectExpression, false); } @Override http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/main/java/org/apache/metamodel/query/parser/SelectItemParser.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/query/parser/SelectItemParser.java b/core/src/main/java/org/apache/metamodel/query/parser/SelectItemParser.java index 9cea18e..d358d72 100644 --- a/core/src/main/java/org/apache/metamodel/query/parser/SelectItemParser.java +++ b/core/src/main/java/org/apache/metamodel/query/parser/SelectItemParser.java @@ -111,7 +111,8 @@ public final class SelectItemParser implements QueryPartProcessor { final int startParenthesis = expression.indexOf('('); if (startParenthesis > 0 && expression.endsWith(")")) { functionApproximation = (expression.startsWith(SelectItem.FUNCTION_APPROXIMATION_PREFIX)); - final String functionName = expression.substring((functionApproximation ? SelectItem.FUNCTION_APPROXIMATION_PREFIX.length() : 0), startParenthesis); + final String functionName = expression.substring( + (functionApproximation ? SelectItem.FUNCTION_APPROXIMATION_PREFIX.length() : 0), startParenthesis); function = FunctionTypeFactory.get(functionName.toUpperCase()); if (function != null) { expression = expression.substring(startParenthesis + 1, expression.length() - 1).trim(); @@ -126,15 +127,20 @@ public final class SelectItemParser implements QueryPartProcessor { functionApproximation = false; } - int lastIndexOfDot = expression.lastIndexOf("."); - String columnName = null; FromItem fromItem = null; - if (lastIndexOfDot != -1) { - String prefix = expression.substring(0, lastIndexOfDot); - columnName = expression.substring(lastIndexOfDot + 1); - fromItem = _query.getFromClause().getItemByReference(prefix); + // attempt to find from item by cutting up the string in prefix and + // suffix around dot. + { + int splitIndex = expression.lastIndexOf('.'); + while (fromItem == null && splitIndex != -1) { + final String prefix = expression.substring(0, splitIndex); + columnName = expression.substring(splitIndex + 1); + fromItem = _query.getFromClause().getItemByReference(prefix); + + splitIndex = expression.lastIndexOf('.', splitIndex - 1); + } } if (fromItem == null) { @@ -151,7 +157,23 @@ public final class SelectItemParser implements QueryPartProcessor { if ("*".equals(columnName)) { throw new MultipleSelectItemsParsedException(fromItem); } else if (fromItem.getTable() != null) { - final Column column = fromItem.getTable().getColumnByName(columnName); + Column column = fromItem.getTable().getColumnByName(columnName); + int offset = -1; + while (function == null && column == null) { + // check for MAP_VALUE shortcut syntax + offset = columnName.indexOf('.', offset + 1); + if (offset == -1) { + break; + } + + final String part1 = columnName.substring(0, offset); + column = fromItem.getTable().getColumnByName(part1); + if (column != null) { + final String part2 = columnName.substring(offset + 1); + return new SelectItem(new MapValueFunction(), new Object[] { part2 }, column, fromItem); + } + } + if (column != null) { final SelectItem selectItem = new SelectItem(function, column, fromItem); selectItem.setFunctionApproximationAllowed(functionApproximation); http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/test/java/org/apache/metamodel/QueryPostprocessDataContextTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/metamodel/QueryPostprocessDataContextTest.java b/core/src/test/java/org/apache/metamodel/QueryPostprocessDataContextTest.java index 7dcaa12..6a8438a 100644 --- a/core/src/test/java/org/apache/metamodel/QueryPostprocessDataContextTest.java +++ b/core/src/test/java/org/apache/metamodel/QueryPostprocessDataContextTest.java @@ -1107,7 +1107,7 @@ public class QueryPostprocessDataContextTest extends MetaModelTestCase { DataSet set = getDataContext().executeQuery(query1); assertEquals(true, set.next()); assertEquals("Row[values=[1, kasper]]", set.getRow().toString()); - Query query2 = new Query().from(table1).select("Greatest(1,2,3),max(contributer_id)"); + Query query2 = new Query().from(table1).select("Greatest(1,2,3),max(contributer_id)", true); assertEquals("SELECT Greatest(1,2,3), MAX(contributer_id) FROM MetaModelSchema.contributor", query2.toString()); Query query3 = new Query().from(table1).select("*,count(*)"); assertEquals("SELECT contributor.contributor_id, contributor.name, contributor.country, COUNT(*)" http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/test/java/org/apache/metamodel/convert/ConvertedDataSetInterceptorTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/metamodel/convert/ConvertedDataSetInterceptorTest.java b/core/src/test/java/org/apache/metamodel/convert/ConvertedDataSetInterceptorTest.java index 04a5e45..15fbb47 100644 --- a/core/src/test/java/org/apache/metamodel/convert/ConvertedDataSetInterceptorTest.java +++ b/core/src/test/java/org/apache/metamodel/convert/ConvertedDataSetInterceptorTest.java @@ -28,65 +28,66 @@ import org.apache.metamodel.MockUpdateableDataContext; import org.apache.metamodel.UpdateableDataContext; import org.apache.metamodel.data.DataSet; import org.apache.metamodel.data.InMemoryDataSet; +import org.apache.metamodel.data.WrappingDataSet; import org.apache.metamodel.query.Query; import org.apache.metamodel.schema.Column; import org.apache.metamodel.schema.Table; public class ConvertedDataSetInterceptorTest extends TestCase { - public void testConvertedQuery() throws Exception { - UpdateableDataContext dc = new MockUpdateableDataContext(); - Column fooColumn = dc.getColumnByQualifiedLabel("schema.table.foo"); - assertNotNull(fooColumn); - - dc = Converters.addTypeConverter(dc, fooColumn, - new StringToIntegerConverter()); - - Table table = dc.getDefaultSchema().getTableByName("table"); - Query query = dc.query().from(table).select(table.getColumns()) - .toQuery(); - assertEquals("SELECT table.foo, table.bar FROM schema.table", - query.toSql()); - - DataSet ds = dc.executeQuery(query); - assertEquals(ConvertedDataSet.class, ds.getClass()); - - assertTrue(ds.next()); - assertEquals("Row[values=[1, hello]]", ds.getRow().toString()); - assertEquals(Integer.class, ds.getRow().getValue(0).getClass()); - assertEquals(String.class, ds.getRow().getValue(1).getClass()); - - assertTrue(ds.next()); - assertEquals("Row[values=[2, there]]", ds.getRow().toString()); - assertEquals(Integer.class, ds.getRow().getValue(0).getClass()); - assertEquals(String.class, ds.getRow().getValue(1).getClass()); - - assertTrue(ds.next()); - assertEquals("Row[values=[3, world]]", ds.getRow().toString()); - assertEquals(Integer.class, ds.getRow().getValue(0).getClass()); - assertEquals(String.class, ds.getRow().getValue(1).getClass()); - - assertFalse(ds.next()); - ds.close(); - } - - public void testNonConvertedQuery() throws Exception { - MockUpdateableDataContext source = new MockUpdateableDataContext(); - Column fooColumn = source.getColumnByQualifiedLabel("schema.table.foo"); - assertNotNull(fooColumn); - - Map<Column, TypeConverter<?, ?>> converters = new HashMap<Column, TypeConverter<?, ?>>(); - converters.put(fooColumn, new StringToIntegerConverter()); - DataContext converted = Converters.addTypeConverter(source, fooColumn, - new StringToIntegerConverter()); - - // only select "bar" which is not converted - Table table = converted.getDefaultSchema().getTableByName("table"); - Query query = converted.query().from(table).select("bar").toQuery(); - assertEquals("SELECT table.bar FROM schema.table", query.toSql()); - - DataSet ds = converted.executeQuery(query); - assertEquals(InMemoryDataSet.class, ds.getClass()); - - } + public void testConvertedQuery() throws Exception { + UpdateableDataContext dc = new MockUpdateableDataContext(); + Column fooColumn = dc.getColumnByQualifiedLabel("schema.table.foo"); + assertNotNull(fooColumn); + + dc = Converters.addTypeConverter(dc, fooColumn, new StringToIntegerConverter()); + + Table table = dc.getDefaultSchema().getTableByName("table"); + Query query = dc.query().from(table).select(table.getColumns()).toQuery(); + assertEquals("SELECT table.foo, table.bar FROM schema.table", query.toSql()); + + DataSet ds = dc.executeQuery(query); + assertEquals(ConvertedDataSet.class, ds.getClass()); + + assertTrue(ds.next()); + assertEquals("Row[values=[1, hello]]", ds.getRow().toString()); + assertEquals(Integer.class, ds.getRow().getValue(0).getClass()); + assertEquals(String.class, ds.getRow().getValue(1).getClass()); + + assertTrue(ds.next()); + assertEquals("Row[values=[2, there]]", ds.getRow().toString()); + assertEquals(Integer.class, ds.getRow().getValue(0).getClass()); + assertEquals(String.class, ds.getRow().getValue(1).getClass()); + + assertTrue(ds.next()); + assertEquals("Row[values=[3, world]]", ds.getRow().toString()); + assertEquals(Integer.class, ds.getRow().getValue(0).getClass()); + assertEquals(String.class, ds.getRow().getValue(1).getClass()); + + assertFalse(ds.next()); + ds.close(); + } + + @SuppressWarnings("resource") + public void testNonConvertedQuery() throws Exception { + MockUpdateableDataContext source = new MockUpdateableDataContext(); + Column fooColumn = source.getColumnByQualifiedLabel("schema.table.foo"); + assertNotNull(fooColumn); + + Map<Column, TypeConverter<?, ?>> converters = new HashMap<Column, TypeConverter<?, ?>>(); + converters.put(fooColumn, new StringToIntegerConverter()); + DataContext converted = Converters.addTypeConverter(source, fooColumn, new StringToIntegerConverter()); + + // only select "bar" which is not converted + Table table = converted.getDefaultSchema().getTableByName("table"); + Query query = converted.query().from(table).select("bar").toQuery(); + assertEquals("SELECT table.bar FROM schema.table", query.toSql()); + + DataSet ds = converted.executeQuery(query); + while (ds instanceof WrappingDataSet) { + ds = ((WrappingDataSet) ds).getWrappedDataSet(); + } + ds.close(); + assertEquals(InMemoryDataSet.class, ds.getClass()); + } } http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/test/java/org/apache/metamodel/query/MapValueFunctionTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/metamodel/query/MapValueFunctionTest.java b/core/src/test/java/org/apache/metamodel/query/MapValueFunctionTest.java new file mode 100644 index 0000000..0d87c0c --- /dev/null +++ b/core/src/test/java/org/apache/metamodel/query/MapValueFunctionTest.java @@ -0,0 +1,57 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.query; + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.metamodel.data.DefaultRow; +import org.apache.metamodel.data.Row; +import org.apache.metamodel.data.SimpleDataSetHeader; +import org.junit.Test; + +public class MapValueFunctionTest { + + private final ScalarFunction function = FunctionType.MAP_VALUE; + + @Test + public void testGetValueFromMap() throws Exception { + final Map<String, Map<String, Object>> value = new HashMap<>(); + final Map<String, Object> innerMap = new HashMap<>(); + innerMap.put("bar", "baz"); + value.put("foo", innerMap); + final SelectItem operandItem = new SelectItem("foo", "f"); + final Row row = new DefaultRow(new SimpleDataSetHeader(new SelectItem[] { operandItem }), + new Object[] { value }); + final Object v1 = function.evaluate(row, new Object[] { "foo.bar" }, operandItem); + assertEquals("baz", v1.toString()); + } + + @Test + public void testNotAMap() throws Exception { + final SelectItem operandItem = new SelectItem("foo", "f"); + final Row row = new DefaultRow(new SimpleDataSetHeader(new SelectItem[] { operandItem }), + new Object[] { "not a map" }); + final Object v1 = function.evaluate(row, new Object[] { "foo.bar" }, operandItem); + assertEquals(null, v1); + } + +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/core/src/test/java/org/apache/metamodel/query/parser/QueryParserTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/metamodel/query/parser/QueryParserTest.java b/core/src/test/java/org/apache/metamodel/query/parser/QueryParserTest.java index 1059f9a..d081c39 100644 --- a/core/src/test/java/org/apache/metamodel/query/parser/QueryParserTest.java +++ b/core/src/test/java/org/apache/metamodel/query/parser/QueryParserTest.java @@ -68,6 +68,18 @@ public class QueryParserTest extends TestCase { assertEquals("SELECT TO_NUMBER(a.foo) FROM sch.tbl a WHERE TO_BOOLEAN(a.bar) = FALSE", q.toSql()); } + public void testSelectMapValueUsingDotNotation() throws Exception { + // set 'baz' column to a MAP column + MutableColumn col = (MutableColumn) dc.getColumnByQualifiedLabel("tbl.baz"); + col.setType(ColumnType.MAP); + + Query q = MetaModelHelper.parseQuery(dc, + "SELECT sch.tbl.baz.foo.bar, baz.helloworld, baz.hello.world FROM sch.tbl"); + assertEquals( + "SELECT MAP_VALUE('foo.bar',tbl.baz), MAP_VALUE('helloworld',tbl.baz), MAP_VALUE('hello.world',tbl.baz) FROM sch.tbl", + q.toSql()); + } + public void testSelectEverythingFromTable() throws Exception { Query q = MetaModelHelper.parseQuery(dc, "SELECT * FROM sch.tbl"); assertEquals("SELECT tbl.foo, tbl.bar, tbl.baz FROM sch.tbl", q.toSql()); @@ -221,7 +233,8 @@ public class QueryParserTest extends TestCase { q = MetaModelHelper.parseQuery(dc, "SELECT COUNT(*) FROM sch.tbl a LEFT JOIN sch.tbl b ON a.foo = b.foo AND a.bar = b.baz"); - assertEquals("SELECT COUNT(*) FROM sch.tbl a LEFT JOIN sch.tbl b ON a.foo = b.foo AND a.bar = b.baz", q.toSql()); + assertEquals("SELECT COUNT(*) FROM sch.tbl a LEFT JOIN sch.tbl b ON a.foo = b.foo AND a.bar = b.baz", + q.toSql()); } public void testSimpleSelectFromWhere() throws Exception { @@ -230,11 +243,11 @@ public class QueryParserTest extends TestCase { FilterClause whereClause = q.getWhereClause(); assertEquals(2, whereClause.getItemCount()); - assertNull("WHERE item was an expression based item, which indicates it was not parsed", whereClause.getItem(0) - .getExpression()); + assertNull("WHERE item was an expression based item, which indicates it was not parsed", + whereClause.getItem(0).getExpression()); assertEquals(2, whereClause.getItemCount()); - assertNull("WHERE item was an expression based item, which indicates it was not parsed", whereClause.getItem(1) - .getExpression()); + assertNull("WHERE item was an expression based item, which indicates it was not parsed", + whereClause.getItem(1).getExpression()); assertEquals("baz", whereClause.getItem(0).getOperand()); assertEquals(Integer.class, whereClause.getItem(1).getOperand().getClass()); @@ -264,8 +277,8 @@ public class QueryParserTest extends TestCase { } public void testCoumpoundWhereClause() throws Exception { - Query q = MetaModelHelper - .parseQuery(dc, "SELECT foo FROM sch.tbl WHERE (bar = 'baz' OR (baz > 5 AND baz < 7))"); + Query q = MetaModelHelper.parseQuery(dc, + "SELECT foo FROM sch.tbl WHERE (bar = 'baz' OR (baz > 5 AND baz < 7))"); assertEquals("SELECT tbl.foo FROM sch.tbl WHERE (tbl.bar = 'baz' OR (tbl.baz > 5 AND tbl.baz < 7))", q.toSql()); FilterClause wc = q.getWhereClause(); @@ -281,8 +294,8 @@ public class QueryParserTest extends TestCase { } public void testCoumpoundWhereClauseDelimInLoweCase() throws Exception { - Query q = MetaModelHelper - .parseQuery(dc, "SELECT foo FROM sch.tbl WHERE (bar = 'baz' OR (baz > 5 and baz < 7))"); + Query q = MetaModelHelper.parseQuery(dc, + "SELECT foo FROM sch.tbl WHERE (bar = 'baz' OR (baz > 5 and baz < 7))"); assertEquals("SELECT tbl.foo FROM sch.tbl WHERE (tbl.bar = 'baz' OR (tbl.baz > 5 AND tbl.baz < 7))", q.toSql()); FilterClause wc = q.getWhereClause(); @@ -302,8 +315,8 @@ public class QueryParserTest extends TestCase { assertEquals("SELECT tbl.foo FROM sch.tbl WHERE tbl.bar IS NULL", q.toSql()); assertEquals(1, q.getWhereClause().getItemCount()); - assertNull("WHERE item was an expression based item, which indicates it was not parsed", q.getWhereClause() - .getItem(0).getExpression()); + assertNull("WHERE item was an expression based item, which indicates it was not parsed", + q.getWhereClause().getItem(0).getExpression()); assertNull(q.getWhereClause().getItem(0).getOperand()); assertEquals(OperatorType.EQUALS_TO, q.getWhereClause().getItem(0).getOperator()); } @@ -313,8 +326,8 @@ public class QueryParserTest extends TestCase { assertEquals("SELECT tbl.foo FROM sch.tbl WHERE tbl.bar IS NOT NULL", q.toSql()); assertEquals(1, q.getWhereClause().getItemCount()); - assertNull("WHERE item was an expression based item, which indicates it was not parsed", q.getWhereClause() - .getItem(0).getExpression()); + assertNull("WHERE item was an expression based item, which indicates it was not parsed", + q.getWhereClause().getItem(0).getExpression()); assertNull(q.getWhereClause().getItem(0).getOperand()); assertEquals(OperatorType.DIFFERENT_FROM, q.getWhereClause().getItem(0).getOperator()); } @@ -407,7 +420,8 @@ public class QueryParserTest extends TestCase { + "GROUP BY foo HAVING COUNT(*) > 2 ORDER BY foo LIMIT 20 OFFSET 10"); assertEquals( "SELECT tbl.foo, COUNT(*), MAX(tbl.baz) FROM sch.tbl WHERE tbl.bar = 'baz' AND tbl.foo = tbl.bar AND tbl.baz > 5 " - + "GROUP BY tbl.foo HAVING COUNT(*) > 2 ORDER BY tbl.foo ASC", q.toSql()); + + "GROUP BY tbl.foo HAVING COUNT(*) > 2 ORDER BY tbl.foo ASC", + q.toSql()); assertEquals(20, q.getMaxRows().intValue()); assertEquals(11, q.getFirstRow().intValue()); http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/elasticsearch/native/src/test/java/org/apache/metamodel/elasticsearch/nativeclient/ElasticSearchDataContextTest.java ---------------------------------------------------------------------- diff --git a/elasticsearch/native/src/test/java/org/apache/metamodel/elasticsearch/nativeclient/ElasticSearchDataContextTest.java b/elasticsearch/native/src/test/java/org/apache/metamodel/elasticsearch/nativeclient/ElasticSearchDataContextTest.java index 0ae170c..8a83263 100644 --- a/elasticsearch/native/src/test/java/org/apache/metamodel/elasticsearch/nativeclient/ElasticSearchDataContextTest.java +++ b/elasticsearch/native/src/test/java/org/apache/metamodel/elasticsearch/nativeclient/ElasticSearchDataContextTest.java @@ -18,6 +18,14 @@ */ package org.apache.metamodel.elasticsearch.nativeclient; +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.io.IOException; import java.util.Arrays; import java.util.Date; @@ -43,6 +51,7 @@ import org.apache.metamodel.elasticsearch.nativeclient.utils.EmbeddedElasticsear import org.apache.metamodel.query.FunctionType; import org.apache.metamodel.query.Query; import org.apache.metamodel.query.SelectItem; +import org.apache.metamodel.query.parser.QueryParserException; import org.apache.metamodel.schema.Column; import org.apache.metamodel.schema.ColumnType; import org.apache.metamodel.schema.Schema; @@ -62,9 +71,6 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; -import static org.junit.Assert.*; - public class ElasticSearchDataContextTest { private static final String indexName = "twitter"; @@ -535,7 +541,7 @@ public class ElasticSearchDataContextTest { dataContext.query().from("nonExistingTable").select("user").and("message").execute(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = QueryParserException.class) public void testQueryForAnExistingTableAndNonExistingField() throws Exception { indexTweeterDocument(indexType1, 1); dataContext.query().from(indexType1).select("nonExistingField").execute(); http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/elasticsearch/rest/src/test/java/org/apache/metamodel/elasticsearch/rest/JestElasticSearchDataContextTest.java ---------------------------------------------------------------------- diff --git a/elasticsearch/rest/src/test/java/org/apache/metamodel/elasticsearch/rest/JestElasticSearchDataContextTest.java b/elasticsearch/rest/src/test/java/org/apache/metamodel/elasticsearch/rest/JestElasticSearchDataContextTest.java index aac963a..9d7d2a8 100644 --- a/elasticsearch/rest/src/test/java/org/apache/metamodel/elasticsearch/rest/JestElasticSearchDataContextTest.java +++ b/elasticsearch/rest/src/test/java/org/apache/metamodel/elasticsearch/rest/JestElasticSearchDataContextTest.java @@ -36,6 +36,7 @@ import org.apache.metamodel.elasticsearch.rest.utils.EmbeddedElasticsearchServer import org.apache.metamodel.query.FunctionType; import org.apache.metamodel.query.Query; import org.apache.metamodel.query.SelectItem; +import org.apache.metamodel.query.parser.QueryParserException; import org.apache.metamodel.schema.Column; import org.apache.metamodel.schema.ColumnType; import org.apache.metamodel.schema.Schema; @@ -539,7 +540,7 @@ public class JestElasticSearchDataContextTest { dataContext.query().from("nonExistingTable").select("user").and("message").execute(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = QueryParserException.class) public void testQueryForAnExistingTableAndNonExistingField() throws Exception { indexTweeterDocument(indexType1, 1); dataContext.query().from(indexType1).select("nonExistingField").execute(); http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/jdbc/src/test/java/org/apache/metamodel/jdbc/DerbyTest.java ---------------------------------------------------------------------- diff --git a/jdbc/src/test/java/org/apache/metamodel/jdbc/DerbyTest.java b/jdbc/src/test/java/org/apache/metamodel/jdbc/DerbyTest.java index 2d0de42..ff6ef22 100644 --- a/jdbc/src/test/java/org/apache/metamodel/jdbc/DerbyTest.java +++ b/jdbc/src/test/java/org/apache/metamodel/jdbc/DerbyTest.java @@ -253,7 +253,7 @@ public class DerbyTest extends TestCase { Schema schema = dc.getSchemaByName("APP"); Table customersTable = schema.getTableByName("CUSTOMERS"); - Query q = dc.query().from(customersTable).as("cus-tomers").select("CUSTOMERNAME").as("c|o|d|e").toQuery(); + Query q = dc.query().from(customersTable).as("cus-tomers").select("CUSTOMERNAME AS c|o|d|e").toQuery(); assertEquals(25000, dc.getFetchSizeCalculator().getFetchSize(q)); q.setMaxRows(5); http://git-wip-us.apache.org/repos/asf/metamodel/blob/27246194/json/src/test/java/org/apache/metamodel/json/JsonDataContextTest.java ---------------------------------------------------------------------- diff --git a/json/src/test/java/org/apache/metamodel/json/JsonDataContextTest.java b/json/src/test/java/org/apache/metamodel/json/JsonDataContextTest.java index 79d0e22..0dba661 100644 --- a/json/src/test/java/org/apache/metamodel/json/JsonDataContextTest.java +++ b/json/src/test/java/org/apache/metamodel/json/JsonDataContextTest.java @@ -24,7 +24,9 @@ import java.util.Arrays; import junit.framework.TestCase; import org.apache.metamodel.data.DataSet; +import org.apache.metamodel.query.FunctionType; import org.apache.metamodel.schema.Column; +import org.apache.metamodel.schema.Schema; import org.apache.metamodel.schema.Table; import org.apache.metamodel.schema.builder.SchemaBuilder; import org.apache.metamodel.schema.builder.SimpleTableDefSchemaBuilder; @@ -90,7 +92,47 @@ public class JsonDataContextTest extends TestCase { assertTrue(ds.next()); assertEquals("Row[values=[John, Doe, MALE, football, null, null]]", ds.getRow().toString()); assertTrue(ds.next()); - assertEquals("Row[values=[John, Doe, MALE, {type=sport, name=soccer}, sport, soccer]]", ds.getRow().toString()); + assertEquals("Row[values=[John, Doe, MALE, {type=sport, name=soccer}, sport, soccer]]", ds.getRow() + .toString()); + assertFalse(ds.next()); + } finally { + ds.close(); + } + } + + public void testUseMapValueFunctionToGetFromNestedMap() throws Exception { + final Resource resource = new FileResource("src/test/resources/nested_fields.json"); + final JsonDataContext dataContext = new JsonDataContext(resource); + + final Schema schema = dataContext.getDefaultSchema(); + assertEquals("[nested_fields.json]", Arrays.toString(schema.getTableNames())); + + final DataSet ds = dataContext.query().from(schema.getTable(0)) + .select(FunctionType.MAP_VALUE, "name", new Object[] { "first" }).execute(); + try { + assertTrue(ds.next()); + assertEquals("Row[values=[John]]", ds.getRow().toString()); + assertTrue(ds.next()); + assertEquals("Row[values=[John]]", ds.getRow().toString()); + assertFalse(ds.next()); + } finally { + ds.close(); + } + } + + public void testUseDotNotationToGetFromNestedMap() throws Exception { + final Resource resource = new FileResource("src/test/resources/nested_fields.json"); + final JsonDataContext dataContext = new JsonDataContext(resource); + + final Schema schema = dataContext.getDefaultSchema(); + assertEquals("[nested_fields.json]", Arrays.toString(schema.getTableNames())); + + final DataSet ds = dataContext.query().from(schema.getTable(0)).select("name.first").execute(); + try { + assertTrue(ds.next()); + assertEquals("Row[values=[John]]", ds.getRow().toString()); + assertTrue(ds.next()); + assertEquals("Row[values=[John]]", ds.getRow().toString()); assertFalse(ds.next()); } finally { ds.close();