METAMODEL-1173: Fixed issue with evaluating ScalarFunctions in WHERE closes apache...
authorKasper Sørensen <i.am.kasper.sorensen@gmail.com>
Fri, 8 Dec 2017 21:58:03 +0000 (22:58 +0100)
committerDennis Du Krøger <losd@apache.org>
Fri, 8 Dec 2017 22:00:33 +0000 (23:00 +0100)
24 files changed:
.travis.yml
CHANGES.md
core/src/main/java/org/apache/metamodel/MetaModelHelper.java
core/src/main/java/org/apache/metamodel/QueryPostprocessDataContext.java
core/src/main/java/org/apache/metamodel/data/FilteredDataSet.java
core/src/main/java/org/apache/metamodel/data/SimpleDataSetHeader.java
core/src/main/java/org/apache/metamodel/query/FilterItem.java
core/src/main/java/org/apache/metamodel/query/FunctionType.java
core/src/main/java/org/apache/metamodel/query/FunctionTypeFactory.java
core/src/main/java/org/apache/metamodel/query/MapValueFunction.java
core/src/main/java/org/apache/metamodel/query/SelectItem.java
core/src/main/java/org/apache/metamodel/query/SubstringFunction.java [new file with mode: 0644]
core/src/main/java/org/apache/metamodel/query/ToStringFunction.java
core/src/main/java/org/apache/metamodel/query/parser/SelectItemParser.java
core/src/main/java/org/apache/metamodel/util/EqualsBuilder.java
core/src/test/java/org/apache/metamodel/MetaModelHelperTest.java
core/src/test/java/org/apache/metamodel/QueryPostprocessDataContextTest.java
core/src/test/java/org/apache/metamodel/query/SelectItemTest.java
core/src/test/java/org/apache/metamodel/query/SubstringFunctionTest.java [new file with mode: 0644]
core/src/test/java/org/apache/metamodel/query/parser/QueryParserTest.java
mongodb/mongo2/src/test/java/org/apache/metamodel/mongodb/mongo2/MongoDbDataContextTest.java
mongodb/mongo3/src/main/java/org/apache/metamodel/mongodb/mongo3/MongoDbDataContext.java
mongodb/mongo3/src/main/java/org/apache/metamodel/mongodb/mongo3/MongoDbDeleteBuilder.java
mongodb/mongo3/src/test/java/org/apache/metamodel/mongodb/mongo3/MongoDbDataContextTest.java

index 33b571c..1b1e342 100644 (file)
@@ -4,6 +4,8 @@ jdk:
   - oraclejdk8
   
 before_install:
+  # copy integration test config
+  - cp travis-metamodel-integrationtest-configuration.properties /home/travis/metamodel-integrationtest-configuration.properties
   # install Neo4j locally:
   - wget dist.neo4j.org/neo4j-community-2.2.3-unix.tar.gz
   - tar -xzf neo4j-community-2.2.3-unix.tar.gz
index 3fd8879..f442440 100644 (file)
@@ -1,4 +1,5 @@
- * [METAMODEL-1169] - Fixed issue with SQL Server milliseconds precision in WHERE
+ * [METAMODEL-1169] - Fixed issue with SQL Server milliseconds precision in WHERE.
+ * [METAMODEL-1173] - Fixed parsing and handling of scalar functions in WHERE clause.
  * [METAMODEL-1171] - Fixed parsing of query operators with DATE, TIME, TIMESTAMP prefix to operand date/time values.
 
 ### Apache MetaModel 5.0
index 2b547da..59900f9 100644 (file)
@@ -32,6 +32,7 @@ import java.util.Map.Entry;
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 
 import org.apache.metamodel.data.CachingDataSetHeader;
 import org.apache.metamodel.data.DataSet;
@@ -40,7 +41,6 @@ import org.apache.metamodel.data.DefaultRow;
 import org.apache.metamodel.data.EmptyDataSet;
 import org.apache.metamodel.data.FilteredDataSet;
 import org.apache.metamodel.data.FirstRowDataSet;
-import org.apache.metamodel.data.IRowFilter;
 import org.apache.metamodel.data.InMemoryDataSet;
 import org.apache.metamodel.data.MaxRowsDataSet;
 import org.apache.metamodel.data.Row;
@@ -270,20 +270,40 @@ public final class MetaModelHelper {
     }
 
     public static DataSet getFiltered(DataSet dataSet, Iterable<FilterItem> filterItems) {
-        List<IRowFilter> filters = CollectionUtils.map(filterItems, filterItem -> {
-            return filterItem;
-        });
-        if (filters.isEmpty()) {
-            return dataSet;
-        }
-
-        return new FilteredDataSet(dataSet, filters.toArray(new IRowFilter[filters.size()]));
+        final FilterItem[] filterItemsArray =
+                StreamSupport.stream(filterItems.spliterator(), false).toArray(FilterItem[]::new);
+        return getFiltered(dataSet, filterItemsArray);
     }
 
     public static DataSet getFiltered(DataSet dataSet, FilterItem... filterItems) {
+        if (filterItems == null || filterItems.length == 0) {
+            return dataSet;
+        }
         return getFiltered(dataSet, Arrays.asList(filterItems));
     }
 
