You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metamodel.apache.org by ka...@apache.org on 2013/07/22 10:10:58 UTC

[45/64] [partial] Hard rename of all 'org/eobjects' folders to 'org/apache'.

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/FilterItem.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/FilterItem.java b/core/src/main/java/org/apache/metamodel/query/FilterItem.java
new file mode 100644
index 0000000..86fdd20
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/FilterItem.java
@@ -0,0 +1,542 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eobjects.metamodel.data.IRowFilter;
+import org.eobjects.metamodel.data.Row;
+import org.eobjects.metamodel.schema.Column;
+import org.eobjects.metamodel.schema.ColumnType;
+import org.eobjects.metamodel.util.BaseObject;
+import org.eobjects.metamodel.util.CollectionUtils;
+import org.eobjects.metamodel.util.FormatHelper;
+import org.eobjects.metamodel.util.ObjectComparator;
+import org.eobjects.metamodel.util.WildcardPattern;
+
+/**
+ * Represents a filter in a query that resides either within a WHERE clause or a
+ * HAVING clause
+ * 
+ * @see FilterClause
+ * @see OperatorType
+ * @see LogicalOperator
+ */
+public class FilterItem extends BaseObject implements QueryItem, Cloneable, IRowFilter {
+
+    private static final long serialVersionUID = 2435322742894653227L;
+
+    private Query _query;
+    private final SelectItem _selectItem;
+    private final OperatorType _operator;
+    private final Object _operand;
+    private final List<FilterItem> _childItems;
+    private final LogicalOperator _logicalOperator;
+    private final String _expression;
+    private transient Set<?> _inValues;
+
+    /**
+     * Private constructor, used for cloning
+     */
+    private FilterItem(SelectItem selectItem, OperatorType operator, Object operand, List<FilterItem> orItems,
+            String expression, LogicalOperator logicalOperator) {
+        _selectItem = selectItem;
+        _operator = operator;
+        _operand = validateOperand(operand);
+        _childItems = orItems;
+        _expression = expression;
+        _logicalOperator = logicalOperator;
+    }
+
+    private Object validateOperand(Object operand) {
+        if (operand instanceof Column) {
+            // gracefully convert to a select item.
+            operand = new SelectItem((Column) operand);
+        }
+        return operand;
+    }
+
+    /**
+     * Creates a single filter item based on a SelectItem, an operator and an
+     * operand.
+     * 
+     * @param selectItem
+     *            the selectItem to put constraints on, cannot be null
+     * @param operator
+     *            The operator to use. Can be OperatorType.EQUALS_TO,
+     *            OperatorType.DIFFERENT_FROM,
+     *            OperatorType.GREATER_THAN,OperatorType.LESS_THAN
+     * @param operand
+     *            The operand. Can be a constant like null or a String, a
+     *            Number, a Boolean, a Date, a Time, a DateTime. Or another
+     *            SelectItem
+     * @throws IllegalArgumentException
+     *             if the SelectItem is null or if the combination of operator
+     *             and operand does not make sense.
+     */
+    public FilterItem(SelectItem selectItem, OperatorType operator, Object operand) throws IllegalArgumentException {
+        this(selectItem, operator, operand, null, null, null);
+        if (_operand == null) {
+            require("Can only use EQUALS or DIFFERENT_FROM operator with null-operand",
+                    _operator == OperatorType.DIFFERENT_FROM || _operator == OperatorType.EQUALS_TO);
+        }
+        if (_operator == OperatorType.LIKE) {
+            ColumnType type = _selectItem.getColumn().getType();
+            if (type != null) {
+                require("Can only use LIKE operator with strings", type.isLiteral()
+                        && (_operand instanceof String || _operand instanceof SelectItem));
+            }
+        }
+        require("SelectItem cannot be null", _selectItem != null);
+    }
+
+    /**
+     * Creates a single unvalidated filter item based on a expression.
+     * Expression based filters are typically NOT datastore-neutral but are
+     * available for special "hacking" needs.
+     * 
+     * Expression based filters can only be used for JDBC based datastores since
+     * they are translated directly into SQL.
+     * 
+     * @param expression
+     *            An expression to use for the filter, for example
+     *            "YEAR(my_date) = 2008".
+     */
+    public FilterItem(String expression) {
+        this(null, null, null, null, expression, null);
+
+        require("Expression cannot be null", _expression != null);
+    }
+
+    /**
+     * Creates a composite filter item based on other filter items. Each
+     * provided filter items will be OR'ed meaning that if one of the evaluates
+     * as true, then the composite filter will be evaluated as true
+     * 
+     * @param items
+     *            a list of items to include in the composite
+     */
+    public FilterItem(List<FilterItem> items) {
+        this(LogicalOperator.OR, items);
+    }
+
+    /**
+     * Creates a compound filter item based on other filter items. Each provided
+     * filter item will be combined according to the {@link LogicalOperator}.
+     * 
+     * @param logicalOperator
+     *            the logical operator to apply
+     * @param items
+     *            a list of items to include in the composite
+     */
+    public FilterItem(LogicalOperator logicalOperator, List<FilterItem> items) {
+        this(null, null, null, items, null, logicalOperator);
+
+        require("Child items cannot be null", _childItems != null);
+        require("Child items cannot be empty", !_childItems.isEmpty());
+    }
+
+    /**
+     * Creates a compound filter item based on other filter items. Each provided
+     * filter item will be combined according to the {@link LogicalOperator}.
+     * 
+     * @param logicalOperator
+     *            the logical operator to apply
+     * @param items
+     *            an array of items to include in the composite
+     */
+    public FilterItem(LogicalOperator logicalOperator, FilterItem... items) {
+        this(logicalOperator, Arrays.asList(items));
+    }
+
+    /**
+     * Creates a compound filter item based on other filter items. Each provided
+     * filter items will be OR'ed meaning that if one of the evaluates as true,
+     * then the compound filter will be evaluated as true
+     * 
+     * @param items
+     *            an array of items to include in the composite
+     */
+    public FilterItem(FilterItem... items) {
+        this(Arrays.asList(items));
+    }
+
+    private void require(String errorMessage, boolean b) {
+        if (!b) {
+            throw new IllegalArgumentException(errorMessage);
+        }
+    }
+
+    public SelectItem getSelectItem() {
+        return _selectItem;
+    }
+
+    public OperatorType getOperator() {
+        return _operator;
+    }
+
+    public Object getOperand() {
+        return _operand;
+    }
+
+    public String getExpression() {
+        return _expression;
+    }
+
+    public Query getQuery() {
+        return _query;
+    }
+
+    public LogicalOperator getLogicalOperator() {
+        return _logicalOperator;
+    }
+
+    public FilterItem setQuery(Query query) {
+        _query = query;
+        if (_childItems == null) {
+            if (_expression == null) {
+                if (_selectItem.getQuery() == null) {
+                    _selectItem.setQuery(_query);
+                }
+                if (_operand instanceof SelectItem) {
+                    SelectItem operand = (SelectItem) _operand;
+                    if (operand.getQuery() == null) {
+                        operand.setQuery(_query);
+                    }
+                }
+            }
+        } else {
+            for (FilterItem item : _childItems) {
+                if (item.getQuery() == null) {
+                    item.setQuery(_query);
+                }
+            }
+        }
+        return this;
+    }
+    
+    @Override
+    public String toSql() {
+        return toSql(false);
+    }
+
+    /**
+     * Parses the constraint as a SQL Where-clause item
+     */
+    @Override
+    public String toSql(boolean includeSchemaInColumnPaths) {
+        if (_expression != null) {
+            return _expression;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        if (_childItems == null) {
+            sb.append(_selectItem.getSameQueryAlias(includeSchemaInColumnPaths));
+
+            if (_operand == null && _operator == OperatorType.EQUALS_TO) {
+                sb.append(" IS NULL");
+            } else if (_operand == null && _operator == OperatorType.DIFFERENT_FROM) {
+                sb.append(" IS NOT NULL");
+            } else {
+                final Object operand = appendOperator(sb, _operand, _operator);
+
+                if (operand instanceof SelectItem) {
+                    final String selectItemString = ((SelectItem) operand).getSameQueryAlias(includeSchemaInColumnPaths);
+                    sb.append(selectItemString);
+                } else {
+                    ColumnType columnType = _selectItem.getExpectedColumnType();
+                    final String sqlValue = FormatHelper.formatSqlValue(columnType, operand);
+                    sb.append(sqlValue);
+                }
+            }
+        } else {
+            sb.append('(');
+            for (int i = 0; i < _childItems.size(); i++) {
+                FilterItem item = _childItems.get(i);
+                if (i != 0) {
+                    sb.append(' ');
+                    sb.append(_logicalOperator.toString());
+                    sb.append(' ');
+                }
+                sb.append(item.toSql());
+            }
+            sb.append(')');
+        }
+
+        return sb.toString();
+    }
+
+    @SuppressWarnings("deprecation")
+    public static Object appendOperator(StringBuilder sb, Object operand, OperatorType operator) {
+        switch (operator) {
+        case DIFFERENT_FROM:
+            sb.append(" <> ");
+            break;
+        case EQUALS_TO:
+            sb.append(" = ");
+            break;
+        case LIKE:
+            sb.append(" LIKE ");
+            break;
+        case GREATER_THAN:
+        case HIGHER_THAN:
+            sb.append(" > ");
+            break;
+        case LESS_THAN:
+        case LOWER_THAN:
+            sb.append(" < ");
+            break;
+        case IN:
+            sb.append(" IN ");
+            operand = CollectionUtils.toList(operand);
+            break;
+        default:
+            throw new IllegalStateException("Operator could not be determined");
+        }
+        return operand;
+    }
+
+    /**
+     * Does a "manual" evaluation, useful for CSV data and alike, where queries
+     * cannot be created.
+     */
+    public boolean evaluate(Row row) {
+        require("Expression-based filters cannot be manually evaluated", _expression == null);
+
+        if (_childItems == null) {
+            // Evaluate a single constraint
+            Object selectItemValue = row.getValue(_selectItem);
+            Object operandValue = _operand;
+            if (_operand instanceof SelectItem) {
+                SelectItem selectItem = (SelectItem) _operand;
+                operandValue = row.getValue(selectItem);
+            }
+            if (operandValue == null) {
+                if (_operator == OperatorType.DIFFERENT_FROM) {
+                    return (selectItemValue != null);
+                } else if (_operator == OperatorType.EQUALS_TO) {
+                    return (selectItemValue == null);
+                } else {
+                    return false;
+                }
+            } else if (selectItemValue == null) {
+                if (_operator == OperatorType.DIFFERENT_FROM) {
+                    return true;
+                } else {
+                    return false;
+                }
+            } else {
+                return compare(selectItemValue, operandValue);
+            }
+        } else {
+
+            // Evaluate several constraints
+            if (_logicalOperator == LogicalOperator.AND) {
+                // require all results to be true
+                for (FilterItem item : _childItems) {
+                    boolean result = item.evaluate(row);
+                    if (!result) {
+                        return false;
+                    }
+                }
+                return true;
+            } else {
+                // require at least one result to be true
+                for (FilterItem item : _childItems) {
+                    boolean result = item.evaluate(row);
+                    if (result) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private boolean compare(Object selectItemValue, Object operandValue) {
+        Comparator<Object> comparator = ObjectComparator.getComparator();
+        if (_operator == OperatorType.DIFFERENT_FROM) {
+            return comparator.compare(selectItemValue, operandValue) != 0;
+        } else if (_operator == OperatorType.EQUALS_TO) {
+            return comparator.compare(selectItemValue, operandValue) == 0;
+        } else if (_operator == OperatorType.GREATER_THAN || _operator == OperatorType.HIGHER_THAN) {
+            return comparator.compare(selectItemValue, operandValue) > 0;
+        } else if (_operator == OperatorType.LESS_THAN || _operator == OperatorType.LOWER_THAN) {
+            return comparator.compare(selectItemValue, operandValue) < 0;
+        } else if (_operator == OperatorType.LIKE) {
+            WildcardPattern matcher = new WildcardPattern((String) operandValue, '%');
+            return matcher.matches((String) selectItemValue);
+        } else if (_operator == OperatorType.IN) {
+            Set<?> inValues = getInValues();
+            return inValues.contains(selectItemValue);
+        } else {
+            throw new IllegalStateException("Operator could not be determined");
+        }
+    }
+
+    /**
+     * Lazy initializes a set (for fast searching) of IN values.
+     * 
+     * @return a hash set appropriate for IN clause evaluation
+     */
+    private Set<?> getInValues() {
+        if (_inValues == null) {
+            if (_operand instanceof Set) {
+                _inValues = (Set<?>) _operand;
+            } else {
+                List<?> list = CollectionUtils.toList(_operand);
+                _inValues = new HashSet<Object>(list);
+            }
+        }
+        return _inValues;
+    }
+
+    @Override
+    protected FilterItem clone() {
+        final List<FilterItem> orItems;
+        if (_childItems == null) {
+            orItems = null;
+        } else {
+            orItems = new ArrayList<FilterItem>(_childItems);
+        }
+
+        final Object operand;
+        if (_operand instanceof SelectItem) {
+            operand = ((SelectItem) _operand).clone();
+        } else {
+            operand = _operand;
+        }
+
+        final SelectItem selectItem;
+        if (_selectItem == null) {
+            selectItem = null;
+        } else {
+            selectItem = _selectItem.clone();
+        }
+
+        return new FilterItem(selectItem, _operator, operand, orItems, _expression, _logicalOperator);
+    }
+
+    public boolean isReferenced(Column column) {
+        if (column != null) {
+            if (_selectItem != null) {
+                if (_selectItem.isReferenced(column)) {
+                    return true;
+                }
+            }
+            if (_operand != null && _operand instanceof SelectItem) {
+                if (((SelectItem) _operand).isReferenced(column)) {
+                    return true;
+                }
+            }
+            if (_childItems != null) {
+                for (FilterItem item : _childItems) {
+                    if (item.isReferenced(column)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    protected void decorateIdentity(List<Object> identifiers) {
+        identifiers.add(_expression);
+        identifiers.add(_operand);
+        identifiers.add(_childItems);
+        identifiers.add(_operator);
+        identifiers.add(_selectItem);
+        identifiers.add(_logicalOperator);
+    }
+
+    /**
+     * Gets the {@link FilterItem}s that this filter item consists of, if it is
+     * a compound filter item.
+     * 
+     * @return
+     * @deprecated use {@link #getChildItems()} instead
+     */
+    @Deprecated
+    public FilterItem[] getOrItems() {
+        return getChildItems();
+    }
+
+    /**
+     * Gets the number of child items, if this is a compound filter item.
+     * 
+     * @return
+     * @deprecated use {@link #getChildItemCount()} instead.
+     */
+    @Deprecated
+    public int getOrItemCount() {
+        return getChildItemCount();
+    }
+
+    /**
+     * Get the number of child items, if this is a compound filter item.
+     * 
+     * @return
+     */
+    public int getChildItemCount() {
+        if (_childItems == null) {
+            return 0;
+        }
+        return _childItems.size();
+    }
+
+    /**
+     * Gets the {@link FilterItem}s that this filter item consists of, if it is
+     * a compound filter item.
+     * 
+     * @return
+     */
+    public FilterItem[] getChildItems() {
+        if (_childItems == null) {
+            return null;
+        }
+        return _childItems.toArray(new FilterItem[_childItems.size()]);
+    }
+
+    /**
+     * Determines whether this {@link FilterItem} is a compound filter or not
+     * (ie. if it has child items or not)
+     * 
+     * @return
+     */
+    public boolean isCompoundFilter() {
+        return _childItems != null;
+    }
+
+    @Override
+    public String toString() {
+        return toSql();
+    }
+
+    @Override
+    public boolean accept(Row row) {
+        return evaluate(row);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/FromClause.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/FromClause.java b/core/src/main/java/org/apache/metamodel/query/FromClause.java
new file mode 100644
index 0000000..950c246
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/FromClause.java
@@ -0,0 +1,103 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+import org.eobjects.metamodel.schema.Table;
+
+/**
+ * Represents the FROM clause of a query containing FromItem's.
+ * 
+ * @see FromItem
+ */
+public class FromClause extends AbstractQueryClause<FromItem> {
+
+    private static final long serialVersionUID = -8227310702249122115L;
+
+    public FromClause(Query query) {
+        super(query, AbstractQueryClause.PREFIX_FROM, AbstractQueryClause.DELIM_COMMA);
+    }
+
+    /**
+     * Gets the alias of a table, if it is registered (and visible, ie. not part
+     * of a sub-query) in the FromClause
+     * 
+     * @param table
+     *            the table to get the alias for
+     * @return the alias or null if none is found
+     */
+    public String getAlias(Table table) {
+        if (table != null) {
+            for (FromItem item : getItems()) {
+                String alias = item.getAlias(table);
+                if (alias != null) {
+                    return alias;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Retrieves a table by it's reference which may be it's alias or it's
+     * qualified table name. Typically, this method is used to resolve a
+     * SelectItem with a reference like "foo.bar", where "foo" may either be an
+     * alias or a table name
+     * 
+     * @param reference
+     * @return a FromItem which matches the provided reference string
+     */
+    public FromItem getItemByReference(String reference) {
+        if (reference == null) {
+            return null;
+        }
+        for (final FromItem item : getItems()) {
+            FromItem result = getItemByReference(item, reference);
+            if (result != null) {
+                return result;
+            }
+        }
+        return null;
+    }
+
+    private FromItem getItemByReference(FromItem item, String reference) {
+        final String alias = item.getAlias();
+        if (reference.equals(alias)) {
+            return item;
+        }
+
+        final Table table = item.getTable();
+        if (alias == null && table != null && reference.equals(table.getName())) {
+            return item;
+        }
+
+        final JoinType join = item.getJoin();
+        if (join != null) {
+            final FromItem leftResult = getItemByReference(item.getLeftSide(), reference);
+            if (leftResult != null) {
+                return leftResult;
+            }
+            final FromItem rightResult = getItemByReference(item.getRightSide(), reference);
+            if (rightResult != null) {
+                return rightResult;
+            }
+        }
+
+        return null;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/FromItem.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/FromItem.java b/core/src/main/java/org/apache/metamodel/query/FromItem.java
new file mode 100644
index 0000000..2ea22bd
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/FromItem.java
@@ -0,0 +1,349 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+import java.util.List;
+
+import org.eobjects.metamodel.schema.Column;
+import org.eobjects.metamodel.schema.Relationship;
+import org.eobjects.metamodel.schema.Table;
+import org.eobjects.metamodel.util.BaseObject;
+
+/**
+ * Represents a FROM item. FROM items can take different forms:
+ * <ul>
+ * <li>table FROMs (eg. "FROM products p")</li>
+ * <lI>join FROMs with an ON clause (eg. "FROM products p INNER JOIN orders o ON
+ * p.id = o.product_id")</li>
+ * <li>subquery FROMs (eg. "FROM (SELECT * FROM products) p")</li>
+ * <li>expression FROM (any string based from item)</li>
+ * </ul>
+ * 
+ * @see FromClause
+ */
+public class FromItem extends BaseObject implements QueryItem, Cloneable {
+
+    private static final long serialVersionUID = -6559220014058975193L;
+    private Table _table;
+    private String _alias;
+    private Query _subQuery;
+    private JoinType _join;
+    private FromItem _leftSide;
+    private FromItem _rightSide;
+    private SelectItem[] _leftOn;
+    private SelectItem[] _rightOn;
+    private Query _query;
+    private String _expression;
+
+    /**
+     * Private constructor, used for cloning
+     */
+    private FromItem() {
+    }
+
+    /**
+     * Constructor for table FROM clauses
+     */
+    public FromItem(Table table) {
+        _table = table;
+    }
+
+    /**
+     * Constructor for sub-query FROM clauses
+     * 
+     * @param subQuery
+     *            the subquery to use
+     */
+    public FromItem(Query subQuery) {
+        _subQuery = subQuery;
+    }
+
+    /**
+     * Constructor for join FROM clauses that join two tables using their
+     * relationship. The primary table of the relationship will be the left side
+     * of the join and the foreign table of the relationship will be the right
+     * side of the join.
+     * 
+     * @param join
+     *            the join type to use
+     * @param relationship
+     *            the relationship to use for joining the tables
+     */
+    public FromItem(JoinType join, Relationship relationship) {
+        _join = join;
+        _leftSide = new FromItem(relationship.getPrimaryTable());
+        Column[] columns = relationship.getPrimaryColumns();
+        _leftOn = new SelectItem[columns.length];
+        for (int i = 0; i < columns.length; i++) {
+            _leftOn[i] = new SelectItem(columns[i]);
+        }
+        _rightSide = new FromItem(relationship.getForeignTable());
+        columns = relationship.getForeignColumns();
+        _rightOn = new SelectItem[columns.length];
+        for (int i = 0; i < columns.length; i++) {
+            _rightOn[i] = new SelectItem(columns[i]);
+        }
+    }
+
+    /**
+     * Constructor for advanced join types with custom relationships
+     * 
+     * @param join
+     *            the join type to use
+     * @param leftSide
+     *            the left side of the join
+     * @param rightSide
+     *            the right side of the join
+     * @param leftOn
+     *            what left-side select items to use for the ON clause
+     * @param rightOn
+     *            what right-side select items to use for the ON clause
+     */
+    public FromItem(JoinType join, FromItem leftSide, FromItem rightSide, SelectItem[] leftOn, SelectItem[] rightOn) {
+        _join = join;
+        _leftSide = leftSide;
+        _rightSide = rightSide;
+        _leftOn = leftOn;
+        _rightOn = rightOn;
+    }
+
+    /**
+     * Creates a single unvalidated from item based on a expression. Expression
+     * based from items are typically NOT datastore-neutral but are available
+     * for special "hacking" needs.
+     * 
+     * Expression based from items can only be used for JDBC based datastores
+     * since they are translated directly into SQL.
+     * 
+     * @param expression
+     *            An expression to use for the from item, for example "MYTABLE".
+     */
+    public FromItem(String expression) {
+        if (expression == null) {
+            throw new IllegalArgumentException("Expression cannot be null");
+        }
+        _expression = expression;
+    }
+
+    public String getAlias() {
+        return _alias;
+    }
+
+    public String getSameQueryAlias() {
+        if (_alias != null) {
+            return _alias;
+        }
+        if (_table != null) {
+            return _table.getQuotedName();
+        }
+        return null;
+    }
+
+    public FromItem setAlias(String alias) {
+        _alias = alias;
+        return this;
+    }
+
+    public Table getTable() {
+        return _table;
+    }
+
+    public Query getSubQuery() {
+        return _subQuery;
+    }
+
+    public JoinType getJoin() {
+        return _join;
+    }
+
+    public FromItem getLeftSide() {
+        return _leftSide;
+    }
+
+    public FromItem getRightSide() {
+        return _rightSide;
+    }
+
+    public SelectItem[] getLeftOn() {
+        return _leftOn;
+    }
+
+    public SelectItem[] getRightOn() {
+        return _rightOn;
+    }
+
+    public String getExpression() {
+        return _expression;
+    }
+
+    @Override
+    public String toSql() {
+        return toSql(false);
+    }
+
+    @Override
+    public String toSql(boolean includeSchemaInColumnPaths) {
+        final String stringNoAlias = toStringNoAlias(includeSchemaInColumnPaths);
+        final StringBuilder sb = new StringBuilder(stringNoAlias);
+        if (_join != null && _alias != null) {
+            sb.insert(0, '(');
+            sb.append(')');
+        }
+        if (_alias != null) {
+            sb.append(' ');
+            sb.append(_alias);
+        }
+        return sb.toString();
+    }
+
+    public String toStringNoAlias() {
+        return toStringNoAlias(false);
+    }
+
+    public String toStringNoAlias(boolean includeSchemaInColumnPaths) {
+        if (_expression != null) {
+            return _expression;
+        }
+        StringBuilder sb = new StringBuilder();
+        if (_table != null) {
+            if (_table.getSchema() != null && _table.getSchema().getName() != null) {
+                sb.append(_table.getSchema().getName());
+                sb.append('.');
+            }
+            sb.append(_table.getQuotedName());
+        } else if (_subQuery != null) {
+            sb.append('(');
+            sb.append(_subQuery.toSql(includeSchemaInColumnPaths));
+            sb.append(')');
+        } else if (_join != null) {
+            String leftSideAlias = _leftSide.getSameQueryAlias();
+            String rightSideAlias = _rightSide.getSameQueryAlias();
+            sb.append(_leftSide.toSql());
+            sb.append(' ');
+            sb.append(_join);
+            sb.append(" JOIN ");
+            sb.append(_rightSide.toSql());
+            for (int i = 0; i < _leftOn.length; i++) {
+                if (i == 0) {
+                    sb.append(" ON ");
+                } else {
+                    sb.append(" AND ");
+                }
+                SelectItem primary = _leftOn[i];
+                appendJoinOnItem(sb, leftSideAlias, primary);
+
+                sb.append(" = ");
+
+                SelectItem foreign = _rightOn[i];
+                appendJoinOnItem(sb, rightSideAlias, foreign);
+            }
+        }
+        return sb.toString();
+    }
+
+    private void appendJoinOnItem(StringBuilder sb, String sideAlias, SelectItem onItem) {
+        final FromItem fromItem = onItem.getFromItem();
+        if (fromItem != null && fromItem.getSubQuery() != null && fromItem.getAlias() != null) {
+            // there's a corner case scenario where an ON item references a
+            // subquery being joined. In that case the getSuperQueryAlias()
+            // method will include the subquery alias.
+            final String superQueryAlias = onItem.getSuperQueryAlias();
+            sb.append(superQueryAlias);
+            return;
+        }
+
+        if (sideAlias != null) {
+            sb.append(sideAlias);
+            sb.append('.');
+        }
+        final String superQueryAlias = onItem.getSuperQueryAlias();
+        sb.append(superQueryAlias);
+    }
+
+    /**
+     * Gets the alias of a table, if it is registered (and visible, ie. not part
+     * of a sub-query) in the FromItem
+     * 
+     * @param table
+     *            the table to get the alias for
+     * @return the alias or null if none is found
+     */
+    public String getAlias(Table table) {
+        String result = null;
+        if (table != null) {
+            // Search recursively through left and right side, unless they
+            // are sub-query FromItems
+            if (table.equals(_table)) {
+                result = _alias;
+            } else if (_join != null) {
+                result = _rightSide.getAlias(table);
+                if (result == null) {
+                    result = _leftSide.getAlias(table);
+                }
+            }
+        }
+        return result;
+    }
+
+    public Query getQuery() {
+        return _query;
+    }
+
+    public QueryItem setQuery(Query query) {
+        _query = query;
+        return this;
+    }
+
+    @Override
+    protected FromItem clone() {
+        FromItem f = new FromItem();
+        f._alias = _alias;
+        f._join = _join;
+        f._table = _table;
+        f._expression = _expression;
+        if (_subQuery != null) {
+            f._subQuery = _subQuery.clone();
+        }
+        if (_leftOn != null && _leftSide != null && _rightOn != null && _rightSide != null) {
+            f._leftSide = _leftSide.clone();
+            f._leftOn = _leftOn.clone();
+            f._rightSide = _rightSide.clone();
+            f._rightOn = _rightOn.clone();
+        }
+        return f;
+    }
+
+    @Override
+    protected void decorateIdentity(List<Object> identifiers) {
+        identifiers.add(_table);
+        identifiers.add(_alias);
+        identifiers.add(_subQuery);
+        identifiers.add(_join);
+        identifiers.add(_leftSide);
+        identifiers.add(_rightSide);
+        identifiers.add(_leftOn);
+        identifiers.add(_rightOn);
+        identifiers.add(_expression);
+    }
+
+    @Override
+    public String toString() {
+        return toSql();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/FunctionType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/FunctionType.java b/core/src/main/java/org/apache/metamodel/query/FunctionType.java
new file mode 100644
index 0000000..c8d79c7
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/FunctionType.java
@@ -0,0 +1,122 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+import org.eobjects.metamodel.schema.Column;
+import org.eobjects.metamodel.schema.ColumnType;
+import org.eobjects.metamodel.util.AggregateBuilder;
+
+/**
+ * Represents an aggregate function to use in a SelectItem.
+ * 
+ * @see SelectItem
+ */
+public enum FunctionType {
+
+    COUNT {
+        @Override
+        public AggregateBuilder<Long> build() {
+            return new CountAggregateBuilder();
+        }
+    },
+    AVG {
+        @Override
+        public AggregateBuilder<Double> build() {
+            return new AverageAggregateBuilder();
+        }
+    },
+    SUM {
+        @Override
+        public AggregateBuilder<Double> build() {
+            return new SumAggregateBuilder();
+        }
+    },
+    MAX {
+        @Override
+        public AggregateBuilder<Object> build() {
+            return new MaxAggregateBuilder();
+        }
+    },
+    MIN {
+        @Override
+        public AggregateBuilder<Object> build() {
+            return new MinAggregateBuilder();
+        }
+    };
+
+    public ColumnType getExpectedColumnType(ColumnType type) {
+        switch (this) {
+        case COUNT:
+            return ColumnType.BIGINT;
+        case AVG:
+        case SUM:
+            return ColumnType.DOUBLE;
+        case MAX:
+        case MIN:
+            return type;
+        default:
+            return type;
+        }
+    }
+
+    public SelectItem createSelectItem(Column column) {
+        return new SelectItem(this, column);
+    }
+
+    public SelectItem createSelectItem(String expression, String alias) {
+        return new SelectItem(this, expression, alias);
+    }
+
+    public Object evaluate(Iterable<?> values) {
+        AggregateBuilder<?> builder = build();
+        for (Object object : values) {
+            builder.add(object);
+        }
+        return builder.getAggregate();
+    }
+
+    /**
+     * Executes the function
+     * 
+     * @param values
+     *            the values to be evaluated. If a value is null it won't be
+     *            evaluated
+     * @return the result of the function execution. The return type class is
+     *         dependent on the FunctionType and the values provided. COUNT
+     *         yields a Long, AVG and SUM yields Double values and MAX and MIN
+     *         yields the type of the provided values.
+     */
+    public Object evaluate(Object... values) {
+        AggregateBuilder<?> builder = build();
+        for (Object object : values) {
+            builder.add(object);
+        }
+        return builder.getAggregate();
+    }
+
+    public abstract AggregateBuilder<?> build();
+
+    public static FunctionType get(String functionName) {
+        try {
+            return valueOf(functionName);
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/GroupByClause.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/GroupByClause.java b/core/src/main/java/org/apache/metamodel/query/GroupByClause.java
new file mode 100644
index 0000000..8e7e6bd
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/GroupByClause.java
@@ -0,0 +1,47 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents the GROUP BY clause of a query that contains GroupByItem's.
+ * 
+ * @see GroupByItem
+ */
+public class GroupByClause extends AbstractQueryClause<GroupByItem> {
+
+	private static final long serialVersionUID = -3824934110331202101L;
+
+	public GroupByClause(Query query) {
+		super(query, AbstractQueryClause.PREFIX_GROUP_BY,
+				AbstractQueryClause.DELIM_COMMA);
+	}
+
+	public List<SelectItem> getEvaluatedSelectItems() {
+		final List<SelectItem> result = new ArrayList<SelectItem>();
+		final List<GroupByItem> items = getItems();
+		for (GroupByItem item : items) {
+			result.add(item.getSelectItem());
+		}
+		return result;
+	}
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/GroupByItem.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/GroupByItem.java b/core/src/main/java/org/apache/metamodel/query/GroupByItem.java
new file mode 100644
index 0000000..cf94623
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/GroupByItem.java
@@ -0,0 +1,91 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+import java.util.List;
+
+import org.eobjects.metamodel.util.BaseObject;
+
+/**
+ * Represents a GROUP BY item. GroupByItems always use a select item (that may
+ * or not be a part of the query already) for grouping.
+ * 
+ * @see GroupByClause
+ */
+public class GroupByItem extends BaseObject implements QueryItem, Cloneable {
+
+    private static final long serialVersionUID = 5218878395877852919L;
+    private final SelectItem _selectItem;
+    private Query _query;
+
+    /**
+     * Constructs a GROUP BY item based on a select item that should be grouped.
+     * 
+     * @param selectItem
+     */
+    public GroupByItem(SelectItem selectItem) {
+        if (selectItem == null) {
+            throw new IllegalArgumentException("SelectItem cannot be null");
+        }
+        _selectItem = selectItem;
+    }
+
+    public SelectItem getSelectItem() {
+        return _selectItem;
+    }
+
+    @Override
+    public String toSql() {
+        return toSql(false);
+    }
+
+    @Override
+    public String toSql(boolean includeSchemaInColumnPaths) {
+        final String sameQueryAlias = _selectItem.getSameQueryAlias(includeSchemaInColumnPaths);
+        return sameQueryAlias;
+    }
+
+    @Override
+    public String toString() {
+        return toSql();
+    }
+
+    public Query getQuery() {
+        return _query;
+    }
+
+    public GroupByItem setQuery(Query query) {
+        _query = query;
+        if (_selectItem != null) {
+            _selectItem.setQuery(query);
+        }
+        return this;
+    }
+
+    @Override
+    protected GroupByItem clone() {
+        GroupByItem g = new GroupByItem(_selectItem.clone());
+        return g;
+    }
+
+    @Override
+    protected void decorateIdentity(List<Object> identifiers) {
+        identifiers.add(_selectItem);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/JoinType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/JoinType.java b/core/src/main/java/org/apache/metamodel/query/JoinType.java
new file mode 100644
index 0000000..c40ab0e
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/JoinType.java
@@ -0,0 +1,29 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+/**
+ * Represents a join type, used in a FromItem.
+ * 
+ * @see FromItem
+ */
+public enum JoinType {
+
+	INNER, LEFT, RIGHT;
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/LogicalOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/LogicalOperator.java b/core/src/main/java/org/apache/metamodel/query/LogicalOperator.java
new file mode 100644
index 0000000..a17cb1e
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/LogicalOperator.java
@@ -0,0 +1,28 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+/**
+ * Represents a logical operator (AND or OR) to use when defining compound
+ * {@link FilterItem}s.
+ */
+public enum LogicalOperator {
+
+    AND, OR
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/MaxAggregateBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/MaxAggregateBuilder.java b/core/src/main/java/org/apache/metamodel/query/MaxAggregateBuilder.java
new file mode 100644
index 0000000..93591f1
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/MaxAggregateBuilder.java
@@ -0,0 +1,48 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+import org.eobjects.metamodel.util.AggregateBuilder;
+import org.eobjects.metamodel.util.ObjectComparator;
+
+final class MaxAggregateBuilder implements AggregateBuilder<Object> {
+
+	private Object max;
+
+	@Override
+	public void add(Object o) {
+		if (o == null) {
+			return;
+		}
+		if (max == null) {
+			max = o;
+		} else {
+			Comparable<Object> comparable = ObjectComparator.getComparable(max);
+			if (comparable.compareTo(o) < 0) {
+				max = o;
+			}
+		}
+	}
+
+	@Override
+	public Object getAggregate() {
+		return max;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/MinAggregateBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/MinAggregateBuilder.java b/core/src/main/java/org/apache/metamodel/query/MinAggregateBuilder.java
new file mode 100644
index 0000000..5321b22
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/MinAggregateBuilder.java
@@ -0,0 +1,48 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+import org.eobjects.metamodel.util.AggregateBuilder;
+import org.eobjects.metamodel.util.ObjectComparator;
+
+final class MinAggregateBuilder implements AggregateBuilder<Object> {
+
+	private Object min;
+
+	@Override
+	public void add(Object o) {
+		if (o == null) {
+			return;
+		}
+		if (min == null) {
+			min = o;
+		} else {
+			Comparable<Object> comparable = ObjectComparator.getComparable(min);
+			if (comparable.compareTo(o) > 0) {
+				min = o;
+			}
+		}
+	}
+
+	@Override
+	public Object getAggregate() {
+		return min;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/OperatorType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/OperatorType.java b/core/src/main/java/org/apache/metamodel/query/OperatorType.java
new file mode 100644
index 0000000..3f4782e
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/OperatorType.java
@@ -0,0 +1,69 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+/**
+ * Defines the types of operators that can be used in filters.
+ * 
+ * @see FilterItem
+ */
+public enum OperatorType {
+
+    EQUALS_TO("="), DIFFERENT_FROM("<>"), LIKE("LIKE"), GREATER_THAN(">"), LESS_THAN("<"), IN("IN"),
+
+    /**
+     * @deprecated use {@link #LESS_THAN} instead.
+     */
+    @Deprecated
+    LOWER_THAN("<"),
+
+    /**
+     * @deprecated use {@link #GREATER_THAN} instead.
+     */
+    @Deprecated
+    HIGHER_THAN(">");
+
+    private final String _sql;
+
+    private OperatorType(String sql) {
+        _sql = sql;
+    }
+
+    public String toSql() {
+        return _sql;
+    }
+
+    /**
+     * Converts from SQL string literals to an OperatorType. Valid SQL values
+     * are "=", "<>", "LIKE", ">" and "<".
+     * 
+     * @param sqlType
+     * @return a OperatorType object representing the specified SQL type
+     */
+    public static OperatorType convertOperatorType(String sqlType) {
+        if (sqlType != null) {
+            for (OperatorType operator : values()) {
+                if (sqlType.equals(operator.toSql())) {
+                    return operator;
+                }
+            }
+        }
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/OrderByClause.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/OrderByClause.java b/core/src/main/java/org/apache/metamodel/query/OrderByClause.java
new file mode 100644
index 0000000..8ce04ec
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/OrderByClause.java
@@ -0,0 +1,49 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents the ORDER BY clause of a query containing OrderByItem's. The order
+ * and direction of the OrderItems define the way that the result of a query
+ * will be sorted.
+ * 
+ * @see OrderByItem
+ */
+public class OrderByClause extends AbstractQueryClause<OrderByItem> {
+
+	private static final long serialVersionUID = 2441926135870143715L;
+
+	public OrderByClause(Query query) {
+		super(query, AbstractQueryClause.PREFIX_ORDER_BY,
+				AbstractQueryClause.DELIM_COMMA);
+	}
+
+	public List<SelectItem> getEvaluatedSelectItems() {
+		final List<SelectItem> result = new ArrayList<SelectItem>();
+		final List<OrderByItem> items = getItems();
+		for (OrderByItem item : items) {
+			result.add(item.getSelectItem());
+		}
+		return result;
+	}
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/OrderByItem.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/OrderByItem.java b/core/src/main/java/org/apache/metamodel/query/OrderByItem.java
new file mode 100644
index 0000000..a52aa1d
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/OrderByItem.java
@@ -0,0 +1,152 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+import java.util.List;
+
+import org.eobjects.metamodel.util.BaseObject;
+
+/**
+ * Represents an ORDER BY item. An OrderByItem sorts the resulting DataSet
+ * according to a SelectItem that may or may not be a part of the query already.
+ * 
+ * @see OrderByClause
+ * @see SelectItem
+ */
+public class OrderByItem extends BaseObject implements QueryItem, Cloneable {
+
+	public enum Direction {
+		ASC, DESC
+	}
+
+	private static final long serialVersionUID = -8397473619828484774L;
+	private final SelectItem _selectItem;
+	private Direction _direction;
+	private Query _query;
+
+	/**
+	 * Creates an OrderByItem
+	 * 
+	 * @param selectItem
+	 *            the select item to order
+	 * @param direction
+	 *            the direction to order the select item
+	 */
+	public OrderByItem(SelectItem selectItem, Direction direction) {
+		if (selectItem == null) {
+			throw new IllegalArgumentException("SelectItem cannot be null");
+		}
+		_selectItem = selectItem;
+		_direction = direction;
+	}
+
+	/**
+	 * Creates an OrderByItem
+	 * 
+	 * @param selectItem
+	 * @param ascending
+	 * @deprecated user OrderByItem(SelectItem, Direction) instead
+	 */
+	@Deprecated
+	public OrderByItem(SelectItem selectItem, boolean ascending) {
+		if (selectItem == null) {
+			throw new IllegalArgumentException("SelectItem cannot be null");
+		}
+		_selectItem = selectItem;
+		if (ascending) {
+			_direction = Direction.ASC;
+		} else {
+			_direction = Direction.DESC;
+		}
+	}
+
+	/**
+	 * Creates an ascending OrderByItem
+	 * 
+	 * @param selectItem
+	 */
+	public OrderByItem(SelectItem selectItem) {
+		this(selectItem, Direction.ASC);
+	}
+	
+
+    @Override
+    public String toSql(boolean includeSchemaInColumnPaths) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(_selectItem.getSameQueryAlias(includeSchemaInColumnPaths) + ' ');
+        sb.append(_direction);
+        return sb.toString();
+    }
+
+	@Override
+	public String toSql() {
+	    return toSql(false);
+	}
+
+	public boolean isAscending() {
+		return (_direction == Direction.ASC);
+	}
+
+	public boolean isDescending() {
+		return (_direction == Direction.DESC);
+	}
+
+	public Direction getDirection() {
+		return _direction;
+	}
+
+	public OrderByItem setDirection(Direction direction) {
+		_direction = direction;
+		return this;
+	}
+
+	public SelectItem getSelectItem() {
+		return _selectItem;
+	}
+
+	public Query getQuery() {
+		return _query;
+	}
+
+	public OrderByItem setQuery(Query query) {
+		_query = query;
+		if (_selectItem != null) {
+			_selectItem.setQuery(query);
+		}
+		return this;
+	}
+
+	@Override
+	protected OrderByItem clone() {
+		OrderByItem o = new OrderByItem(_selectItem.clone());
+		o._direction = _direction;
+		return o;
+	}
+
+	@Override
+	protected void decorateIdentity(List<Object> identifiers) {
+		identifiers.add(_direction);
+		identifiers.add(_selectItem);
+	}
+
+	@Override
+	public String toString() {
+		return toSql();
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/Query.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/Query.java b/core/src/main/java/org/apache/metamodel/query/Query.java
new file mode 100644
index 0000000..0986b6b
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/Query.java
@@ -0,0 +1,603 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eobjects.metamodel.DataContext;
+import org.eobjects.metamodel.MetaModelException;
+import org.eobjects.metamodel.query.OrderByItem.Direction;
+import org.eobjects.metamodel.query.parser.QueryParserException;
+import org.eobjects.metamodel.query.parser.QueryPartCollectionProcessor;
+import org.eobjects.metamodel.query.parser.QueryPartParser;
+import org.eobjects.metamodel.query.parser.QueryPartProcessor;
+import org.eobjects.metamodel.query.parser.SelectItemParser;
+import org.eobjects.metamodel.schema.Column;
+import org.eobjects.metamodel.schema.ColumnType;
+import org.eobjects.metamodel.schema.Table;
+import org.eobjects.metamodel.util.BaseObject;
+import org.eobjects.metamodel.util.BooleanComparator;
+import org.eobjects.metamodel.util.FormatHelper;
+import org.eobjects.metamodel.util.NumberComparator;
+
+/**
+ * Represents a query to retrieve data by. A query is made up of six clauses,
+ * equivalent to the SQL standard:
+ * <ul>
+ * <li>the SELECT clause, which define the wanted columns of the resulting
+ * DataSet</li>
+ * <li>the FROM clause, which define where to retrieve the data from</li>
+ * <li>the WHERE clause, which define filters on the retrieved data</li>
+ * <li>the GROUP BY clause, which define if the result should be grouped and
+ * aggregated according to some columns acting as categories</li>
+ * <li>the HAVING clause, which define filters on the grouped data</li>
+ * <li>the ORDER BY clause, which define sorting of the resulting dataset</li>
+ * </ul>
+ * 
+ * In addition two properties are applied to queries to limit the resulting
+ * dataset:
+ * <ul>
+ * <li>First row: The first row (aka. offset) of the result of the query.</li>
+ * <li>Max rows: The maximum amount of rows to return when executing the query.</li>
+ * </ul>
+ * 
+ * Queries are executed using the DataContext.executeQuery method or can
+ * alternatively be used directly in JDBC by using the toString() method.
+ * 
+ * @see DataContext
+ */
+public final class Query extends BaseObject implements Cloneable, Serializable {
+
+    private static final long serialVersionUID = -5976325207498574216L;
+
+    private final SelectClause _selectClause;
+    private final FromClause _fromClause;
+    private final FilterClause _whereClause;
+    private final GroupByClause _groupByClause;
+    private final FilterClause _havingClause;
+    private final OrderByClause _orderByClause;
+
+    private Integer _maxRows;
+    private Integer _firstRow;
+
+    public Query() {
+        _selectClause = new SelectClause(this);
+        _fromClause = new FromClause(this);
+        _whereClause = new FilterClause(this, AbstractQueryClause.PREFIX_WHERE);
+        _groupByClause = new GroupByClause(this);
+        _havingClause = new FilterClause(this, AbstractQueryClause.PREFIX_HAVING);
+        _orderByClause = new OrderByClause(this);
+    }
+
+    public Query select(Column column, FromItem fromItem) {
+        SelectItem selectItem = new SelectItem(column, fromItem);
+        return select(selectItem);
+    }
+
+    public Query select(Column... columns) {
+        for (Column column : columns) {
+            SelectItem selectItem = new SelectItem(column);
+            selectItem.setQuery(this);
+            _selectClause.addItem(selectItem);
+        }
+        return this;
+    }
+
+    public Query select(SelectItem... items) {
+        _selectClause.addItems(items);
+        return this;
+    }
+
+    public Query select(FunctionType functionType, Column column) {
+        _selectClause.addItem(new SelectItem(functionType, column));
+        return this;
+    }
+
+    public Query select(String expression, String alias) {
+        return select(new SelectItem(expression, alias));
+    }
+
+    /**
+     * Adds a selection to this query.
+     * 
+     * @param expression
+     * @return
+     */
+    public Query select(String expression) {
+        if ("*".equals(expression)) {
+            return selectAll();
+        }
+
+        SelectItem selectItem = findSelectItem(expression, true);
+        return select(selectItem);
+    }
+
+    private SelectItem findSelectItem(String expression, boolean allowExpressionBasedSelectItem) {
+        SelectItemParser parser = new SelectItemParser(this, allowExpressionBasedSelectItem);
+        return parser.findSelectItem(expression);
+    }
+
+    /**
+     * Select all available select items from all currently available FROM
+     * items. Equivalent of the expression "SELECT * FROM ..." in SQL.
+     * 
+     * @return
+     */
+    public Query selectAll() {
+        List<FromItem> items = getFromClause().getItems();
+        for (FromItem fromItem : items) {
+            selectAll(fromItem);
+        }
+        return this;
+    }
+
+    public Query selectAll(final FromItem fromItem) {
+        if (fromItem.getTable() != null) {
+            final Column[] columns = fromItem.getTable().getColumns();
+            for (final Column column : columns) {
+                select(column, fromItem);
+            }
+        } else if (fromItem.getJoin() != null) {
+            selectAll(fromItem.getLeftSide());
+            selectAll(fromItem.getRightSide());
+        } else if (fromItem.getSubQuery() != null) {
+            final List<SelectItem> items = fromItem.getSubQuery().getSelectClause().getItems();
+            for (final SelectItem subQuerySelectItem : items) {
+                select(new SelectItem(subQuerySelectItem, fromItem));
+            }
+        } else {
+            throw new MetaModelException("All select items ('*') not determinable with from item: " + fromItem);
+        }
+        return this;
+    }
+
+    public Query selectDistinct() {
+        _selectClause.setDistinct(true);
+        return this;
+    }
+
+    public Query selectCount() {
+        return select(SelectItem.getCountAllItem());
+    }
+
+    public Query from(FromItem... items) {
+        _fromClause.addItems(items);
+        return this;
+    }
+
+    public Query from(Table table) {
+        return from(new FromItem(table));
+    }
+
+    public Query from(String expression) {
+        return from(new FromItem(expression));
+    }
+
+    public Query from(Table table, String alias) {
+        return from(new FromItem(table).setAlias(alias));
+    }
+
+    public Query from(Table leftTable, Table rightTable, JoinType joinType, Column leftOnColumn, Column rightOnColumn) {
+        SelectItem[] leftOn = new SelectItem[] { new SelectItem(leftOnColumn) };
+        SelectItem[] rightOn = new SelectItem[] { new SelectItem(rightOnColumn) };
+        FromItem fromItem = new FromItem(joinType, new FromItem(leftTable), new FromItem(rightTable), leftOn, rightOn);
+        return from(fromItem);
+    }
+
+    public Query groupBy(String... groupByTokens) {
+        for (String groupByToken : groupByTokens) {
+            SelectItem selectItem = findSelectItem(groupByToken, true);
+            groupBy(new GroupByItem(selectItem));
+        }
+        return this;
+    }
+
+    public Query groupBy(GroupByItem... items) {
+        for (GroupByItem item : items) {
+            SelectItem selectItem = item.getSelectItem();
+            if (selectItem != null && selectItem.getQuery() == null) {
+                selectItem.setQuery(this);
+            }
+        }
+        _groupByClause.addItems(items);
+        return this;
+    }
+
+    public Query groupBy(Column... columns) {
+        for (Column column : columns) {
+            SelectItem selectItem = new SelectItem(column).setQuery(this);
+            _groupByClause.addItem(new GroupByItem(selectItem));
+        }
+        return this;
+    }
+
+    public Query orderBy(OrderByItem... items) {
+        _orderByClause.addItems(items);
+        return this;
+    }
+
+    public Query orderBy(String... orderByTokens) {
+        for (String orderByToken : orderByTokens) {
+            orderByToken = orderByToken.trim();
+            final Direction direction;
+            if (orderByToken.toUpperCase().endsWith("DESC")) {
+                direction = Direction.DESC;
+                orderByToken = orderByToken.substring(0, orderByToken.length() - 4).trim();
+            } else if (orderByToken.toUpperCase().endsWith("ASC")) {
+                direction = Direction.ASC;
+                orderByToken = orderByToken.substring(0, orderByToken.length() - 3).trim();
+            } else {
+                direction = Direction.ASC;
+            }
+
+            OrderByItem orderByItem = new OrderByItem(findSelectItem(orderByToken, true), direction);
+            orderBy(orderByItem);
+        }
+        return this;
+    }
+
+    public Query orderBy(Column column) {
+        return orderBy(column, Direction.ASC);
+    }
+
+    /**
+     * @deprecated use orderBy(Column, Direction) instead
+     */
+    @Deprecated
+    public Query orderBy(Column column, boolean ascending) {
+        if (ascending) {
+            return orderBy(column, Direction.ASC);
+        } else {
+            return orderBy(column, Direction.DESC);
+        }
+    }
+
+    public Query orderBy(Column column, Direction direction) {
+        SelectItem selectItem = _selectClause.getSelectItem(column);
+        if (selectItem == null) {
+            selectItem = new SelectItem(column);
+        }
+        return orderBy(new OrderByItem(selectItem, direction));
+    }
+
+    public Query where(FilterItem... items) {
+        _whereClause.addItems(items);
+        return this;
+    }
+
+    public Query where(Iterable<FilterItem> items) {
+        _whereClause.addItems(items);
+        return this;
+    }
+
+    public Query where(String... whereItemTokens) {
+        for (String whereItemToken : whereItemTokens) {
+            FilterItem filterItem = findFilterItem(whereItemToken);
+            where(filterItem);
+        }
+        return this;
+    }
+
+    private FilterItem findFilterItem(String expression) {
+        final QueryPartCollectionProcessor collectionProcessor = new QueryPartCollectionProcessor();
+        new QueryPartParser(collectionProcessor, expression, " AND ", " OR ").parse();
+
+        final List<String> tokens = collectionProcessor.getTokens();
+        final List<String> delims = collectionProcessor.getDelims();
+        if (tokens.size() == 1) {
+            expression = tokens.get(0);
+        } else {
+            final LogicalOperator logicalOperator = LogicalOperator.valueOf(delims.get(1).trim());
+
+            final List<FilterItem> filterItems = new ArrayList<FilterItem>();
+            for (int i = 0; i < tokens.size(); i++) {
+                String token = tokens.get(i);
+                FilterItem filterItem = findFilterItem(token);
+                filterItems.add(filterItem);
+            }
+            return new FilterItem(logicalOperator, filterItems);
+        }
+
+        OperatorType operator = null;
+        String leftSide = null;
+        final String rightSide;
+        {
+            String rightSideCandidate = null;
+            final OperatorType[] operators = OperatorType.values();
+            for (OperatorType operatorCandidate : operators) {
+                final int operatorIndex = expression.indexOf(' ' + operatorCandidate.toSql() + ' ');
+                if (operatorIndex > 0) {
+                    operator = operatorCandidate;
+                    leftSide = expression.substring(0, operatorIndex).trim();
+                    rightSideCandidate = expression.substring(operatorIndex + operator.toSql().length() + 2).trim();
+                    break;
+                }
+            }
+
+            if (operator == null) {
+                // check special cases for IS NULL and IS NOT NULL
+                if (expression.endsWith(" IS NOT NULL")) {
+                    operator = OperatorType.DIFFERENT_FROM;
+                    leftSide = expression.substring(0, expression.lastIndexOf(" IS NOT NULL")).trim();
+                    rightSideCandidate = "NULL";
+                } else if (expression.endsWith(" IS NULL")) {
+                    operator = OperatorType.EQUALS_TO;
+                    leftSide = expression.substring(0, expression.lastIndexOf(" IS NULL")).trim();
+                    rightSideCandidate = "NULL";
+                }
+            }
+
+            rightSide = rightSideCandidate;
+        }
+
+        if (operator == null) {
+            return new FilterItem(expression);
+        }
+
+        final SelectItem selectItem = findSelectItem(leftSide, false);
+        if (selectItem == null) {
+            return new FilterItem(expression);
+        }
+
+        final Object operand;
+        if (operator == OperatorType.IN) {
+            final List<Object> list = new ArrayList<Object>();
+            new QueryPartParser(new QueryPartProcessor() {
+                @Override
+                public void parse(String delim, String itemToken) {
+                    Object operand = createOperand(itemToken, selectItem, false);
+                    list.add(operand);
+                }
+            }, rightSide, ",").parse();
+            operand = list;
+        } else {
+            operand = createOperand(rightSide, selectItem, true);
+        }
+
+        return new FilterItem(selectItem, operator, operand);
+    }
+
+    private Object createOperand(final String token, final SelectItem leftSelectItem, final boolean searchSelectItems) {
+        if (token.equalsIgnoreCase("NULL")) {
+            return null;
+        }
+
+        if (token.startsWith("'") && token.endsWith("'") && token.length() > 2) {
+            String stringOperand = token.substring(1, token.length() - 1);
+            stringOperand = stringOperand.replaceAll("\\\\'", "'");
+            return stringOperand;
+        }
+
+        if (searchSelectItems) {
+            final SelectItem selectItem = findSelectItem(token, false);
+            if (selectItem != null) {
+                return selectItem;
+            }
+        }
+
+        final ColumnType expectedColumnType = leftSelectItem.getExpectedColumnType();
+        final Object result;
+        if (expectedColumnType == null) {
+            // We're assuming number here, but it could also be boolean or a
+            // time based type. But anyways, this should not happen since
+            // expected column type should be available.
+            result = NumberComparator.toNumber(token);
+        } else if (expectedColumnType.isBoolean()) {
+            result = BooleanComparator.toBoolean(token);
+        } else if (expectedColumnType.isTimeBased()) {
+            result = FormatHelper.parseSqlTime(expectedColumnType, token);
+        } else {
+            result = NumberComparator.toNumber(token);
+        }
+
+        if (result == null) {
+            // shouldn't happen since only "NULL" is parsed as null.
+            throw new QueryParserException("Could not parse operand: " + token);
+        }
+
+        return result;
+    }
+
+    public Query where(SelectItem selectItem, OperatorType operatorType, Object operand) {
+        return where(new FilterItem(selectItem, operatorType, operand));
+    }
+
+    public Query where(Column column, OperatorType operatorType, Object operand) {
+        SelectItem selectItem = _selectClause.getSelectItem(column);
+        if (selectItem == null) {
+            selectItem = new SelectItem(column);
+        }
+        return where(selectItem, operatorType, operand);
+    }
+
+    public Query having(FilterItem... items) {
+        _havingClause.addItems(items);
+        return this;
+    }
+
+    public Query having(FunctionType function, Column column, OperatorType operatorType, Object operand) {
+        SelectItem selectItem = new SelectItem(function, column);
+        return having(new FilterItem(selectItem, operatorType, operand));
+    }
+
+    public Query having(Column column, OperatorType operatorType, Object operand) {
+        SelectItem selectItem = _selectClause.getSelectItem(column);
+        if (selectItem == null) {
+            selectItem = new SelectItem(column);
+        }
+        return having(new FilterItem(selectItem, operatorType, operand));
+    }
+
+    public Query having(String... havingItemTokens) {
+        for (String havingItemToken : havingItemTokens) {
+            FilterItem filterItem = findFilterItem(havingItemToken);
+            having(filterItem);
+        }
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return toSql();
+    }
+
+    /*
+     * A string representation of this query. This representation will be SQL 99
+     * compatible and can thus be used for database queries on databases that
+     * meet SQL standards.
+     */
+    public String toSql() {
+        return toSql(false);
+    }
+
+    protected String toSql(boolean includeSchemaInColumnPaths) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(_selectClause.toSql(includeSchemaInColumnPaths));
+        sb.append(_fromClause.toSql(includeSchemaInColumnPaths));
+        sb.append(_whereClause.toSql(includeSchemaInColumnPaths));
+        sb.append(_groupByClause.toSql(includeSchemaInColumnPaths));
+        sb.append(_havingClause.toSql(includeSchemaInColumnPaths));
+        sb.append(_orderByClause.toSql(includeSchemaInColumnPaths));
+        return sb.toString();
+    }
+
+    public SelectClause getSelectClause() {
+        return _selectClause;
+    }
+
+    public FromClause getFromClause() {
+        return _fromClause;
+    }
+
+    public FilterClause getWhereClause() {
+        return _whereClause;
+    }
+
+    public GroupByClause getGroupByClause() {
+        return _groupByClause;
+    }
+
+    public FilterClause getHavingClause() {
+        return _havingClause;
+    }
+
+    public OrderByClause getOrderByClause() {
+        return _orderByClause;
+    }
+
+    /**
+     * Sets the maximum number of rows to be queried. If the result of the query
+     * yields more rows they should be discarded.
+     * 
+     * @param maxRows
+     *            the number of desired maximum rows. Can be null (default) for
+     *            no limits
+     * @return this query
+     */
+    public Query setMaxRows(Integer maxRows) {
+        if (maxRows != null) {
+            final int maxRowsValue = maxRows.intValue();
+            if (maxRowsValue == 0) {
+                throw new IllegalArgumentException("Max rows cannot be zero");
+            }
+            if (maxRowsValue < 0) {
+                throw new IllegalArgumentException("Max rows cannot be negative");
+            }
+        }
+        _maxRows = maxRows;
+        return this;
+    }
+
+    /**
+     * @return the number of maximum rows to yield from executing this query or
+     *         null if no maximum/limit is set.
+     */
+    public Integer getMaxRows() {
+        return _maxRows;
+    }
+
+    /**
+     * Sets the first row (aka offset) of the query's result. The row number is
+     * 1-based, so setting a first row value of 1 is equivalent to not setting
+     * it at all..
+     * 
+     * @param firstRow
+     *            the first row, where 1 is the first row.
+     * @return this query
+     */
+    public Query setFirstRow(Integer firstRow) {
+        if (firstRow != null && firstRow.intValue() < 1) {
+            throw new IllegalArgumentException("First row cannot be negative or zero");
+        }
+        _firstRow = firstRow;
+        return this;
+    }
+
+    /**
+     * Gets the first row (aka offset) of the query's result, or null if none is
+     * specified. The row number is 1-based, so setting a first row value of 1
+     * is equivalent to not setting it at all..
+     * 
+     * @return the first row (aka offset) of the query's result, or null if no
+     *         offset is specified.
+     */
+    public Integer getFirstRow() {
+        return _firstRow;
+    }
+
+    @Override
+    protected void decorateIdentity(List<Object> identifiers) {
+        identifiers.add(_maxRows);
+        identifiers.add(_selectClause);
+        identifiers.add(_fromClause);
+        identifiers.add(_whereClause);
+        identifiers.add(_groupByClause);
+        identifiers.add(_havingClause);
+        identifiers.add(_orderByClause);
+    }
+
+    @Override
+    public Query clone() {
+        Query q = new Query();
+        q.setMaxRows(_maxRows);
+        q.setFirstRow(_firstRow);
+        q.getSelectClause().setDistinct(_selectClause.isDistinct());
+        for (FromItem item : _fromClause.getItems()) {
+            q.from(item.clone());
+        }
+        for (SelectItem item : _selectClause.getItems()) {
+            q.select(item.clone());
+        }
+        for (FilterItem item : _whereClause.getItems()) {
+            q.where(item.clone());
+        }
+        for (GroupByItem item : _groupByClause.getItems()) {
+            q.groupBy(item.clone());
+        }
+        for (FilterItem item : _havingClause.getItems()) {
+            q.having(item.clone());
+        }
+        for (OrderByItem item : _orderByClause.getItems()) {
+            q.orderBy(item.clone());
+        }
+        return q;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/QueryClause.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/QueryClause.java b/core/src/main/java/org/apache/metamodel/query/QueryClause.java
new file mode 100644
index 0000000..e3dddc8
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/QueryClause.java
@@ -0,0 +1,53 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+import java.io.Serializable;
+import java.util.List;
+
+public interface QueryClause<E> extends Serializable {
+
+	public QueryClause<E> setItems(E... items);
+
+	public QueryClause<E> addItems(E... items);
+
+	public QueryClause<E> addItems(Iterable<E> items);
+
+	public QueryClause<E> addItem(int index, E item);
+	
+	public QueryClause<E> addItem(E item);
+	
+	public boolean isEmpty();
+
+	public int getItemCount();
+
+	public E getItem(int index);
+
+	public List<E> getItems();
+
+	public QueryClause<E> removeItem(int index);
+
+	public QueryClause<E> removeItem(E item);
+
+	public QueryClause<E> removeItems();
+	
+	public String toSql(boolean includeSchemaInColumnPaths);
+
+	public String toSql();
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/QueryItem.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/QueryItem.java b/core/src/main/java/org/apache/metamodel/query/QueryItem.java
new file mode 100644
index 0000000..ab79959
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/QueryItem.java
@@ -0,0 +1,39 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+import java.io.Serializable;
+
+/**
+ * Interface for items in a query. All QueryItems reside within a QueryClause.
+ * 
+ * @see AbstractQueryClause
+ */
+public interface QueryItem extends Serializable {
+
+	public QueryItem setQuery(Query query);
+
+	public Query getQuery();
+	
+	public String toSql();
+	
+	public String toSql(boolean includeSchemaInColumnPaths);
+	
+	public String toString();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/core/src/main/java/org/apache/metamodel/query/QueryParameter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/query/QueryParameter.java b/core/src/main/java/org/apache/metamodel/query/QueryParameter.java
new file mode 100644
index 0000000..c8c0401
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/query/QueryParameter.java
@@ -0,0 +1,37 @@
+/**
+ * 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.eobjects.metamodel.query;
+
+import org.eobjects.metamodel.DataContext;
+
+/**
+ * Represents a query parameter, in SQL represented with a '?' symbol.
+ * Parameters are values in the query that will be defined at execution time,
+ * not parsing/preparation time.
+ * 
+ * @see CompiledQuery
+ * @see DataContext#compileQuery(Query) 
+ */
+public class QueryParameter {
+
+    @Override
+    public String toString() {
+        return "?";
+    }
+}