/* Copyright (c) 2001-2010, 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.ParserDQL.CompileContext; import org.hsqldb.RangeVariable.RangeVariableConditions; import org.hsqldb.error.Error; import org.hsqldb.error.ErrorCode; import org.hsqldb.index.Index; import org.hsqldb.lib.ArrayUtil; import org.hsqldb.lib.HsqlArrayList; import org.hsqldb.lib.IntKeyIntValueHashMap; import org.hsqldb.lib.Iterator; import org.hsqldb.lib.MultiValueHashMap; import org.hsqldb.lib.OrderedHashSet; import org.hsqldb.lib.OrderedIntHashSet; /** * Determines how JOIN and WHERE expressions are used in query * processing and which indexes are used for table access. * * @author Fred Toussi (fredt@users dot sourceforge.net) * @version 2.0.0 * @since 1.9.0 */ public class RangeVariableResolver { Session session; RangeVariable[] rangeVariables; Expression conditions; OrderedHashSet rangeVarSet = new OrderedHashSet(); CompileContext compileContext; // HsqlArrayList[] tempJoinExpressions; HsqlArrayList[] joinExpressions; HsqlArrayList[] whereExpressions; HsqlArrayList queryExpressions = new HsqlArrayList(); // Expression[] inExpressions; boolean[] inInJoin; int inExpressionCount = 0; // boolean hasOuterJoin = false; // OrderedIntHashSet colIndexSetEqual = new OrderedIntHashSet(); IntKeyIntValueHashMap colIndexSetOther = new IntKeyIntValueHashMap(); OrderedHashSet tempSet = new OrderedHashSet(); MultiValueHashMap tempMap = new MultiValueHashMap(); RangeVariableResolver(RangeVariable[] rangeVars, Expression conditions, CompileContext compileContext) { this.rangeVariables = rangeVars; this.conditions = conditions; this.compileContext = compileContext; for (int i = 0; i < rangeVars.length; i++) { RangeVariable range = rangeVars[i]; rangeVarSet.add(range); if (range.isLeftJoin || range.isRightJoin) { hasOuterJoin = true; } } inExpressions = new Expression[rangeVars.length]; inInJoin = new boolean[rangeVars.length]; tempJoinExpressions = new HsqlArrayList[rangeVars.length]; for (int i = 0; i < rangeVars.length; i++) { tempJoinExpressions[i] = new HsqlArrayList(); } joinExpressions = new HsqlArrayList[rangeVars.length]; for (int i = 0; i < rangeVars.length; i++) { joinExpressions[i] = new HsqlArrayList(); } whereExpressions = new HsqlArrayList[rangeVars.length]; for (int i = 0; i < rangeVars.length; i++) { whereExpressions[i] = new HsqlArrayList(); } } void processConditions(Session session) { this.session = session; decomposeAndConditions(conditions, queryExpressions); for (int i = 0; i < rangeVariables.length; i++) { if (rangeVariables[i].joinCondition == null) { continue; } decomposeAndConditions(rangeVariables[i].joinCondition, tempJoinExpressions[i]); } conditions = null; assignToLists(); if (!hasOuterJoin) { // getIndexableColumn(whereExpressions[0], 0); } expandConditions(); assignToRangeVariables(); } /** * Divides AND and OR conditions and assigns */ static Expression decomposeAndConditions(Expression e, HsqlArrayList conditions) { if (e == null) { return Expression.EXPR_TRUE; } Expression arg1 = e.getLeftNode(); Expression arg2 = e.getRightNode(); int type = e.getType(); if (type == OpTypes.AND) { arg1 = decomposeAndConditions(arg1, conditions); arg2 = decomposeAndConditions(arg2, conditions); if (arg1 == Expression.EXPR_TRUE) { return arg2; } if (arg2 == Expression.EXPR_TRUE) { return arg1; } e.setLeftNode(arg1); e.setRightNode(arg2); return e; } else if (type == OpTypes.EQUAL) { if (arg1.getType() == OpTypes.ROW && arg2.getType() == OpTypes.ROW) { for (int i = 0; i < arg1.nodes.length; i++) { Expression part = new ExpressionLogical(arg1.nodes[i], arg2.nodes[i]); part.resolveTypes(null, null); conditions.add(part); } return Expression.EXPR_TRUE; } } if (e != Expression.EXPR_TRUE) { conditions.add(e); } return Expression.EXPR_TRUE; } /** * Divides AND and OR conditions and assigns */ static Expression decomposeOrConditions(Expression e, HsqlArrayList conditions) { if (e == null) { return Expression.EXPR_FALSE; } Expression arg1 = e.getLeftNode(); Expression arg2 = e.getRightNode(); int type = e.getType(); if (type == OpTypes.OR) { arg1 = decomposeOrConditions(arg1, conditions); arg2 = decomposeOrConditions(arg2, conditions); if (arg1 == Expression.EXPR_FALSE) { return arg2; } if (arg2 == Expression.EXPR_FALSE) { return arg1; } e = new ExpressionLogical(OpTypes.OR, arg1, arg2); return e; } if (e != Expression.EXPR_FALSE) { conditions.add(e); } return Expression.EXPR_FALSE; } /** * Assigns the conditions to separate lists */ void assignToLists() { int lastOuterIndex = -1; int lastRightIndex = -1; for (int i = 0; i < rangeVariables.length; i++) { if (rangeVariables[i].isLeftJoin) { lastOuterIndex = i; } if (rangeVariables[i].isRightJoin) { lastOuterIndex = i; lastRightIndex = i; } if (lastOuterIndex == i) { joinExpressions[i].addAll(tempJoinExpressions[i]); } else { for (int j = 0; j < tempJoinExpressions[i].size(); j++) { assignToJoinLists( (Expression) tempJoinExpressions[i].get(j), joinExpressions, lastOuterIndex + 1); } } } for (int i = 0; i < queryExpressions.size(); i++) { assignToWhereLists((Expression) queryExpressions.get(i), whereExpressions, lastRightIndex); } } /** * Assigns a single condition to the relevant list of conditions * * Parameter first indicates the first range variable to which condition * can be assigned */ void assignToJoinLists(Expression e, HsqlArrayList[] expressionLists, int first) { tempSet.clear(); e.collectRangeVariables(rangeVariables, tempSet); int index = rangeVarSet.getLargestIndex(tempSet); // condition is independent of tables if no range variable is found if (index == -1) { index = 0; } // condition is assigned to first non-outer range variable if (index < first) { index = first; } expressionLists[index].add(e); } /** * Assigns a single condition to the relevant list of conditions * * Parameter first indicates the first range variable to which condition * can be assigned */ void assignToWhereLists(Expression e, HsqlArrayList[] expressionLists, int first) { tempSet.clear(); e.collectRangeVariables(rangeVariables, tempSet); int index = rangeVarSet.getLargestIndex(tempSet); // condition is independent of tables if no range variable is found if (index == -1) { index = 0; } // condition is assigned to first non-outer range variable if (index < first) { index = first; } expressionLists[index].add(e); } void expandConditions() { expandConditions(joinExpressions, true); if (hasOuterJoin) { return; } expandConditions(whereExpressions, false); } void expandConditions(HsqlArrayList[] array, boolean isJoin) { for (int i = 0; i < rangeVariables.length; i++) { HsqlArrayList list = array[i]; tempMap.clear(); tempSet.clear(); boolean hasChain = false; for (int j = 0; j < list.size(); j++) { Expression e = (Expression) list.get(j); if (!e.isColumnEqual) { continue; } if (e.getLeftNode().getRangeVariable() == e.getRightNode().getRangeVariable()) { continue; } if (e.getLeftNode().getRangeVariable() == rangeVariables[i]) { tempMap.put(e.getLeftNode().getColumn(), e.getRightNode()); if (!tempSet.add(e.getLeftNode().getColumn())) { hasChain = true; } } else if (e.getRightNode().getRangeVariable() == rangeVariables[i]) { tempMap.put(e.getRightNode().getColumn(), e.getLeftNode()); if (!tempSet.add(e.getRightNode().getColumn())) { hasChain = true; } } } if (hasChain) { Iterator keyIt = tempMap.keySet().iterator(); while (keyIt.hasNext()) { Object key = keyIt.next(); Iterator it = tempMap.get(key); tempSet.clear(); while (it.hasNext()) { tempSet.add(it.next()); } while (tempSet.size() > 1) { Expression e1 = (Expression) tempSet.remove(tempSet.size() - 1); for (int j = 0; j < tempSet.size(); j++) { Expression e2 = (Expression) tempSet.get(j); closeJoinChain(array, e1, e2); } } } } } } void closeJoinChain(HsqlArrayList[] array, Expression e1, Expression e2) { int idx1 = rangeVarSet.getIndex(e1.getRangeVariable()); int idx2 = rangeVarSet.getIndex(e2.getRangeVariable()); int index = idx1 > idx2 ? idx1 : idx2; Expression e = new ExpressionLogical(e1, e2); for (int i = 0; i < array[index].size(); i++) { if (e.equals(array[index].get(i))) { return; } } array[index].add(e); } /** * Assigns conditions to range variables and converts suitable IN conditions * to table lookup. */ void assignToRangeVariables() { for (int i = 0; i < rangeVariables.length; i++) { boolean hasIndex = false; boolean isOuter = rangeVariables[i].isLeftJoin || rangeVariables[i].isRightJoin; RangeVariableConditions conditions; if (isOuter) { conditions = rangeVariables[i].joinConditions[0]; assignToRangeVariable(rangeVariables[i], conditions, i, joinExpressions[i]); // index only on one condition -- right and full can have index conditions = rangeVariables[i].joinConditions[0]; if (conditions.hasIndexCondition()) { hasIndex = true; } conditions = rangeVariables[i].whereConditions[0]; if (rangeVariables[i].isRightJoin) { assignToRangeVariable(conditions, whereExpressions[i]); } else if (hasIndex) { assignToRangeVariable(conditions, whereExpressions[i]); } else { assignToRangeVariable(rangeVariables[i], conditions, i, whereExpressions[i]); } } else { conditions = rangeVariables[i].joinConditions[0]; if (hasOuterJoin) { assignToRangeVariable(rangeVariables[i].whereConditions[0], whereExpressions[i]); } else { joinExpressions[i].addAll(whereExpressions[i]); } assignToRangeVariable(rangeVariables[i], conditions, i, joinExpressions[i]); } } if (inExpressionCount != 0) { setInConditionsAsTables(); } } void assignToRangeVariable(RangeVariableConditions conditions, HsqlArrayList exprList) { for (int j = 0, size = exprList.size(); j < size; j++) { Expression e = (Expression) exprList.get(j); conditions.addCondition(e); } } Expression getIndexableColumn(HsqlArrayList exprList, int start) { for (int j = start, size = exprList.size(); j < size; j++) { Expression e = (Expression) exprList.get(j); if (e.getType() != OpTypes.EQUAL) { continue; } if (e.exprSubType == OpTypes.ALL_QUANTIFIED) { continue; } if (e.exprSubType == OpTypes.ANY_QUANTIFIED) { // can process in the future continue; } tempSet.clear(); e.collectRangeVariables(rangeVariables, tempSet); if (tempSet.size() != 1) { continue; } RangeVariable range = (RangeVariable) tempSet.get(0); e = e.getIndexableExpression(range); if (e == null) { continue; } e = e.getLeftNode(); if (e.getType() != OpTypes.COLUMN) { continue; } int colIndex = e.getColumnIndex(); if (range.rangeTable.canGetIndexForColumn(session, colIndex)) { return e; } } return null; } /** * Assigns a set of conditions to a range variable. */ void assignToRangeVariable(RangeVariable rangeVar, RangeVariableConditions conditions, int rangeVarIndex, HsqlArrayList exprList) { if (exprList.isEmpty()) { return; } setIndexConditions(conditions, exprList, rangeVarIndex, true); } private void setIndexConditions(RangeVariableConditions conditions, HsqlArrayList exprList, int rangeVarIndex, boolean includeOr) { boolean hasIndex; colIndexSetEqual.clear(); colIndexSetOther.clear(); for (int j = 0, size = exprList.size(); j < size; j++) { Expression e = (Expression) exprList.get(j); if (e == null) { continue; } // repeat check required for OR if (!e.isIndexable(conditions.rangeVar)) { continue; } int type = e.getType(); switch (type) { case OpTypes.OR : { continue; } case OpTypes.COLUMN : { continue; } case OpTypes.EQUAL : { if (e.exprSubType == OpTypes.ANY_QUANTIFIED || e.exprSubType == OpTypes.ALL_QUANTIFIED) { continue; } if (e.getLeftNode().getRangeVariable() != conditions.rangeVar) { continue; } int colIndex = e.getLeftNode().getColumnIndex(); colIndexSetEqual.add(colIndex); break; } case OpTypes.IS_NULL : { if (e.getLeftNode().getRangeVariable() != conditions.rangeVar) { continue; } int colIndex = e.getLeftNode().getColumnIndex(); colIndexSetEqual.add(colIndex); break; } case OpTypes.NOT : { if (e.getLeftNode().getLeftNode().getRangeVariable() != conditions.rangeVar) { continue; } int colIndex = e.getLeftNode().getLeftNode().getColumnIndex(); int count = colIndexSetOther.get(colIndex, 0); colIndexSetOther.put(colIndex, count + 1); break; } case OpTypes.SMALLER : case OpTypes.SMALLER_EQUAL : case OpTypes.GREATER : case OpTypes.GREATER_EQUAL : { if (e.getLeftNode().getRangeVariable() != conditions.rangeVar) { continue; } int colIndex = e.getLeftNode().getColumnIndex(); int count = colIndexSetOther.get(colIndex, 0); colIndexSetOther.put(colIndex, count + 1); break; } default : { Error.runtimeError(ErrorCode.U_S0500, "RangeVariableResolver"); } } } setEqaulityConditions(conditions, exprList); hasIndex = conditions.hasIndexCondition(); if (!hasIndex) { setNonEqualityConditions(conditions, exprList); } hasIndex = conditions.hasIndexCondition(); // no index found boolean isOR = false; if (!hasIndex && includeOr) { for (int j = 0, size = exprList.size(); j < size; j++) { Expression e = (Expression) exprList.get(j); if (e == null) { continue; } if (e.getType() == OpTypes.OR) { // hasIndex = ((ExpressionLogical) e).isIndexable( conditions.rangeVar); if (hasIndex) { hasIndex = setOrConditions(conditions, (ExpressionLogical) e, rangeVarIndex); } if (hasIndex) { exprList.set(j, null); isOR = true; break; } } else if (e.getType() == OpTypes.EQUAL && e.exprSubType == OpTypes.ANY_QUANTIFIED) { if (e.getRightNode().isCorrelated()) { continue; } OrderedIntHashSet set = new OrderedIntHashSet(); ((ExpressionLogical) e).addLeftColumnsForAllAny( conditions.rangeVar, set); Index index = conditions.rangeVar.rangeTable.getIndexForColumns( session, set, false); // code to disable IN optimisation // index = null; if (index != null && inExpressions[rangeVarIndex] == null) { inExpressions[rangeVarIndex] = e; inInJoin[rangeVarIndex] = conditions.isJoin; inExpressionCount++; exprList.set(j, null); break; } } } } for (int i = 0, size = exprList.size(); i < size; i++) { Expression e = (Expression) exprList.get(i); if (e == null) { continue; } if (isOR) { for (int j = 0; j < conditions.rangeVar.joinConditions.length; j++) { if (conditions.isJoin) { conditions.rangeVar.joinConditions[j] .nonIndexCondition = ExpressionLogical .andExpressions(e, conditions.rangeVar .joinConditions[j].nonIndexCondition); } else { conditions.rangeVar.whereConditions[j] .nonIndexCondition = ExpressionLogical .andExpressions(e, conditions.rangeVar .whereConditions[j].nonIndexCondition); } } } else { conditions.addCondition(e); } } } private boolean setOrConditions(RangeVariableConditions conditions, ExpressionLogical orExpression, int rangeVarIndex) { HsqlArrayList orExprList = new HsqlArrayList(); decomposeOrConditions(orExpression, orExprList); RangeVariableConditions[] conditionsArray = new RangeVariableConditions[orExprList.size()]; for (int i = 0; i < orExprList.size(); i++) { HsqlArrayList exprList = new HsqlArrayList(); Expression e = (Expression) orExprList.get(i); decomposeAndConditions(e, exprList); RangeVariableConditions c = new RangeVariableConditions(conditions); setIndexConditions(c, exprList, rangeVarIndex, false); conditionsArray[i] = c; if (!c.hasIndexCondition()) { // deep OR return false; } } Expression e = null; for (int i = 0; i < conditionsArray.length; i++) { RangeVariableConditions c = conditionsArray[i]; conditionsArray[i].excludeConditions = e; if (i > 1) { Expression lastExpr = conditionsArray[i - 1].excludeConditions; e = new ExpressionLogical(OpTypes.OR, e, lastExpr); } if (c.indexCond != null) { for (int k = 0; k < c.indexedColumnCount; k++) { e = ExpressionLogical.andExpressions(e, c.indexCond[k]); } } e = ExpressionLogical.andExpressions(e, c.indexEndCondition); e = ExpressionLogical.andExpressions(e, c.nonIndexCondition); } if (e != null) { // return false; } if (conditions.isJoin) { conditions.rangeVar.joinConditions = conditionsArray; conditionsArray = new RangeVariableConditions[orExprList.size()]; ArrayUtil.fillArray(conditionsArray, conditions.rangeVar.whereConditions[0]); conditions.rangeVar.whereConditions = conditionsArray; } else { conditions.rangeVar.whereConditions = conditionsArray; conditionsArray = new RangeVariableConditions[orExprList.size()]; ArrayUtil.fillArray(conditionsArray, conditions.rangeVar.joinConditions[0]); conditions.rangeVar.joinConditions = conditionsArray; } return true; } private void setEqaulityConditions(RangeVariableConditions conditions, HsqlArrayList exprList) { Index idx = conditions.rangeVar.rangeTable.getIndexForColumns(session, colIndexSetEqual, false); if (idx == null) { return; } int[] cols = idx.getColumns(); int colCount = cols.length; Expression[] firstRowExpressions = new Expression[cols.length]; for (int j = 0; j < exprList.size(); j++) { Expression e = (Expression) exprList.get(j); if (e == null) { continue; } int type = e.getType(); if (type == OpTypes.EQUAL || type == OpTypes.IS_NULL) { if (e.getLeftNode().getRangeVariable() != conditions.rangeVar) { continue; } int offset = ArrayUtil.find(cols, e.getLeftNode().getColumnIndex()); if (offset != -1 && firstRowExpressions[offset] == null) { firstRowExpressions[offset] = e; exprList.set(j, null); continue; } } } boolean hasNull = false; for (int i = 0; i < firstRowExpressions.length; i++) { Expression e = firstRowExpressions[i]; if (e == null) { if (colCount == cols.length) { colCount = i; } hasNull = true; continue; } if (hasNull) { conditions.addCondition(e); firstRowExpressions[i] = null; } } conditions.addIndexCondition(firstRowExpressions, idx, colCount); } private void setNonEqualityConditions(RangeVariableConditions conditions, HsqlArrayList exprList) { if (colIndexSetOther.isEmpty()) { return; } int currentCount = 0; int currentIndex = 0; Iterator it = colIndexSetOther.keySet().iterator(); while (it.hasNext()) { int colIndex = it.nextInt(); int colCount = colIndexSetOther.get(colIndex); if (colCount > currentCount) { currentIndex = colIndex; } } Index idx = conditions.rangeVar.rangeTable.getIndexForColumn(session, currentIndex); if (idx == null) { it = colIndexSetOther.keySet().iterator(); while (it.hasNext()) { int colIndex = it.nextInt(); if (colIndex != currentIndex) { idx = conditions.rangeVar.rangeTable.getIndexForColumn( session, colIndex); if (idx != null) { break; } } } } if (idx == null) { return; } int[] cols = idx.getColumns(); for (int j = 0; j < exprList.size(); j++) { Expression e = (Expression) exprList.get(j); if (e == null) { continue; } boolean isIndexed = false; switch (e.getType()) { case OpTypes.NOT : { if (e.getLeftNode().getType() == OpTypes.IS_NULL && cols[0] == e.getLeftNode().getLeftNode() .getColumnIndex()) { isIndexed = true; } break; } case OpTypes.SMALLER : case OpTypes.SMALLER_EQUAL : case OpTypes.GREATER : case OpTypes.GREATER_EQUAL : { if (cols[0] == e.getLeftNode().getColumnIndex()) { if (e.getRightNode() != null && !e.getRightNode().isCorrelated()) { isIndexed = true; } } break; } } if (isIndexed) { Expression[] firstRowExpressions = new Expression[idx.getColumnCount()]; firstRowExpressions[0] = e; conditions.addIndexCondition(firstRowExpressions, idx, 1); exprList.set(j, null); break; } } } /** * Converts an IN conditions into a JOIN */ void setInConditionsAsTables() { for (int i = rangeVariables.length - 1; i >= 0; i--) { RangeVariable rangeVar = rangeVariables[i]; ExpressionLogical in = (ExpressionLogical) inExpressions[i]; if (in != null) { OrderedIntHashSet set = new OrderedIntHashSet(); in.addLeftColumnsForAllAny(rangeVar, set); Index index = rangeVar.rangeTable.getIndexForColumns(session, set, false); int colCount = 0; for (int j = 0; j < index.getColumnCount(); j++) { if (set.contains(index.getColumns()[j])) { colCount++; } } RangeVariable newRangeVar = new RangeVariable(in.getRightNode().getTable(), null, null, null, compileContext); newRangeVar.isGenerated = true; RangeVariable[] newList = new RangeVariable[rangeVariables.length + 1]; ArrayUtil.copyAdjustArray(rangeVariables, newList, newRangeVar, i, 1); rangeVariables = newList; // make two columns as arg Expression[] exprList = new Expression[index.getColumnCount()]; for (int j = 0; j < colCount; j++) { int leftIndex = index.getColumns()[j]; int rightIndex = set.getIndex(leftIndex); Expression e = new ExpressionLogical(rangeVar, leftIndex, newRangeVar, rightIndex); exprList[j] = e; } boolean isOuter = rangeVariables[i].isLeftJoin || rangeVariables[i].isRightJoin; RangeVariableConditions conditions = !inInJoin[i] && isOuter ? rangeVar.whereConditions[0] : rangeVar.joinConditions[0]; conditions.addIndexCondition(exprList, index, colCount); if (isOuter) { conditions.addCondition(in); } } } } }