You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metamodel.apache.org by ka...@apache.org on 2015/12/09 11:44:49 UTC

metamodel git commit: METAMODEL-195: Fixed

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....@gmail.com>
Authored: Wed Dec 9 11:44:41 2015 +0100
Committer: Kasper Sørensen <i....@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();