All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.metamodel.MetaModelHelper Maven / Gradle / Ivy

/**
 * 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.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.apache.metamodel.data.CachingDataSetHeader;
import org.apache.metamodel.data.DataSet;
import org.apache.metamodel.data.DataSetHeader;
import org.apache.metamodel.data.DefaultRow;
import org.apache.metamodel.data.EmptyDataSet;
import org.apache.metamodel.data.FilteredDataSet;
import org.apache.metamodel.data.FirstRowDataSet;
import org.apache.metamodel.data.InMemoryDataSet;
import org.apache.metamodel.data.MaxRowsDataSet;
import org.apache.metamodel.data.Row;
import org.apache.metamodel.data.ScalarFunctionDataSet;
import org.apache.metamodel.data.SimpleDataSetHeader;
import org.apache.metamodel.data.SubSelectionDataSet;
import org.apache.metamodel.query.FilterItem;
import org.apache.metamodel.query.FromItem;
import org.apache.metamodel.query.GroupByItem;
import org.apache.metamodel.query.OrderByItem;
import org.apache.metamodel.query.Query;
import org.apache.metamodel.query.ScalarFunction;
import org.apache.metamodel.query.SelectItem;
import org.apache.metamodel.query.parser.QueryParser;
import org.apache.metamodel.schema.Column;
import org.apache.metamodel.schema.ColumnType;
import org.apache.metamodel.schema.Schema;
import org.apache.metamodel.schema.SuperColumnType;
import org.apache.metamodel.schema.Table;
import org.apache.metamodel.schema.WrappingSchema;
import org.apache.metamodel.schema.WrappingTable;
import org.apache.metamodel.util.AggregateBuilder;
import org.apache.metamodel.util.CollectionUtils;
import org.apache.metamodel.util.ObjectComparator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class contains various helper functionality to common tasks in MetaModel, eg.:
 * 
 * 
    *
  • Easy-access for traversing common schema items
  • *
  • Manipulate data in memory. These methods are primarily used to enable queries for non-queryable data sources like * CSV files and spreadsheets.
  • *
  • Query rewriting, traversing and manipulation.
  • *
* * The class is mainly intended for internal use within the framework operations, but is kept stable, so it can also be * used by framework users. */ public final class MetaModelHelper { private final static Logger logger = LoggerFactory.getLogger(MetaModelHelper.class); private MetaModelHelper() { // Prevent instantiation } /** * Creates an array of tables where all occurrences of tables in the provided list of tables and columns are included */ public static Table[] getTables(Collection tableList, Iterable columnList) { HashSet
set = new HashSet
(); set.addAll(tableList); for (Column column : columnList) { set.add(column.getTable()); } return set.toArray(new Table[set.size()]); } /** * Determines if a schema is an information schema * * @param schema * @return */ public static boolean isInformationSchema(Schema schema) { String name = schema.getName(); return isInformationSchema(name); } /** * Determines if a schema name is the name of an information schema * * @param name * @return */ public static boolean isInformationSchema(String name) { if (name == null) { return false; } return QueryPostprocessDataContext.INFORMATION_SCHEMA_NAME.equals(name.toLowerCase()); } /** * Converts a list of columns to a corresponding array of tables * * @param columns the columns that the tables will be extracted from * @return an array containing the tables of the provided columns. */ public static Table[] getTables(Iterable columns) { ArrayList
result = new ArrayList
(); for (Column column : columns) { Table table = column.getTable(); if (!result.contains(table)) { result.add(table); } } return result.toArray(new Table[result.size()]); } /** * Creates a subset array of columns, where only columns that are contained within the specified table are included. * * @param table * @param columns * @return an array containing the columns that exist in the table */ public static Column[] getTableColumns(Table table, Iterable columns) { if (table == null) { return new Column[0]; } final List result = new ArrayList(); for (Column column : columns) { final boolean sameTable = table.equals(column.getTable()); if (sameTable) { result.add(column); } } return result.toArray(new Column[result.size()]); } /** * Creates a subset array of columns, where only columns that are contained within the specified table are included. * * @param table * @param columns * @return an array containing the columns that exist in the table */ public static Column[] getTableColumns(Table table, Column[] columns) { return getTableColumns(table, Arrays.asList(columns)); } public static DataSet getCarthesianProduct(DataSet... fromDataSets) { return getCarthesianProduct(fromDataSets, new FilterItem[0]); } public static DataSet getCarthesianProduct(DataSet[] fromDataSets, FilterItem... filterItems) { return getCarthesianProduct(fromDataSets, Arrays.asList(filterItems)); } public static DataSet getCarthesianProduct(DataSet[] fromDataSets, Iterable whereItems) { assert (fromDataSets.length > 0); // First check if carthesian product is even necessary if (fromDataSets.length == 1) { return getFiltered(fromDataSets[0], whereItems); } // do a nested loop join, no matter what Iterator dsIter = Arrays.asList(fromDataSets).iterator(); DataSet joined = dsIter.next(); while (dsIter.hasNext()) { joined = nestedLoopJoin(dsIter.next(), joined, (whereItems)); } return joined; } /** * Executes a simple nested loop join. The innerLoopDs will be copied in an in-memory dataset. * */ public static InMemoryDataSet nestedLoopJoin(DataSet innerLoopDs, DataSet outerLoopDs, Iterable filtersIterable) { List filters = new ArrayList<>(); for (FilterItem fi : filtersIterable) { filters.add(fi); } List innerRows = innerLoopDs.toRows(); List allItems = new ArrayList<>(outerLoopDs.getSelectItems()); allItems.addAll(innerLoopDs.getSelectItems()); Set applicableFilters = applicableFilters(filters, allItems); DataSetHeader jointHeader = new CachingDataSetHeader(allItems); List resultRows = new ArrayList<>(); for (Row outerRow : outerLoopDs) { for (Row innerRow : innerRows) { Object[] joinedRowObjects = new Object[outerRow.getValues().length + innerRow.getValues().length]; System.arraycopy(outerRow.getValues(), 0, joinedRowObjects, 0, outerRow.getValues().length); System.arraycopy(innerRow.getValues(), 0, joinedRowObjects, outerRow.getValues().length, innerRow.getValues().length); Row joinedRow = new DefaultRow(jointHeader, joinedRowObjects); if (applicableFilters.isEmpty() || applicableFilters.stream().allMatch(fi -> fi.accept(joinedRow))) { resultRows.add(joinedRow); } } } return new InMemoryDataSet(jointHeader, resultRows); } /** * Filters the FilterItems such that only the FilterItems are returned, which contain SelectItems that are contained * in selectItemList * * @param filters * @param selectItemList * @return */ private static Set applicableFilters(Collection filters, Collection selectItemList) { final Set items = new HashSet<>(selectItemList); return filters.stream().filter(fi -> items.containsAll(getSelectItems(fi))).collect(Collectors.toSet()); } private static Set getSelectItems(final FilterItem filterItem) { final Set itemsInFilter = new HashSet<>(); if (filterItem.getChildItemCount() == 0) { itemsInFilter.add(filterItem.getSelectItem()); final Object operand = filterItem.getOperand(); if (operand instanceof SelectItem) { itemsInFilter.add((SelectItem) operand); } } else { for (FilterItem childFilterItem : filterItem.getChildItems()) { itemsInFilter.addAll(getSelectItems(childFilterItem)); } } return itemsInFilter; } public static DataSet getFiltered(DataSet dataSet, Iterable filterItems) { final FilterItem[] filterItemsArray = StreamSupport.stream(filterItems.spliterator(), false).toArray(FilterItem[]::new); return getFiltered(dataSet, filterItemsArray); } public static DataSet getFiltered(DataSet dataSet, FilterItem... filterItems) { if (filterItems == null || filterItems.length == 0) { return dataSet; } return getFiltered(dataSet, Arrays.asList(filterItems)); } public static DataSet getFiltered(DataSet dataSet, Collection filterItems) { if (filterItems == null || filterItems.isEmpty()) { return dataSet; } final List selectItemsOnOutput = dataSet.getSelectItems(); final Iterable selectItems = filterItems.stream().map(f -> f.getSelectItem()).filter(s -> s != null)::iterator; final List scalarFunctionSelectItems = getUnmaterializedScalarFunctionSelectItems(selectItems, dataSet); final boolean calculateScalarFunctions = !scalarFunctionSelectItems.isEmpty(); if (calculateScalarFunctions) { // scalar functions are needed in evaluation of the filters dataSet = new ScalarFunctionDataSet(scalarFunctionSelectItems, dataSet); } final FilteredDataSet filteredDataSet = new FilteredDataSet(dataSet, filterItems); if (calculateScalarFunctions) { return getSelection(selectItemsOnOutput, filteredDataSet); } else { return filteredDataSet; } } public static DataSet getSelection(final List selectItems, final DataSet dataSet) { final List dataSetSelectItems = dataSet.getSelectItems(); // check if the selection is already the same if (selectItems.equals(dataSetSelectItems)) { // return the DataSet unmodified return dataSet; } final List scalarFunctionSelectItemsToEvaluate = new ArrayList<>(); for (SelectItem selectItem : selectItems) { if (selectItem.getScalarFunction() != null) { if (!dataSetSelectItems.contains(selectItem) && dataSetSelectItems.contains(selectItem.replaceFunction(null))) { scalarFunctionSelectItemsToEvaluate.add(selectItem); } } } if (scalarFunctionSelectItemsToEvaluate.isEmpty()) { return new SubSelectionDataSet(selectItems, dataSet); } final ScalarFunctionDataSet scalaFunctionDataSet = new ScalarFunctionDataSet(scalarFunctionSelectItemsToEvaluate, dataSet); return new SubSelectionDataSet(selectItems, scalaFunctionDataSet); } public static DataSet getSelection(SelectItem[] selectItems, DataSet dataSet) { return getSelection(Arrays.asList(selectItems), dataSet); } public static DataSet getGrouped(List selectItems, DataSet dataSet, Collection groupByItems) { DataSet result = dataSet; if (groupByItems != null && groupByItems.size() > 0) { Map>> uniqueRows = new HashMap>>(); final List groupBySelects = groupByItems.stream().map(gbi -> gbi.getSelectItem()).collect(Collectors.toList()); final DataSetHeader groupByHeader = new CachingDataSetHeader(groupBySelects); // Creates a list of SelectItems that have aggregate functions List functionItems = getAggregateFunctionSelectItems(selectItems); // Loop through the dataset and identify groups while (dataSet.next()) { Row row = dataSet.getRow(); // Subselect a row prototype with only the unique values that // define the group Row uniqueRow = row.getSubSelection(groupByHeader); // function input is the values used for calculating aggregate // functions in the group Map> functionInput; if (!uniqueRows.containsKey(uniqueRow)) { // If this group already exist, use an existing function // input functionInput = new HashMap>(); for (SelectItem item : functionItems) { functionInput.put(item, new ArrayList()); } uniqueRows.put(uniqueRow, functionInput); } else { // If this is a new group, create a new function input functionInput = uniqueRows.get(uniqueRow); } // Loop through aggregate functions to check for validity for (SelectItem item : functionItems) { List objects = functionInput.get(item); Column column = item.getColumn(); if (column != null) { Object value = row.getValue(new SelectItem(column)); objects.add(value); } else if (SelectItem.isCountAllItem(item)) { // Just use the empty string, since COUNT(*) don't // evaluate values (but null values should be prevented) objects.add(""); } else { throw new IllegalArgumentException("Expression function not supported: " + item); } } } dataSet.close(); final List resultData = new ArrayList(); final DataSetHeader resultHeader = new CachingDataSetHeader(selectItems); // Loop through the groups to generate aggregates for (Entry>> entry : uniqueRows.entrySet()) { Row row = entry.getKey(); Map> functionInput = entry.getValue(); Object[] resultRow = new Object[selectItems.size()]; // Loop through select items to generate a row int i = 0; for (SelectItem item : selectItems) { int uniqueRowIndex = row.indexOf(item); if (uniqueRowIndex != -1) { // If there's already a value for the select item in the // row, keep it (it's one of the grouped by columns) resultRow[i] = row.getValue(uniqueRowIndex); } else { // Use the function input to calculate the aggregate // value List objects = functionInput.get(item); if (objects != null) { Object functionResult = item.getAggregateFunction().evaluate(objects.toArray()); resultRow[i] = functionResult; } else { if (item.getAggregateFunction() != null) { logger.error("No function input found for SelectItem: {}", item); } } } i++; } resultData.add(new DefaultRow(resultHeader, resultRow, null)); } if (resultData.isEmpty()) { result = new EmptyDataSet(selectItems); } else { result = new InMemoryDataSet(resultHeader, resultData); } } result = getSelection(selectItems, result); return result; } /** * Applies aggregate values to a dataset. This method is to be invoked AFTER any filters have been applied. * * @param workSelectItems all select items included in the processing of the query (including those originating from * other clauses than the SELECT clause). * @param dataSet * @return */ public static DataSet getAggregated(List workSelectItems, DataSet dataSet) { final List functionItems = getAggregateFunctionSelectItems(workSelectItems); if (functionItems.isEmpty()) { return dataSet; } final Map> aggregateBuilders = new HashMap>(); for (SelectItem item : functionItems) { aggregateBuilders.put(item, item.getAggregateFunction().createAggregateBuilder()); } final DataSetHeader header; final boolean onlyAggregates; if (functionItems.size() != workSelectItems.size()) { onlyAggregates = false; header = new CachingDataSetHeader(workSelectItems); } else { onlyAggregates = true; header = new SimpleDataSetHeader(workSelectItems); } final List resultRows = new ArrayList(); while (dataSet.next()) { final Row inputRow = dataSet.getRow(); for (SelectItem item : functionItems) { final AggregateBuilder aggregateBuilder = aggregateBuilders.get(item); final Column column = item.getColumn(); if (column != null) { Object value = inputRow.getValue(new SelectItem(column)); aggregateBuilder.add(value); } else if (SelectItem.isCountAllItem(item)) { // Just use the empty string, since COUNT(*) don't // evaluate values (but null values should be prevented) aggregateBuilder.add(""); } else { throw new IllegalArgumentException("Expression function not supported: " + item); } } // If the result should also contain non-aggregated values, we // will keep those in the rows list if (!onlyAggregates) { final Object[] values = new Object[header.size()]; for (int i = 0; i < header.size(); i++) { final Object value = inputRow.getValue(header.getSelectItem(i)); if (value != null) { values[i] = value; } } resultRows.add(new DefaultRow(header, values)); } } dataSet.close(); // Collect the aggregates Map functionResult = new HashMap(); for (SelectItem item : functionItems) { AggregateBuilder aggregateBuilder = aggregateBuilders.get(item); Object result = aggregateBuilder.getAggregate(); functionResult.put(item, result); } // if there are no result rows (no matching records at all), we still // need to return a record with the aggregates final boolean noResultRows = resultRows.isEmpty(); if (onlyAggregates || noResultRows) { // We will only create a single row with all the aggregates Object[] values = new Object[header.size()]; for (int i = 0; i < header.size(); i++) { values[i] = functionResult.get(header.getSelectItem(i)); } Row row = new DefaultRow(header, values); resultRows.add(row); } else { // We will create the aggregates as well as regular values for (int i = 0; i < resultRows.size(); i++) { Row row = resultRows.get(i); Object[] values = row.getValues(); for (Entry entry : functionResult.entrySet()) { SelectItem item = entry.getKey(); int itemIndex = row.indexOf(item); if (itemIndex != -1) { Object value = entry.getValue(); values[itemIndex] = value; } } resultRows.set(i, new DefaultRow(header, values)); } } return new InMemoryDataSet(header, resultRows); } public static List getAggregateFunctionSelectItems(Iterable selectItems) { return CollectionUtils.filter(selectItems, arg -> { return arg.getAggregateFunction() != null; }); } public static List getScalarFunctionSelectItems(Iterable selectItems) { return getUnmaterializedScalarFunctionSelectItems(selectItems, null); } /** * Gets select items with scalar functions that haven't already been materialized in a data set. * * @param selectItems * @param dataSetWithMaterializedSelectItems a {@link DataSet} containing the already materialized select items * @return */ public static List getUnmaterializedScalarFunctionSelectItems(Iterable selectItems, DataSet dataSetWithMaterializedSelectItems) { return CollectionUtils.filter(selectItems, arg -> { return arg.getScalarFunction() != null && (dataSetWithMaterializedSelectItems == null || dataSetWithMaterializedSelectItems.indexOf(arg) == -1); }); } public static DataSet getOrdered(DataSet dataSet, List orderByItems) { return getOrdered(dataSet, orderByItems.toArray(new OrderByItem[orderByItems.size()])); } public static DataSet getOrdered(DataSet dataSet, final OrderByItem... orderByItems) { if (orderByItems != null && orderByItems.length != 0) { final int[] sortIndexes = new int[orderByItems.length]; for (int i = 0; i < orderByItems.length; i++) { OrderByItem item = orderByItems[i]; int indexOf = dataSet.indexOf(item.getSelectItem()); sortIndexes[i] = indexOf; } final List data = readDataSetFull(dataSet); if (data.isEmpty()) { return new EmptyDataSet(dataSet.getSelectItems()); } final Comparator valueComparator = ObjectComparator.getComparator(); // create a comparator for doing the actual sorting/ordering final Comparator comparator = new Comparator() { public int compare(Row o1, Row o2) { for (int i = 0; i < sortIndexes.length; i++) { int sortIndex = sortIndexes[i]; Object sortObj1 = o1.getValue(sortIndex); Object sortObj2 = o2.getValue(sortIndex); int compare = valueComparator.compare(sortObj1, sortObj2); if (compare != 0) { OrderByItem orderByItem = orderByItems[i]; boolean ascending = orderByItem.isAscending(); if (ascending) { return compare; } else { return compare * -1; } } } return 0; } }; Collections.sort(data, comparator); dataSet = new InMemoryDataSet(data); } return dataSet; } public static List readDataSetFull(DataSet dataSet) { final List result; if (dataSet instanceof InMemoryDataSet) { // if dataset is an in memory dataset we have a shortcut to avoid // creating a new list result = ((InMemoryDataSet) dataSet).getRows(); } else { result = new ArrayList(); while (dataSet.next()) { result.add(dataSet.getRow()); } } dataSet.close(); return result; } /** * Examines a query and extracts an array of FromItem's that refer (directly) to tables (hence Joined FromItems and * SubQuery FromItems are traversed but not included). * * @param q the query to examine * @return an array of FromItem's that refer directly to tables */ public static FromItem[] getTableFromItems(Query q) { List result = new ArrayList(); List items = q.getFromClause().getItems(); for (FromItem item : items) { result.addAll(getTableFromItems(item)); } return result.toArray(new FromItem[result.size()]); } public static List getTableFromItems(FromItem item) { List result = new ArrayList(); if (item.getTable() != null) { result.add(item); } else if (item.getSubQuery() != null) { FromItem[] sqItems = getTableFromItems(item.getSubQuery()); for (int i = 0; i < sqItems.length; i++) { result.add(sqItems[i]); } } else if (item.getJoin() != null) { FromItem leftSide = item.getLeftSide(); result.addAll(getTableFromItems(leftSide)); FromItem rightSide = item.getRightSide(); result.addAll(getTableFromItems(rightSide)); } else { throw new IllegalStateException("FromItem was neither of Table type, SubQuery type or Join type: " + item); } return result; } /** * Executes a single row query, like "SELECT COUNT(*), MAX(SOME_COLUMN) FROM MY_TABLE" or similar. * * @param dataContext the DataContext object to use for executing the query * @param query the query to execute * @return a row object representing the single row returned from the query * @throws MetaModelException if less or more than one Row is returned from the query */ public static Row executeSingleRowQuery(DataContext dataContext, Query query) throws MetaModelException { DataSet dataSet = dataContext.executeQuery(query); boolean next = dataSet.next(); if (!next) { throw new MetaModelException("No rows returned from query: " + query); } Row row = dataSet.getRow(); next = dataSet.next(); if (next) { throw new MetaModelException("More than one row returned from query: " + query); } dataSet.close(); return row; } /** * Performs a left join (aka left outer join) operation on two datasets. * * @param ds1 the left dataset * @param ds2 the right dataset * @param onConditions the conditions to join by * @return the left joined result dataset */ public static DataSet getLeftJoin(DataSet ds1, DataSet ds2, FilterItem[] onConditions) { if (ds1 == null) { throw new IllegalArgumentException("Left DataSet cannot be null"); } if (ds2 == null) { throw new IllegalArgumentException("Right DataSet cannot be null"); } List si1 = ds1.getSelectItems(); List si2 = ds2.getSelectItems(); List selectItems = Stream.concat(si1.stream(), si2.stream()).collect(Collectors.toList()); List resultRows = new ArrayList(); List ds2data = readDataSetFull(ds2); if (ds2data.isEmpty()) { // no need to join, simply return a new view (with null values) on // the previous dataset. return getSelection(selectItems, ds1); } final DataSetHeader header = new CachingDataSetHeader(selectItems); while (ds1.next()) { // Construct a single-row dataset for making a carthesian product // against ds2 Row ds1row = ds1.getRow(); List ds1rows = new ArrayList(); ds1rows.add(ds1row); DataSet carthesianProduct = getCarthesianProduct(new DataSet[] { new InMemoryDataSet(new CachingDataSetHeader(si1), ds1rows), new InMemoryDataSet(new CachingDataSetHeader(si2), ds2data) }, onConditions); List carthesianRows = readDataSetFull(carthesianProduct); if (carthesianRows.size() > 0) { resultRows.addAll(carthesianRows); } else { Object[] values = ds1row.getValues(); Object[] row = new Object[selectItems.size()]; System.arraycopy(values, 0, row, 0, values.length); resultRows.add(new DefaultRow(header, row)); } } ds1.close(); if (resultRows.isEmpty()) { return new EmptyDataSet(selectItems); } return new InMemoryDataSet(header, resultRows); } /** * Performs a right join (aka right outer join) operation on two datasets. * * @param ds1 the left dataset * @param ds2 the right dataset * @param onConditions the conditions to join by * @return the right joined result dataset */ public static DataSet getRightJoin(DataSet ds1, DataSet ds2, FilterItem[] onConditions) { List ds1selects = ds1.getSelectItems(); List ds2selects = ds2.getSelectItems(); List leftOrderedSelects = new ArrayList<>(); leftOrderedSelects.addAll(ds1selects); leftOrderedSelects.addAll(ds2selects); // We will reuse the left join algorithm (but switch the datasets // around) DataSet dataSet = getLeftJoin(ds2, ds1, onConditions); dataSet = getSelection(leftOrderedSelects, dataSet); return dataSet; } public static SelectItem[] createSelectItems(Column... columns) { final SelectItem[] items = new SelectItem[columns.length]; for (int i = 0; i < items.length; i++) { items[i] = new SelectItem(columns[i]); } return items; } public static DataSet getDistinct(DataSet dataSet) { List selectItems = dataSet.getSelectItems(); List groupByItems = selectItems.stream().map(GroupByItem::new).collect(Collectors.toList()); return getGrouped(selectItems, dataSet, groupByItems); } public static Table[] getTables(Column[] columns) { return getTables(Arrays.asList(columns)); } public static Column[] getColumnsByType(Column[] columns, final ColumnType columnType) { return CollectionUtils.filter(columns, column -> { return column.getType() == columnType; }).toArray(new Column[0]); } public static Column[] getColumnsBySuperType(Column[] columns, final SuperColumnType superColumnType) { return CollectionUtils.filter(columns, column -> { return column.getType().getSuperType() == superColumnType; }).toArray(new Column[0]); } public static Query parseQuery(DataContext dc, String queryString) { final QueryParser parser = new QueryParser(dc, queryString); return parser.parse(); } public static DataSet getPaged(DataSet dataSet, int firstRow, int maxRows) { if (firstRow > 1) { dataSet = new FirstRowDataSet(dataSet, firstRow); } if (maxRows != -1) { dataSet = new MaxRowsDataSet(dataSet, maxRows); } return dataSet; } public static List getEvaluatedSelectItems(final List items) { final List result = new ArrayList(); for (FilterItem item : items) { addEvaluatedSelectItems(result, item); } return result; } private static void addEvaluatedSelectItems(List result, FilterItem item) { final FilterItem[] orItems = item.getChildItems(); if (orItems != null) { for (FilterItem filterItem : orItems) { addEvaluatedSelectItems(result, filterItem); } } final SelectItem selectItem = item.getSelectItem(); if (selectItem != null && !result.contains(selectItem)) { result.add(selectItem); } final Object operand = item.getOperand(); if (operand != null && operand instanceof SelectItem && !result.contains(operand)) { result.add((SelectItem) operand); } } /** * This method returns the select item of the given alias name. * * @param query * @return */ public static SelectItem getSelectItemByAlias(Query query, String alias) { List selectItems = query.getSelectClause().getItems(); for (SelectItem selectItem : selectItems) { if (selectItem.getAlias() != null && selectItem.getAlias().equals(alias)) { return selectItem; } } return null; } /** * Determines if a query contains {@link ScalarFunction}s in any clause of the query EXCEPT for the SELECT clause. * This is a handy thing to determine because decorating with {@link ScalarFunctionDataSet} only gives you * select-item evaluation so if the rest of the query is pushed to an underlying datastore, then it may create * issues. * * @param query * @return */ public static boolean containsNonSelectScalaFunctions(Query query) { // check FROM clause final List fromItems = query.getFromClause().getItems(); for (FromItem fromItem : fromItems) { // check sub-queries final Query subQuery = fromItem.getSubQuery(); if (subQuery != null) { if (containsNonSelectScalaFunctions(subQuery)) { return true; } if (!getScalarFunctionSelectItems(subQuery.getSelectClause().getItems()).isEmpty()) { return true; } } } // check WHERE clause if (!getScalarFunctionSelectItems(query.getWhereClause().getEvaluatedSelectItems()).isEmpty()) { return true; } // check GROUP BY clause if (!getScalarFunctionSelectItems(query.getGroupByClause().getEvaluatedSelectItems()).isEmpty()) { return true; } // check HAVING clause if (!getScalarFunctionSelectItems(query.getHavingClause().getEvaluatedSelectItems()).isEmpty()) { return true; } // check ORDER BY clause if (!getScalarFunctionSelectItems(query.getOrderByClause().getEvaluatedSelectItems()).isEmpty()) { return true; } return false; } public static Table resolveTable(FromItem fromItem) { final Table table = fromItem.getTable(); return resolveUnderlyingTable(table); } public static Table resolveUnderlyingTable(Table table) { while (table instanceof WrappingTable) { table = ((WrappingTable) table).getWrappedTable(); } return table; } public static Schema resolveUnderlyingSchema(Schema schema) { while (schema instanceof WrappingSchema) { schema = ((WrappingSchema) schema).getWrappedSchema(); } return schema; } }