/**
* 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.List;
import java.util.Map;
import java.util.Map.Entry;
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.IRowFilter;
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.util.AggregateBuilder;
import org.apache.metamodel.util.CollectionUtils;
import org.apache.metamodel.util.Func;
import org.apache.metamodel.util.ObjectComparator;
import org.apache.metamodel.util.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class contains various helper functionality to common tasks in
* MetaModel, eg.:
*
* <ul>
* <li>Easy-access for traversing common schema items</li>
* <li>Manipulate data in memory. These methods are primarily used to enable
* queries for non-queryable data sources like CSV files and spreadsheets.</li>
* <li>Query rewriting, traversing and manipulation.</li>
* </ul>
*
* 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 occurences of tables in the provided
* list of tables and columns are included
*/
public static Table[] getTables(Collection<Table> tableList, Iterable<Column> columnList) {
HashSet<Table> set = new HashSet<Table>();
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<Column> columns) {
ArrayList<Table> result = new ArrayList<Table>();
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<Column> columns) {
if (table == null) {
return new Column[0];
}
final List<Column> result = new ArrayList<Column>();
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, Iterable<FilterItem> whereItems) {
// First check if carthesian product is even nescesary
if (fromDataSets.length == 1) {
return getFiltered(fromDataSets[0], whereItems);
}
List<SelectItem> selectItems = new ArrayList<SelectItem>();
for (DataSet dataSet : fromDataSets) {
for (int i = 0; i < dataSet.getSelectItems().length; i++) {
SelectItem item = dataSet.getSelectItems()[i];
selectItems.add(item);
}
}
int selectItemOffset = 0;
List<Object[]> data = new ArrayList<Object[]>();
for (int fromDataSetIndex = 0; fromDataSetIndex < fromDataSets.length; fromDataSetIndex++) {
DataSet fromDataSet = fromDataSets[fromDataSetIndex];
SelectItem[] fromSelectItems = fromDataSet.getSelectItems();
if (fromDataSetIndex == 0) {
while (fromDataSet.next()) {
Object[] values = fromDataSet.getRow().getValues();
Object[] row = new Object[selectItems.size()];
System.arraycopy(values, 0, row, selectItemOffset, values.length);
data.add(row);
}
fromDataSet.close();
} else {
List<Object[]> fromDataRows = new ArrayList<Object[]>();
while (fromDataSet.next()) {
fromDataRows.add(fromDataSet.getRow().getValues());
}
fromDataSet.close();
for (int i = 0; i < data.size(); i = i + fromDataRows.size()) {
Object[] originalRow = data.get(i);
data.remove(i);
for (int j = 0; j < fromDataRows.size(); j++) {
Object[] newRow = fromDataRows.get(j);
System.arraycopy(newRow, 0, originalRow, selectItemOffset, newRow.length);
data.add(i + j, originalRow.clone());
}
}
}
selectItemOffset += fromSelectItems.length;
}
if (data.isEmpty()) {
return new EmptyDataSet(selectItems);
}
final DataSetHeader header = new CachingDataSetHeader(selectItems);
final List<Row> rows = new ArrayList<Row>(data.size());
for (Object[] objects : data) {
rows.add(new DefaultRow(header, objects, null));
}
DataSet result = new InMemoryDataSet(header, rows);
if (whereItems != null) {
DataSet filteredResult = getFiltered(result, whereItems);
result = filteredResult;
}
return result;
}
public static DataSet getCarthesianProduct(DataSet[] fromDataSets, FilterItem... filterItems) {
return getCarthesianProduct(fromDataSets, Arrays.asList(filterItems));
}
public static DataSet getFiltered(DataSet dataSet, Iterable<FilterItem> filterItems) {
List<IRowFilter> filters = CollectionUtils.map(filterItems, new Func<FilterItem, IRowFilter>() {
@Override
public IRowFilter eval(FilterItem filterItem) {
return filterItem;
}
});
if (filters.isEmpty()) {
return dataSet;
}
return new FilteredDataSet(dataSet, filters.toArray(new IRowFilter[filters.size()]));
}
public static DataSet getFiltered(DataSet dataSet, FilterItem... filterItems) {
return getFiltered(dataSet, Arrays.asList(filterItems));
}
public static DataSet getSelection(final List<SelectItem> selectItems, final DataSet dataSet) {
final List<SelectItem> dataSetSelectItems = Arrays.asList(dataSet.getSelectItems());
// check if the selection is already the same
if (selectItems.equals(dataSetSelectItems)) {
// return the DataSet unmodified
return dataSet;
}
final List<SelectItem> 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<SelectItem> selectItems, DataSet dataSet,
Collection<GroupByItem> groupByItems) {
return getGrouped(selectItems, dataSet, groupByItems.toArray(new GroupByItem[groupByItems.size()]));
}
public static DataSet getGrouped(List<SelectItem> selectItems, DataSet dataSet, GroupByItem[] groupByItems) {
DataSet result = dataSet;
if (groupByItems != null && groupByItems.length > 0) {
Map<Row, Map<SelectItem, List<Object>>> uniqueRows = new HashMap<Row, Map<SelectItem, List<Object>>>();
final SelectItem[] groupBySelects = new SelectItem[groupByItems.length];
for (int i = 0; i < groupBySelects.length; i++) {
groupBySelects[i] = groupByItems[i].getSelectItem();
}
final DataSetHeader groupByHeader = new CachingDataSetHeader(groupBySelects);
// Creates a list of SelectItems that have functions
List<SelectItem> functionItems = getFunctionSelectItems(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<SelectItem, List<Object>> functionInput;
if (!uniqueRows.containsKey(uniqueRow)) {
// If this group already exist, use an existing function
// input
functionInput = new HashMap<SelectItem, List<Object>>();
for (SelectItem item : functionItems) {
functionInput.put(item, new ArrayList<Object>());
}
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<Object> 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<Row> resultData = new ArrayList<Row>();
final DataSetHeader resultHeader = new CachingDataSetHeader(selectItems);
// Loop through the groups to generate aggregates
for (Entry<Row, Map<SelectItem, List<Object>>> entry : uniqueRows.entrySet()) {
Row row = entry.getKey();
Map<SelectItem, List<Object>> 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<Object> 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<SelectItem> workSelectItems, DataSet dataSet) {
final List<SelectItem> functionItems = getAggregateFunctionSelectItems(workSelectItems);
if (functionItems.isEmpty()) {
return dataSet;
}
final Map<SelectItem, AggregateBuilder<?>> aggregateBuilders = new HashMap<SelectItem, AggregateBuilder<?>>();
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<Row> resultRows = new ArrayList<Row>();
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<SelectItem, Object> functionResult = new HashMap<SelectItem, Object>();
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<SelectItem, Object> 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);
}
/**
*
* @param selectItems
* @return
*
* @deprecated use {@link #getAggregateFunctionSelectItems(Iterable)} or
* {@link #getScalarFunctionSelectItems(Iterable)} instead
*/
@Deprecated
public static List<SelectItem> getFunctionSelectItems(Iterable<SelectItem> selectItems) {
return CollectionUtils.filter(selectItems, new Predicate<SelectItem>() {
@Override
public Boolean eval(SelectItem arg) {
return arg.getFunction() != null;
}
});
}
public static List<SelectItem> getAggregateFunctionSelectItems(Iterable<SelectItem> selectItems) {
return CollectionUtils.filter(selectItems, new Predicate<SelectItem>() {
@Override
public Boolean eval(SelectItem arg) {
return arg.getAggregateFunction() != null;
}
});
}
public static List<SelectItem> getScalarFunctionSelectItems(Iterable<SelectItem> selectItems) {
return CollectionUtils.filter(selectItems, new Predicate<SelectItem>() {
@Override
public Boolean eval(SelectItem arg) {
return arg.getScalarFunction() != null;
}
});
}
public static DataSet getOrdered(DataSet dataSet, List<OrderByItem> 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<Row> data = readDataSetFull(dataSet);
if (data.isEmpty()) {
return new EmptyDataSet(dataSet.getSelectItems());
}
final Comparator<Object> valueComparator = ObjectComparator.getComparator();
// create a comparator for doing the actual sorting/ordering
final Comparator<Row> comparator = new Comparator<Row>() {
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<Row> readDataSetFull(DataSet dataSet) {
final List<Row> 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<Row>();
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<FromItem> result = new ArrayList<FromItem>();
List<FromItem> items = q.getFromClause().getItems();
for (FromItem item : items) {
result.addAll(getTableFromItems(item));
}
return result.toArray(new FromItem[result.size()]);
}
public static List<FromItem> getTableFromItems(FromItem item) {
List<FromItem> result = new ArrayList<FromItem>();
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");
}
SelectItem[] si1 = ds1.getSelectItems();
SelectItem[] si2 = ds2.getSelectItems();
SelectItem[] selectItems = new SelectItem[si1.length + si2.length];
System.arraycopy(si1, 0, selectItems, 0, si1.length);
System.arraycopy(si2, 0, selectItems, si1.length, si2.length);
List<Row> resultRows = new ArrayList<Row>();
List<Row> 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<Row> ds1rows = new ArrayList<Row>();
ds1rows.add(ds1row);
DataSet carthesianProduct = getCarthesianProduct(
new DataSet[] { new InMemoryDataSet(new CachingDataSetHeader(si1), ds1rows),
new InMemoryDataSet(new CachingDataSetHeader(si2), ds2data) },
onConditions);
List<Row> carthesianRows = readDataSetFull(carthesianProduct);
if (carthesianRows.size() > 0) {
resultRows.addAll(carthesianRows);
} else {
Object[] values = ds1row.getValues();
Object[] row = new Object[selectItems.length];
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) {
SelectItem[] ds1selects = ds1.getSelectItems();
SelectItem[] ds2selects = ds2.getSelectItems();
SelectItem[] leftOrderedSelects = new SelectItem[ds1selects.length + ds2selects.length];
System.arraycopy(ds1selects, 0, leftOrderedSelects, 0, ds1selects.length);
System.arraycopy(ds2selects, 0, leftOrderedSelects, ds1selects.length, ds2selects.length);
// 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) {
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) {
SelectItem[] selectItems = dataSet.getSelectItems();
GroupByItem[] groupByItems = new GroupByItem[selectItems.length];
for (int i = 0; i < groupByItems.length; i++) {
groupByItems[i] = new GroupByItem(selectItems[i]);
}
return getGrouped(Arrays.asList(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, new Predicate<Column>() {
@Override
public Boolean eval(Column column) {
return column.getType() == columnType;
}
}).toArray(new Column[0]);
}
public static Column[] getColumnsBySuperType(Column[] columns, final SuperColumnType superColumnType) {
return CollectionUtils.filter(columns, new Predicate<Column>() {
@Override
public Boolean eval(Column 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<SelectItem> getEvaluatedSelectItems(final List<FilterItem> items) {
final List<SelectItem> result = new ArrayList<SelectItem>();
for (FilterItem item : items) {
addEvaluatedSelectItems(result, item);
}
return result;
}
private static void addEvaluatedSelectItems(List<SelectItem> 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<SelectItem> 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<FromItem> 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;
}
}