+    public static DataSet getFiltered(DataSet dataSet, Collection<FilterItem> filterItems) {
+        if (filterItems == null || filterItems.isEmpty()) {
+            return dataSet;
+        }
+        final List<SelectItem> selectItemsOnOutput = dataSet.getSelectItems();
+        final Iterable<SelectItem> selectItems =
+                filterItems.stream().map(f -> f.getSelectItem()).filter(s -> s != null)::iterator;
+        final List<SelectItem> scalarFunctionSelectItems =
+                getUnmaterializedScalarFunctionSelectItems(selectItems, dataSet);
+        final boolean calculateScalarFunctions = !scalarFunctionSelectItems.isEmpty();
+        if (calculateScalarFunctions) {
+            // scalar functions are needed in evaluation of the filters
+            dataSet = new ScalarFunctionDataSet(scalarFunctionSelectItems, dataSet);
+        }
+        final FilteredDataSet filteredDataSet = new FilteredDataSet(dataSet, filterItems);
+        if (calculateScalarFunctions) {
+            return getSelection(selectItemsOnOutput, filteredDataSet);
+        } else {
+            return filteredDataSet;
+        }
+    }
+
     public static DataSet getSelection(final List<SelectItem> selectItems, final DataSet dataSet) {
         final List<SelectItem> dataSetSelectItems = dataSet.getSelectItems();
 
@@ -317,7 +337,8 @@ public final class MetaModelHelper {
         return getSelection(Arrays.asList(selectItems), dataSet);
     }
 
-    public static DataSet getGrouped(List<SelectItem> selectItems, DataSet dataSet, Collection<GroupByItem> groupByItems) {
+    public static DataSet getGrouped(List<SelectItem> selectItems, DataSet dataSet,
+            Collection<GroupByItem> groupByItems) {
         DataSet result = dataSet;
         if (groupByItems != null && groupByItems.size() > 0) {
             Map<Row, Map<SelectItem, List<Object>>> uniqueRows = new HashMap<Row, Map<SelectItem, List<Object>>>();
@@ -524,8 +545,21 @@ public final class MetaModelHelper {
     }
 
     public static List<SelectItem> getScalarFunctionSelectItems(Iterable<SelectItem> selectItems) {
+        return getUnmaterializedScalarFunctionSelectItems(selectItems, null);
+    }
+
+    /**
+     * Gets select items with scalar functions that haven't already been materialized in a data set.
+     * 
+     * @param selectItems
+     * @param dataSetWithMaterializedSelectItems a {@link DataSet} containing the already materialized select items
+     * @return
+     */
+    public static List<SelectItem> getUnmaterializedScalarFunctionSelectItems(Iterable<SelectItem> selectItems,
+            DataSet dataSetWithMaterializedSelectItems) {
         return CollectionUtils.filter(selectItems, arg -> {
-            return arg.getScalarFunction() != null;
+            return arg.getScalarFunction() != null && (dataSetWithMaterializedSelectItems == null
+                    || dataSetWithMaterializedSelectItems.indexOf(arg) == -1);
         });
     }
 
index c77479b..d6e287d 100644 (file)
@@ -22,6 +22,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -157,7 +158,8 @@ public abstract class QueryPostprocessDataContext extends AbstractDataContext im
                     if (whereItems.size() == 1) {
                         final FilterItem whereItem = whereItems.get(0);
                         final SelectItem selectItem = whereItem.getSelectItem();
-                        if (!whereItem.isCompoundFilter() && selectItem != null && selectItem.getColumn() != null) {
+                        if (!whereItem.isCompoundFilter() && selectItem != null && !selectItem.hasFunction()
+                                && selectItem.getColumn() != null) {
                             final Column column = selectItem.getColumn();
                             if (column.isPrimaryKey() && OperatorType.EQUALS_TO.equals(whereItem.getOperator())) {
                                 logger.debug(
@@ -284,7 +286,7 @@ public abstract class QueryPostprocessDataContext extends AbstractDataContext im
             // We need to materialize a single table
             final Table table = MetaModelHelper.resolveTable(fromItem);
             final List<SelectItem> selectItemsToMaterialize = new ArrayList<SelectItem>();
-
+            
             for (final SelectItem selectItem : selectItems) {
                 final FromItem selectedFromItem = selectItem.getFromItem();
                 if (selectedFromItem != null) {
@@ -358,6 +360,7 @@ public abstract class QueryPostprocessDataContext extends AbstractDataContext im
         if (dataSet == null) {
             throw new IllegalStateException("FromItem was not succesfully materialized: " + fromItem);
         }
+        
         return dataSet;
     }
 
@@ -406,18 +409,15 @@ public abstract class QueryPostprocessDataContext extends AbstractDataContext im
     }
 
     private List<SelectItem> buildWorkingSelectItems(List<SelectItem> selectItems, List<FilterItem> whereItems) {
-        final List<SelectItem> primarySelectItems = new ArrayList<>(selectItems.size());
-        for (SelectItem selectItem : selectItems) {
-            final ScalarFunction scalarFunction = selectItem.getScalarFunction();
-            if (scalarFunction == null || isScalarFunctionMaterialized(scalarFunction)) {
-                primarySelectItems.add(selectItem);
-            } else {
-                final SelectItem copySelectItem = selectItem.replaceFunction(null);
-                primarySelectItems.add(copySelectItem);
-            }
+        if (whereItems == null || whereItems.isEmpty()) {
+            return selectItems;
         }
         final List<SelectItem> evaluatedSelectItems = MetaModelHelper.getEvaluatedSelectItems(whereItems);
-        return CollectionUtils.concat(true, primarySelectItems, evaluatedSelectItems);
+        
+        final LinkedHashSet<SelectItem> workingSelectItems = new LinkedHashSet<>();
+        workingSelectItems.addAll(selectItems);
+        workingSelectItems.addAll(evaluatedSelectItems);
+        return new ArrayList<>(workingSelectItems);
     }
 
     /**
index c322401..dc7ec05 100644 (file)
  */
 package org.apache.metamodel.data;
 
+import java.util.Collection;
 
 /**
  * Wraps another DataSet and transparently applies a set of filters to it.
  */
 public final class FilteredDataSet extends AbstractDataSet implements WrappingDataSet {
 
-       private final DataSet _dataSet;
-       private final IRowFilter[] _filters;
-       private Row _row;
+    private final DataSet _dataSet;
+    private final IRowFilter[] _filters;
+    private Row _row;
 
-       public FilteredDataSet(DataSet dataSet, IRowFilter... filters) {
-           super(dataSet);
-               _dataSet = dataSet;
-               _filters = filters;
-       }
+    public FilteredDataSet(DataSet dataSet, Collection<? extends IRowFilter> filterItems) {
+        this(dataSet, filterItems.stream().toArray(IRowFilter[]::new));
+    }
 
-       @Override
-       public void close() {
-               super.close();
-               _dataSet.close();
-       }
-       
-       @Override
-       public DataSet getWrappedDataSet() {
-           return _dataSet;
-       }
+    public FilteredDataSet(DataSet dataSet, IRowFilter... filters) {
+        super(dataSet);
+        _dataSet = dataSet;
+        _filters = filters;
+    }
 
-       @Override
-       public boolean next() {
-               boolean next = false;
-               while (_dataSet.next()) {
-                       Row row = _dataSet.getRow();
-                       for (IRowFilter filter : _filters) {
-                               next = filter.accept(row);
-                               if (!next) {
-                                       break;
-                               }
-                       }
-                       if (next) {
-                               _row = row;
-                               break;
-                       }
-               }
-               return next;
-       }
+    @Override
+    public void close() {
+        super.close();
+        _dataSet.close();
+    }
 
-       @Override
-       public Row getRow() {
-               return _row;
-       }
+    @Override
+    public DataSet getWrappedDataSet() {
+        return _dataSet;
+    }
+
+    @Override
+    public boolean next() {
+        boolean next = false;
+        while (_dataSet.next()) {
+            Row row = _dataSet.getRow();
+            for (IRowFilter filter : _filters) {
+                next = filter.accept(row);
+                if (!next) {
+                    break;
+                }
+            }
+            if (next) {
+                _row = row;
+                break;
+            }
+        }
+        return next;
+    }
+
+    @Override
+    public Row getRow() {
+        return _row;
+    }
 }
\ No newline at end of file
index 5dc2ca9..3232bad 100644 (file)
@@ -104,12 +104,6 @@ public class SimpleDataSetHeader implements DataSetHeader {
             }
             i++;
         }
-        
-        final boolean scalarFunctionQueried = item.getScalarFunction() != null;
-        if (scalarFunctionQueried) {
-            final SelectItem itemWithoutFunction = item.replaceFunction(null);
-            return indexOf(itemWithoutFunction);
-        }
 
         return -1;
     }
index 427844b..fe13250 100644 (file)
@@ -252,7 +252,7 @@ public class FilterItem extends BaseObject implements QueryItem, Cloneable, IRow
             return _expression;
         }
 
-        StringBuilder sb = new StringBuilder();
+        final StringBuilder sb = new StringBuilder();
         if (_childItems == null) {
             sb.append(_selectItem.getSameQueryAlias(includeSchemaInColumnPaths));
 
@@ -268,7 +268,7 @@ public class FilterItem extends BaseObject implements QueryItem, Cloneable, IRow
                             .getSameQueryAlias(includeSchemaInColumnPaths);
                     sb.append(selectItemString);
                 } else {
-                    ColumnType columnType = _selectItem.getExpectedColumnType();
+                    final ColumnType columnType = _selectItem.getExpectedColumnType();
                     final String sqlValue = FormatHelper.formatSqlValue(columnType, operand);
                     sb.append(sqlValue);
                 }
@@ -310,10 +310,10 @@ public class FilterItem extends BaseObject implements QueryItem, Cloneable, IRow
 
         if (_childItems == null) {
             // Evaluate a single constraint
-            Object selectItemValue = row.getValue(_selectItem);
+            final Object selectItemValue = row.getValue(_selectItem);
             Object operandValue = _operand;
             if (_operand instanceof SelectItem) {
-                SelectItem selectItem = (SelectItem) _operand;
+                final SelectItem selectItem = (SelectItem) _operand;
                 operandValue = row.getValue(selectItem);
             }
             if (operandValue == null) {
@@ -339,7 +339,7 @@ public class FilterItem extends BaseObject implements QueryItem, Cloneable, IRow
             if (_logicalOperator == LogicalOperator.AND) {
                 // require all results to be true
                 for (FilterItem item : _childItems) {
-                    boolean result = item.evaluate(row);
+                    final boolean result = item.evaluate(row);
                     if (!result) {
                         return false;
                     }
@@ -348,7 +348,7 @@ public class FilterItem extends BaseObject implements QueryItem, Cloneable, IRow
             } else {
                 // require at least one result to be true
                 for (FilterItem item : _childItems) {
-                    boolean result = item.evaluate(row);
+                    final boolean result = item.evaluate(row);
                     if (result) {
                         return true;
                     }
index ecedc97..2dad077 100644 (file)
@@ -41,6 +41,8 @@ public interface FunctionType extends Serializable {
     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 SUBSTRING = SubstringFunction.createSqlStyle();
+    public static final ScalarFunction JAVA_SUBSTRING = SubstringFunction.createJavaStyle();
     public static final ScalarFunction MAP_VALUE = new MapValueFunction();
 
     public ColumnType getExpectedColumnType(ColumnType type);
index 95e25ce..1de7f86 100644 (file)
@@ -67,6 +67,13 @@ public class FunctionTypeFactory {
         case "TO_DATE":
         case "DATE":
             return FunctionType.TO_DATE;
+        case "SUBSTRING":
+        case "SUBSTR":
+        case "SUB_STRING":
+        case "SUB_STR":
+            return FunctionType.SUBSTRING;
+        case "JAVA_SUBSTRING":
+            return FunctionType.JAVA_SUBSTRING;
         case "MAP_VALUE":
             return FunctionType.MAP_VALUE;
         default:
index 099bcab..e017747 100644 (file)
@@ -37,9 +37,9 @@ public final class MapValueFunction extends DefaultScalarFunction {
         if (parameters.length == 0) {
             throw new IllegalArgumentException("Expecting path parameter to MAP_VALUE function");
         }
-        Object value = row.getValue(operandItem);
+        final Object value = row.getValue(operandItem);
         if (value instanceof Map) {
-            Map<?, ?> map = (Map<?, ?>) value;
+            final Map<?, ?> map = (Map<?, ?>) value;
             return CollectionUtils.find(map, (String) parameters[0]);
         }
         return null;
index fec900d..40ac3fc 100644 (file)
@@ -83,7 +83,7 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable {
         _column = column;
         _fromItem = fromItem;
         _function = function;
-        _functionParameters = functionParameters;
+        _functionParameters = (functionParameters != null && functionParameters.length == 0) ? null : functionParameters;
         _expression = expression;
         _subQuerySelectItem = subQuerySelectItem;
         _alias = alias;
@@ -351,21 +351,12 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable {
             return _alias;
         } else if (_column != null) {
             final StringBuilder sb = new StringBuilder();
-            if (_function != null) {
-                if (_functionApproximationAllowed) {
-                    sb.append(FUNCTION_APPROXIMATION_PREFIX);
-                }
-                sb.append(_function.getFunctionName());
-                sb.append('(');
-            }
             if (includeQuotes) {
                 sb.append(_column.getQuotedName());
             } else {
                 sb.append(_column.getName());
             }
-            if (_function != null) {
-                sb.append(')');
-            }
+            appendFunctionSql(sb);
             return sb.toString();
         } else {
             logger.debug("Could not resolve a reasonable super-query alias for SelectItem: {}", toSql());
@@ -382,24 +373,18 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable {
      */
     public String getSameQueryAlias(boolean includeSchemaInColumnPath) {
         if (_column != null) {
-            StringBuilder sb = new StringBuilder();
-            String columnPrefix = getToStringColumnPrefix(includeSchemaInColumnPath);
+            final StringBuilder sb = new StringBuilder();
+            final String columnPrefix = getToStringColumnPrefix(includeSchemaInColumnPath);
             sb.append(columnPrefix);
             sb.append(_column.getQuotedName());
-            if (_function != null) {
-                if (_functionApproximationAllowed) {
-                    sb.insert(0, FUNCTION_APPROXIMATION_PREFIX + _function.getFunctionName() + "(");
-                } else {
-                    sb.insert(0, _function.getFunctionName() + "(");
-                }
-                sb.append(")");
-            }
+            appendFunctionSql(sb);
             return sb.toString();
         }
-        String alias = getAlias();
+        final String alias = getAlias();
         if (alias == null) {
-            alias = toStringNoAlias(includeSchemaInColumnPath).toString();
-            logger.debug("Could not resolve a reasonable same-query alias for SelectItem: {}", toSql());
+            final String result = toStringNoAlias(includeSchemaInColumnPath).toString();
+            logger.debug("Could not resolve a reasonable same-query alias for SelectItem: {}", result);
+            return result;
         }
         return alias;
     }
@@ -438,27 +423,32 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable {
             }
             sb.append(_subQuerySelectItem.getSuperQueryAlias());
         }
-        if (_function != null) {
-            final StringBuilder functionBeginning = new StringBuilder();
-            if (_functionApproximationAllowed) {
-                functionBeginning.append(FUNCTION_APPROXIMATION_PREFIX);
-            }
+        appendFunctionSql(sb);
+        return sb;
+    }
 
-            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(',');
-                }
+    private void appendFunctionSql(StringBuilder sb) {
+        if (_function == null) {
+            return;
+        }
+        final StringBuilder functionBeginning = new StringBuilder();
+        if (_functionApproximationAllowed) {
+            functionBeginning.append(FUNCTION_APPROXIMATION_PREFIX);
+        }
+
+        functionBeginning.append(_function.getFunctionName());
+        functionBeginning.append('(');
+        sb.insert(0, functionBeginning.toString());
+        final Object[] functionParameters = getFunctionParameters();
+        if (functionParameters != null && functionParameters.length != 0) {
+            for (int i = 0; i < functionParameters.length; i++) {
+                sb.append(',');
+                sb.append('\'');
+                sb.append(functionParameters[i]);
+                sb.append('\'');
             }
-            sb.insert(0, functionBeginning.toString());
-            sb.append(")");
         }
-        return sb;
+        sb.append(")");
     }
 
     private String getToStringColumnPrefix(boolean includeSchemaInColumnPath) {
@@ -509,7 +499,7 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable {
             return true;
         }
 
-        EqualsBuilder eb = new EqualsBuilder();
+        final EqualsBuilder eb = new EqualsBuilder();
         if (exactColumnCompare) {
             eb.append(this._column == that._column);
             eb.append(this._fromItem, that._fromItem);
@@ -517,6 +507,7 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable {
             eb.append(this._column, that._column);
         }
         eb.append(this._function, that._function);
+        eb.appendArrays(this._functionParameters, that._functionParameters);
         eb.append(this._functionApproximationAllowed, that._functionApproximationAllowed);
         eb.append(this._expression, that._expression);
         if (_subQuerySelectItem != null) {
@@ -528,13 +519,14 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable {
         }
         return eb.isEquals();
     }
-
+    
     @Override
     protected void decorateIdentity(List<Object> identifiers) {
         identifiers.add(_expression);
         identifiers.add(_alias);
         identifiers.add(_column);
         identifiers.add(_function);
+        identifiers.add(_functionParameters);
         identifiers.add(_functionApproximationAllowed);
         if (_fromItem == null && _column != null && _column.getTable() != null) {
             // add a FromItem representing the column's table - this makes equal
@@ -587,7 +579,18 @@ public class SelectItem extends BaseObject implements QueryItem, Cloneable {
      * @return
      */
     public SelectItem replaceFunction(FunctionType function) {
-        return new SelectItem(_column, _fromItem, function, _functionParameters, _expression, _subQuerySelectItem,
+        return replaceFunction(function, new Object[0]);
+    }
+
+    /**
+     * Creates a copy of the {@link SelectItem}, with a different {@link FunctionType} and parameters.
+     * 
+     * @param function
+     * @param functionParameters
+     * @return
+     */
+    public SelectItem replaceFunction(FunctionType function, Object... functionParameters) {
+        return new SelectItem(_column, _fromItem, function, functionParameters, _expression, _subQuerySelectItem,
                 _alias, _functionApproximationAllowed);
     }
 
diff --git a/core/src/main/java/org/apache/metamodel/query/SubstringFunction.java b/core/src/main/java/org/apache/metamodel/query/SubstringFunction.java
new file mode 100644 (file)
index 0000000..d26c3f9
--- /dev/null
@@ -0,0 +1,116 @@
+/**
+ * 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 org.apache.metamodel.data.Row;
+import org.apache.metamodel.schema.ColumnType;
+import org.apache.metamodel.util.NumberComparator;
+
+public class SubstringFunction implements ScalarFunction {
+
+    private static final long serialVersionUID = 1L;
+
+    public static SubstringFunction createSqlStyle() {
+        return new SubstringFunction(true, true);
+    }
+
+    public static SubstringFunction createJavaStyle() {
+        return new SubstringFunction(false, false);
+    }
+
+    private final boolean oneBased;
+    private final boolean secondParamIsCharCount;
+
+    /**
+     * Creates a {@link SubstringFunction}
+     * 
+     * @param oneBased true if the character index parameters are 1 based, like most SQL SUBSTRING functions (instead of
+     *            0 based, like Java).
+     * @param secondParamIsCharCount true if the (optional) second parameter is a "character count", like most SQL
+     *            SUBSTRING functions (instead of end-index, like Java)
+     * 
+     */
+    public SubstringFunction(boolean oneBased, boolean secondParamIsCharCount) {
+        this.oneBased = oneBased;
+        this.secondParamIsCharCount = secondParamIsCharCount;
+    }
+
+    @Override
+    public ColumnType getExpectedColumnType(ColumnType type) {
+        return ColumnType.STRING;
+    }
+
+    @Override
+    public String getFunctionName() {
+        if (!oneBased && !secondParamIsCharCount) {
+            return "JAVA_SUBSTRING";
+        }
+        return "SUBSTRING";
+    }
+
+    @Override
+    public Object evaluate(Row row, Object[] parameters, SelectItem operandItem) {
+        final String str = (String) FunctionType.TO_STRING.evaluate(row, null, operandItem);
+        if (str == null) {
+            return null;
+        }
+        final int numParameters = parameters == null ? 0 : parameters.length;
+        switch (numParameters) {
+        case 0:
+            return str;
+        case 1:
+            int begin = toInt(parameters[0]);
+            if (oneBased) {
+                begin--;
+            }
+            if (begin >= str.length()) {
+                return "";
+            }
+            return str.substring(begin);
+        default:
+            int from = toInt(parameters[0]);
+            if (oneBased) {
+                from--;
+            }
+            int to = toInt(parameters[1]);
+            if (secondParamIsCharCount) {
+                to = from + to;
+            } else if (oneBased) {
+                to--;
+            }
+
+            if (from >= str.length() || from > to) {
+                return "";
+            }
+            if (to >= str.length()) {
+                return str.substring(from);
+            }
+            return str.substring(from, to);
+        }
+    }
+
+    private int toInt(Object parameter) {
+        final Number number = NumberComparator.toNumber(parameter);
+        if (number == null) {
+            throw new IllegalArgumentException("Not a valid substring parameter: " + parameter);
+        }
+        return Math.max(0, number.intValue());
+    }
+
+}
index 97d3fa9..eb88e23 100644 (file)
  */
 package org.apache.metamodel.query;
 
+import java.io.Reader;
+import java.sql.Clob;
+import java.sql.SQLException;
+
+import org.apache.metamodel.MetaModelException;
 import org.apache.metamodel.data.Row;
 import org.apache.metamodel.schema.ColumnType;
+import org.apache.metamodel.util.FileHelper;
 
 public class ToStringFunction extends DefaultScalarFunction {
     
@@ -44,6 +50,16 @@ public class ToStringFunction extends DefaultScalarFunction {
         if (value == null || value instanceof String) {
             return value;
         }
+        if (value instanceof Clob) {
+            final Clob clob = (Clob) value;
+            try {
+                final Reader reader = clob.getCharacterStream();
+                final String result = FileHelper.readAsString(reader);
+                return result;
+            } catch (SQLException e) {
+                throw new MetaModelException("Failed to read CLOB to String", e);
+            }
+        }
         return String.valueOf(value);
     }
 
index d358d72..79d5dc3 100644 (file)
  */
 package org.apache.metamodel.query.parser;
 
+import java.util.Arrays;
+
 import org.apache.metamodel.MetaModelException;
 import org.apache.metamodel.MetaModelHelper;
-import org.apache.metamodel.query.*;
+import org.apache.metamodel.query.CountAggregateFunction;
+import org.apache.metamodel.query.FromItem;
+import org.apache.metamodel.query.FunctionType;
+import org.apache.metamodel.query.FunctionTypeFactory;
+import org.apache.metamodel.query.Query;
+import org.apache.metamodel.query.SelectItem;
 import org.apache.metamodel.schema.Column;
 
 public final class SelectItemParser implements QueryPartProcessor {
@@ -108,6 +115,8 @@ public final class SelectItemParser implements QueryPartProcessor {
 
         final boolean functionApproximation;
         final FunctionType function;
+        Object[] functionParameters = null;
+        
         final int startParenthesis = expression.indexOf('(');
         if (startParenthesis > 0 && expression.endsWith(")")) {
             functionApproximation = (expression.startsWith(SelectItem.FUNCTION_APPROXIMATION_PREFIX));
@@ -120,10 +129,18 @@ public final class SelectItemParser implements QueryPartProcessor {
                     final SelectItem selectItem = SelectItem.getCountAllItem();
                     selectItem.setFunctionApproximationAllowed(functionApproximation);
                     return selectItem;
+                } else {
+                    final String[] expressionSplit = expression.split(",");
+                    if (expressionSplit.length > 1) {
+                        // there are multiple parameters to the function
+                        expression = expressionSplit[0].trim();
+                        functionParameters = Arrays.copyOfRange(expressionSplit, 1, expressionSplit.length, String[].class);
+                    }
                 }
             }
         } else {
             function = null;
+            functionParameters = null;
             functionApproximation = false;
         }
 
@@ -170,19 +187,19 @@ public final class SelectItemParser implements QueryPartProcessor {
                     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);
+                        return new SelectItem(FunctionType.MAP_VALUE, new Object[] { part2 }, column, fromItem);
                     }
                 }
 
                 if (column != null) {
-                    final SelectItem selectItem = new SelectItem(function, column, fromItem);
+                    final SelectItem selectItem = new SelectItem(function, functionParameters, column, fromItem);
                     selectItem.setFunctionApproximationAllowed(functionApproximation);
                     return selectItem;
                 }
             } else if (fromItem.getSubQuery() != null) {
                 final Query subQuery = fromItem.getSubQuery();
-                final SelectItem subQuerySelectItem = new SelectItemParser(subQuery, _allowExpressionBasedSelectItems)
-                        .findSelectItem(columnName);
+                final SelectItem subQuerySelectItem =
+                        new SelectItemParser(subQuery, _allowExpressionBasedSelectItems).findSelectItem(columnName);
                 if (subQuerySelectItem == null) {
                     return null;
                 }
index 656952f..b27d964 100644 (file)
@@ -40,12 +40,31 @@ public final class EqualsBuilder {
                return this;
        }
 
-       public EqualsBuilder append(Object o1, Object o2) {
-               if (equals) {
-                       equals = equals(o1, o2);
-               }
-               return this;
-       }
+    public EqualsBuilder append(Object o1, Object o2) {
+        if (equals) {
+            equals = equals(o1, o2);
+        }
+        return this;
+    }
+    
+    public EqualsBuilder appendArrays(Object[] o1, Object[] o2) {
+        if (equals) {
+            if (o1 == null) {
+                o1 = new Object[0];
+            }
+            if (o2 == null) {
+                o2 = new Object[0];
+            }
+            if (o1.length != o2.length) {
+                equals = false;
+            } else {
+                for (int i = 0; i < o1.length; i++) {
+                    append(o1[i], o2[i]);
+                }
+            }
+        }
+        return this;
+    }
 
        public static boolean equals(final Object obj1, final Object obj2) {
                if (obj1 == obj2) {
index 7196bf4..a2425d3 100644 (file)
@@ -33,6 +33,7 @@ import org.apache.metamodel.data.SimpleDataSetHeader;
 import org.apache.metamodel.data.SubSelectionDataSet;
 import org.apache.metamodel.query.FilterItem;
 import org.apache.metamodel.query.FromItem;
+import org.apache.metamodel.query.FunctionType;
 import org.apache.metamodel.query.JoinType;
 import org.apache.metamodel.query.OperatorType;
 import org.apache.metamodel.query.OrderByItem;
@@ -44,9 +45,11 @@ import org.apache.metamodel.schema.MutableColumn;
 import org.apache.metamodel.schema.MutableTable;
 import org.apache.metamodel.schema.Schema;
 import org.apache.metamodel.schema.Table;
+import org.junit.Test;
 
 public class MetaModelHelperTest extends MetaModelTestCase {
 
+    @Test
     public void testLeftJoin() throws Exception {
         SelectItem si1 = new SelectItem(new MutableColumn("person_id", ColumnType.INTEGER));
         SelectItem si2 = new SelectItem(new MutableColumn("person_name", ColumnType.VARCHAR));
@@ -67,8 +70,8 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         data2.add(new Object[] { 2, "bad boy", "bb" });
         data2.add(new Object[] { 4, "trying harder", "try" });
 
-        DataSet ds1 = createDataSet(Lists.newArrayList(si1, si2, si3, si4 ), data1);
-        DataSet ds2 = createDataSet(Lists.newArrayList(si5, si6, si7 ), data2);
+        DataSet ds1 = createDataSet(Lists.newArrayList(si1, si2, si3, si4), data1);
+        DataSet ds2 = createDataSet(Lists.newArrayList(si5, si6, si7), data2);
         FilterItem[] onConditions = new FilterItem[1];
         onConditions[0] = new FilterItem(si4, OperatorType.EQUALS_TO, si5);
 
@@ -82,6 +85,7 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         assertEquals(5, objectArrays.size());
     }
 
+    @Test
     public void testRightJoin() throws Exception {
         SelectItem si1 = new SelectItem(new MutableColumn("person_id", ColumnType.INTEGER));
         SelectItem si2 = new SelectItem(new MutableColumn("person_name", ColumnType.VARCHAR));
@@ -101,8 +105,8 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         data2.add(new Object[] { 2, "bad boy", "bb" });
         data2.add(new Object[] { 4, "trying harder", "try" });
 
-        DataSet ds1 = createDataSet(Lists.newArrayList(si1, si2, si3, si4 ), data1);
-        DataSet ds2 = createDataSet(Lists.newArrayList( si5, si6, si7 ), data2);
+        DataSet ds1 = createDataSet(Lists.newArrayList(si1, si2, si3, si4), data1);
+        DataSet ds2 = createDataSet(Lists.newArrayList(si5, si6, si7), data2);
         FilterItem[] onConditions = new FilterItem[1];
         onConditions[0] = new FilterItem(si4, OperatorType.EQUALS_TO, si5);
 
@@ -114,11 +118,11 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         assertEquals(3, objectArrays.size());
     }
 
+    @Test
     public void testSimpleCarthesianProduct() throws Exception {
         DataSet dataSet = MetaModelHelper.getCarthesianProduct(createDataSet1(), createDataSet2());
         List<String> results = new ArrayList<String>();
 
-
         while (dataSet.next()) {
             results.add(dataSet.getRow().toString());
         }
@@ -130,9 +134,45 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         assertTrue(results.contains("Row[values=[o, b]]"));
         assertTrue(results.contains("Row[values=[o, a]]"));
         assertTrue(results.contains("Row[values=[o, r]]"));
+    }
+
+    @Test
+    public void testGetFilteredWithScalarFunctionInWhere() throws Exception {
+        final DataSet ds1 = createDataSet3(); // contains ["w00p",true] and ["yippie",false]
+        final SelectItem selectItem1 = ds1.getSelectItems().get(0);
+        final DataSet ds2 = MetaModelHelper.getFiltered(ds1, new FilterItem(
+                selectItem1.replaceFunction(FunctionType.SUBSTRING, 2, 2), OperatorType.EQUALS_TO, "00"));
+        final List<Object[]> resultRows = ds2.toObjectArrays();
+
+        assertEquals(1, resultRows.size());
+        assertEquals("[w00p, true]", Arrays.toString(resultRows.get(0)));
+    }
+
+    @Test
+    public void testGetSelectionWithScalarFunctionInSelectItem() throws Exception {
+        final DataSet ds1 = createDataSet3(); // contains ["w00p",true] and ["yippie",false]
+        final SelectItem selectItem1 = ds1.getSelectItems().get(0);
+        final DataSet ds2 = MetaModelHelper.getSelection(new SelectItem[] { selectItem1.replaceFunction(FunctionType.SUBSTRING, 2, 2) }, ds1);
+        final List<Object[]> resultRows = ds2.toObjectArrays();
+        
+        assertEquals(2, resultRows.size());
+        assertEquals("[00]", Arrays.toString(resultRows.get(0)));
+        assertEquals("[ip]", Arrays.toString(resultRows.get(1)));
+    }
 
+    @Test
+    public void testGetSelectionWithScalarFunctionAndNonFunctionInSelectItem() throws Exception {
+        final DataSet ds1 = createDataSet3(); // contains ["w00p",true] and ["yippie",false]
+        final SelectItem selectItem1 = ds1.getSelectItems().get(0);
+        final DataSet ds2 = MetaModelHelper.getSelection(new SelectItem[] { selectItem1, selectItem1.replaceFunction(FunctionType.SUBSTRING, 2, 2) }, ds1);
+        final List<Object[]> resultRows = ds2.toObjectArrays();
+        
+        assertEquals(2, resultRows.size());
+        assertEquals("[w00p, 00]", Arrays.toString(resultRows.get(0)));
+        assertEquals("[yippie, ip]", Arrays.toString(resultRows.get(1)));
     }
 
+    @Test
     public void testTripleCarthesianProduct() throws Exception {
         DataSet dataSet = MetaModelHelper.getCarthesianProduct(createDataSet1(), createDataSet2(), createDataSet3());
         assertEquals(4, dataSet.getSelectItems().size());
@@ -142,6 +182,7 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         assertFalse(dataSet.next());
     }
 
+    @Test
     public void testTripleCarthesianProductWithWhereItems() throws Exception {
         DataSet ds1 = createDataSet1();
         DataSet ds2 = createDataSet2();
@@ -156,6 +197,7 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         assertFalse(dataSet.next());
     }
 
+    @Test
     public void testGetCarthesianProductNoRows() throws Exception {
         DataSet dataSet = MetaModelHelper.getCarthesianProduct(createDataSet4(), createDataSet2(), createDataSet3());
         assertEquals(4, dataSet.getSelectItems().size());
@@ -170,6 +212,7 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         assertFalse(dataSet.next());
     }
 
+    @Test
     public void testGetOrdered() throws Exception {
         DataSet dataSet = createDataSet3();
         List<OrderByItem> orderByItems = new ArrayList<OrderByItem>();
@@ -189,8 +232,8 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         data1.add(new Object[] { "o" });
         data1.add(new Object[] { "o" });
 
-        DataSet dataSet1 = createDataSet(
-                Lists.newArrayList( new SelectItem(new MutableColumn("foo", ColumnType.VARCHAR)) ), data1);
+        DataSet dataSet1 =
+                createDataSet(Lists.newArrayList(new SelectItem(new MutableColumn("foo", ColumnType.VARCHAR))), data1);
 
         return dataSet1;
     }
@@ -200,7 +243,7 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         data2.add(new Object[] { "b" });
         data2.add(new Object[] { "a" });
         data2.add(new Object[] { "r" });
-        DataSet dataSet2 = createDataSet(Lists.newArrayList(new SelectItem("bar", "bar") ), data2);
+        DataSet dataSet2 = createDataSet(Lists.newArrayList(new SelectItem("bar", "bar")), data2);
         return dataSet2;
     }
 
@@ -209,15 +252,15 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         data3.add(new Object[] { "w00p", true });
         data3.add(new Object[] { "yippie", false });
 
-        DataSet dataSet3 = createDataSet(Lists.newArrayList(new SelectItem("expression", "e"),
-                new SelectItem("webish?", "w") ), data3);
+        DataSet dataSet3 = createDataSet(
+                Lists.newArrayList(new SelectItem("expression", "e"), new SelectItem("webish?", "w")), data3);
 
         return dataSet3;
     }
 
     private DataSet createDataSet4() {
         List<Object[]> data4 = new ArrayList<Object[]>();
-        DataSet dataSet4 = createDataSet(Lists.newArrayList(new SelectItem("abc", "abc") ), data4);
+        DataSet dataSet4 = createDataSet(Lists.newArrayList(new SelectItem("abc", "abc")), data4);
         return dataSet4;
     }
 
@@ -234,9 +277,9 @@ public class MetaModelHelperTest extends MetaModelTestCase {
             data5.add(new Object[] { i, "Person_" + i, bigDataSetSize - (i + 1) });
         }
 
-        DataSet dataSet5 = createDataSet(Lists.newArrayList( new SelectItem(new MutableColumn("nr", ColumnType.BIGINT)),
-                new SelectItem(new MutableColumn("name", ColumnType.STRING)), new SelectItem(new MutableColumn("dnr",
-                        ColumnType.BIGINT)) ), data5);
+        DataSet dataSet5 = createDataSet(Lists.newArrayList(new SelectItem(new MutableColumn("nr", ColumnType.BIGINT)),
+                new SelectItem(new MutableColumn("name", ColumnType.STRING)),
+                new SelectItem(new MutableColumn("dnr", ColumnType.BIGINT))), data5);
         return dataSet5;
     }
 
@@ -256,6 +299,7 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         return dataSet6;
     }
 
+    @Test
     public void testGetTables() throws Exception {
         MutableTable table1 = new MutableTable("table1");
         MutableTable table2 = new MutableTable("table2");
@@ -281,6 +325,7 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         assertTrue(Arrays.asList(tables).contains(table2));
     }
 
+    @Test
     public void testGetTableColumns() throws Exception {
         MutableTable table1 = new MutableTable("table1");
         MutableColumn column1 = new MutableColumn("c1", ColumnType.BIGINT);
@@ -307,6 +352,7 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         assertSame(column3, columns[1]);
     }
 
+    @Test
     public void testGetTableFromItems() throws Exception {
         Schema schema = getExampleSchema();
         Table contributorTable = schema.getTableByName(TABLE_CONTRIBUTOR);
@@ -324,6 +370,7 @@ public class MetaModelHelperTest extends MetaModelTestCase {
                 Arrays.toString(fromItems));
     }
 
+    @Test
     public void testGetSelectionNoRows() throws Exception {
         SelectItem item1 = new SelectItem("foo", "f");
         SelectItem item2 = new SelectItem("bar", "b");
@@ -337,6 +384,7 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         assertEquals("[bar AS b, foo AS f]", Arrays.toString(ds.getSelectItems().toArray()));
     }
 
+    @Test
     public void testLeftJoinNoRowsOrSingleRow() throws Exception {
         SelectItem item1 = new SelectItem("foo", "f");
         SelectItem item2 = new SelectItem("bar", "b");
@@ -347,8 +395,8 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         DataSet ds1 = new EmptyDataSet(selectItems1);
         DataSet ds2 = new EmptyDataSet(selectItems2);
 
-        DataSet joinedDs = MetaModelHelper.getLeftJoin(ds1, ds2, new FilterItem[] { new FilterItem(item2,
-                OperatorType.EQUALS_TO, item3) });
+        DataSet joinedDs = MetaModelHelper.getLeftJoin(ds1, ds2,
+                new FilterItem[] { new FilterItem(item2, OperatorType.EQUALS_TO, item3) });
 
         assertEquals(SubSelectionDataSet.class, joinedDs.getClass());
         assertEquals("[foo AS f, bar AS b, baz AS z]", Arrays.toString(joinedDs.getSelectItems().toArray()));
@@ -357,21 +405,22 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         Row row = new DefaultRow(header1, new Object[] { 1, 2 }, null);
         ds1 = new InMemoryDataSet(header1, row);
 
-        joinedDs = MetaModelHelper.getLeftJoin(ds1, ds2, new FilterItem[] { new FilterItem(item2,
-                OperatorType.EQUALS_TO, item3) });
+        joinedDs = MetaModelHelper.getLeftJoin(ds1, ds2,
+                new FilterItem[] { new FilterItem(item2, OperatorType.EQUALS_TO, item3) });
         assertEquals("[foo AS f, bar AS b, baz AS z]", Arrays.toString(joinedDs.getSelectItems().toArray()));
         assertTrue(joinedDs.next());
         assertEquals("Row[values=[1, 2, null]]", joinedDs.getRow().toString());
         assertFalse(joinedDs.next());
     }
 
+    @Test
     public void testCarthesianProductScalability() {
 
         DataSet employees = createDataSet5();
         DataSet departmens = createDataSet6();
 
-        FilterItem fi = new FilterItem(employees.getSelectItems().get(2), OperatorType.EQUALS_TO, departmens
-                .getSelectItems().get(0));
+        FilterItem fi = new FilterItem(employees.getSelectItems().get(2), OperatorType.EQUALS_TO,
+                departmens.getSelectItems().get(0));
 
         DataSet joined = MetaModelHelper.getCarthesianProduct(new DataSet[] { employees, departmens }, fi);
         int count = 0;
@@ -380,6 +429,5 @@ public class MetaModelHelperTest extends MetaModelTestCase {
         }
 
         assertTrue(count == bigDataSetSize);
-
     }
 }
index 04cfae9..79ca76e 100644 (file)
@@ -62,18 +62,18 @@ public class QueryPostprocessDataContextTest extends MetaModelTestCase {
 
     public void testSchemaTraversalWithAliasTable() {
         final MockUpdateableDataContext dc = new MockUpdateableDataContext();
-        
+
         final Column column = dc.getColumnByQualifiedLabel("foo");
         assertEquals("table", column.getTable().getName());
     }
-    
+
     public void testNoAliasTableWhenSystemPropertySet() {
         System.setProperty(QueryPostprocessDataContext.SYSTEM_PROPERTY_CREATE_DEFAULT_TABLE_ALIAS, "false");
         try {
             final MockUpdateableDataContext dc = new MockUpdateableDataContext();
             final List<Table> tables = dc.getDefaultSchema().getTables();
             assertEquals(1, tables.size());
-            
+
             assertEquals("table", tables.get(0).getName());
         } finally {
             System.clearProperty(QueryPostprocessDataContext.SYSTEM_PROPERTY_CREATE_DEFAULT_TABLE_ALIAS);
@@ -87,7 +87,7 @@ public class QueryPostprocessDataContextTest extends MetaModelTestCase {
 
         assertEquals("table", tables.get(0).getName());
     }
-    
+
     public void testAliasTableQueries() {
         final MockUpdateableDataContext dc = new MockUpdateableDataContext();
         final List<Table> tables = dc.getDefaultSchema().getTables();
@@ -338,17 +338,15 @@ public class QueryPostprocessDataContextTest extends MetaModelTestCase {
     }
 
     public void testScalarFunctionWhere() throws Exception {
-        MockDataContext dc = new MockDataContext("sch", "tab", "1");
-        Table table = dc.getDefaultSchema().getTable(0);
+        final MockDataContext dc = new MockDataContext("sch", "tab", "1");
+        final Table table = dc.getDefaultSchema().getTable(0);
 
-        Query query = dc.query().from(table).select("foo").where(FunctionType.TO_NUMBER, "bar").eq(1).toQuery();
+        final Query query = dc.query().from(table).select("foo").where(FunctionType.TO_NUMBER, "bar").eq(1).toQuery();
         assertEquals("SELECT tab.foo FROM sch.tab WHERE TO_NUMBER(tab.bar) = 1", query.toSql());
 
-        DataSet ds = dc.executeQuery(query);
+        final DataSet ds = dc.executeQuery(query);
         assertTrue(ds.next());
-        Row row;
-
-        row = ds.getRow();
+        final Row row = ds.getRow();
         assertEquals("Row[values=[2]]", row.toString());
 
         assertFalse(ds.next());
@@ -1183,4 +1181,70 @@ public class QueryPostprocessDataContextTest extends MetaModelTestCase {
         assertEquals("[hello, world]", values.toString());
     }
 
+    public void testColumnOnlyUsedInScalarFunctionInWhereClause() throws Exception {
+        final DataContext dc = getDataContext();
+        final Query query = dc.parseQuery(
+                "SELECT contributor_id FROM contributor WHERE JAVA_SUBSTRING(name, 3, 6) = 'per' ORDER BY contributor_id");
+        try (DataSet ds = dc.executeQuery(query)) {
+            assertTrue(ds.next());
+            // kasper
+            assertEquals("1", ds.getRow().getValue(0).toString());
+            assertTrue(ds.next());
+            // jesper
+            assertEquals("6", ds.getRow().getValue(0).toString());
+            assertFalse(ds.next());
+        }
+    }
+
+    public void testQueryDifferentScalarFunctionsOnSameColumnInBothSelectAndWhere() throws Exception {
+        final DataContext dc = getDataContext();
+        final Query query = dc.parseQuery(
+                "SELECT SUBSTRING(name, 1, 3) FROM contributor WHERE JAVA_SUBSTRING(name, 3, 6) = 'per' ORDER BY contributor_id");
+
+        // assert on the parsed select items just to ensure that nothing gets mangled in the parsing
+        assertSame(FunctionType.SUBSTRING, query.getSelectClause().getItem(0).getScalarFunction());
+        assertEquals(" 1", query.getSelectClause().getItem(0).getFunctionParameters()[0]);
+        assertEquals(" 3", query.getSelectClause().getItem(0).getFunctionParameters()[1]);
+        assertSame(FunctionType.JAVA_SUBSTRING, query.getWhereClause().getItem(0).getSelectItem().getScalarFunction());
+        assertEquals(" 3", query.getWhereClause().getItem(0).getSelectItem().getFunctionParameters()[0]);
+        assertEquals(" 6", query.getWhereClause().getItem(0).getSelectItem().getFunctionParameters()[1]);
+
+        try (DataSet ds = dc.executeQuery(query)) {
+            assertTrue(ds.next());
+            // name is "kasper"
+            final Object value1 = ds.getRow().getValue(0);
+            assertEquals("kas", value1.toString());
+            assertTrue(ds.next());
+            // name is "jesper"
+            final Object value2 = ds.getRow().getValue(0);
+            assertEquals("jes", value2.toString());
+            assertFalse(ds.next());
+        }
+    }
+    
+    public void testQuerySameScalarFunctionOnSameColumnButDifferentParamsInBothSelectAndWhere() throws Exception {
+        final DataContext dc = getDataContext();
+        final Query query = dc.parseQuery(
+                "SELECT SUBSTRING(name, 1, 3) FROM contributor WHERE SUBSTRING(name, 4, 3) = 'per' ORDER BY contributor_id");
+
+        // assert on the parsed select items just to ensure that nothing gets mangled in the parsing
+        assertSame(FunctionType.SUBSTRING, query.getSelectClause().getItem(0).getScalarFunction());
+        assertEquals(" 1", query.getSelectClause().getItem(0).getFunctionParameters()[0]);
+        assertEquals(" 3", query.getSelectClause().getItem(0).getFunctionParameters()[1]);
+        assertSame(FunctionType.SUBSTRING, query.getWhereClause().getItem(0).getSelectItem().getScalarFunction());
+        assertEquals(" 4", query.getWhereClause().getItem(0).getSelectItem().getFunctionParameters()[0]);
+        assertEquals(" 3", query.getWhereClause().getItem(0).getSelectItem().getFunctionParameters()[1]);
+
+        try (DataSet ds = dc.executeQuery(query)) {
+            assertTrue(ds.next());
+            // name is "kasper"
+            final Object value1 = ds.getRow().getValue(0);
+            assertEquals("kas", value1.toString());
+            assertTrue(ds.next());
+            // name is "jesper"
+            final Object value2 = ds.getRow().getValue(0);
+            assertEquals("jes", value2.toString());
+            assertFalse(ds.next());
+        }
+    }
 }
\ No newline at end of file
index 7f412c0..dd46094 100644 (file)
@@ -24,11 +24,26 @@ import org.apache.metamodel.schema.MutableColumn;
 import org.apache.metamodel.schema.Schema;
 import org.apache.metamodel.schema.Table;
 
+import static org.junit.Assert.assertNotEquals;
+
 import java.util.List;
 
 public class SelectItemTest extends MetaModelTestCase {
 
     private Schema _schema = getExampleSchema();
+    
+    public void testEqualsAndHashCodeWithScalarFunctionParameters() {
+        final SelectItem selectItem = new SelectItem(_schema.getTableByName(TABLE_PROJECT).getColumns().get(0));
+        final SelectItem item1 = selectItem.replaceFunction(FunctionType.SUBSTRING, 2, 2);
+        final SelectItem item2 = selectItem.replaceFunction(FunctionType.SUBSTRING, 2, 2);
+        final SelectItem item3 = selectItem.replaceFunction(FunctionType.SUBSTRING, 2, 3);
+        
+        assertEquals(item1, item2);
+        assertEquals(item1.hashCode(), item2.hashCode());
+        
+        assertNotEquals(item1, item3);
+        assertNotEquals(item1.hashCode(), item3.hashCode());
+    }
 
     public void testSelectColumnInFromItem() throws Exception {
         final Table projectTable = _schema.getTableByName(TABLE_PROJECT);
diff --git a/core/src/test/java/org/apache/metamodel/query/SubstringFunctionTest.java b/core/src/test/java/org/apache/metamodel/query/SubstringFunctionTest.java
new file mode 100644 (file)
index 0000000..7566bca
--- /dev/null
@@ -0,0 +1,77 @@
+/**
+ * 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 org.apache.metamodel.data.DataSetHeader;
+import org.apache.metamodel.data.DefaultRow;
+import org.apache.metamodel.data.SimpleDataSetHeader;
+import org.apache.metamodel.schema.MutableColumn;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SubstringFunctionTest {
+
+    private final SubstringFunction javaStyleFunction = SubstringFunction.createJavaStyle();
+    private final SubstringFunction sqlStyleFunction = SubstringFunction.createSqlStyle();
+
+    @Test
+    public void testSubstringVanilla() {
+        Assert.assertEquals("2", runTest(javaStyleFunction, "123456", 1, 2));
+        Assert.assertEquals("1234", runTest(javaStyleFunction, "123456", 0, 4));
+        Assert.assertEquals("34", runTest(javaStyleFunction, "123456", 2, 4));
+        
+        Assert.assertEquals("1", runTest(sqlStyleFunction, "123456", 1, 1));
+        Assert.assertEquals("1234", runTest(sqlStyleFunction, "123456", 1, 4));
+        Assert.assertEquals("34", runTest(sqlStyleFunction, "123456", 3, 2));
+    }
+    
+    @Test
+    public void testSubstringBadOrWeirdParamValues() {
+        Assert.assertEquals("", runTest(javaStyleFunction, "123456", 0, 0));
+        Assert.assertEquals("1234", runTest(javaStyleFunction, "123456", -10, 4));
+        Assert.assertEquals("", runTest(javaStyleFunction, "123456", 4, -1));
+    }
+
+    @Test
+    public void testSubstringEndIndexTooLarge() {
+        Assert.assertEquals("123456", runTest(javaStyleFunction, "123456", 0, 200));
+        Assert.assertEquals("56", runTest(javaStyleFunction, "123456", 4, 8));
+        
+        Assert.assertEquals("123456", runTest(sqlStyleFunction, "123456", 1, 200));
+        Assert.assertEquals("56", runTest(sqlStyleFunction, "123456", 5, 5));
+    }
+
+    @Test
+    public void testSubstringStartIndexTooLarge() {
+        Assert.assertEquals("", runTest(javaStyleFunction, "123456", 200, 2));
+    }
+
+    @Test
+    public void testSubstringOnlyStartIndex() {
+        Assert.assertEquals("123456", runTest(javaStyleFunction, "123456", 0));
+        Assert.assertEquals("", runTest(javaStyleFunction, "123456", 10));
+        Assert.assertEquals("3456", runTest(javaStyleFunction, "123456", 2));
+    }
+
+    private String runTest(ScalarFunction f, String str, Object... params) {
+        SelectItem selectItem = new SelectItem(new MutableColumn("column"));
+        DataSetHeader header = new SimpleDataSetHeader(new SelectItem[] { selectItem });
+        return (String) f.evaluate(new DefaultRow(header, new Object[] { str }), params, selectItem);
+    }
+}
index fa9b66d..9e16fe4 100644 (file)
@@ -27,6 +27,7 @@ import org.apache.metamodel.MockDataContext;
 import org.apache.metamodel.query.FilterClause;
 import org.apache.metamodel.query.FilterItem;
 import org.apache.metamodel.query.FromItem;
+import org.apache.metamodel.query.FunctionType;
 import org.apache.metamodel.query.OperatorType;
 import org.apache.metamodel.query.OrderByItem;
 import org.apache.metamodel.query.OrderByItem.Direction;
@@ -83,7 +84,25 @@ public class QueryParserTest extends TestCase {
         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",
+                "SELECT MAP_VALUE(tbl.baz,'foo.bar'), MAP_VALUE(tbl.baz,'helloworld'), MAP_VALUE(tbl.baz,'hello.world') FROM sch.tbl",
+                q.toSql());
+    }
+    
+    public void testWhereMapValueUsingDotNotation() 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 baz.lorem, baz.ipsum FROM sch.tbl WHERE baz.hello = 'world'");
+        
+        final SelectItem whereSelectItem = q.getWhereClause().getItem(0).getSelectItem();
+        assertEquals(whereSelectItem.getScalarFunction(), FunctionType.MAP_VALUE);
+        assertEquals(col, whereSelectItem.getColumn());
+        assertEquals("[hello]", Arrays.toString(whereSelectItem.getFunctionParameters()));
+        
+        assertEquals(
+                "SELECT MAP_VALUE(tbl.baz,'lorem'), MAP_VALUE(tbl.baz,'ipsum') FROM sch.tbl WHERE MAP_VALUE(tbl.baz,'hello') = 'world'",
                 q.toSql());
     }
 
index 398c408..47a009c 100644 (file)
@@ -278,7 +278,7 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         // Instantiate the actual data context
         final DataContext dataContext = new MongoDbDataContext(db);
 
-        assertTrue(Arrays.asList(dataContext.getDefaultSchema().getTableNames()).contains(getCollectionName()));
+        assertTrue(dataContext.getDefaultSchema().getTableNames().contains(getCollectionName()));
         Table table = dataContext.getDefaultSchema().getTableByName(getCollectionName());
         assertEquals("[_id, baz, foo, id, list, name]", Arrays.toString(table.getColumnNames().toArray()));
 
index c7a7487..c625103 100644 (file)
@@ -26,11 +26,13 @@ import java.util.Map.Entry;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
+import java.util.function.Consumer;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 import org.apache.metamodel.DataContext;
 import org.apache.metamodel.MetaModelException;
+import org.apache.metamodel.MetaModelHelper;
 import org.apache.metamodel.QueryPostprocessDataContext;
 import org.apache.metamodel.UpdateScript;
 import org.apache.metamodel.UpdateSummary;
@@ -71,10 +73,8 @@ import com.mongodb.client.MongoIterable;
 /**
  * DataContext implementation for MongoDB.
  *
- * Since MongoDB has no schema, a virtual schema will be used in this
- * DataContext. This implementation supports either automatic discovery of a
- * schema or manual specification of a schema, through the
- * {@link SimpleTableDef} class.
+ * Since MongoDB has no schema, a virtual schema will be used in this DataContext. This implementation supports either
+ * automatic discovery of a schema or manual specification of a schema, through the {@link SimpleTableDef} class.
  */
 public class MongoDbDataContext extends QueryPostprocessDataContext implements UpdateableDataContext {
 
@@ -86,17 +86,13 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
     private Schema _schema;
 
     /**
-     * Constructs a {@link MongoDbDataContext}. This constructor accepts a
-     * custom array of {@link SimpleTableDef}s which allows the user to define
-     * his own view on the collections in the database.
+     * Constructs a {@link MongoDbDataContext}. This constructor accepts a custom array of {@link SimpleTableDef}s which
+     * allows the user to define his own view on the collections in the database.
      *
-     * @param mongoDb
-     *            the mongo db connection
-     * @param tableDefs
-     *            an array of {@link SimpleTableDef}s, which define the table
-     *            and column model of the mongo db collections. (consider using
-     *            {@link #detectSchema(MongoDatabase)} or {@link #detectTable(MongoDatabase, String)}
-     *            ).
+     * @param mongoDb the mongo db connection
+     * @param tableDefs an array of {@link SimpleTableDef}s, which define the table and column model of the mongo db
+     *            collections. (consider using {@link #detectSchema(MongoDatabase)} or
+     *            {@link #detectTable(MongoDatabase, String)} ).
      */
     public MongoDbDataContext(MongoDatabase mongoDb, SimpleTableDef... tableDefs) {
         super(false);
@@ -106,25 +102,21 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
     }
 
     /**
-     * Constructs a {@link MongoDbDataContext} and automatically detects the
-     * schema structure/view on all collections (see {@link #detectSchema(MongoDatabase)}).
+     * Constructs a {@link MongoDbDataContext} and automatically detects the schema structure/view on all collections
+     * (see {@link #detectSchema(MongoDatabase)}).
      *
-     * @param mongoDb
-     *            the mongo db connection
+     * @param mongoDb the mongo db connection
      */
     public MongoDbDataContext(MongoDatabase mongoDb) {
         this(mongoDb, detectSchema(mongoDb));
     }
 
     /**
-     * Performs an analysis of the available collections in a Mongo {@link DB}
-     * instance and tries to detect the table's structure based on the first
-     * 1000 documents in each collection.
+     * Performs an analysis of the available collections in a Mongo {@link DB} instance and tries to detect the table's
+     * structure based on the first 1000 documents in each collection.
      *
-     * @param mongoDb
-     *            the mongo db to inspect
-     * @return a mutable schema instance, useful for further fine tuning by the
-     *         user.
+     * @param mongoDb the mongo db to inspect
+     * @return a mutable schema instance, useful for further fine tuning by the user.
      * @see #detectTable(MongoDatabase, String)
      */
     public static SimpleTableDef[] detectSchema(MongoDatabase mongoDb) {
@@ -139,18 +131,15 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
     }
 
     /**
-     * Performs an analysis of an available collection in a Mongo {@link DB}
-     * instance and tries to detect the table structure based on the first 1000
-     * documents in the collection.
+     * Performs an analysis of an available collection in a Mongo {@link DB} instance and tries to detect the table
+     * structure based on the first 1000 documents in the collection.
      *
-     * @param mongoDb
-     *            the mongo DB
-     * @param collectionName
-     *            the name of the collection
+     * @param mongoDb the mongo DB
+     * @param collectionName the name of the collection
      * @return a table definition for mongo db.
      */
     public static SimpleTableDef detectTable(MongoDatabase mongoDb, String collectionName) {
-        
+
         final MongoCollection<Document> collection = mongoDb.getCollection(collectionName);
         final FindIterable<Document> iterable = collection.find().limit(1000);
 
@@ -169,7 +158,7 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
                 }
             }
         }
-           
+
         final String[] columnNames = new String[columnsAndTypes.size()];
         final ColumnType[] columnTypes = new ColumnType[columnsAndTypes.size()];
 
@@ -226,7 +215,15 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
     protected Number executeCountQuery(Table table, List<FilterItem> whereItems, boolean functionApproximationAllowed) {
         final MongoCollection<Document> collection = _mongoDb.getCollection(table.getName());
 
-        final Document query = createMongoDbQuery(table, whereItems);
+        final List<FilterItem> postProcessFilters = new ArrayList<>();
+        final Document query = createMongoDbQuery(table, whereItems, whereItem -> {
+            postProcessFilters.add(whereItem);
+        });
+
+        if (!postProcessFilters.isEmpty()) {
+            // not possible to use the native API for this
+            return null;
+        }
 
         logger.info("Executing MongoDB 'count' query: {}", query);
         final long count = collection.count(query);
@@ -239,16 +236,16 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
             Object keyValue) {
         final MongoCollection<Document> collection = _mongoDb.getCollection(table.getName());
 
-        List<FilterItem> whereItems = new ArrayList<FilterItem>();
-        SelectItem selectItem = new SelectItem(primaryKeyColumn);
-        FilterItem primaryKeyWhereItem = new FilterItem(selectItem, OperatorType.EQUALS_TO, keyValue);
+        final List<FilterItem> whereItems = new ArrayList<FilterItem>();
+        final SelectItem selectItem = new SelectItem(primaryKeyColumn);
+        final FilterItem primaryKeyWhereItem = new FilterItem(selectItem, OperatorType.EQUALS_TO, keyValue);
         whereItems.add(primaryKeyWhereItem);
-        final Document query = createMongoDbQuery(table, whereItems);
+        final Document query = createMongoDbQuery(table, whereItems, null);
         final Document resultDoc = collection.find(query).first();
 
-        DataSetHeader header = new SimpleDataSetHeader(selectItems);
+        final DataSetHeader header = new SimpleDataSetHeader(selectItems);
 
-        Row row = MongoDBUtils.toRow(resultDoc, header);
+        final Row row = MongoDBUtils.toRow(resultDoc, header);
 
         return row;
     }
@@ -289,7 +286,6 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
 
                     // prepare for a non-post-processed query
 
-
                     // checking if the query is a primary key lookup query
                     if (whereItems.size() == 1) {
                         final FilterItem whereItem = whereItems.get(0);
@@ -297,12 +293,14 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
                         if (!whereItem.isCompoundFilter() && selectItem != null && selectItem.getColumn() != null) {
                             final Column column = selectItem.getColumn();
                             if (column.isPrimaryKey() && OperatorType.EQUALS_TO.equals(whereItem.getOperator())) {
-                                logger.debug("Query is a primary key lookup query. Trying executePrimaryKeyLookupQuery(...)");
+                                logger.debug(
+                                        "Query is a primary key lookup query. Trying executePrimaryKeyLookupQuery(...)");
                                 final Object operand = whereItem.getOperand();
                                 final Row row = executePrimaryKeyLookupQuery(table, selectItems, column, operand);
                                 if (row == null) {
-                                    logger.debug("DataContext did not return any primary key lookup query results. Proceeding "
-                                            + "with manual lookup.");
+                                    logger.debug(
+                                            "DataContext did not return any primary key lookup query results. Proceeding "
+                                                    + "with manual lookup.");
                                 } else {
                                     final DataSetHeader header = new SimpleDataSetHeader(selectItems);
                                     return new InMemoryDataSet(header, row);
@@ -324,16 +322,12 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
                     }
 
                     if (thereIsAtLeastOneAlias) {
-                        final DataSet dataSet = materializeMainSchemaTableInternal(
-                                table,
-                                selectItems,
-                                whereItems,
-                                firstRow,
-                                maxRows, false);
+                        final DataSet dataSet = materializeMainSchemaTableInternal(table, selectItems, whereItems,
+                                firstRow, maxRows, false);
                         return dataSet;
                     } else {
-                        final DataSet dataSet = materializeMainSchemaTableInternal(table, selectItems, whereItems, firstRow,
-                                maxRows, false);
+                        final DataSet dataSet = materializeMainSchemaTableInternal(table, selectItems, whereItems,
+                                firstRow, maxRows, false);
                         return dataSet;
                     }
                 }
@@ -344,19 +338,38 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
         return super.executeQuery(query);
     }
 
+    private DataSet materializeMainSchemaTableInternal(Table table, List<SelectItem> selectItems,
+            List<FilterItem> whereItems, int firstRow, int maxRows, boolean queryPostProcessed) {
+        final List<FilterItem> postProcessWhereItems = new ArrayList<>();
+        final MongoCursor<Document> cursor = getDocumentMongoCursor(table, whereItems, firstRow, maxRows, whereItem -> {
+            postProcessWhereItems.add(whereItem);
+        });
 
-    private DataSet materializeMainSchemaTableInternal(Table table, List<SelectItem> selectItems, List<FilterItem> whereItems,
-            int firstRow, int maxRows, boolean queryPostProcessed) {
-        MongoCursor<Document> cursor = getDocumentMongoCursor(table, whereItems, firstRow, maxRows);
-
-        return new MongoDbDataSet(cursor, selectItems, queryPostProcessed);
+        final DataSet dataSet;
+        if (postProcessWhereItems.isEmpty()) {
+            dataSet = new MongoDbDataSet(cursor, selectItems, queryPostProcessed);
+        } else {
+            final List<SelectItem> selectItemsToQuery = new ArrayList<>(selectItems);
+            postProcessWhereItems.forEach(whereItem -> {
+                final Column column = whereItem.getSelectItem().getColumn();
+                if (column != null) {
+                    // TODO: Minor optimization possible here to avoid having multiple select items for the same column.
+                    // We could check if the column is already being queried.
+                    selectItemsToQuery.add(new SelectItem(column));
+                }
+            });
+            final DataSet innerDataSet1 = new MongoDbDataSet(cursor, selectItemsToQuery, queryPostProcessed);
+            final DataSet innerDataSet2 = MetaModelHelper.getFiltered(innerDataSet1, postProcessWhereItems);
+            dataSet = MetaModelHelper.getSelection(selectItems, innerDataSet2);
+        }
+        return dataSet;
     }
 
     private MongoCursor<Document> getDocumentMongoCursor(Table table, List<FilterItem> whereItems, int firstRow,
-            int maxRows) {
+            int maxRows, Consumer<FilterItem> filterItemsToPostProcessConsumer) {
         final MongoCollection<Document> collection = _mongoDb.getCollection(table.getName());
 
-        final Document query = createMongoDbQuery(table, whereItems);
+        final Document query = createMongoDbQuery(table, whereItems, filterItemsToPostProcessConsumer);
 
         logger.info("Executing MongoDB 'find' query: {}", query);
         FindIterable<Document> iterable = collection.find(query);
@@ -372,13 +385,18 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
         return iterable.iterator();
     }
 
-    protected Document createMongoDbQuery(Table table, List<FilterItem> whereItems) {
+    protected Document createMongoDbQuery(Table table, List<FilterItem> whereItems,
+            Consumer<FilterItem> whereItemToPostProcessConsumer) {
         assert _schema == table.getSchema();
 
         final Document query = new Document();
         if (whereItems != null && !whereItems.isEmpty()) {
             for (FilterItem item : whereItems) {
-                convertToCursorObject(query, item);
+                final boolean converted = convertToCursorObject(query, item);
+                if (!converted) {
+                    // it wasn't possible to push down the filter item
+                    whereItemToPostProcessConsumer.accept(item);
+                }
             }
         }
 
@@ -387,52 +405,74 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
 
     private static Object convertArrayToList(Object arr) {
         if (arr instanceof boolean[]) {
-            return Arrays.asList((boolean[])arr);
+            return Arrays.asList((boolean[]) arr);
         } else if (arr instanceof byte[]) {
-            return Arrays.asList((byte[])arr);
+            return Arrays.asList((byte[]) arr);
         } else if (arr instanceof short[]) {
-            return Arrays.asList((short[])arr);
+            return Arrays.asList((short[]) arr);
         } else if (arr instanceof char[]) {
-            return Arrays.asList((char[])arr);
+            return Arrays.asList((char[]) arr);
         } else if (arr instanceof int[]) {
-            return Arrays.asList((int[])arr);
+            return Arrays.asList((int[]) arr);
         } else if (arr instanceof long[]) {
-            return Arrays.asList((long[])arr);
+            return Arrays.asList((long[]) arr);
         } else if (arr instanceof float[]) {
-            return Arrays.asList((float[])arr);
+            return Arrays.asList((float[]) arr);
         } else if (arr instanceof double[]) {
-            return Arrays.asList((double[])arr);
+            return Arrays.asList((double[]) arr);
         } else if (arr instanceof Object[]) {
-            return Arrays.asList((Object[])arr);
+            return Arrays.asList((Object[]) arr);
         }
         // It's not an array.
         return null;
     }
-    
-    private void convertToCursorObject(Document query, FilterItem item) {
+
+    /**
+     * Attempts to convert a FilterItem into a refinement of a MongoDB query
+     * 
+     * @param query
+     * @param item
+     * @return true if the conversion was successful, false if not
+     */
+    private boolean convertToCursorObject(Document query, FilterItem item) {
         if (item.isCompoundFilter()) {
 
-            List<Document> orList = new ArrayList<Document>();
+            final List<Document> orList = new ArrayList<Document>();
 
             final FilterItem[] childItems = item.getChildItems();
             for (FilterItem childItem : childItems) {
-                Document childDoc = new Document();
-                convertToCursorObject(childDoc, childItem);
+                final Document childDoc = new Document();
+                boolean converted = convertToCursorObject(childDoc, childItem);
+                if (!converted) {
+                    return false;
+                }
                 orList.add(childDoc);
             }
 
             query.put("$or", orList);
-
+            return true;
         } else {
 
-            final Column column = item.getSelectItem().getColumn();
+            final SelectItem selectItem = item.getSelectItem();
+            if (selectItem.hasFunction()) {
+                // at this point, (scalar) functions in filters aren't possible to push down to the query
+                return false;
+            }
+
+            final Column column = selectItem.getColumn();
             final String columnName = column.getName();
-            final String operatorName = getOperatorName(item);
+            final String operatorName;
+            try {
+                operatorName = getOperatorName(item);
+            } catch (UnsupportedOperationException e) {
+                // not possible to push this operator down to the query
+                return false;
+            }
 
             Object operand = item.getOperand();
             if (ObjectId.isValid(String.valueOf(operand))) {
                 operand = new ObjectId(String.valueOf(operand));
-            } else if (operand != null && operand.getClass().isArray()){
+            } else if (operand != null && operand.getClass().isArray()) {
                 operand = convertArrayToList(operand);
             }
 
@@ -449,15 +489,18 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
                 }
             } else {
                 if (operatorName == null) {
-                    throw new IllegalStateException("Cannot retrieve records for a column with two EQUALS_TO operators");
+                    throw new IllegalStateException(
+                            "Cannot retrieve records for a column with two EQUALS_TO operators");
                 } else {
                     existingFilterObject.append(operatorName, operand);
                 }
             }
+
+            return true;
         }
     }
 
-    private String getOperatorName(FilterItem item) {
+    private String getOperatorName(FilterItem item) throws UnsupportedOperationException {
         final OperatorType operator = item.getOperator();
 
         if (OperatorType.EQUALS_TO.equals(operator)) {
@@ -485,7 +528,7 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
             return "$in";
         }
 
-        throw new IllegalStateException("Unsupported operator type: " + operator);
+        throw new UnsupportedOperationException("Unsupported operator type: " + operator);
     }
 
     private Pattern turnOperandIntoRegExp(Object operand) {
@@ -501,24 +544,14 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
     @Override
     protected DataSet materializeMainSchemaTable(Table table, List<Column> columns, int maxRows) {
 
-        return materializeMainSchemaTableInternal(
-                table,
-                columns.stream().map(SelectItem::new).collect(Collectors.toList()),
-                null,
-                1,
-                maxRows,
-                true);
+        return materializeMainSchemaTableInternal(table,
+                columns.stream().map(SelectItem::new).collect(Collectors.toList()), null, 1, maxRows, true);
     }
 
     @Override
     protected DataSet materializeMainSchemaTable(Table table, List<Column> columns, int firstRow, int maxRows) {
-        return materializeMainSchemaTableInternal(
-                table,
-                columns.stream().map(SelectItem::new).collect(Collectors.toList()),
-                null,
-                firstRow,
-                maxRows,
-                true);
+        return materializeMainSchemaTableInternal(table,
+                columns.stream().map(SelectItem::new).collect(Collectors.toList()), null, firstRow, maxRows, true);
     }
 
     /**
@@ -547,8 +580,7 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
     }
 
     /**
-     * Gets the {@link WriteConcernAdvisor} to use on
-     * {@link #executeUpdate(UpdateScript)} calls.
+     * Gets the {@link WriteConcernAdvisor} to use on {@link #executeUpdate(UpdateScript)} calls.
      */
     public WriteConcernAdvisor getWriteConcernAdvisor() {
         if (_writeConcernAdvisor == null) {
@@ -558,8 +590,7 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
     }
 
     /**
-     * Sets a global {@link WriteConcern} advisor to use on
-     * {@link #executeUpdate(UpdateScript)}.
+     * Sets a global {@link WriteConcern} advisor to use on {@link #executeUpdate(UpdateScript)}.
      */
     public void setWriteConcernAdvisor(WriteConcernAdvisor writeConcernAdvisor) {
         _writeConcernAdvisor = writeConcernAdvisor;
@@ -567,7 +598,8 @@ public class MongoDbDataContext extends QueryPostprocessDataContext implements U
 
     /**
      * Gets the {@link DB} instance that this {@link DataContext} is backed by.
-     * @return 
+     * 
+     * @return
      */
     public MongoDatabase getMongoDb() {
         return _mongoDb;
index 714b8c2..be667d2 100644 (file)
@@ -44,8 +44,10 @@ final class MongoDbDeleteBuilder extends AbstractRowDeletionBuilder {
         final MongoCollection<Document> collection = _updateCallback.getCollection(getTable().getName());
 
         final MongoDbDataContext dataContext = _updateCallback.getDataContext();
-        final Document query = dataContext.createMongoDbQuery(getTable(), getWhereItems());
-        
+        final Document query = dataContext.createMongoDbQuery(getTable(), getWhereItems(), whereItem -> {
+            throw new UnsupportedOperationException("Unable to handle WHERE item in DELETE: " + whereItem.toSql());
+        });
+
         DeleteResult result = collection.deleteMany(query);
         logger.info("Remove returned result: {}", result);
     }
index 78fbddc..35c74cc 100644 (file)
@@ -32,10 +32,10 @@ import org.apache.metamodel.data.InMemoryDataSet;
 import org.apache.metamodel.query.FunctionType;
 import org.apache.metamodel.query.SelectItem;
 import org.apache.metamodel.schema.ColumnType;
-import org.apache.metamodel.schema.Schema;
 import org.apache.metamodel.schema.Table;
 import org.apache.metamodel.util.SimpleTableDef;
 import org.bson.Document;
+import org.junit.Test;
 
 import com.mongodb.MongoClient;
 import com.mongodb.MongoClientURI;
@@ -47,12 +47,12 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
 
     private MongoDatabase mongoDb;
     private MongoClient client;
-    
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         if (isConfigured()) {
-            client = new MongoClient(new MongoClientURI("mongodb://"+getHostname()+":27017"));
+            client = new MongoClient(new MongoClientURI("mongodb://" + getHostname() + ":27017"));
             mongoDb = client.getDatabase(getDatabaseName());
         }
     }
@@ -66,6 +66,7 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         }
     }
 
+    @Test
     public void testNestedObjectFetching() throws Exception {
         if (!isConfigured()) {
             System.err.println(getInvalidConfigurationMessage());
@@ -84,16 +85,16 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         dbRow.append("addresses", list);
         col.insertOne(dbRow);
 
-        final MongoDbDataContext dc = new MongoDbDataContext(mongoDb, new SimpleTableDef(getCollectionName(), new String[] {
-                "name.first", "name.last", "gender", "addresses", "addresses[0].city", "addresses[0].country",
-                "addresses[5].foobar" }));
+        final MongoDbDataContext dc = new MongoDbDataContext(mongoDb,
+                new SimpleTableDef(getCollectionName(), new String[] { "name.first", "name.last", "gender", "addresses",
+                        "addresses[0].city", "addresses[0].country", "addresses[5].foobar" }));
 
         final DataSet ds = dc.query().from(getCollectionName()).selectAll().execute();
         try {
             assertTrue(ds.next());
             final Object addresses = ds.getRow().getValue(3);
-            assertEquals("Row[values=[John, Doe, MALE, " + addresses + ", Copenhagen, Denmark, null]]", ds.getRow()
-                    .toString());
+            assertEquals("Row[values=[John, Doe, MALE, " + addresses + ", Copenhagen, Denmark, null]]",
+                    ds.getRow().toString());
             assertTrue(addresses instanceof List);
             assertFalse(ds.next());
         } finally {
@@ -101,12 +102,13 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         }
     }
 
+    @Test
     public void testQueriesWithAutoGeneratedID() throws Exception {
         if (!isConfigured()) {
             System.err.println(getInvalidConfigurationMessage());
             return;
         }
-        
+
         mongoDb.createCollection(getCollectionName());
         MongoCollection<Document> col = mongoDb.getCollection(getCollectionName());
         col.withWriteConcern(WriteConcern.ACKNOWLEDGED);
@@ -138,8 +140,7 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         DataSet ds;
 
         // check all 3 entries inserted
-        ds = dc.query().from(getCollectionName()).selectAll()
-                .where("category").eq("gen_id").execute();
+        ds = dc.query().from(getCollectionName()).selectAll().where("category").eq("gen_id").execute();
         assertEquals(3, ds.toRows().size());
         ds.close();
 
@@ -150,17 +151,13 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         ds.close();
 
         // select by multiple autogenerated ids
-        ds = dc.query().from(getCollectionName()).select("name")
-                .where("_id").eq(autoGenID1)
-                .or("_id").eq(autoGenID2)
+        ds = dc.query().from(getCollectionName()).select("name").where("_id").eq(autoGenID1).or("_id").eq(autoGenID2)
                 .execute();
         assertEquals(2, ds.toRows().size());
         ds.close();
 
         // select by both autogenerated id and fixed id
-        ds = dc.query().from(getCollectionName()).select("name")
-                .where("_id").eq(autoGenID1)
-                .or("_id").eq(fixedID3)
+        ds = dc.query().from(getCollectionName()).select("name").where("_id").eq(autoGenID1).or("_id").eq(fixedID3)
                 .execute();
         assertEquals(2, ds.toRows().size());
         ds.close();
@@ -169,9 +166,7 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         dc.executeUpdate(new UpdateScript() {
             @Override
             public void run(UpdateCallback callback) {
-                callback.deleteFrom(getCollectionName())
-                        .where("_id").eq(autoGenID1)
-                        .execute();
+                callback.deleteFrom(getCollectionName()).where("_id").eq(autoGenID1).execute();
             }
         });
 
@@ -182,6 +177,7 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
 
     }
 
+    @Test
     public void testFirstRowAndMaxRows() throws Exception {
         if (!isConfigured()) {
             System.err.println(getInvalidConfigurationMessage());
@@ -226,8 +222,79 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         ds.close();
     }
 
+    @Test
+    public void testSelectNestedObject() {
+        if (!isConfigured()) {
+            System.err.println(getInvalidConfigurationMessage());
+            return;
+        }
+
+        try (DataSet ds =
+                executeNestedObjectQuery("SELECT name.given FROM " + getCollectionName() + " WHERE id = 42")) {
+            assertTrue(ds.next());
+            assertEquals("John", (String) ds.getRow().getValue(0));
+            assertFalse(ds.next());
+        }
+    }
+
+    @Test
+    public void testWhereNestedObject() {
+        if (!isConfigured()) {
+            System.err.println(getInvalidConfigurationMessage());
+            return;
+        }
+
+        try (DataSet ds =
+                executeNestedObjectQuery("SELECT id FROM " + getCollectionName() + " WHERE name.given = 'Jane'")) {
+            assertTrue(ds.next());
+            assertEquals(43, ((Number) ds.getRow().getValue(0)).intValue());
+            assertFalse(ds.next());
+        }
+    }
+
+    @Test
+    public void testSelectAndWhereNestedObject() {
+        if (!isConfigured()) {
+            System.err.println(getInvalidConfigurationMessage());
+            return;
+        }
+
+        try (DataSet ds = executeNestedObjectQuery(
+                "SELECT name.family FROM " + getCollectionName() + " WHERE name.given = 'Jane'")) {
+            assertTrue(ds.next());
+            assertEquals("Johnson", (String) ds.getRow().getValue(0));
+            assertFalse(ds.next());
+        }
+    }
+
+    // reusable method for a couple of test cases above
+    private DataSet executeNestedObjectQuery(String sql) {
+        if (mongoDb.getCollection(getCollectionName()) != null) {
+            mongoDb.getCollection(getCollectionName()).drop();
+        }
+        mongoDb.createCollection(getCollectionName());
+        final MongoCollection<Document> col = mongoDb.getCollection(getCollectionName());
+
+        // record 1: 42 John Doe
+        col.insertOne(new Document().append("id", 42).append("name",
+                new Document().append("given", "John").append("family", "Doe")));
+
+        // record 2: 43 Jane Johnson
+        col.insertOne(new Document().append("id", 43).append("name",
+                new Document().append("given", "Jane").append("family", "Johnson")));
+
+        final DataContext dataContext = new MongoDbDataContext(mongoDb);
+
+        assertTrue(dataContext.getDefaultSchema().getTableNames().contains(getCollectionName()));
+
+        final Table table = dataContext.getDefaultSchema().getTableByName(getCollectionName());
+        assertEquals("[_id, id, name]", table.getColumnNames().toString());
+
+        return dataContext.executeQuery(sql);
+    }
+
+    @Test
     public void testRead() throws Exception {
-        // Adding a comment to commit something and invoke a build in Travis...
         if (!isConfigured()) {
             System.err.println(getInvalidConfigurationMessage());
             return;
@@ -259,9 +326,9 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         // Instantiate the actual data context
         final DataContext dataContext = new MongoDbDataContext(mongoDb);
 
-        assertTrue(Arrays.asList(dataContext.getDefaultSchema().getTableNames()).contains(getCollectionName()));
-        
-        Table table = dataContext.getDefaultSchema().getTableByName(getCollectionName());
+        assertTrue(dataContext.getDefaultSchema().getTableNames().contains(getCollectionName()));
+
+        final Table table = dataContext.getDefaultSchema().getTableByName(getCollectionName());
         assertEquals("[_id, baz, foo, id, list, name]", Arrays.toString(table.getColumnNames().toArray()));
 
         assertEquals(ColumnType.MAP, table.getColumnByName("baz").getType());
@@ -276,26 +343,22 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         assertFalse(((MongoDbDataSet) ds).isQueryPostProcessed());
         try {
             assertTrue(ds.next());
-            assertEquals(
-                    "Row[values=[record no. 0, bar, Document{{count=0, constant=foobarbaz}}, [l1, l2, l3, 0]]]",
+            assertEquals("Row[values=[record no. 0, bar, Document{{count=0, constant=foobarbaz}}, [l1, l2, l3, 0]]]",
                     ds.getRow().toString());
 
             assertTrue(ds.next());
-            assertEquals(
-                    "Row[values=[record no. 5, bar, Document{{count=5, constant=foobarbaz}}, [l1, l2, l3, 5]]]",
+            assertEquals("Row[values=[record no. 5, bar, Document{{count=5, constant=foobarbaz}}, [l1, l2, l3, 5]]]",
                     ds.getRow().toString());
 
             assertTrue(ds.next());
-            assertEquals(
-                    "Row[values=[record no. 10, bar, Document{{count=10, constant=foobarbaz}}, [l1, l2, l3, 10]]]",
+            assertEquals("Row[values=[record no. 10, bar, Document{{count=10, constant=foobarbaz}}, [l1, l2, l3, 10]]]",
                     ds.getRow().toString());
 
             for (int j = 15; j < 801; j++) {
                 if (j % 5 == 0) {
                     assertTrue(ds.next());
                     assertEquals("Row[values=[record no. " + j + ", bar, Document{{count=" + j
-                            + ", constant=foobarbaz}}, [l1, l2, l3, " + j + "]]]", ds.getRow()
-                            .toString());
+                            + ", constant=foobarbaz}}, [l1, l2, l3, " + j + "]]]", ds.getRow().toString());
                 }
             }
 
@@ -365,8 +428,8 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         }
 
         // test GREATER_THAN_OR_EQUAL
-        ds = dataContext.query().from(getCollectionName()).select("id").and("name").where("id")
-                .greaterThanOrEquals(500).and("foo").isEquals("bar").execute();
+        ds = dataContext.query().from(getCollectionName()).select("id").and("name").where("id").greaterThanOrEquals(500)
+                .and("foo").isEquals("bar").execute();
         assertEquals(MongoDbDataSet.class, ds.getClass());
         assertFalse(((MongoDbDataSet) ds).isQueryPostProcessed());
 
@@ -378,8 +441,8 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
             ds.close();
         }
 
-        ds = dataContext.query().from(getCollectionName()).select("id").and("name").where("id")
-                .greaterThanOrEquals(501).and("foo").isEquals("bar").execute();
+        ds = dataContext.query().from(getCollectionName()).select("id").and("name").where("id").greaterThanOrEquals(501)
+                .and("foo").isEquals("bar").execute();
         assertEquals(MongoDbDataSet.class, ds.getClass());
         assertFalse(((MongoDbDataSet) ds).isQueryPostProcessed());
 
@@ -449,30 +512,24 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         ds.close();
     }
 
+    @Test
     public void testCreateAndWriteData() throws Exception {
         if (!isConfigured()) {
             System.err.println(getInvalidConfigurationMessage());
             return;
         }
+        
+        if (mongoDb.getCollection(getCollectionName()) != null) {
+            mongoDb.getCollection(getCollectionName()).drop();
+        }
+        
         final MongoDbDataContext dc = new MongoDbDataContext(mongoDb);
-        final Schema defaultSchema = dc.getDefaultSchema();
-
-        dc.executeUpdate(new UpdateScript() {
-            @Override
-            public void run(UpdateCallback callback) {
-                for (Table table : defaultSchema.getTables()) {
-                    callback.deleteFrom(table).execute();
-                }
-            }
-        });
-
-        assertEquals(0, defaultSchema.getTableCount());
 
         dc.executeUpdate(new UpdateScript() {
 
             @Override
             public void run(UpdateCallback callback) {
-                Table table = callback.createTable(defaultSchema, "some_entries").withColumn("foo").withColumn("bar")
+                Table table = callback.createTable(dc.getDefaultSchema(), getCollectionName()).withColumn("foo").withColumn("bar")
                         .withColumn("baz").withColumn("list").execute();
 
                 callback.insertInto(table).value("foo", 1).value("bar", "hello").execute();
@@ -487,12 +544,13 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
                         .value("list", Arrays.asList(1, 2, 3)).execute();
             }
         });
+        dc.refreshSchemas();
 
         DataSet dataSet;
-        assertEquals(1, defaultSchema.getTableCount());
+        assertEquals(1, dc.getDefaultSchema().getTableCount());
 
         // "Pure" SELECT COUNT(*) query
-        dataSet = dc.query().from("some_entries").selectCount().execute();
+        dataSet = dc.query().from(getCollectionName()).selectCount().execute();
         dataSet.close();
         assertTrue(dataSet.next());
         assertEquals(1, dataSet.getSelectItems().size());
@@ -502,7 +560,7 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         assertEquals(InMemoryDataSet.class, dataSet.getClass());
 
         // A conditional SELECT COUNT(*) query
-        dataSet = dc.query().from("some_entries").selectCount().where("foo").greaterThan(2).execute();
+        dataSet = dc.query().from(getCollectionName()).selectCount().where("foo").greaterThan(2).execute();
         dataSet.close();
         assertTrue(dataSet.next());
         assertEquals(1, dataSet.getSelectItems().size());
@@ -512,7 +570,7 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         assertEquals(InMemoryDataSet.class, dataSet.getClass());
 
         // Select columns
-        dataSet = dc.query().from("some_entries").select("foo").and("bar").and("baz").and("list").execute();
+        dataSet = dc.query().from(getCollectionName()).select("foo").and("bar").and("baz").and("list").execute();
         assertTrue(dataSet.next());
         assertEquals("Row[values=[1, hello, null, null]]", dataSet.getRow().toString());
         assertTrue(dataSet.next());
@@ -529,11 +587,11 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         dc.executeUpdate(new UpdateScript() {
             @Override
             public void run(UpdateCallback callback) {
-                callback.deleteFrom("some_entries").where("foo").greaterThan(2).where("baz").isNotNull().execute();
+                callback.deleteFrom(getCollectionName()).where("foo").greaterThan(2).where("baz").isNotNull().execute();
             }
         });
 
-        dataSet = dc.query().from("some_entries").select("foo").execute();
+        dataSet = dc.query().from(getCollectionName()).select("foo").execute();
         assertTrue(dataSet.next());
         assertEquals("Row[values=[1]]", dataSet.getRow().toString());
         assertTrue(dataSet.next());
@@ -548,16 +606,17 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         dc.executeUpdate(new UpdateScript() {
             @Override
             public void run(UpdateCallback callback) {
-                callback.dropTable("some_entries").execute();
+                callback.dropTable(getCollectionName()).execute();
             }
         });
 
-        assertNull(dc.getTableByQualifiedLabel("some_entries"));
+        assertNull(dc.getTableByQualifiedLabel(getCollectionName()));
 
         dc.refreshSchemas();
-        assertEquals(0, defaultSchema.getTableCount());
+        assertEquals(0, dc.getDefaultSchema().getTableCount());
     }
 
+    @Test
     public void testSelectWithLikeOperator() throws Exception {
         if (!isConfigured()) {
             System.err.println(getInvalidConfigurationMessage());
@@ -582,9 +641,9 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         dbRow3.append("gender", "UNKNOWN");
         col.insertOne(dbRow3);
 
-        final MongoDbDataContext dc = new MongoDbDataContext(mongoDb, new SimpleTableDef(getCollectionName(), new String[] {
-                "name.first", "name.last", "gender", "addresses", "addresses[0].city", "addresses[0].country",
-                "addresses[5].foobar" }));
+        final MongoDbDataContext dc = new MongoDbDataContext(mongoDb,
+                new SimpleTableDef(getCollectionName(), new String[] { "name.first", "name.last", "gender", "addresses",
+                        "addresses[0].city", "addresses[0].country", "addresses[5].foobar" }));
 
         final DataSet ds1 = dc.executeQuery("select * from my_collection where gender LIKE '%MALE%'");
         final DataSet ds2 = dc.executeQuery("select * from my_collection where gender LIKE 'MALE%'");
@@ -609,6 +668,7 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         }
     }
 
+    @Test
     public void testSelectWithAlias() throws Exception {
         if (!isConfigured()) {
             System.err.println(getInvalidConfigurationMessage());
@@ -624,11 +684,11 @@ public class MongoDbDataContextTest extends MongoDbTestCase {
         col.insertOne(dbRow);
 
         final MongoDbDataContext dc = new MongoDbDataContext(mongoDb,
-                new SimpleTableDef(getCollectionName(), new String[] {
-                        "name.first", "name.last", "gender", "addresses", "addresses[0].city", "addresses[0].country",
-                        "addresses[5].foobar" }));
+                new SimpleTableDef(getCollectionName(), new String[] { "name.first", "name.last", "gender", "addresses",
+                        "addresses[0].city", "addresses[0].country", "addresses[5].foobar" }));
 
-        final DataSet ds1 = dc.executeQuery("select gender as my_gender, name.first as my_name from my_collection where gender LIKE '%MALE%'");
+        final DataSet ds1 = dc.executeQuery(
+                "select gender as my_gender, name.first as my_name from my_collection where gender LIKE '%MALE%'");
         final SelectItem[] selectItems = ds1.getSelectItems().toArray(new SelectItem[ds1.getSelectItems().size()]);
         SelectItem firstSelectItem = selectItems[0];
         SelectItem secondSelectItem = selectItems[1];