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/19 11:32:50 UTC

[07/61] [partial] Hard rename of all 'org/eobjects' folders to 'org/apache'.

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcUtils.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcUtils.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcUtils.java
new file mode 100644
index 0000000..80f0875
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcUtils.java
@@ -0,0 +1,265 @@
+/**
+ * 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.jdbc;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.NClob;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import org.eobjects.metamodel.MetaModelException;
+import org.eobjects.metamodel.jdbc.dialects.IQueryRewriter;
+import org.eobjects.metamodel.query.FilterItem;
+import org.eobjects.metamodel.query.OperatorType;
+import org.eobjects.metamodel.query.QueryParameter;
+import org.eobjects.metamodel.schema.Column;
+import org.eobjects.metamodel.schema.ColumnType;
+import org.eobjects.metamodel.schema.TableType;
+import org.eobjects.metamodel.util.FileHelper;
+import org.eobjects.metamodel.util.FormatHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class JdbcUtils {
+
+	private static final Logger logger = LoggerFactory
+			.getLogger(JdbcUtils.class);
+
+	public static MetaModelException wrapException(SQLException e,
+			String actionDescription) throws MetaModelException {
+		String message = e.getMessage();
+		if (message == null || message.isEmpty()) {
+			message = "Could not " + actionDescription;
+		} else {
+			message = "Could not " + actionDescription + ": " + message;
+		}
+
+		logger.error(message, e);
+		logger.error("Error code={}, SQL state={}", e.getErrorCode(),
+				e.getSQLState());
+
+		final SQLException nextException = e.getNextException();
+		if (nextException != null) {
+			logger.error("Next SQL exception: " + nextException.getMessage(),
+					nextException);
+		}
+
+		return new MetaModelException(message, e);
+	}
+
+	/**
+	 * Method which handles the action of setting a parameterized value on a
+	 * statement. Traditionally this is done using the
+	 * {@link PreparedStatement#setObject(int, Object)} method but for some
+	 * types we use more specific setter methods.
+	 * 
+	 * @param st
+	 * @param valueIndex
+	 * @param column
+	 * @param value
+	 * @throws SQLException
+	 */
+	public static void setStatementValue(final PreparedStatement st,
+			final int valueIndex, final Column column, Object value)
+			throws SQLException {
+		final ColumnType type = (column == null ? null : column.getType());
+
+		if (type == null || type == ColumnType.OTHER) {
+			// type is not known - nothing more we can do to narrow the type
+			st.setObject(valueIndex, value);
+			return;
+		}
+
+		if (value == null && type != null) {
+			try {
+				final int jdbcType = type.getJdbcType();
+				st.setNull(valueIndex, jdbcType);
+				return;
+			} catch (Exception e) {
+				logger.warn(
+						"Exception occurred while calling setNull(...) for value index "
+								+ valueIndex
+								+ ". Attempting value-based setter method instead.",
+						e);
+			}
+		}
+
+		if (type == ColumnType.VARCHAR && value instanceof Date) {
+			// some drivers (SQLite and JTDS for MS SQL server) treat dates as
+			// VARCHARS. In that case we need to convert the dates to the
+			// correct format
+			String nativeType = column.getNativeType();
+			Date date = (Date) value;
+			if ("DATE".equalsIgnoreCase(nativeType)) {
+				value = FormatHelper
+						.formatSqlTime(ColumnType.DATE, date, false);
+			} else if ("TIME".equalsIgnoreCase(nativeType)) {
+				value = FormatHelper
+						.formatSqlTime(ColumnType.TIME, date, false);
+			} else if ("TIMESTAMP".equalsIgnoreCase(nativeType)
+					|| "DATETIME".equalsIgnoreCase(nativeType)) {
+				value = FormatHelper.formatSqlTime(ColumnType.TIMESTAMP, date,
+						false);
+			}
+		}
+
+		if (type != null && type.isTimeBased() && value instanceof String) {
+			value = FormatHelper.parseSqlTime(type, (String) value);
+		}
+
+		try {
+			if (type == ColumnType.DATE && value instanceof Date) {
+				Calendar cal = Calendar.getInstance();
+				cal.setTime((Date) value);
+				st.setDate(valueIndex,
+						new java.sql.Date(cal.getTimeInMillis()), cal);
+			} else if (type == ColumnType.TIME && value instanceof Date) {
+				Calendar cal = Calendar.getInstance();
+				cal.setTime((Date) value);
+				st.setTime(valueIndex,
+						new java.sql.Time(cal.getTimeInMillis()), cal);
+			} else if (type == ColumnType.TIMESTAMP && value instanceof Date) {
+				Calendar cal = Calendar.getInstance();
+				cal.setTime((Date) value);
+				st.setTimestamp(valueIndex,
+						new java.sql.Timestamp(cal.getTimeInMillis()), cal);
+			} else if (type == ColumnType.CLOB || type == ColumnType.NCLOB) {
+				if (value instanceof InputStream) {
+					InputStream inputStream = (InputStream) value;
+					st.setAsciiStream(valueIndex, inputStream);
+				} else if (value instanceof Reader) {
+					Reader reader = (Reader) value;
+					st.setCharacterStream(valueIndex, reader);
+				} else if (value instanceof NClob) {
+					NClob nclob = (NClob) value;
+					st.setNClob(valueIndex, nclob);
+				} else if (value instanceof Clob) {
+					Clob clob = (Clob) value;
+					st.setClob(valueIndex, clob);
+				} else if (value instanceof String) {
+					st.setString(valueIndex, (String) value);
+				} else {
+					st.setObject(valueIndex, value);
+				}
+			} else if (type == ColumnType.BLOB || type == ColumnType.BINARY) {
+				if (value instanceof byte[]) {
+					byte[] bytes = (byte[]) value;
+					st.setBytes(valueIndex, bytes);
+				} else if (value instanceof InputStream) {
+					InputStream inputStream = (InputStream) value;
+					st.setBinaryStream(valueIndex, inputStream);
+				} else if (value instanceof Blob) {
+					Blob blob = (Blob) value;
+					st.setBlob(valueIndex, blob);
+				} else {
+					st.setObject(valueIndex, value);
+				}
+			} else if (type.isLiteral()) {
+				final String str;
+				if (value instanceof Reader) {
+					Reader reader = (Reader) value;
+					str = FileHelper.readAsString(reader);
+				} else {
+					str = value.toString();
+				}
+				st.setString(valueIndex, str);
+			} else {
+				st.setObject(valueIndex, value);
+			}
+		} catch (SQLException e) {
+			logger.error("Failed to set parameter {} to value: {}", valueIndex,
+					value);
+			throw e;
+		}
+	}
+
+	public static String getValueAsSql(Column column, Object value,
+			IQueryRewriter queryRewriter) {
+		if (value == null) {
+			return "NULL";
+		}
+		final ColumnType columnType = column.getType();
+		if (columnType.isLiteral() && value instanceof String) {
+			value = queryRewriter.escapeQuotes((String) value);
+		}
+		String formatSqlValue = FormatHelper.formatSqlValue(columnType, value);
+		return formatSqlValue;
+	}
+
+	public static String createWhereClause(List<FilterItem> whereItems,
+			IQueryRewriter queryRewriter, boolean inlineValues) {
+		if (whereItems.isEmpty()) {
+			return "";
+		}
+		StringBuilder sb = new StringBuilder();
+		sb.append(" WHERE ");
+		boolean firstValue = true;
+		for (FilterItem whereItem : whereItems) {
+			if (firstValue) {
+				firstValue = false;
+			} else {
+				sb.append(" AND ");
+			}
+			if (!inlineValues) {
+				if (isPreparedParameterCandidate(whereItem)) {
+					// replace operator with parameter
+					whereItem = new FilterItem(whereItem.getSelectItem(),
+							whereItem.getOperator(), new QueryParameter());
+				}
+			}
+			final String whereItemLabel = queryRewriter
+					.rewriteFilterItem(whereItem);
+			sb.append(whereItemLabel);
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * Determines if a particular {@link FilterItem} will have it's parameter
+	 * (operand) replaced during SQL generation. Such filter items should
+	 * succesively have their parameters set at execution time.
+	 * 
+	 * @param whereItem
+	 * @return
+	 */
+	public static boolean isPreparedParameterCandidate(FilterItem whereItem) {
+		return !whereItem.isCompoundFilter()
+				&& whereItem.getOperator() != OperatorType.IN;
+	}
+
+	public static String[] getTableTypesAsStrings(TableType[] tableTypes) {
+		String[] types = new String[tableTypes.length];
+		for (int i = 0; i < types.length; i++) {
+			if (tableTypes[i] == TableType.OTHER) {
+				// if the OTHER type has been selected, don't use a table
+				// pattern (ie. include all types)
+				types = null;
+				break;
+			}
+			types[i] = tableTypes[i].toString();
+		}
+		return types;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/MetadataLoader.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/MetadataLoader.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/MetadataLoader.java
new file mode 100644
index 0000000..07b17de
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/MetadataLoader.java
@@ -0,0 +1,36 @@
+/**
+ * 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.jdbc;
+
+/**
+ * Defines the interface for a component capable of loading schema-model
+ * metadata.
+ */
+interface MetadataLoader {
+
+	public void loadTables(JdbcSchema jdbcSchema);
+
+	public void loadRelations(JdbcSchema jdbcSchema);
+
+	public void loadColumns(JdbcTable jdbcTable);
+
+	public void loadIndexes(JdbcTable jdbcTable);
+
+	public void loadPrimaryKeys(JdbcTable jdbcTable);
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/QuerySplitter.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/QuerySplitter.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/QuerySplitter.java
new file mode 100644
index 0000000..1f74866
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/QuerySplitter.java
@@ -0,0 +1,336 @@
+/**
+ * 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.jdbc;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.eobjects.metamodel.DataContext;
+import org.eobjects.metamodel.MetaModelHelper;
+import org.eobjects.metamodel.data.DataSet;
+import org.eobjects.metamodel.data.Row;
+import org.eobjects.metamodel.query.FilterItem;
+import org.eobjects.metamodel.query.FromClause;
+import org.eobjects.metamodel.query.FromItem;
+import org.eobjects.metamodel.query.FunctionType;
+import org.eobjects.metamodel.query.GroupByItem;
+import org.eobjects.metamodel.query.OperatorType;
+import org.eobjects.metamodel.query.Query;
+import org.eobjects.metamodel.query.SelectItem;
+import org.eobjects.metamodel.schema.Column;
+import org.eobjects.metamodel.schema.Table;
+
+/**
+ * The QuerySplitter class makes it possible to split up queries that are
+ * expected to yield a huge result set which may cause performance problems like
+ * OutOfMemoryError's or very long processing periods. The resulting queries
+ * will in union produce the same result, but in smaller bits (resultsets with
+ * less rows).
+ * 
+ * Note that there is an initial performance-penalty associated with splitting
+ * the query since some queries will be executed in order to determine
+ * reasonable intervals to use for the resulting queries WHERE clauses.
+ * 
+ * @see Query
+ * @see DataContext
+ */
+public final class QuerySplitter {
+
+    public final static long DEFAULT_MAX_ROWS = 300000;
+    private static final int MINIMUM_MAX_ROWS = 100;
+    private final static Logger logger = LoggerFactory.getLogger(QuerySplitter.class);
+
+    private final Query _query;
+    private final DataContext _dataContext;
+    private long _maxRows = DEFAULT_MAX_ROWS;
+    private Long _cachedRowCount = null;
+
+    public QuerySplitter(DataContext dc, Query q) {
+        if (dc == null) {
+            throw new IllegalArgumentException("DataContext cannot be null");
+        }
+        if (q == null) {
+            throw new IllegalArgumentException("Query cannot be null");
+        }
+        _dataContext = dc;
+        _query = q;
+    }
+
+    /**
+     * Splits the query into several queries that will together yield the same
+     * result set
+     * 
+     * @return a list of queries that can be executed to yield the same
+     *         collective result as this QuerySplitter's query
+     */
+    public List<Query> splitQuery() {
+        List<Query> result = new ArrayList<Query>();
+        if (isSplittable()) {
+            if (getRowCount() > _maxRows) {
+                Integer subQueryIndex = getSubQueryFromItemIndex();
+                List<Query> splitQueries = null;
+                if (subQueryIndex != null) {
+                    splitQueries = splitQueryBasedOnSubQueries(subQueryIndex);
+                } else {
+                    List<Column> splitColumns = getSplitColumns();
+                    splitQueries = splitQueryBasedOnColumns(splitColumns);
+                }
+                result.addAll(splitQueries);
+            } else {
+                if (logger.isInfoEnabled()) {
+                    logger.info("Accepted query, maxRows not exceeded: " + _query);
+                }
+                result.add(_query);
+            }
+        }
+        if (result.isEmpty()) {
+            logger.debug("Cannot further split query: {}", _query);
+            result.add(_query);
+        }
+        return result;
+    }
+
+    private List<Query> splitQueryBasedOnColumns(List<Column> splitColumns) {
+        List<Query> result = new ArrayList<Query>();
+        if (splitColumns.isEmpty() || getRowCount() <= _maxRows) {
+            if (getRowCount() > 0) {
+                result.add(_query);
+            }
+        } else {
+            Column firstColumn = splitColumns.get(0);
+            splitColumns.remove(0);
+            List<Query> splitQueries = splitQueryBasedOnColumn(firstColumn);
+            for (Query splitQuery : splitQueries) {
+                QuerySplitter qs = new QuerySplitter(_dataContext, splitQuery).setMaxRows(_maxRows);
+                if (qs.getRowCount() > _maxRows) {
+                    // Recursively use the next columns to split queries
+                    // subsequently
+                    result.addAll(qs.splitQueryBasedOnColumns(splitColumns));
+                } else {
+                    if (qs.getRowCount() > 0) {
+                        result.add(splitQuery);
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    private List<Query> splitQueryBasedOnColumn(Column column) {
+        SelectItem maxItem = new SelectItem(FunctionType.MAX, column);
+        SelectItem minItem = new SelectItem(FunctionType.MIN, column);
+        Query q = new Query().from(column.getTable()).select(maxItem, minItem);
+        Row row = MetaModelHelper.executeSingleRowQuery(_dataContext, q);
+        long max = ceil((Number) row.getValue(maxItem));
+        long min = floor((Number) row.getValue(minItem));
+        long wholeRange = max - min;
+        List<Query> result = new ArrayList<Query>();
+        if (wholeRange <= 1) {
+            result.add(_query);
+        } else {
+            long numSplits = ceil(getRowCount() / _maxRows);
+            if (numSplits < 2) {
+                // Must as a minimum yield two new queries
+                numSplits = 2;
+            }
+            int splitInterval = (int) (wholeRange / numSplits);
+            for (int i = 0; i < numSplits; i++) {
+                q = _query.clone();
+                long lowLimit = min + (i * splitInterval);
+                long highLimit = lowLimit + splitInterval;
+
+                FilterItem lowerThanFilter = new FilterItem(new SelectItem(column), OperatorType.LESS_THAN, highLimit);
+                FilterItem higherThanFilter = new FilterItem(new SelectItem(column), OperatorType.GREATER_THAN,
+                        lowLimit);
+                FilterItem equalsFilter = new FilterItem(new SelectItem(column), OperatorType.EQUALS_TO, lowLimit);
+
+                if (i == 0) {
+                    // This is the first split query: no higherThan filter and
+                    // include
+                    // IS NULL
+                    FilterItem nullFilter = new FilterItem(new SelectItem(column), OperatorType.EQUALS_TO, null);
+                    FilterItem orFilterItem = new FilterItem(lowerThanFilter, nullFilter);
+                    q.where(orFilterItem);
+                } else if (i + 1 == numSplits) {
+                    // This is the lats split query: no lowerThan filter,
+                    FilterItem orFilterItem = new FilterItem(higherThanFilter, equalsFilter);
+                    q.where(orFilterItem);
+                } else {
+                    higherThanFilter = new FilterItem(higherThanFilter, equalsFilter);
+                    lowerThanFilter = new FilterItem(lowerThanFilter, equalsFilter);
+                    q.where(higherThanFilter);
+                    q.where(lowerThanFilter);
+                }
+                result.add(q);
+            }
+        }
+        return result;
+    }
+
+    private static long floor(Number value) {
+        Double floor = Math.floor(value.doubleValue());
+        return floor.longValue();
+    }
+
+    private static long ceil(Number value) {
+        Double ceil = Math.ceil(value.doubleValue());
+        return ceil.longValue();
+    }
+
+    private List<Query> splitQueryBasedOnSubQueries(int fromItemIndex) {
+        Query subQuery = _query.getFromClause().getItem(fromItemIndex).getSubQuery();
+        QuerySplitter subQuerySplitter = new QuerySplitter(_dataContext, subQuery);
+
+        subQuerySplitter.setMaxRows(_maxRows);
+        List<Query> splitQueries = subQuerySplitter.splitQuery();
+        List<Query> result = new ArrayList<Query>(splitQueries.size());
+        for (Query splitQuery : splitQueries) {
+            Query newQuery = _query.clone();
+            FromClause fromClause = newQuery.getFromClause();
+            String alias = fromClause.getItem(fromItemIndex).getAlias();
+            fromClause.removeItem(fromItemIndex);
+            newQuery.from(new FromItem(splitQuery).setAlias(alias));
+            result.add(newQuery);
+        }
+        return result;
+    }
+
+    private Integer getSubQueryFromItemIndex() {
+        List<FromItem> fromItems = _query.getFromClause().getItems();
+        for (int i = 0; i < fromItems.size(); i++) {
+            Query subQuery = fromItems.get(i).getSubQuery();
+            if (subQuery != null) {
+                if (isSplittable(subQuery)) {
+                    return i;
+                }
+            }
+        }
+        return null;
+    }
+
+    private boolean isSplittable() {
+        return isSplittable(_query);
+    }
+
+    public static boolean isSplittable(Query q) {
+        if (q.getOrderByClause().getItemCount() != 0) {
+            return false;
+        }
+        return true;
+    }
+
+    private List<Column> getSplitColumns() {
+        List<Column> result = new ArrayList<Column>();
+        if (_query.getGroupByClause().getItemCount() != 0) {
+            List<GroupByItem> groupByItems = _query.getGroupByClause().getItems();
+            for (GroupByItem groupByItem : groupByItems) {
+                Column column = groupByItem.getSelectItem().getColumn();
+                if (column != null) {
+                    if (column.isIndexed()) {
+                        // Indexed columns have first priority, they will be
+                        // added to the beginning of the list
+                        result.add(0, column);
+                    } else {
+                        result.add(column);
+                    }
+                }
+            }
+        } else {
+            List<FromItem> fromItems = _query.getFromClause().getItems();
+            for (FromItem fromItem : fromItems) {
+                if (fromItem.getTable() != null) {
+                    addColumnsToResult(fromItem.getTable(), result);
+                }
+                if (fromItem.getJoin() != null && fromItem.getAlias() == null) {
+                    if (fromItem.getLeftSide().getTable() != null) {
+                        addColumnsToResult(fromItem.getLeftSide().getTable(), result);
+                    }
+                    if (fromItem.getRightSide().getTable() != null) {
+                        addColumnsToResult(fromItem.getRightSide().getTable(), result);
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    private static void addColumnsToResult(Table table, List<Column> result) {
+        Column[] numberColumns = table.getNumberColumns();
+        for (int i = 0; i < numberColumns.length; i++) {
+            Column column = numberColumns[i];
+            if (column.isIndexed()) {
+                // Indexed columns have first priority, they will be
+                // added to the beginning of the list
+                result.add(0, column);
+            } else {
+                result.add(column);
+            }
+        }
+    }
+
+    /**
+     * @return the total number of rows expected from executing the query.
+     */
+    public long getRowCount() {
+        if (_cachedRowCount == null) {
+            _cachedRowCount = getRowCount(_query);
+        }
+        return _cachedRowCount;
+    }
+
+    private long getRowCount(Query q) {
+        q = q.clone();
+        SelectItem countAllItem = SelectItem.getCountAllItem();
+        if (q.getGroupByClause().getItemCount() > 0) {
+            q = new Query().from(new FromItem(q).setAlias("sq")).select(countAllItem);
+        } else {
+            q.getSelectClause().removeItems();
+            q.select(countAllItem);
+        }
+        Row row = MetaModelHelper.executeSingleRowQuery(_dataContext, q);
+        Number count = (Number) row.getValue(countAllItem);
+        return count.longValue();
+    }
+
+    /**
+     * Sets the desired maximum result set row count. Note that this size cannot
+     * be guaranteed, but will serve as an indicator for determining the
+     * split-size
+     * 
+     * @param maxRows
+     */
+    public QuerySplitter setMaxRows(long maxRows) {
+        if (maxRows < MINIMUM_MAX_ROWS) {
+            throw new IllegalArgumentException("maxRows must be higher than " + MINIMUM_MAX_ROWS);
+        }
+        _maxRows = maxRows;
+        return this;
+    }
+
+    public DataSet executeQueries() {
+        return executeQueries(splitQuery());
+    }
+
+    public DataSet executeQueries(List<Query> splitQueries) {
+        return new SplitQueriesDataSet(_dataContext, splitQueries);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/SplitQueriesDataSet.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/SplitQueriesDataSet.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/SplitQueriesDataSet.java
new file mode 100644
index 0000000..68cc442
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/SplitQueriesDataSet.java
@@ -0,0 +1,106 @@
+/**
+ * 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.jdbc;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.eobjects.metamodel.DataContext;
+import org.eobjects.metamodel.MetaModelException;
+import org.eobjects.metamodel.data.AbstractDataSet;
+import org.eobjects.metamodel.data.DataSet;
+import org.eobjects.metamodel.data.Row;
+import org.eobjects.metamodel.query.Query;
+import org.eobjects.metamodel.query.SelectItem;
+
+/**
+ * DataSet for split queries. Queries will be executed as needed, not at once.
+ * 
+ * @see org.eobjects.metamodel.jdbc.QuerySplitter
+ */
+final class SplitQueriesDataSet extends AbstractDataSet {
+
+    private static final Logger logger = LoggerFactory.getLogger(SplitQueriesDataSet.class);
+    private final DataContext _dataContext;
+    private Iterator<Query> _queryIterator;
+    private DataSet _currentDataSet;
+    private int _queryIndex = 0;
+
+    public SplitQueriesDataSet(DataContext dataContext, List<Query> splitQueries) {
+        super(getSelectItems(splitQueries));
+        if (dataContext == null || splitQueries == null) {
+            throw new IllegalArgumentException("Arguments cannot be null");
+        }
+        _dataContext = dataContext;
+        _queryIterator = splitQueries.iterator();
+    }
+
+    private static List<SelectItem> getSelectItems(List<Query> splitQueries) {
+        if (splitQueries.isEmpty()) {
+            return new ArrayList<SelectItem>(0);
+        }
+        return splitQueries.get(0).getSelectClause().getItems();
+    }
+
+    @Override
+    public void close() {
+        if (_currentDataSet != null) {
+            logger.debug("currentDataSet.close()");
+            _currentDataSet.close();
+        }
+        _currentDataSet = null;
+        _queryIterator = null;
+    }
+
+    @Override
+    public Row getRow() throws MetaModelException {
+        if (_currentDataSet != null) {
+            return _currentDataSet.getRow();
+        }
+        throw new IllegalStateException("No rows available. Either DataSet is closed or next() hasn't been called");
+    }
+
+    @Override
+    public boolean next() {
+        boolean result;
+        if (_currentDataSet == null) {
+            result = false;
+        } else {
+            result = _currentDataSet.next();
+        }
+        if (!result && _queryIterator.hasNext()) {
+            if (_currentDataSet != null) {
+                logger.debug("currentDataSet.close()");
+                _currentDataSet.close();
+            }
+            Query q = _queryIterator.next();
+            _currentDataSet = _dataContext.executeQuery(q);
+            if (logger.isDebugEnabled()) {
+                _queryIndex++;
+                logger.debug("Executing query #{}", _queryIndex);
+            }
+            result = next();
+        }
+        return result;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/SqlKeywords.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/SqlKeywords.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/SqlKeywords.java
new file mode 100644
index 0000000..349e2c0
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/SqlKeywords.java
@@ -0,0 +1,51 @@
+/**
+ * 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.jdbc;
+
+import java.util.HashSet;
+import java.util.Set;
+
+class SqlKeywords {
+
+	private static final Set<String> KEYWORDS;
+
+	static {
+		KEYWORDS = new HashSet<String>();
+		KEYWORDS.add("SELECT");
+		KEYWORDS.add("DISTINCT");
+		KEYWORDS.add("AS");
+		KEYWORDS.add("COUNT");
+		KEYWORDS.add("SUM");
+		KEYWORDS.add("MIN");
+		KEYWORDS.add("MAX");
+		KEYWORDS.add("FROM");
+		KEYWORDS.add("WHERE");
+		KEYWORDS.add("LIKE");
+		KEYWORDS.add("IN");
+		KEYWORDS.add("GROUP");
+		KEYWORDS.add("BY");
+		KEYWORDS.add("HAVING");
+		KEYWORDS.add("ORDER");
+	}
+
+	public static boolean isKeyword(String str) {
+		str = str.toUpperCase();
+		return KEYWORDS.contains(str);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/AbstractQueryRewriter.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/AbstractQueryRewriter.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/AbstractQueryRewriter.java
new file mode 100644
index 0000000..2cc8ece
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/AbstractQueryRewriter.java
@@ -0,0 +1,259 @@
+/**
+ * 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.jdbc.dialects;
+
+import java.util.List;
+
+import org.eobjects.metamodel.jdbc.JdbcDataContext;
+import org.eobjects.metamodel.query.AbstractQueryClause;
+import org.eobjects.metamodel.query.FilterClause;
+import org.eobjects.metamodel.query.FilterItem;
+import org.eobjects.metamodel.query.FromClause;
+import org.eobjects.metamodel.query.FromItem;
+import org.eobjects.metamodel.query.GroupByClause;
+import org.eobjects.metamodel.query.GroupByItem;
+import org.eobjects.metamodel.query.OperatorType;
+import org.eobjects.metamodel.query.OrderByClause;
+import org.eobjects.metamodel.query.OrderByItem;
+import org.eobjects.metamodel.query.Query;
+import org.eobjects.metamodel.query.SelectClause;
+import org.eobjects.metamodel.query.SelectItem;
+import org.eobjects.metamodel.schema.ColumnType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract implementation of query rewriter. This implementation delegates the
+ * rewriting of the Query into several subtasks according to the query items to
+ * be rendered. This makes it easy to overload single methods in order to
+ * correct syntax quirks.
+ */
+public abstract class AbstractQueryRewriter implements IQueryRewriter {
+
+    protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final JdbcDataContext _dataContext;
+
+    public AbstractQueryRewriter(JdbcDataContext dataContext) {
+        _dataContext = dataContext;
+    }
+
+    public JdbcDataContext getDataContext() {
+        return _dataContext;
+    }
+
+    @Override
+    public ColumnType getColumnType(int jdbcType, String nativeType, Integer columnSize) {
+        return ColumnType.convertColumnType(jdbcType);
+    }
+
+    public String rewriteQuery(Query query) {
+        query = beforeRewrite(query);
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append(rewriteSelectClause(query, query.getSelectClause()));
+        sb.append(rewriteFromClause(query, query.getFromClause()));
+        sb.append(rewriteWhereClause(query, query.getWhereClause()));
+        sb.append(rewriteGroupByClause(query, query.getGroupByClause()));
+        sb.append(rewriteHavingClause(query, query.getHavingClause()));
+        sb.append(rewriteOrderByClause(query, query.getOrderByClause()));
+        return sb.toString();
+    }
+
+    public boolean isSchemaIncludedInColumnPaths() {
+        return false;
+    }
+
+    /**
+     * Method to modify query before rewriting begins. Overwrite this method if
+     * you want to change parts of the query that are not just rendering
+     * related. Cloning the query before modifying is recommended in order to
+     * not violate referential integrity of clients (the query is mutable).
+     * 
+     * @param strategy
+     * @param query
+     * @return the modified query
+     */
+    protected Query beforeRewrite(Query query) {
+        return query;
+    }
+
+    @Override
+    public String rewriteColumnType(ColumnType columnType) {
+        return columnType.toString();
+    }
+
+    protected String rewriteOrderByClause(Query query, OrderByClause orderByClause) {
+        StringBuilder sb = new StringBuilder();
+        if (orderByClause.getItemCount() > 0) {
+            sb.append(AbstractQueryClause.PREFIX_ORDER_BY);
+            List<OrderByItem> items = orderByClause.getItems();
+            for (int i = 0; i < items.size(); i++) {
+                OrderByItem item = items.get(i);
+                if (i != 0) {
+                    sb.append(AbstractQueryClause.DELIM_COMMA);
+                }
+                sb.append(rewriteOrderByItem(query, item));
+            }
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String rewriteFromItem(FromItem item) {
+        return rewriteFromItem(item.getQuery(), item);
+    }
+
+    protected String rewriteOrderByItem(Query query, OrderByItem item) {
+        return item.toSql(isSchemaIncludedInColumnPaths());
+    }
+
+    protected String rewriteHavingClause(Query query, FilterClause havingClause) {
+        StringBuilder sb = new StringBuilder();
+        if (havingClause.getItemCount() > 0) {
+            sb.append(AbstractQueryClause.PREFIX_HAVING);
+            List<FilterItem> items = havingClause.getItems();
+            for (int i = 0; i < items.size(); i++) {
+                FilterItem item = items.get(i);
+                if (i != 0) {
+                    sb.append(AbstractQueryClause.DELIM_AND);
+                }
+                sb.append(rewriteFilterItem(item));
+            }
+        }
+        return sb.toString();
+    }
+
+    protected String rewriteGroupByClause(Query query, GroupByClause groupByClause) {
+        StringBuilder sb = new StringBuilder();
+        if (groupByClause.getItemCount() > 0) {
+            sb.append(AbstractQueryClause.PREFIX_GROUP_BY);
+            List<GroupByItem> items = groupByClause.getItems();
+            for (int i = 0; i < items.size(); i++) {
+                GroupByItem item = items.get(i);
+                if (i != 0) {
+                    sb.append(AbstractQueryClause.DELIM_COMMA);
+                }
+                sb.append(rewriteGroupByItem(query, item));
+            }
+        }
+        return sb.toString();
+    }
+
+    protected String rewriteGroupByItem(Query query, GroupByItem item) {
+        return item.toSql(isSchemaIncludedInColumnPaths());
+    }
+
+    protected String rewriteWhereClause(Query query, FilterClause whereClause) {
+        StringBuilder sb = new StringBuilder();
+        if (whereClause.getItemCount() > 0) {
+            sb.append(AbstractQueryClause.PREFIX_WHERE);
+            List<FilterItem> items = whereClause.getItems();
+            for (int i = 0; i < items.size(); i++) {
+                FilterItem item = items.get(i);
+                if (i != 0) {
+                    sb.append(AbstractQueryClause.DELIM_AND);
+                }
+                sb.append(rewriteFilterItem(item));
+            }
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String rewriteFilterItem(FilterItem item) {
+        if (item.isCompoundFilter()) {
+            FilterItem[] childItems = item.getChildItems();
+            StringBuilder sb = new StringBuilder();
+            sb.append('(');
+            for (int i = 0; i < childItems.length; i++) {
+                FilterItem child = childItems[i];
+                if (i != 0) {
+                    sb.append(' ');
+                    sb.append(item.getLogicalOperator().toString());
+                    sb.append(' ');
+                }
+                sb.append(rewriteFilterItem(child));
+            }
+            sb.append(')');
+            return sb.toString();
+        }
+
+        final String primaryFilterSql = item.toSql(isSchemaIncludedInColumnPaths());
+
+        final OperatorType operator = item.getOperator();
+        if (operator == OperatorType.DIFFERENT_FROM) {
+            final Object operand = item.getOperand();
+            if (operand != null) {
+                // special case in SQL where NULL is not treated as a value -
+                // see Ticket #1058
+
+                FilterItem isNullFilter = new FilterItem(item.getSelectItem(), OperatorType.EQUALS_TO, null);
+                final String secondaryFilterSql = rewriteFilterItem(isNullFilter);
+
+                return '(' + primaryFilterSql + " OR " + secondaryFilterSql + ')';
+            }
+        }
+
+        return primaryFilterSql;
+    }
+
+    protected String rewriteFromClause(Query query, FromClause fromClause) {
+        StringBuilder sb = new StringBuilder();
+        if (fromClause.getItemCount() > 0) {
+            sb.append(AbstractQueryClause.PREFIX_FROM);
+            List<FromItem> items = fromClause.getItems();
+            for (int i = 0; i < items.size(); i++) {
+                FromItem item = items.get(i);
+                if (i != 0) {
+                    sb.append(AbstractQueryClause.DELIM_COMMA);
+                }
+                sb.append(rewriteFromItem(query, item));
+            }
+        }
+        return sb.toString();
+    }
+
+    protected String rewriteFromItem(Query query, FromItem item) {
+        return item.toSql(isSchemaIncludedInColumnPaths());
+    }
+
+    protected String rewriteSelectClause(Query query, SelectClause selectClause) {
+        StringBuilder sb = new StringBuilder();
+        if (selectClause.getItemCount() > 0) {
+            sb.append(AbstractQueryClause.PREFIX_SELECT);
+            if (selectClause.isDistinct()) {
+                sb.append("DISTINCT ");
+            }
+            List<SelectItem> items = selectClause.getItems();
+            for (int i = 0; i < items.size(); i++) {
+                SelectItem item = items.get(i);
+                if (i != 0) {
+                    sb.append(AbstractQueryClause.DELIM_COMMA);
+                }
+                sb.append(rewriteSelectItem(query, item));
+            }
+        }
+        return sb.toString();
+    }
+
+    protected String rewriteSelectItem(Query query, SelectItem item) {
+        return item.toSql(isSchemaIncludedInColumnPaths());
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/DB2QueryRewriter.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/DB2QueryRewriter.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/DB2QueryRewriter.java
new file mode 100644
index 0000000..90eeb9d
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/DB2QueryRewriter.java
@@ -0,0 +1,161 @@
+/**
+ * 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.jdbc.dialects;
+
+import java.util.Date;
+import java.util.List;
+
+import org.eobjects.metamodel.jdbc.JdbcDataContext;
+import org.eobjects.metamodel.query.FilterItem;
+import org.eobjects.metamodel.query.FromItem;
+import org.eobjects.metamodel.query.OperatorType;
+import org.eobjects.metamodel.query.Query;
+import org.eobjects.metamodel.query.SelectItem;
+import org.eobjects.metamodel.schema.ColumnType;
+import org.eobjects.metamodel.util.FormatHelper;
+import org.eobjects.metamodel.util.TimeComparator;
+
+/**
+ * Query rewriter for IBM DB2
+ */
+public class DB2QueryRewriter extends DefaultQueryRewriter implements IQueryRewriter {
+
+    public DB2QueryRewriter(JdbcDataContext dataContext) {
+        super(dataContext);
+    }
+
+    @Override
+    public String escapeQuotes(String filterItemOperand) {
+        return filterItemOperand.replaceAll("\\'", "\\\\'");
+    }
+
+    /**
+     * DB2 expects the fully qualified column name, including schema, in select
+     * items.
+     */
+    @Override
+    public boolean isSchemaIncludedInColumnPaths() {
+        return true;
+    }
+
+    @Override
+    public boolean isMaxRowsSupported() {
+        return true;
+    }
+
+    @Override
+    public boolean isFirstRowSupported() {
+        return true;
+    }
+
+    @Override
+    public String rewriteQuery(Query query) {
+        final Integer firstRow = query.getFirstRow();
+        final Integer maxRows = query.getMaxRows();
+
+        if (maxRows == null && firstRow == null) {
+            return super.rewriteQuery(query);
+        }
+
+        if (firstRow == null || firstRow.intValue() == 1) {
+            // We prefer to use the "FETCH FIRST [n] ROWS ONLY" approach, if
+            // firstRow is not specified.
+            return super.rewriteQuery(query) + " FETCH FIRST " + maxRows + " ROWS ONLY";
+
+        } else {
+            // build a ROW_NUMBER() query like this:
+
+            // SELECT [original select clause]
+            // FROM ([original select clause],
+            // ROW_NUMBER() AS metamodel_row_number
+            // FROM [remainder of regular query])
+            // WHERE metamodel_row_number BETWEEN [firstRow] and [maxRows];
+
+            final Query innerQuery = query.clone();
+            innerQuery.setFirstRow(null);
+            innerQuery.setMaxRows(null);
+
+            final Query outerQuery = new Query();
+            final FromItem subQuerySelectItem = new FromItem(innerQuery).setAlias("metamodel_subquery");
+            outerQuery.from(subQuerySelectItem);
+
+            final List<SelectItem> innerSelectItems = innerQuery.getSelectClause().getItems();
+            for (SelectItem selectItem : innerSelectItems) {
+                outerQuery.select(new SelectItem(selectItem, subQuerySelectItem));
+            }
+
+            innerQuery.select(new SelectItem("ROW_NUMBER() OVER()", "metamodel_row_number"));
+
+            final String baseQueryString = rewriteQuery(outerQuery);
+
+            if (maxRows == null) {
+                return baseQueryString + " WHERE metamodel_row_number > " + (firstRow - 1);
+            }
+
+            return baseQueryString + " WHERE metamodel_row_number BETWEEN " + firstRow + " AND " + (firstRow - 1 + maxRows);
+        }
+    }
+
+    @Override
+    public String rewriteColumnType(ColumnType columnType) {
+        switch (columnType) {
+        case BOOLEAN:
+        case BIT:
+            return "SMALLINT";
+        default:
+            return super.rewriteColumnType(columnType);
+        }
+    }
+
+    @Override
+    public String rewriteFilterItem(FilterItem item) {
+        SelectItem _selectItem = item.getSelectItem();
+        Object _operand = item.getOperand();
+        OperatorType _operator = item.getOperator();
+        if (null != _selectItem && _operand != null) {
+            ColumnType columnType = _selectItem.getExpectedColumnType();
+            if (columnType != null) {
+                if (columnType.isTimeBased()) {
+                    // special logic for DB2 based time operands.
+
+                    StringBuilder sb = new StringBuilder();
+                    sb.append(_selectItem.getSameQueryAlias(true));
+                    final Object operand = FilterItem.appendOperator(sb, _operand, _operator);
+
+                    if (operand instanceof SelectItem) {
+                        final String selectItemString = ((SelectItem) operand).getSameQueryAlias(true);
+                        sb.append(selectItemString);
+                    } else {
+                        Date date = TimeComparator.toDate(_operand);
+                        if (date == null) {
+                            throw new IllegalStateException("Could not convert " + _operand + " to date");
+                        }
+
+                        final String sqlValue = FormatHelper.formatSqlTime(columnType, date, true, "('", "')");
+                        sb.append(sqlValue);
+                    }
+
+                    return sb.toString();
+                }
+            }
+        }
+        return super.rewriteFilterItem(item);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/DefaultQueryRewriter.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/DefaultQueryRewriter.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/DefaultQueryRewriter.java
new file mode 100644
index 0000000..d3356cd
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/DefaultQueryRewriter.java
@@ -0,0 +1,146 @@
+/**
+ * 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.jdbc.dialects;
+
+import java.util.List;
+import java.util.ListIterator;
+
+import org.eobjects.metamodel.jdbc.JdbcDataContext;
+import org.eobjects.metamodel.query.FilterItem;
+import org.eobjects.metamodel.query.FromItem;
+import org.eobjects.metamodel.query.OperatorType;
+import org.eobjects.metamodel.query.Query;
+import org.eobjects.metamodel.query.SelectItem;
+import org.eobjects.metamodel.util.CollectionUtils;
+
+/**
+ * Generic query rewriter that adds syntax enhancements that are only possible
+ * to resolve just before execution time.
+ */
+public class DefaultQueryRewriter extends AbstractQueryRewriter {
+
+    private static final String SPECIAL_ALIAS_CHARACTERS = "- ,.|*%()!#¤/\\=?;:~";
+
+    public DefaultQueryRewriter(JdbcDataContext dataContext) {
+        super(dataContext);
+    }
+
+    @Override
+    protected Query beforeRewrite(Query query) {
+        query = query.clone();
+
+        JdbcDataContext dataContext = getDataContext();
+        if (dataContext != null) {
+            String identifierQuoteString = dataContext.getIdentifierQuoteString();
+            if (identifierQuoteString != null) {
+                List<SelectItem> selectItems = query.getSelectClause().getItems();
+                for (SelectItem item : selectItems) {
+                    String alias = item.getAlias();
+                    if (needsQuoting(alias, identifierQuoteString)) {
+                        item.setAlias(identifierQuoteString + alias + identifierQuoteString);
+                    }
+                }
+                List<FromItem> fromItems = query.getFromClause().getItems();
+                for (FromItem item : fromItems) {
+                    String alias = item.getAlias();
+                    if (needsQuoting(alias, identifierQuoteString)) {
+                        item.setAlias(identifierQuoteString + alias + identifierQuoteString);
+                    }
+                }
+            }
+        }
+        return query;
+    }
+
+    private boolean needsQuoting(String alias, String identifierQuoteString) {
+        boolean result = false;
+        if (alias != null && identifierQuoteString != null) {
+            if (alias.indexOf(identifierQuoteString) == -1) {
+                for (int i = 0; i < SPECIAL_ALIAS_CHARACTERS.length(); i++) {
+                    char specialCharacter = SPECIAL_ALIAS_CHARACTERS.charAt(i);
+                    if (alias.indexOf(specialCharacter) != -1) {
+                        result = true;
+                        break;
+                    }
+                }
+            }
+        }
+        if (logger.isDebugEnabled()) {
+            logger.debug("needsQuoting(" + alias + "," + identifierQuoteString + ") = " + result);
+        }
+        return result;
+    }
+
+    @Override
+    public String rewriteFilterItem(FilterItem item) {
+        Object operand = item.getOperand();
+        if (operand != null) {
+            if (operand instanceof String) {
+                String str = (String) operand;
+                // escape single quotes
+                if (str.indexOf('\'') != -1) {
+                    str = escapeQuotes(str);
+                    FilterItem replacementFilterItem = new FilterItem(item.getSelectItem(), item.getOperator(), str);
+                    return super.rewriteFilterItem(replacementFilterItem);
+                }
+            } else if (operand instanceof Iterable || operand.getClass().isArray()) {
+                // operand is a set of values (typically in combination with an
+                // IN operator). Each individual element must be escaped.
+
+                assert item.getOperator() == OperatorType.IN;
+
+                @SuppressWarnings("unchecked")
+                final List<Object> elements = (List<Object>) CollectionUtils.toList(operand);
+
+                for (ListIterator<Object> it = elements.listIterator(); it.hasNext();) {
+                    Object next = it.next();
+                    if (next == null) {
+                        logger.warn("element in IN list is NULL, which isn't supported by SQL. Stripping the element from the list: {}", item);
+                        it.remove();
+                    } else if (next instanceof String) {
+                        String str = (String) next;
+                        if (str.indexOf('\'') != -1) {
+                            str = escapeQuotes(str);
+                            it.set(str);
+                        }
+                    }
+                }
+
+                FilterItem replacementFilterItem = new FilterItem(item.getSelectItem(), item.getOperator(), elements);
+                return super.rewriteFilterItem(replacementFilterItem);
+            }
+        }
+        return super.rewriteFilterItem(item);
+    }
+
+    @Override
+    public boolean isFirstRowSupported() {
+        return false;
+    }
+
+    @Override
+    public boolean isMaxRowsSupported() {
+        return false;
+    }
+
+    @Override
+    public String escapeQuotes(String item) {
+        return item.replaceAll("\\'", "\\'\\'");
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/H2QueryRewriter.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/H2QueryRewriter.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/H2QueryRewriter.java
new file mode 100644
index 0000000..f6d1c31
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/H2QueryRewriter.java
@@ -0,0 +1,31 @@
+/**
+ * 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.jdbc.dialects;
+
+import org.eobjects.metamodel.jdbc.JdbcDataContext;
+
+/**
+ * Query rewriter for H2
+ */
+public class H2QueryRewriter extends LimitOffsetQueryRewriter {
+
+    public H2QueryRewriter(JdbcDataContext dataContext) {
+        super(dataContext);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/HsqldbQueryRewriter.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/HsqldbQueryRewriter.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/HsqldbQueryRewriter.java
new file mode 100644
index 0000000..36366cd
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/HsqldbQueryRewriter.java
@@ -0,0 +1,99 @@
+/**
+ * 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.jdbc.dialects;
+
+import org.eobjects.metamodel.jdbc.JdbcDataContext;
+import org.eobjects.metamodel.query.FilterItem;
+import org.eobjects.metamodel.query.Query;
+import org.eobjects.metamodel.query.SelectClause;
+import org.eobjects.metamodel.query.SelectItem;
+import org.eobjects.metamodel.schema.Column;
+import org.eobjects.metamodel.schema.ColumnType;
+
+/**
+ * Query rewriter for HSQLDB
+ */
+public class HsqldbQueryRewriter extends DefaultQueryRewriter {
+
+    public HsqldbQueryRewriter(JdbcDataContext dataContext) {
+        super(dataContext);
+    }
+
+    @Override
+    public String rewriteColumnType(ColumnType columnType) {
+        if (columnType == ColumnType.BLOB) {
+            return "LONGVARBINARY";
+        }
+        return super.rewriteColumnType(columnType);
+    }
+
+    @Override
+    public boolean isFirstRowSupported() {
+        return true;
+    }
+
+    @Override
+    public boolean isMaxRowsSupported() {
+        return true;
+    }
+
+    @Override
+    protected String rewriteSelectClause(Query query, SelectClause selectClause) {
+        String result = super.rewriteSelectClause(query, selectClause);
+
+        Integer firstRow = query.getFirstRow();
+        Integer maxRows = query.getMaxRows();
+        if (maxRows != null || firstRow != null) {
+            if (maxRows == null) {
+                maxRows = Integer.MAX_VALUE;
+            }
+            if (firstRow == null || firstRow <= 0) {
+                result = "SELECT TOP " + maxRows + " " + result.substring(7);
+            } else {
+                final int offset = firstRow - 1;
+                result = "SELECT LIMIT " + offset + " " + maxRows + " " + result.substring(7);
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public String rewriteFilterItem(FilterItem item) {
+        if (!item.isCompoundFilter()) {
+            final SelectItem selectItem = item.getSelectItem();
+            final Column column = selectItem.getColumn();
+            if (column != null) {
+                if (column.getType() == ColumnType.TIMESTAMP) {
+                    // HSQLDB does not treat (TIMESTAMP 'yyyy-MM-dd hh:mm:ss')
+                    // tokens correctly
+                    String result = super.rewriteFilterItem(item);
+                    int indexOfTimestamp = result.lastIndexOf("TIMESTAMP");
+                    if (indexOfTimestamp != -1) {
+                        result = result.substring(0, indexOfTimestamp)
+                                + result.substring(indexOfTimestamp + "TIMESTAMP".length());
+                    }
+                    return result;
+                }
+            }
+        }
+        return super.rewriteFilterItem(item);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/IQueryRewriter.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/IQueryRewriter.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/IQueryRewriter.java
new file mode 100644
index 0000000..4d94d40
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/IQueryRewriter.java
@@ -0,0 +1,93 @@
+/**
+ * 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.jdbc.dialects;
+
+import java.sql.Types;
+
+import org.eobjects.metamodel.jdbc.JdbcDataContext;
+import org.eobjects.metamodel.query.FilterItem;
+import org.eobjects.metamodel.query.FromItem;
+import org.eobjects.metamodel.query.Query;
+import org.eobjects.metamodel.schema.ColumnType;
+
+/**
+ * A query rewriter can be used for rewriting (part of) a query's string
+ * representation. This is usefull for databases that deviate from the SQL 99
+ * compliant syntax which is delievered by the query and it's query item's
+ * toString() methods.
+ * 
+ * @see AbstractQueryRewriter
+ * @see JdbcDataContext
+ */
+public interface IQueryRewriter {
+
+	public String rewriteFromItem(FromItem item);
+
+	public String rewriteQuery(Query query);
+
+	public String rewriteFilterItem(FilterItem whereItem);
+
+	/**
+	 * Gets whether this query rewriter is able to write the "Max rows" query
+	 * property to the query string.
+	 * 
+	 * @return whether this query rewriter is able to write the "Max rows" query
+	 *         property to the query string.
+	 */
+	public boolean isMaxRowsSupported();
+
+	/**
+	 * Gets whether this query rewriter is able to write the "First row" query
+	 * property to the query string.
+	 * 
+	 * @return whether this query rewriter is able to write the "First row"
+	 *         query property to the query string.
+	 */
+	public boolean isFirstRowSupported();
+
+	/**
+	 * Escapes the quotes within a String literal of a query item.
+	 * 
+	 * @return String item with quotes escaped.
+	 */
+	public String escapeQuotes(String item);
+
+	/**
+	 * Rewrites the name of a column type, as it is written in CREATE TABLE
+	 * statements. Some databases dont support all column types, or have
+	 * different names for them. The implementation of this method will do that
+	 * conversion.
+	 * 
+	 * @param columnType
+	 * @return
+	 */
+	public String rewriteColumnType(ColumnType columnType);
+
+	/**
+	 * Gets the column type for a specific JDBC type (as defined in
+	 * {@link Types}), native type name and column size.
+	 * 
+	 * @param jdbcType
+	 * @param nativeType
+	 * @param columnSize
+	 * @return
+	 */
+	public ColumnType getColumnType(int jdbcType, String nativeType, Integer columnSize);
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/LimitOffsetQueryRewriter.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/LimitOffsetQueryRewriter.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/LimitOffsetQueryRewriter.java
new file mode 100644
index 0000000..76a35a8
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/LimitOffsetQueryRewriter.java
@@ -0,0 +1,71 @@
+/**
+ * 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.jdbc.dialects;
+
+import org.eobjects.metamodel.jdbc.JdbcDataContext;
+import org.eobjects.metamodel.query.Query;
+
+/**
+ * Query rewriter for databases that support LIMIT and OFFSET keywords for max
+ * rows and first row properties.
+ */
+public abstract class LimitOffsetQueryRewriter extends DefaultQueryRewriter {
+
+    public LimitOffsetQueryRewriter(JdbcDataContext dataContext) {
+        super(dataContext);
+    }
+
+    @Override
+    public final boolean isFirstRowSupported() {
+        return true;
+    }
+
+    @Override
+    public final boolean isMaxRowsSupported() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * If the Max rows and/or First row property of the query is set, then we
+     * will use the database's LIMIT and OFFSET functions.
+     */
+    @Override
+    public String rewriteQuery(Query query) {
+        String queryString = super.rewriteQuery(query);
+        Integer maxRows = query.getMaxRows();
+        Integer firstRow = query.getFirstRow();
+        if (maxRows != null || firstRow != null) {
+            if (maxRows == null) {
+                maxRows = Integer.MAX_VALUE;
+            }
+            queryString = queryString + " LIMIT " + maxRows;
+
+            if (firstRow != null && firstRow > 1) {
+                // offset is 0-based
+                int offset = firstRow - 1;
+                queryString = queryString + " OFFSET " + offset;
+            }
+        }
+
+
+        return queryString;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/MysqlQueryRewriter.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/MysqlQueryRewriter.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/MysqlQueryRewriter.java
new file mode 100644
index 0000000..88757be
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/MysqlQueryRewriter.java
@@ -0,0 +1,36 @@
+/**
+ * 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.jdbc.dialects;
+
+import org.eobjects.metamodel.jdbc.JdbcDataContext;
+
+/**
+ * Query rewriter for MySQL
+ */
+public class MysqlQueryRewriter extends LimitOffsetQueryRewriter implements IQueryRewriter {
+
+    public MysqlQueryRewriter(JdbcDataContext dataContext) {
+        super(dataContext);
+    }
+
+    @Override
+    public String escapeQuotes(String filterItemOperand) {
+        return filterItemOperand.replaceAll("\\'", "\\\\'");
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/PostgresqlQueryRewriter.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/PostgresqlQueryRewriter.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/PostgresqlQueryRewriter.java
new file mode 100644
index 0000000..d303c50
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/PostgresqlQueryRewriter.java
@@ -0,0 +1,70 @@
+/**
+ * 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.jdbc.dialects;
+
+import org.eobjects.metamodel.jdbc.JdbcDataContext;
+import org.eobjects.metamodel.query.FromItem;
+import org.eobjects.metamodel.query.Query;
+import org.eobjects.metamodel.schema.ColumnType;
+import org.eobjects.metamodel.schema.Schema;
+import org.eobjects.metamodel.schema.Table;
+
+/**
+ * Query rewriter for PostgreSQL
+ */
+public class PostgresqlQueryRewriter extends LimitOffsetQueryRewriter implements IQueryRewriter {
+
+    public PostgresqlQueryRewriter(JdbcDataContext dataContext) {
+        super(dataContext);
+    }
+
+    @Override
+    public ColumnType getColumnType(int jdbcType, String nativeType, Integer columnSize) {
+        if ("bool".equals(nativeType)) {
+            // override the normal behaviour of postgresql which maps "bool" to
+            // a BIT.
+            return ColumnType.BOOLEAN;
+        }
+        return super.getColumnType(jdbcType, nativeType, columnSize);
+    }
+
+    @Override
+    public String rewriteColumnType(ColumnType columnType) {
+        if (columnType == ColumnType.BLOB) {
+            return "bytea";
+        }
+        return super.rewriteColumnType(columnType);
+    }
+
+    @Override
+    protected String rewriteFromItem(Query query, FromItem item) {
+        String result = super.rewriteFromItem(query, item);
+        Table table = item.getTable();
+        if (table != null) {
+            Schema schema = table.getSchema();
+            if (schema != null) {
+                String schemaName = schema.getName();
+                if (schemaName != null) {
+                    result = result.replaceFirst(schemaName, '\"' + schema.getName() + '\"');
+                }
+            }
+        }
+        return result;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/SQLServerQueryRewriter.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/SQLServerQueryRewriter.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/SQLServerQueryRewriter.java
new file mode 100644
index 0000000..732ec34
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/SQLServerQueryRewriter.java
@@ -0,0 +1,56 @@
+/**
+ * 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.jdbc.dialects;
+
+import org.eobjects.metamodel.jdbc.JdbcDataContext;
+import org.eobjects.metamodel.query.Query;
+import org.eobjects.metamodel.query.SelectClause;
+
+public class SQLServerQueryRewriter extends DefaultQueryRewriter {
+
+	public SQLServerQueryRewriter(JdbcDataContext dataContext) {
+		super(dataContext);
+	}
+
+	@Override
+	public boolean isMaxRowsSupported() {
+		return true;
+	}
+
+	/**
+	 * SQL server expects the fully qualified column name, including schema, in
+	 * select items.
+	 */
+	@Override
+	public boolean isSchemaIncludedInColumnPaths() {
+		return true;
+	}
+
+	@Override
+	protected String rewriteSelectClause(Query query, SelectClause selectClause) {
+		String result = super.rewriteSelectClause(query, selectClause);
+
+		Integer maxRows = query.getMaxRows();
+		if (maxRows != null) {
+			result = "SELECT TOP " + maxRows + " " + result.substring(7);
+		}
+
+		return result;
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/package-info.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/package-info.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/package-info.java
new file mode 100644
index 0000000..fac1963
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/package-info.java
@@ -0,0 +1,23 @@
+/**
+ * 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.
+ */
+/**
+ * Module package for JDBC compliant databases
+ */
+package org.eobjects.metamodel.jdbc;
+

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/eobjects/metamodel/jdbc/FetchSizeCalculator.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/eobjects/metamodel/jdbc/FetchSizeCalculator.java b/jdbc/src/main/java/org/eobjects/metamodel/jdbc/FetchSizeCalculator.java
deleted file mode 100644
index 155adf9..0000000
--- a/jdbc/src/main/java/org/eobjects/metamodel/jdbc/FetchSizeCalculator.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/**
- * 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.jdbc;
-
-import java.util.List;
-
-import org.eobjects.metamodel.query.Query;
-import org.eobjects.metamodel.query.SelectItem;
-import org.eobjects.metamodel.schema.Column;
-import org.eobjects.metamodel.schema.ColumnType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Class used to calculate an appropriate fetch size of a JDBC query.
- * 
- * The approach used in this class is largely based on the documentation of
- * Oracle's caching size, see <a href=
- * "http://www.oracle.com/technetwork/database/enterprise-edition/memory.pdf"
- * >JDBC Memory Management</a>, section "Where does it all go?".
- * 
- * @author Kasper Sørensen
- */
-final class FetchSizeCalculator {
-
-	/**
-	 * 22 bytes is a reasonable approximation for remaining row types, we add a
-	 * few bytes to be on the safe side.
-	 */
-	private static final int DEFAULT_COLUMN_SIZE = 30;
-
-	/**
-	 * A kilobyte (kb)
-	 */
-	private static final int KB = 1024;
-
-	private static final Logger logger = LoggerFactory
-			.getLogger(FetchSizeCalculator.class);
-
-	private static final int MIN_FETCH_SIZE = 1;
-	private static final int MAX_FETCH_SIZE = 25000;
-	private final int _bytesInMemory;
-
-	public FetchSizeCalculator(int bytesInMemory) {
-		_bytesInMemory = bytesInMemory;
-	}
-
-	/**
-	 * Gets the fetch size of a query
-	 * 
-	 * @param query
-	 *            the query to execute
-	 * @return an integer representing how many rows to eagerly fetch for the
-	 *         query
-	 */
-	public int getFetchSize(Query query) {
-		if (isSingleRowQuery(query)) {
-			return 1;
-		}
-		int bytesPerRow = getRowSize(query);
-		int result = getFetchSize(bytesPerRow);
-		final Integer maxRows = query.getMaxRows();
-		if (maxRows != null && result > maxRows) {
-			logger.debug("Result ({}) was below max rows ({}), adjusting.",
-					result, maxRows);
-			result = maxRows;
-		}
-		return result;
-	}
-
-	/**
-	 * Gets whether a query is guaranteed to only yield a single row. Such
-	 * queries are queries that only consist of aggregation functions and no
-	 * group by clause.
-	 * 
-	 * @param query
-	 * @return
-	 */
-	private boolean isSingleRowQuery(Query query) {
-		if (!query.getGroupByClause().isEmpty()) {
-			return false;
-		}
-
-		List<SelectItem> items = query.getSelectClause().getItems();
-		for (SelectItem item : items) {
-			if (item.getFunction() == null) {
-				return false;
-			}
-		}
-		return true;
-	}
-
-	/**
-	 * Gets the fetch size of a query based on the columns to query.
-	 * 
-	 * @param columns
-	 *            the columns to query
-	 * @return an integer representing how many rows to eagerly fetch for the
-	 *         query
-	 */
-	public int getFetchSize(Column... columns) {
-		int bytesPerRow = getRowSize(columns);
-		return getFetchSize(bytesPerRow);
-	}
-
-	/**
-	 * Gets the size of a row (in bytes).
-	 * 
-	 * @param query
-	 *            the query that will yield the rows
-	 * @return an integer representing the size of a row from the given query
-	 *         (in bytes).
-	 */
-	protected int getRowSize(Query query) {
-		List<SelectItem> items = query.getSelectClause().getItems();
-		int bytesPerRow = 0;
-		for (SelectItem selectItem : items) {
-			bytesPerRow += getValueSize(selectItem);
-		}
-		return bytesPerRow;
-	}
-
-	/**
-	 * Gets the size of a row (in bytes).
-	 * 
-	 * @param columns
-	 *            the columns in the row
-	 * @return an integer representing the size of a row with the given columns
-	 *         (in bytes).
-	 */
-	protected int getRowSize(Column... columns) {
-		int bytesPerRow = 0;
-		for (Column column : columns) {
-			bytesPerRow += getValueSize(column);
-		}
-		return bytesPerRow;
-	}
-
-	/**
-	 * Gets the principal fetch size for a query where a row has the given size.
-	 * 
-	 * @param bytesPerRow
-	 *            the size (in bytes) of a single row in the result set.
-	 * @return an appropriate fetch size.
-	 */
-	protected int getFetchSize(int bytesPerRow) {
-		if (bytesPerRow == 0) {
-			// prevent divide by zero
-			return MAX_FETCH_SIZE;
-		}
-		int result = _bytesInMemory / bytesPerRow;
-		if (result < MIN_FETCH_SIZE) {
-			logger.debug(
-					"Result ({}) was below minimum fetch size ({}), adjusting.",
-					result, MIN_FETCH_SIZE);
-			result = MIN_FETCH_SIZE;
-		} else if (result > MAX_FETCH_SIZE) {
-			logger.debug(
-					"Result ({}) was above maximum fetch size ({}), adjusting.",
-					result, MAX_FETCH_SIZE);
-			result = MAX_FETCH_SIZE;
-		}
-		return result;
-	}
-
-	/**
-	 * Gets the size (in bytes) of a single {@link SelectItem}
-	 */
-	protected int getValueSize(SelectItem selectItem) {
-		Column column = selectItem.getColumn();
-		if (column == null) {
-			return DEFAULT_COLUMN_SIZE;
-		} else {
-			return getValueSize(column);
-		}
-	}
-
-	/**
-	 * Gets the size (in bytes) of a single {@link Column}
-	 */
-	protected int getValueSize(Column column) {
-		ColumnType type = column.getType();
-		if (type == null) {
-			return DEFAULT_COLUMN_SIZE;
-		} else {
-			Integer columnSize = column.getColumnSize();
-			if (columnSize == null) {
-				// if column size is missing, then use
-				// size-indifferent approach
-				return getSize(type);
-			} else if (columnSize > 10000 && !type.isLargeObject()) {
-				// if column size is unrealistically high, then use
-				// size-indifferent approach
-				return getSize(type);
-			} else {
-				return getSize(type, columnSize);
-			}
-		}
-	}
-
-	/**
-	 * Gets the size (in bytes) of a column with a specific {@link ColumnType}
-	 * and size
-	 */
-	private int getSize(ColumnType type, int columnSize) {
-		final int baseSize;
-		if (type.isBinary()) {
-			baseSize = 1;
-		} else if (type.isBoolean()) {
-			baseSize = 1;
-		} else if (type.isLiteral()) {
-			baseSize = 2;
-		} else if (type.isNumber()) {
-			baseSize = 16;
-		} else {
-			baseSize = DEFAULT_COLUMN_SIZE;
-		}
-
-		int result = baseSize * columnSize;
-
-		if (type.isLargeObject()) {
-			// assign at least 4KB for LOBs.
-			result = Math.max(result, 4 * KB);
-		}
-
-		return result;
-	}
-
-	/**
-	 * Gets the (approximate) size (in bytes) of a column with a specific
-	 * {@link ColumnType}.
-	 */
-	private int getSize(ColumnType type) {
-		if (type.isBinary()) {
-			return 4 * KB;
-		} else if (type.isBoolean()) {
-			return 2;
-		} else if (type.isLargeObject()) {
-			return 4 * KB;
-		} else if (type.isLiteral()) {
-			return KB;
-		} else if (type.isNumber()) {
-			return 16;
-		} else {
-			return DEFAULT_COLUMN_SIZE;
-		}
-	}
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/eobjects/metamodel/jdbc/JdbcBatchUpdateCallback.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/eobjects/metamodel/jdbc/JdbcBatchUpdateCallback.java b/jdbc/src/main/java/org/eobjects/metamodel/jdbc/JdbcBatchUpdateCallback.java
deleted file mode 100644
index 1fe31d3..0000000
--- a/jdbc/src/main/java/org/eobjects/metamodel/jdbc/JdbcBatchUpdateCallback.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/**
- * 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.jdbc;
-
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.sql.Statement;
-
-import org.eobjects.metamodel.UpdateCallback;
-import org.eobjects.metamodel.util.FileHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Jdbc {@link UpdateCallback} for databases that support the JDBC Batch
- * features.
- * 
- * @author Kasper Sørensen
- */
-final class JdbcBatchUpdateCallback extends JdbcUpdateCallback {
-
-    private static final Logger logger = LoggerFactory.getLogger(JdbcBatchUpdateCallback.class);
-
-    public JdbcBatchUpdateCallback(JdbcDataContext dataContext) {
-        super(dataContext);
-    }
-
-    @Override
-    protected void closePreparedStatement(PreparedStatement preparedStatement) {
-        try {
-            int[] results = preparedStatement.executeBatch();
-            if (logger.isDebugEnabled()) {
-                for (int i = 0; i < results.length; i++) {
-                    int result = results[i];
-                    final String resultString;
-                    switch (result) {
-                    case Statement.SUCCESS_NO_INFO:
-                        resultString = "SUCCESS_NO_INFO";
-                        break;
-                    case Statement.EXECUTE_FAILED:
-                        resultString = "EXECUTE_FAILED";
-                        break;
-                    default:
-                        resultString = result + " rows updated";
-                    }
-                    logger.debug("batch execute result[" + i + "]:" + resultString);
-                }
-            }
-        } catch (SQLException e) {
-            throw JdbcUtils.wrapException(e, "execute batch: " + preparedStatement);
-        } finally {
-            FileHelper.safeClose(preparedStatement);
-        }
-    }
-
-    @Override
-    protected void executePreparedStatement(PreparedStatement st) throws SQLException {
-        st.addBatch();
-    }
-}