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:20 UTC
[07/64] [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();
- }
-}