/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.metamodel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.metamodel.convert.ConvertedDataSetInterceptor;
import org.apache.metamodel.convert.Converters;
import org.apache.metamodel.convert.HasReadTypeConverters;
import org.apache.metamodel.convert.TypeConverter;
import org.apache.metamodel.data.DataSet;
import org.apache.metamodel.data.DataSetHeader;
import org.apache.metamodel.data.DefaultRow;
import org.apache.metamodel.data.EmptyDataSet;
import org.apache.metamodel.data.FirstRowDataSet;
import org.apache.metamodel.data.InMemoryDataSet;
import org.apache.metamodel.data.Row;
import org.apache.metamodel.data.SimpleDataSetHeader;
import org.apache.metamodel.query.FilterItem;
import org.apache.metamodel.query.FromItem;
import org.apache.metamodel.query.GroupByItem;
import org.apache.metamodel.query.JoinType;
import org.apache.metamodel.query.OperatorType;
import org.apache.metamodel.query.OrderByItem;
import org.apache.metamodel.query.Query;
import org.apache.metamodel.query.ScalarFunction;
import org.apache.metamodel.query.SelectClause;
import org.apache.metamodel.query.SelectItem;
import org.apache.metamodel.schema.Column;
import org.apache.metamodel.schema.ColumnType;
import org.apache.metamodel.schema.MutableColumn;
import org.apache.metamodel.schema.MutableRelationship;
import org.apache.metamodel.schema.MutableSchema;
import org.apache.metamodel.schema.MutableTable;
import org.apache.metamodel.schema.Relationship;
import org.apache.metamodel.schema.Schema;
import org.apache.metamodel.schema.Table;
import org.apache.metamodel.schema.TableType;
import org.apache.metamodel.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract DataContext for data sources that do not support SQL queries
* natively.
*
* Instead this superclass only requires that a subclass can materialize a
* single table at a time. Then the query will be executed by post processing
* the datasets client-side.
*/
public abstract class QueryPostprocessDataContext extends AbstractDataContext implements HasReadTypeConverters {
private static final Logger logger = LoggerFactory.getLogger(QueryPostprocessDataContext.class);
public static final String INFORMATION_SCHEMA_NAME = "information_schema";
private final Map<Column, TypeConverter<?, ?>> _converters;
public QueryPostprocessDataContext() {
super();
_converters = new HashMap<Column, TypeConverter<?, ?>>();
}
@Override
public DataSet executeQuery(final Query query) {
final List<SelectItem> selectItems = query.getSelectClause().getItems();
final List<FromItem> fromItems = query.getFromClause().getItems();
final List<FilterItem> whereItems = query.getWhereClause().getItems();
final List<SelectItem> whereSelectItems = query.getWhereClause().getEvaluatedSelectItems();
final List<GroupByItem> groupByItems = query.getGroupByClause().getItems();
final List<SelectItem> groupBySelectItems = query.getGroupByClause().getEvaluatedSelectItems();
final List<SelectItem> havingSelectItems = query.getHavingClause().getEvaluatedSelectItems();
final List<SelectItem> orderBySelectItems = query.getOrderByClause().getEvaluatedSelectItems();
final List<FilterItem> havingItems = query.getHavingClause().getItems();
final List<OrderByItem> orderByItems = query.getOrderByClause().getItems();
final int firstRow = (query.getFirstRow() == null ? 1 : query.getFirstRow());
final int maxRows = (query.getMaxRows() == null ? -1 : query.getMaxRows());
if (maxRows == 0) {
// no rows requested - no reason to do anything
return new EmptyDataSet(selectItems);
}
// check certain common query types that can often be optimized by
// subclasses
final boolean singleFromItem = fromItems.size() == 1;
final boolean noGrouping = groupByItems.isEmpty() && havingItems.isEmpty();
if (singleFromItem && noGrouping) {
final FromItem fromItem = query.getFromClause().getItem(0);
final Table table = fromItem.getTable();
if (table != null) {
// check for SELECT COUNT(*) queries
if (selectItems.size() == 1) {
final SelectItem selectItem = query.getSelectClause().getItem(0);
if (SelectItem.isCountAllItem(selectItem)) {
final boolean functionApproximationAllowed = selectItem.isFunctionApproximationAllowed();
if (isMainSchemaTable(table)) {
logger.debug("Query is a COUNT query with {} where items. Trying executeCountQuery(...)",
whereItems.size());
final Number count = executeCountQuery(table, whereItems, functionApproximationAllowed);
if (count == null) {
logger.debug(
"DataContext did not return any count query results. Proceeding with manual counting.");
} else {
List<Row> data = new ArrayList<Row>(1);
final DataSetHeader header = new SimpleDataSetHeader(new SelectItem[] { selectItem });
data.add(new DefaultRow(header, new Object[] { count }));
return new InMemoryDataSet(header, data);
}
}
}
}
final boolean isSimpleSelect = isSimpleSelect(query.getSelectClause());
if (isSimpleSelect) {
// check for lookup query by primary key
if (whereItems.size() == 1) {
final FilterItem whereItem = whereItems.get(0);
final SelectItem selectItem = whereItem.getSelectItem();
if (!whereItem.isCompoundFilter() && selectItem != null && selectItem.getColumn() != null) {
final Column column = selectItem.getColumn();
if (column.isPrimaryKey() && OperatorType.EQUALS_TO.equals(whereItem.getOperator())) {
logger.debug(
"Query is a primary key lookup query. Trying executePrimaryKeyLookupQuery(...)");
if (table != null) {
if (isMainSchemaTable(table)) {
final Object operand = whereItem.getOperand();
final Row row = executePrimaryKeyLookupQuery(table, selectItems, column,
operand);
if (row == null) {
logger.debug(
"DataContext did not return any GET query results. Proceeding with manual lookup.");
} else {
final DataSetHeader header = new SimpleDataSetHeader(selectItems);
return new InMemoryDataSet(header, row);
}
}
}
}
}
}
// check for simple queries with or without simple criteria
if (orderByItems.isEmpty()) {
// no WHERE criteria set
if (whereItems.isEmpty()) {
final DataSet dataSet = materializeTable(table, selectItems, firstRow, maxRows);
return dataSet;
}
final DataSet dataSet = materializeTable(table, selectItems, whereItems, firstRow, maxRows);
return dataSet;
}
}
}
}
// Creates a list for all select items that are needed to execute query
// (some may only be used as part of a filter, but not shown in result)
List<SelectItem> workSelectItems = CollectionUtils.concat(true, selectItems, whereSelectItems,
groupBySelectItems, havingSelectItems, orderBySelectItems);
// Materialize the tables in the from clause
final DataSet[] fromDataSets = new DataSet[fromItems.size()];
for (int i = 0; i < fromDataSets.length; i++) {
FromItem fromItem = fromItems.get(i);
fromDataSets[i] = materializeFromItem(fromItem, workSelectItems);
}
// Execute the query using the raw data
DataSet dataSet = MetaModelHelper.getCarthesianProduct(fromDataSets, whereItems);
// we can now exclude the select items imposed by the WHERE clause (and
// should, to make the aggregation process faster)
workSelectItems = CollectionUtils.concat(true, selectItems, groupBySelectItems, havingSelectItems,
orderBySelectItems);
if (groupByItems.size() > 0) {
dataSet = MetaModelHelper.getGrouped(workSelectItems, dataSet, groupByItems);
} else {
dataSet = MetaModelHelper.getAggregated(workSelectItems, dataSet);
}
dataSet = MetaModelHelper.getFiltered(dataSet, havingItems);
if (query.getSelectClause().isDistinct()) {
dataSet = MetaModelHelper.getSelection(selectItems, dataSet);
dataSet = MetaModelHelper.getDistinct(dataSet);
dataSet = MetaModelHelper.getOrdered(dataSet, orderByItems);
} else {
dataSet = MetaModelHelper.getOrdered(dataSet, orderByItems);
dataSet = MetaModelHelper.getSelection(selectItems, dataSet);
}
dataSet = MetaModelHelper.getPaged(dataSet, firstRow, maxRows);
return dataSet;
}
/**
* Determines if all the select items are 'simple' meaning that they just
* represent scans of values in columns.
*
* @param clause
* @return
*/
private boolean isSimpleSelect(SelectClause clause) {
if (clause.isDistinct()) {
return false;
}
for (SelectItem item : clause.getItems()) {
if (item.getAggregateFunction() != null || item.getExpression() != null) {
return false;
}
}
return true;
}
/**
* Executes a simple count query, if possible. This method is provided to
* allow subclasses to optimize count queries since they are quite common
* and often a datastore can retrieve the count using some specialized means
* which is much more performant than counting all records manually.
*
* @param table
* the table on which the count is requested.
* @param whereItems
* a (sometimes empty) list of WHERE items.
* @param functionApproximationAllowed
* whether approximation is allowed or not.
* @return the count of the particular table, or null if not available.
*/
protected Number executeCountQuery(Table table, List<FilterItem> whereItems, boolean functionApproximationAllowed) {
return null;
}
/**
* Executes a query which obtains a row by primary key (as defined by
* {@link Column#isPrimaryKey()}). This method is provided to allow
* subclasses to optimize lookup queries since they are quite common and
* often a datastore can retrieve the row using some specialized means which
* is much more performant than scanning all records manually.
*
* @param table
* the table on which the lookup is requested.
* @param selectItems
* the items to select from the lookup query.
* @param primaryKeyColumn
* the column that is the primary key
* @param keyValue
* the primary key value that is specified in the lookup query.
* @return the row if the particular table, or null if not available.
*/
protected Row executePrimaryKeyLookupQuery(Table table, List<SelectItem> selectItems, Column primaryKeyColumn,
Object keyValue) {
return null;
}
protected DataSet materializeFromItem(final FromItem fromItem, final List<SelectItem> selectItems) {
DataSet dataSet;
JoinType joinType = fromItem.getJoin();
if (fromItem.getTable() != null) {
// We need to materialize a single table
final Table table = fromItem.getTable();
final List<SelectItem> selectItemsToMaterialize = new ArrayList<SelectItem>();
for (final SelectItem selectItem : selectItems) {
final FromItem selectedFromItem = selectItem.getFromItem();
if (selectedFromItem != null) {
if (selectedFromItem.equals(fromItem)) {
selectItemsToMaterialize.add(selectItem.replaceFunction(null));
}
} else {
// the select item does not specify a specific
// from-item
final Column selectedColumn = selectItem.getColumn();
if (selectedColumn != null) {
// we assume that if the table matches, we will use the
// column
if (selectedColumn.getTable() != null && selectedColumn.getTable().equals(table)) {
selectItemsToMaterialize.add(selectItem.replaceFunction(null));
}
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("calling materializeTable(" + table.getName() + "," + selectItemsToMaterialize + ",1,-1");
}
// Dispatching to the concrete subclass of
// QueryPostprocessDataContextStrategy
dataSet = materializeTable(table, selectItemsToMaterialize, 1, -1);
} else if (joinType != null) {
// We need to (recursively) materialize a joined FromItem
if (fromItem.getLeftSide() == null || fromItem.getRightSide() == null) {
throw new IllegalArgumentException("Joined FromItem requires both left and right side: " + fromItem);
}
final DataSet[] fromItemDataSets = new DataSet[2];
// materialize left side
final List<SelectItem> leftOn = Arrays.asList(fromItem.getLeftOn());
fromItemDataSets[0] = materializeFromItem(fromItem.getLeftSide(),
CollectionUtils.concat(true, selectItems, leftOn));
// materialize right side
final List<SelectItem> rightOn = Arrays.asList(fromItem.getRightOn());
fromItemDataSets[1] = materializeFromItem(fromItem.getRightSide(),
CollectionUtils.concat(true, selectItems, rightOn));
final FilterItem[] onConditions = new FilterItem[leftOn.size()];
for (int i = 0; i < onConditions.length; i++) {
final FilterItem whereItem = new FilterItem(leftOn.get(i), OperatorType.EQUALS_TO, rightOn.get(i));
onConditions[i] = whereItem;
}
switch (joinType) {
case INNER:
dataSet = MetaModelHelper.getCarthesianProduct(fromItemDataSets, onConditions);
break;
case LEFT:
dataSet = MetaModelHelper.getLeftJoin(fromItemDataSets[0], fromItemDataSets[1], onConditions);
break;
case RIGHT:
dataSet = MetaModelHelper.getRightJoin(fromItemDataSets[0], fromItemDataSets[1], onConditions);
break;
default:
throw new IllegalArgumentException("FromItem type not supported: " + fromItem);
}
} else if (fromItem.getSubQuery() != null) {
// We need to (recursively) materialize a subquery
dataSet = executeQuery(fromItem.getSubQuery());
} else {
throw new IllegalArgumentException("FromItem type not supported: " + fromItem);
}
if (dataSet == null) {
throw new IllegalStateException("FromItem was not succesfully materialized: " + fromItem);
}
return dataSet;
}
protected DataSet materializeTable(final Table table, final List<SelectItem> selectItems,
final List<FilterItem> whereItems, final int firstRow, final int maxRows) {
if (table == null) {
throw new IllegalArgumentException("Table cannot be null");
}
if (selectItems == null || selectItems.isEmpty()) {
// add any column (typically this occurs because of COUNT(*)
// queries)
Column[] columns = table.getColumns();
if (columns.length == 0) {
logger.warn("Queried table has no columns: {}", table);
} else {
selectItems.add(new SelectItem(columns[0]));
}
}
final Schema schema = table.getSchema();
final String schemaName;
if (schema == null) {
schemaName = null;
} else {
schemaName = schema.getName();
}
final DataSet dataSet;
if (INFORMATION_SCHEMA_NAME.equals(schemaName)) {
DataSet informationDataSet = materializeInformationSchemaTable(table,
buildWorkingSelectItems(selectItems, whereItems));
informationDataSet = MetaModelHelper.getFiltered(informationDataSet, whereItems);
informationDataSet = MetaModelHelper.getSelection(selectItems, informationDataSet);
informationDataSet = MetaModelHelper.getPaged(informationDataSet, firstRow, maxRows);
dataSet = informationDataSet;
} else {
final DataSet tableDataSet = materializeMainSchemaTable(table, selectItems, whereItems, firstRow, maxRows);
// conversion is done at materialization time, since it enables
// the refined types to be used also in eg. where clauses.
dataSet = new ConvertedDataSetInterceptor(_converters).intercept(tableDataSet);
}
return dataSet;
}
private List<SelectItem> buildWorkingSelectItems(List<SelectItem> selectItems, List<FilterItem> whereItems) {
final List<SelectItem> primarySelectItems = new ArrayList<>(selectItems.size());
for (SelectItem selectItem : selectItems) {
final ScalarFunction scalarFunction = selectItem.getScalarFunction();
if (scalarFunction == null || isScalarFunctionMaterialized(scalarFunction)) {
primarySelectItems.add(selectItem);
} else {
final SelectItem copySelectItem = selectItem.replaceFunction(null);
primarySelectItems.add(copySelectItem);
}
}
final List<SelectItem> evaluatedSelectItems = MetaModelHelper.getEvaluatedSelectItems(whereItems);
return CollectionUtils.concat(true, primarySelectItems, evaluatedSelectItems);
}
/**
* Determines if the subclass of this class can materialize
* {@link SelectItem}s with the given {@link ScalarFunction}. Usually scalar
* functions are applied by MetaModel on the client side, but when possible
* they can also be handled by e.g.
* {@link #materializeMainSchemaTable(Table, List, int, int)} and
* {@link #materializeMainSchemaTable(Table, List, List, int, int)} in which
* case MetaModel will not evaluate it client-side.
*
* @param function
* @return
*/
protected boolean isScalarFunctionMaterialized(ScalarFunction function) {
return false;
}
@Deprecated
protected DataSet materializeTable(final Table table, final List<SelectItem> selectItems, final int firstRow,
final int maxRows) {
return materializeTable(table, selectItems, Collections.<FilterItem> emptyList(), firstRow, maxRows);
}
protected boolean isMainSchemaTable(Table table) {
Schema schema = table.getSchema();
if (INFORMATION_SCHEMA_NAME.equals(schema.getName())) {
return false;
} else {
return true;
}
}
@Override
protected final String[] getSchemaNamesInternal() throws MetaModelException {
final String[] schemaNames = new String[2];
schemaNames[0] = INFORMATION_SCHEMA_NAME;
schemaNames[1] = getMainSchemaName();
return schemaNames;
}
@Override
protected String getDefaultSchemaName() throws MetaModelException {
return getMainSchemaName();
}
@Override
protected final Schema getSchemaByNameInternal(final String name) throws MetaModelException {
final String mainSchemaName = getMainSchemaName();
if (name == null) {
if (mainSchemaName == null) {
return getMainSchema();
}
return null;
}
if (name.equalsIgnoreCase(mainSchemaName)) {
return getMainSchema();
} else if (name.equals(INFORMATION_SCHEMA_NAME)) {
return getInformationSchema();
}
logger.warn("Could not find matching schema of name '{}'. Main schema name is: '{}'. Returning null.", name,
mainSchemaName);
return null;
}
private Schema getInformationSchema() {
// Create schema
MutableSchema informationSchema = new MutableSchema(INFORMATION_SCHEMA_NAME);
MutableTable tablesTable = new MutableTable("tables", TableType.TABLE, informationSchema);
MutableTable columnsTable = new MutableTable("columns", TableType.TABLE, informationSchema);
MutableTable relationshipsTable = new MutableTable("relationships", TableType.TABLE, informationSchema);
informationSchema.addTable(tablesTable).addTable(columnsTable).addTable(relationshipsTable);
// Create "tables" table: name, type, num_columns, remarks
tablesTable.addColumn(new MutableColumn("name", ColumnType.VARCHAR, tablesTable, 0, false));
tablesTable.addColumn(new MutableColumn("type", ColumnType.VARCHAR, tablesTable, 1, true));
tablesTable.addColumn(new MutableColumn("num_columns", ColumnType.INTEGER, tablesTable, 2, true));
tablesTable.addColumn(new MutableColumn("remarks", ColumnType.VARCHAR, tablesTable, 3, true));
// Create "columns" table: name, type, native_type, size, nullable,
// indexed, table, remarks
columnsTable.addColumn(new MutableColumn("name", ColumnType.VARCHAR, columnsTable, 0, false));
columnsTable.addColumn(new MutableColumn("type", ColumnType.VARCHAR, columnsTable, 1, true));
columnsTable.addColumn(new MutableColumn("native_type", ColumnType.VARCHAR, columnsTable, 2, true));
columnsTable.addColumn(new MutableColumn("size", ColumnType.INTEGER, columnsTable, 3, true));
columnsTable.addColumn(new MutableColumn("nullable", ColumnType.BOOLEAN, columnsTable, 4, true));
columnsTable.addColumn(new MutableColumn("indexed", ColumnType.BOOLEAN, columnsTable, 5, true));
columnsTable.addColumn(new MutableColumn("table", ColumnType.VARCHAR, columnsTable, 6, false));
columnsTable.addColumn(new MutableColumn("remarks", ColumnType.VARCHAR, columnsTable, 7, true));
// Create "relationships" table: primary_table, primary_column,
// foreign_table, foreign_column
relationshipsTable
.addColumn(new MutableColumn("primary_table", ColumnType.VARCHAR, relationshipsTable, 0, false));
relationshipsTable
.addColumn(new MutableColumn("primary_column", ColumnType.VARCHAR, relationshipsTable, 1, false));
relationshipsTable
.addColumn(new MutableColumn("foreign_table", ColumnType.VARCHAR, relationshipsTable, 2, false));
relationshipsTable
.addColumn(new MutableColumn("foreign_column", ColumnType.VARCHAR, relationshipsTable, 3, false));
MutableRelationship.createRelationship(tablesTable.getColumnByName("name"),
columnsTable.getColumnByName("table"));
MutableRelationship.createRelationship(tablesTable.getColumnByName("name"),
relationshipsTable.getColumnByName("primary_table"));
MutableRelationship.createRelationship(tablesTable.getColumnByName("name"),
relationshipsTable.getColumnByName("foreign_table"));
MutableRelationship.createRelationship(columnsTable.getColumnByName("name"),
relationshipsTable.getColumnByName("primary_column"));
MutableRelationship.createRelationship(columnsTable.getColumnByName("name"),
relationshipsTable.getColumnByName("foreign_column"));
return informationSchema;
}
private DataSet materializeInformationSchemaTable(final Table table, final List<SelectItem> selectItems) {
final String tableName = table.getName();
final SelectItem[] columnSelectItems = MetaModelHelper.createSelectItems(table.getColumns());
final SimpleDataSetHeader header = new SimpleDataSetHeader(columnSelectItems);
final Table[] tables = getDefaultSchema().getTables();
final List<Row> data = new ArrayList<Row>();
if ("tables".equals(tableName)) {
// "tables" columns: name, type, num_columns, remarks
for (Table t : tables) {
String typeString = null;
if (t.getType() != null) {
typeString = t.getType().toString();
}
data.add(new DefaultRow(header,
new Object[] { t.getName(), typeString, t.getColumnCount(), t.getRemarks() }));
}
} else if ("columns".equals(tableName)) {
// "columns" columns: name, type, native_type, size, nullable,
// indexed, table, remarks
for (Table t : tables) {
for (Column c : t.getColumns()) {
String typeString = null;
if (t.getType() != null) {
typeString = c.getType().toString();
}
data.add(new DefaultRow(header, new Object[] { c.getName(), typeString, c.getNativeType(),
c.getColumnSize(), c.isNullable(), c.isIndexed(), t.getName(), c.getRemarks() }));
}
}
} else if ("relationships".equals(tableName)) {
// "relationships" columns: primary_table, primary_column,
// foreign_table, foreign_column
for (Relationship r : getDefaultSchema().getRelationships()) {
Column[] primaryColumns = r.getPrimaryColumns();
Column[] foreignColumns = r.getForeignColumns();
Table pTable = r.getPrimaryTable();
Table fTable = r.getForeignTable();
for (int i = 0; i < primaryColumns.length; i++) {
Column pColumn = primaryColumns[i];
Column fColumn = foreignColumns[i];
data.add(new DefaultRow(header,
new Object[] { pTable.getName(), pColumn.getName(), fTable.getName(), fColumn.getName() }));
}
}
} else {
throw new IllegalArgumentException("Cannot materialize non information_schema table: " + table);
}
DataSet dataSet;
if (data.isEmpty()) {
dataSet = new EmptyDataSet(selectItems);
} else {
dataSet = new InMemoryDataSet(header, data);
}
// Handle column subset
final DataSet selectionDataSet = MetaModelHelper.getSelection(selectItems, dataSet);
dataSet = selectionDataSet;
return dataSet;
}
/**
*
* @return
*
* @deprecated use {@link #getDefaultSchema()} instead
*/
@Deprecated
protected Schema getMainSchemaInternal() {
return getDefaultSchema();
}
/**
* Adds a {@link TypeConverter} to this DataContext's query engine (Query
* Postprocessor) for read operations. Note that this method should NOT be
* invoked directly by consuming code. Rather use
* {@link Converters#addTypeConverter(DataContext, Column, TypeConverter)}
* to ensure conversion on both reads and writes.
*/
@Override
public void addConverter(Column column, TypeConverter<?, ?> converter) {
_converters.put(column, converter);
}
/**
* @return the main schema that subclasses of this class produce
*/
protected abstract Schema getMainSchema() throws MetaModelException;
/**
* @return the name of the main schema that subclasses of this class produce
*/
protected abstract String getMainSchemaName() throws MetaModelException;
/**
* Execute a simple one-table query against a table in the main schema of
* the subclasses of this class. This default implementation will delegate
* to {@link #materializeMainSchemaTable(Table, List, int, int)} and apply
* WHERE item filtering afterwards.
*
* @param table
* @param selectItems
* @param whereItems
* @param firstRow
* @param maxRows
* @return
*/
protected DataSet materializeMainSchemaTable(Table table, List<SelectItem> selectItems, List<FilterItem> whereItems,
int firstRow, int maxRows) {
final List<SelectItem> workingSelectItems = buildWorkingSelectItems(selectItems, whereItems);
DataSet dataSet;
if (whereItems.isEmpty()) {
// paging is pushed down to materializeMainSchemaTable
dataSet = materializeMainSchemaTable(table, workingSelectItems, firstRow, maxRows);
dataSet = MetaModelHelper.getSelection(selectItems, dataSet);
} else {
// do not push down paging, first we have to apply filtering
dataSet = materializeMainSchemaTable(table, workingSelectItems, 1, -1);
dataSet = MetaModelHelper.getFiltered(dataSet, whereItems);
dataSet = MetaModelHelper.getPaged(dataSet, firstRow, maxRows);
dataSet = MetaModelHelper.getSelection(selectItems, dataSet);
}
return dataSet;
}
/**
* Executes a simple one-table query against a table in the main schema of
* the subclasses of this class. This default implementation will delegate
* to {@link #materializeMainSchemaTable(Table, Column[], int, int)}.
*
* @param table
* @param selectItems
* @param firstRow
* @param maxRows
* @return
*/
protected DataSet materializeMainSchemaTable(Table table, List<SelectItem> selectItems, int firstRow, int maxRows) {
Column[] columns = new Column[selectItems.size()];
for (int i = 0; i < columns.length; i++) {
columns[i] = selectItems.get(i).getColumn();
}
DataSet dataSet = materializeMainSchemaTable(table, columns, firstRow, maxRows);
dataSet = MetaModelHelper.getSelection(selectItems, dataSet);
return dataSet;
}
/**
* Executes a simple one-table query against a table in the main schema of
* the subclasses of this class. This default implementation will delegate
* to {@link #materializeMainSchemaTable(Table, Column[], int)} and apply a
* {@link FirstRowDataSet} if necessary.
*
* @param table
* @param columns
* @param firstRow
* @param maxRows
* @return
*/
protected DataSet materializeMainSchemaTable(Table table, Column[] columns, int firstRow, int maxRows) {
final int rowsToMaterialize;
if (firstRow == 1) {
rowsToMaterialize = maxRows;
} else {
rowsToMaterialize = maxRows + (firstRow - 1);
}
DataSet dataSet = materializeMainSchemaTable(table, columns, rowsToMaterialize);
if (firstRow > 1) {
dataSet = new FirstRowDataSet(dataSet, firstRow);
}
return dataSet;
}
/**
* Executes a simple one-table query against a table in the main schema of
* the subclasses of this class.
*
* @param table
* the table to query
* @param columns
* the columns of the table to query
* @param maxRows
* the maximum amount of rows needed or -1 if all rows are
* wanted.
* @return a dataset with the raw table/column content.
*/
protected abstract DataSet materializeMainSchemaTable(Table table, Column[] columns, int maxRows);
}