/* Copyright (c) 2001-2009, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hsqldb;
import org.hsqldb.HsqlNameManager.HsqlName;
import org.hsqldb.ParserDQL.CompileContext;
import org.hsqldb.index.Index;
import org.hsqldb.lib.ArrayListIdentity;
import org.hsqldb.lib.ArrayUtil;
import org.hsqldb.lib.HashMappedList;
import org.hsqldb.lib.HsqlList;
import org.hsqldb.lib.OrderedHashSet;
import org.hsqldb.lib.OrderedIntHashSet;
import org.hsqldb.lib.Set;
import org.hsqldb.navigator.RowSetNavigatorData;
import org.hsqldb.result.Result;
import org.hsqldb.result.ResultMetaData;
import org.hsqldb.types.Type;
/**
* Implementation of an SQL query expression
*
* @author Fred Toussi (fredt@users dot sourceforge.net)
* @version 1.9.0
* @since 1.9.0
*/
/**
* @todo 1.9.0 - review these
* - work out usage of getMainSelect etc and add relevant methods
* - Result metadata for the final result of QueryExpression
*
*/
public class QueryExpression {
public static final int NOUNION = 0,
UNION = 1,
UNION_ALL = 2,
INTERSECT = 3,
INTERSECT_ALL = 4,
EXCEPT_ALL = 5,
EXCEPT = 6,
UNION_TERM = 7;
//
int columnCount;
private QueryExpression leftQueryExpression;
private QueryExpression rightQueryExpression;
SortAndSlice sortAndSlice;
private int unionType;
private boolean unionCorresponding;
private OrderedHashSet unionCorrespondingColumns;
int[] unionColumnMap;
Type[] unionColumnTypes;
boolean isFullOrder;
//
HsqlList unresolvedExpressions;
//
boolean isResolved;
//
int persistenceScope = TableBase.SCOPE_STATEMENT;
//
int columnMode = TableBase.COLUMNS_REFERENCED;
//
ResultMetaData resultMetaData;
boolean[] accessibleColumns;
//
View view;
boolean isUpdatable;
boolean isInsertable;
boolean isCheckable;
boolean isTopLevel;
//
public TableBase resultTable;
public Index mainIndex;
public Index fullIndex;
public Index orderIndex;
//
CompileContext compileContext;
QueryExpression(CompileContext compileContext) {
this.compileContext = compileContext;
sortAndSlice = SortAndSlice.noSort;
}
public QueryExpression(CompileContext compileContext,
QueryExpression leftQueryExpression) {
this(compileContext);
sortAndSlice = SortAndSlice.noSort;
this.leftQueryExpression = leftQueryExpression;
}
void addUnion(QueryExpression queryExpression, int unionType) {
sortAndSlice = SortAndSlice.noSort;
this.rightQueryExpression = queryExpression;
this.unionType = unionType;
setFullOrder();
}
void addSortAndSlice(SortAndSlice sortAndSlice) {
this.sortAndSlice = sortAndSlice;
sortAndSlice.sortUnion = true;
}
public void setUnionCorresoponding() {
unionCorresponding = true;
}
public void setUnionCorrespondingColumns(OrderedHashSet names) {
unionCorrespondingColumns = names;
}
public void setFullOrder() {
isFullOrder = true;
if (leftQueryExpression == null) {
return;
}
leftQueryExpression.setFullOrder();
rightQueryExpression.setFullOrder();
}
public void resolve(Session session) {
resolveReferences(session);
ExpressionColumn.checkColumnsResolved(unresolvedExpressions);
resolveTypes(session);
}
public void resolve(Session session, RangeVariable[] outerRanges) {
resolveReferences(session);
if (unresolvedExpressions != null) {
for (int i = 0; i < unresolvedExpressions.size(); i++) {
Expression e = (Expression) unresolvedExpressions.get(i);
HsqlList list = e.resolveColumnReferences(outerRanges, null);
ExpressionColumn.checkColumnsResolved(list);
}
}
resolveTypes(session);
}
public void resolveReferences(Session session) {
leftQueryExpression.resolveReferences(session);
rightQueryExpression.resolveReferences(session);
addUnresolvedExpressions(leftQueryExpression.unresolvedExpressions);
addUnresolvedExpressions(rightQueryExpression.unresolvedExpressions);
if (!unionCorresponding) {
columnCount = leftQueryExpression.getColumnCount();
int rightCount = rightQueryExpression.getColumnCount();
if (columnCount != rightCount) {
throw Error.error(ErrorCode.X_42594);
}
unionColumnTypes = new Type[columnCount];
leftQueryExpression.unionColumnMap =
rightQueryExpression.unionColumnMap = new int[columnCount];
ArrayUtil.fillSequence(leftQueryExpression.unionColumnMap);
resolveColumnRefernecesInUnionOrderBy();
return;
}
String[] leftNames = leftQueryExpression.getColumnNames();
String[] rightNames = rightQueryExpression.getColumnNames();
if (unionCorrespondingColumns == null) {
unionCorrespondingColumns = new OrderedHashSet();
OrderedIntHashSet leftColumns = new OrderedIntHashSet();
OrderedIntHashSet rightColumns = new OrderedIntHashSet();
for (int i = 0; i < leftNames.length; i++) {
String name = leftNames[i];
int index = ArrayUtil.find(rightNames, name);
if (name.length() > 0 && index != -1) {
leftColumns.add(i);
rightColumns.add(index);
unionCorrespondingColumns.add(name);
}
}
if (unionCorrespondingColumns.isEmpty()) {
throw Error.error(ErrorCode.X_42579);
}
leftQueryExpression.unionColumnMap = leftColumns.toArray();
rightQueryExpression.unionColumnMap = rightColumns.toArray();
} else {
leftQueryExpression.unionColumnMap =
new int[unionCorrespondingColumns.size()];
rightQueryExpression.unionColumnMap =
new int[unionCorrespondingColumns.size()];
for (int i = 0; i < unionCorrespondingColumns.size(); i++) {
String name = (String) unionCorrespondingColumns.get(i);
int index = ArrayUtil.find(leftNames, name);
if (index == -1) {
throw Error.error(ErrorCode.X_42579);
}
leftQueryExpression.unionColumnMap[i] = index;
index = ArrayUtil.find(rightNames, name);
if (index == -1) {
throw Error.error(ErrorCode.X_42579);
}
rightQueryExpression.unionColumnMap[i] = index;
}
}
columnCount = unionCorrespondingColumns.size();
unionColumnTypes = new Type[columnCount];
resolveColumnRefernecesInUnionOrderBy();
}
/**
* Only simple column reference or column position allowed
*/
void resolveColumnRefernecesInUnionOrderBy() {
int orderCount = sortAndSlice.getOrderLength();
if (orderCount == 0) {
return;
}
String[] unionColumnNames = getColumnNames();
for (int i = 0; i < orderCount; i++) {
Expression sort = (Expression) sortAndSlice.exprList.get(i);
Expression e = sort.getLeftNode();
if (e.getType() == OpTypes.VALUE) {
if (e.getDataType().typeCode == Types.SQL_INTEGER) {
int index = ((Integer) e.getValue(null)).intValue();
if (0 < index && index <= unionColumnNames.length) {
sort.getLeftNode().queryTableColumnIndex = index - 1;
continue;
}
}
} else if (e.getType() == OpTypes.COLUMN) {
int index = ArrayUtil.find(unionColumnNames,
e.getColumnName());
if (index >= 0) {
sort.getLeftNode().queryTableColumnIndex = index;
continue;
}
}
throw Error.error(ErrorCode.X_42576);
}
sortAndSlice.prepare(null);
}
private void addUnresolvedExpressions(HsqlList expressions) {
if (expressions == null) {
return;
}
if (unresolvedExpressions == null) {
unresolvedExpressions = new ArrayListIdentity();
}
unresolvedExpressions.addAll(expressions);
}
public void resolveTypes(Session session) {
if (isResolved) {
return;
}
resolveTypesPartOne(session);
resolveTypesPartTwo(session);
isResolved = true;
}
void resolveTypesPartOne(Session session) {
ArrayUtil.projectRowReverse(leftQueryExpression.unionColumnTypes,
leftQueryExpression.unionColumnMap,
unionColumnTypes);
leftQueryExpression.resolveTypesPartOne(session);
ArrayUtil.projectRow(leftQueryExpression.unionColumnTypes,
leftQueryExpression.unionColumnMap,
unionColumnTypes);
ArrayUtil.projectRowReverse(rightQueryExpression.unionColumnTypes,
rightQueryExpression.unionColumnMap,
unionColumnTypes);
rightQueryExpression.resolveTypesPartOne(session);
ArrayUtil.projectRow(rightQueryExpression.unionColumnTypes,
rightQueryExpression.unionColumnMap,
unionColumnTypes);
}
void resolveTypesPartTwo(Session session) {
ArrayUtil.projectRowReverse(leftQueryExpression.unionColumnTypes,
leftQueryExpression.unionColumnMap,
unionColumnTypes);
leftQueryExpression.resolveTypesPartTwo(session);
ArrayUtil.projectRowReverse(rightQueryExpression.unionColumnTypes,
rightQueryExpression.unionColumnMap,
unionColumnTypes);
rightQueryExpression.resolveTypesPartTwo(session);
//
if (unionCorresponding) {
resultMetaData = leftQueryExpression.getMetaData().getNewMetaData(
leftQueryExpression.unionColumnMap);
createTable(session);
}
if (sortAndSlice.hasOrder()) {
QueryExpression queryExpression = this;
while (true) {
if (queryExpression.leftQueryExpression == null
|| queryExpression.unionCorresponding) {
sortAndSlice.setIndex(queryExpression.resultTable);
break;
}
queryExpression = queryExpression.leftQueryExpression;
}
}
}
public Object[] getValues(Session session) {
Result r = getResult(session, 2);
int size = r.getNavigator().getSize();
if (size == 0) {
return new Object[r.metaData.getColumnCount()];
} else if (size == 1) {
return r.getSingleRowData();
} else {
throw Error.error(ErrorCode.X_21000);
}
}
public Object[] getSingleRowValues(Session session) {
Result r = getResult(session, 2);
int size = r.getNavigator().getSize();
if (size == 0) {
return null;
} else if (size == 1) {
return r.getSingleRowData();
} else {
throw Error.error(ErrorCode.X_21000);
}
}
public Object getValue(Session session) {
Object[] values = getValues(session);
return values[0];
}
Result getResult(Session session, int maxRows) {
int currentMaxRows = unionType == UNION_ALL ? maxRows
: Integer.MAX_VALUE;
Result first = leftQueryExpression.getResult(session, currentMaxRows);
RowSetNavigatorData navigator =
(RowSetNavigatorData) first.getNavigator();
Result second = rightQueryExpression.getResult(session,
currentMaxRows);
RowSetNavigatorData rightNavigator =
(RowSetNavigatorData) second.getNavigator();
if (unionCorresponding) {
RowSetNavigatorData rowSet = new RowSetNavigatorData(session,
this);
rowSet.copy(navigator, leftQueryExpression.unionColumnMap);
navigator = rowSet;
first.setNavigator(navigator);
first.metaData = this.getMetaData();
rowSet = new RowSetNavigatorData(session, this);
if (unionType != UNION && unionType != UNION_ALL) {
rowSet.copy(rightNavigator,
rightQueryExpression.unionColumnMap);
rightNavigator = rowSet;
}
}
switch (unionType) {
case UNION :
navigator.union(rightNavigator,
rightQueryExpression.unionColumnMap);
break;
case UNION_ALL :
navigator.unionAll(rightNavigator,
rightQueryExpression.unionColumnMap);
break;
case INTERSECT :
navigator.intersect(rightNavigator);
break;
case INTERSECT_ALL :
navigator.intersectAll(rightNavigator);
break;
case EXCEPT :
navigator.except(rightNavigator);
break;
case EXCEPT_ALL :
navigator.exceptAll(rightNavigator);
break;
default :
throw Error.runtimeError(ErrorCode.U_S0500, "QueryExpression");
}
if (sortAndSlice.hasOrder()) {
RowSetNavigatorData nav =
(RowSetNavigatorData) first.getNavigator();
nav.sortUnion(sortAndSlice);
nav.trim(sortAndSlice.getLimitStart(session),
sortAndSlice.getLimitCount(session, maxRows));
}
navigator.reset();
return first;
}
public boolean isSingleColumn() {
return leftQueryExpression.isSingleColumn();
}
public ResultMetaData getMetaData() {
if (resultMetaData != null) {
return resultMetaData;
}
return leftQueryExpression.getMetaData();
}
public QuerySpecification getMainSelect() {
if (leftQueryExpression == null) {
return (QuerySpecification) this;
}
return leftQueryExpression.getMainSelect();
}
/** @todo 1.9.0 review */
public String describe(Session session) {
return leftQueryExpression.describe(session);
}
public HsqlList getUnresolvedExpressions() {
return unresolvedExpressions;
}
public boolean areColumnsResolved() {
return unresolvedExpressions == null
|| unresolvedExpressions.isEmpty();
}
String[] getColumnNames() {
if (unionCorrespondingColumns == null) {
return leftQueryExpression.getColumnNames();
}
String[] names = new String[unionCorrespondingColumns.size()];
unionCorrespondingColumns.toArray(names);
return names;
}
public Type[] getColumnTypes() {
return unionColumnTypes;
}
public int getColumnCount() {
if (unionCorrespondingColumns == null) {
int left = leftQueryExpression.getColumnCount();
int right = rightQueryExpression.getColumnCount();
if (left != right) {
throw Error.error(ErrorCode.X_42594);
}
return left;
}
return unionCorrespondingColumns.size();
}
public void collectAllExpressions(HsqlList set, OrderedIntHashSet typeSet,
OrderedIntHashSet stopAtTypeSet) {
leftQueryExpression.collectAllExpressions(set, typeSet, stopAtTypeSet);
if (rightQueryExpression != null) {
rightQueryExpression.collectAllExpressions(set, typeSet,
stopAtTypeSet);
}
}
public void collectObjectNames(Set set) {
leftQueryExpression.collectObjectNames(set);
if (rightQueryExpression != null) {
rightQueryExpression.collectObjectNames(set);
}
}
public HashMappedList getColumns() {
this.getResultTable();
return ((TableDerived) getResultTable()).columnList;
}
/**
* Used prior to type resolution
*/
public void setView(View view) {
this.view = view;
}
/**
* Used in views after full type resolution
*/
public void setTableColumnNames(HashMappedList list) {
if (resultTable != null) {
((TableDerived) resultTable).columnList = list;
return;
}
leftQueryExpression.setTableColumnNames(list);
}
void createTable(Session session) {
createResultTable(session);
mainIndex = resultTable.getPrimaryIndex();
if (sortAndSlice.hasOrder()) {
orderIndex = resultTable.createAndAddIndexStructure(null,
sortAndSlice.sortOrder, sortAndSlice.sortDescending,
sortAndSlice.sortNullsLast, false, false, false);
}
int[] fullCols = new int[columnCount];
ArrayUtil.fillSequence(fullCols);
fullIndex = resultTable.createAndAddIndexStructure(null, fullCols,
null, null, false, false, false);
resultTable.fullIndex = fullIndex;
}
void createResultTable(Session session) {
HsqlName tableName;
HashMappedList columnList;
int tableType;
tableName = session.database.nameManager.getSubqueryTableName();
tableType = persistenceScope == TableBase.SCOPE_STATEMENT
? TableBase.SYSTEM_SUBQUERY
: TableBase.RESULT_TABLE;
columnList = leftQueryExpression.getUnionColumns();
try {
resultTable = new TableDerived(session.database, tableName,
tableType, unionColumnTypes,
columnList, null);
} catch (Exception e) {}
}
public void setColumnsDefined() {
columnMode = TableBase.COLUMNS_REFERENCED;
if (leftQueryExpression != null) {
leftQueryExpression.setColumnsDefined();
}
}
/**
* Not for views. Only used on root node.
*/
public void setAsTopLevel() {
if (compileContext.getSequences().length > 0) {
throw Error.error(ErrorCode.X_42598);
}
isTopLevel = true;
setReturningResultSet();
}
/**
* Sets the scope to SESSION for the QueryExpression object that creates
* the table
*/
void setReturningResultSet() {
if (unionCorresponding) {
persistenceScope = TableBase.SCOPE_SESSION;
columnMode = TableBase.COLUMNS_UNREFERENCED;
return;
}
leftQueryExpression.setReturningResultSet();
}
private HashMappedList getUnionColumns() {
if (unionCorresponding || leftQueryExpression == null) {
HashMappedList columns = ((TableDerived) resultTable).columnList;
HashMappedList list = new HashMappedList();
for (int i = 0; i < unionColumnMap.length; i++) {
ColumnSchema column = (ColumnSchema) columns.get(i);
list.add(column.getName().name, column);
}
return list;
}
return leftQueryExpression.getUnionColumns();
}
public HsqlName[] getResultColumnNames() {
if (resultTable == null) {
return leftQueryExpression.getResultColumnNames();
}
HashMappedList list = ((TableDerived) resultTable).columnList;
HsqlName[] resultColumnNames = new HsqlName[list.size()];
for (int i = 0; i < resultColumnNames.length; i++) {
resultColumnNames[i] = ((ColumnSchema) list.get(i)).getName();
}
return resultColumnNames;
}
public TableBase getResultTable() {
if (resultTable != null) {
return resultTable;
}
if (leftQueryExpression != null) {
return leftQueryExpression.getResultTable();
}
return null;
}
//
public Table getBaseTable() {
return null;
}
public boolean isUpdatable() {
return isUpdatable;
}
public boolean isInsertable() {
return isInsertable;
}
public int[] getBaseTableColumnMap() {
return null;
}
public Expression getCheckCondition() {
return null;
}
public boolean hasReference(RangeVariable range) {
if (leftQueryExpression.hasReference(range)) {
return true;
}
if (rightQueryExpression.hasReference(range)) {
return true;
}
return false;
}
void getBaseTableNames(OrderedHashSet set) {
leftQueryExpression.getBaseTableNames(set);
rightQueryExpression.getBaseTableNames(set);
}
}