-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improving search by compound indexes. #3915
Changes from 8 commits
2b14525
48b72bf
c57a62f
1e4c3e8
0dbfc32
1b9cb56
5b80dd9
e7b8be5
89363a2
a8ba33a
705d965
76f34d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
package org.h2.expression.condition; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.TreeSet; | ||
|
||
import org.h2.engine.SessionLocal; | ||
|
@@ -139,12 +140,36 @@ public void createIndexConditions(SessionLocal session, TableFilter filter) { | |
} else if (left instanceof ExpressionList) { | ||
ExpressionList list = (ExpressionList) left; | ||
if (!list.isArray()) { | ||
createIndexConditions(filter, list); | ||
// First we create a compound index condition. | ||
createCompoundIndexCondition(filter); | ||
// If there is no compound index, then the TableFilter#prepare() method will drop this condition. | ||
// Then we create a unique index condition for each column. | ||
createUniqueIndexConditions(filter, list); | ||
// If there are two or more index conditions, IndexCursor will only use the first one. | ||
// See: IndexCursor#canUseIndexForIn(Column) | ||
} | ||
} | ||
} | ||
|
||
private void createIndexConditions(TableFilter filter, ExpressionList list) { | ||
/** | ||
* Creates a compound index condition containing every item in the expression list. | ||
* @see IndexCondition#getCompoundInList(ExpressionList, List) | ||
*/ | ||
private void createCompoundIndexCondition(TableFilter filter) { | ||
// We do not check filter here, because the IN condition can contain columns from multiple tables. | ||
ExpressionVisitor visitor = ExpressionVisitor.getNotFromResolverVisitor(filter); | ||
for (Expression e : valueList) { | ||
if (!e.isEverything(visitor)) | ||
return; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. {} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
} | ||
filter.addIndexCondition(IndexCondition.getCompoundInList((ExpressionList) left, valueList)); | ||
} | ||
|
||
/** | ||
* Creates a unique index condition for every item in the expression list. | ||
* @see IndexCondition#getInList(ExpressionColumn, List) | ||
*/ | ||
private void createUniqueIndexConditions(TableFilter filter, ExpressionList list) { | ||
int c = list.getSubexpressionCount(); | ||
for (int i = 0; i < c; i++) { | ||
Expression e = list.getSubexpression(i); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
import org.h2.engine.SessionLocal; | ||
import org.h2.expression.Expression; | ||
import org.h2.expression.ExpressionColumn; | ||
import org.h2.expression.ExpressionList; | ||
import org.h2.expression.ExpressionVisitor; | ||
import org.h2.expression.condition.Comparison; | ||
import org.h2.message.DbException; | ||
|
@@ -22,9 +23,10 @@ | |
import org.h2.table.TableType; | ||
import org.h2.value.Value; | ||
import org.h2.value.ValueArray; | ||
import org.h2.value.ValueRow; | ||
|
||
/** | ||
* A index condition object is made for each condition that can potentially use | ||
* An index condition object is made for each condition that can potentially use | ||
* an index. This class does not extend expression, but in general there is one | ||
* expression that maps to each index condition. | ||
* | ||
|
@@ -65,81 +67,104 @@ public class IndexCondition { | |
public static final int SPATIAL_INTERSECTS = 16; | ||
|
||
private final Column column; | ||
private final Column[] columns; | ||
private final boolean compoundColumns; | ||
|
||
/** | ||
* see constants in {@link Comparison} | ||
*/ | ||
private final int compareType; | ||
|
||
private final Expression expression; | ||
private List<Expression> expressionList; | ||
private Query expressionQuery; | ||
private final List<Expression> expressionList; | ||
private final Query expressionQuery; | ||
|
||
/** | ||
* @param compareType the comparison type, see constants in | ||
* {@link Comparison} | ||
*/ | ||
private IndexCondition(int compareType, ExpressionColumn column, | ||
Expression expression) { | ||
private IndexCondition(int compareType, ExpressionColumn column, ExpressionList columns, Expression expression, | ||
List<Expression> list, Query query) { | ||
|
||
this.compareType = compareType; | ||
this.column = column == null ? null : column.getColumn(); | ||
if (column != null) { | ||
this.column = column.getColumn(); | ||
this.columns = null; | ||
this.compoundColumns = false; | ||
} else if (columns !=null) { | ||
this.column = null; | ||
int listSize = columns.getSubexpressionCount(); | ||
Column[] result = new Column[listSize]; | ||
for (int i = listSize; --i >= 0; ) { | ||
result[i] = ((ExpressionColumn) columns.getSubexpression(i)).getColumn(); | ||
} | ||
this.columns = result; | ||
this.compoundColumns = true; | ||
} else { | ||
this.column = null; | ||
this.columns = null; | ||
this.compoundColumns = false; | ||
} | ||
this.expression = expression; | ||
this.expressionList = list; | ||
this.expressionQuery = query; | ||
} | ||
|
||
/** | ||
* Create an index condition with the given parameters. | ||
* | ||
* @param compareType the comparison type, see constants in | ||
* {@link Comparison} | ||
* @param compareType the comparison type, see constants in {@link Comparison} | ||
* @param column the column | ||
* @param expression the expression | ||
* @return the index condition | ||
*/ | ||
public static IndexCondition get(int compareType, ExpressionColumn column, | ||
Expression expression) { | ||
return new IndexCondition(compareType, column, expression); | ||
public static IndexCondition get(int compareType, ExpressionColumn column, Expression expression) { | ||
return new IndexCondition(compareType, column, null, expression, null, null); | ||
} | ||
|
||
/** | ||
* Create an index condition with the compare type IN_LIST and with the | ||
* given parameters. | ||
* Create an index condition with the compare type IN_LIST and with the given parameters. | ||
* | ||
* @param column the column | ||
* @param list the expression list | ||
* @return the index condition | ||
*/ | ||
public static IndexCondition getInList(ExpressionColumn column, | ||
List<Expression> list) { | ||
IndexCondition cond = new IndexCondition(Comparison.IN_LIST, column, | ||
null); | ||
cond.expressionList = list; | ||
return cond; | ||
public static IndexCondition getInList(ExpressionColumn column, List<Expression> list) { | ||
return new IndexCondition(Comparison.IN_LIST, column, null, null, list, null); | ||
} | ||
|
||
/** | ||
* Create an index condition with the compare type IN_ARRAY and with the | ||
* given parameters. | ||
* Create a compound index condition with the compare type IN_LIST and with the given parameters. | ||
* | ||
* @param columns the columns | ||
* @param list the expression list | ||
* @return the index condition | ||
*/ | ||
public static IndexCondition getCompoundInList(ExpressionList columns, List<Expression> list) { | ||
return new IndexCondition(Comparison.IN_LIST, null, columns, null, list, null); | ||
} | ||
|
||
/** | ||
* Create an index condition with the compare type IN_ARRAY and with the given parameters. | ||
* | ||
* @param column the column | ||
* @param array the array | ||
* @return the index condition | ||
*/ | ||
public static IndexCondition getInArray(ExpressionColumn column, Expression array) { | ||
return new IndexCondition(Comparison.IN_ARRAY, column, array); | ||
return new IndexCondition(Comparison.IN_ARRAY, column, null, array, null, null); | ||
} | ||
|
||
/** | ||
* Create an index condition with the compare type IN_QUERY and with the | ||
* given parameters. | ||
* Create an index condition with the compare type IN_QUERY and with the given parameters. | ||
* | ||
* @param column the column | ||
* @param query the select statement | ||
* @return the index condition | ||
*/ | ||
public static IndexCondition getInQuery(ExpressionColumn column, Query query) { | ||
assert query.isRandomAccessResult(); | ||
IndexCondition cond = new IndexCondition(Comparison.IN_QUERY, column, null); | ||
cond.expressionQuery = query; | ||
return cond; | ||
return new IndexCondition(Comparison.IN_QUERY, column, null, null, null, query); | ||
} | ||
|
||
/** | ||
|
@@ -162,10 +187,21 @@ public Value getCurrentValue(SessionLocal session) { | |
public Value[] getCurrentValueList(SessionLocal session) { | ||
TreeSet<Value> valueSet = new TreeSet<>(session.getDatabase().getCompareMode()); | ||
if (compareType == Comparison.IN_LIST) { | ||
for (Expression e : expressionList) { | ||
Value v = e.getValue(session); | ||
v = column.convert(session, v); | ||
valueSet.add(v); | ||
if (isCompoundColumns()) { | ||
Column[] columns = getColumns(); | ||
for (Expression e : expressionList) { | ||
ValueRow v = (ValueRow) e.getValue(session); | ||
v = Column.convert(session, columns, v); | ||
valueSet.add(v); | ||
} | ||
} | ||
else { | ||
Column column = getColumn(); | ||
for (Expression e : expressionList) { | ||
Value v = e.getValue(session); | ||
v = column.convert(session, v); | ||
valueSet.add(v); | ||
} | ||
} | ||
} else if (compareType == Comparison.IN_ARRAY) { | ||
Value v = expression.getValue(session); | ||
|
@@ -203,6 +239,26 @@ public String getSQL(int sqlFlags) { | |
return "FALSE"; | ||
} | ||
StringBuilder builder = new StringBuilder(); | ||
builder = isCompoundColumns() ? buildSql(sqlFlags, builder) : buildSql(sqlFlags, getColumn(), builder); | ||
return builder.toString(); | ||
} | ||
|
||
private StringBuilder buildSql(int sqlFlags, StringBuilder builder) { | ||
if (compareType == Comparison.IN_LIST) { | ||
builder.append(" IN("); | ||
for (int i = 0, s = expressionList.size(); i < s; i++) { | ||
if (i > 0) | ||
builder.append(", "); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. {} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
builder.append(expressionList.get(i).getSQL(sqlFlags)); | ||
} | ||
return builder.append(')'); | ||
} | ||
else { | ||
throw DbException.getInternalError("Multiple columns can only be used with compound IN lists."); | ||
} | ||
} | ||
|
||
private StringBuilder buildSql(int sqlFlags, Column column, StringBuilder builder) { | ||
column.getSQL(builder, sqlFlags); | ||
switch (compareType) { | ||
case Comparison.EQUAL: | ||
|
@@ -230,8 +286,7 @@ public String getSQL(int sqlFlags) { | |
Expression.writeExpressions(builder.append(" IN("), expressionList, sqlFlags).append(')'); | ||
break; | ||
case Comparison.IN_ARRAY: | ||
return expression.getSQL(builder.append(" = ANY("), sqlFlags, Expression.AUTO_PARENTHESES).append(')') | ||
.toString(); | ||
return expression.getSQL(builder.append(" = ANY("), sqlFlags, Expression.AUTO_PARENTHESES).append(')'); | ||
case Comparison.IN_QUERY: | ||
builder.append(" IN("); | ||
builder.append(expressionQuery.getPlanSQL(sqlFlags)); | ||
|
@@ -246,7 +301,7 @@ public String getSQL(int sqlFlags) { | |
if (expression != null) { | ||
expression.getSQL(builder, sqlFlags, Expression.AUTO_PARENTHESES); | ||
} | ||
return builder.toString(); | ||
return builder; | ||
} | ||
|
||
/** | ||
|
@@ -266,7 +321,14 @@ public int getMask(ArrayList<IndexCondition> indexConditions) { | |
case Comparison.IN_ARRAY: | ||
case Comparison.IN_QUERY: | ||
if (indexConditions.size() > 1) { | ||
if (TableType.TABLE != column.getTable().getTableType()) { | ||
if (isCompoundColumns()) { | ||
Column[] columns = getColumns(); | ||
for (int i = columns.length; --i >= 0; ) { | ||
if (TableType.TABLE != columns[i].getTable().getTableType()) | ||
return 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. {} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
} | ||
} | ||
else if (TableType.TABLE != getColumn().getTable().getTableType()) { | ||
// if combined with other conditions, | ||
// IN(..) can only be used for regular tables | ||
// test case: | ||
|
@@ -323,7 +385,7 @@ public boolean isStart() { | |
* Check if this index condition is of the type column smaller or equal to | ||
* value. | ||
* | ||
* @return true if this is a end condition | ||
* @return true if this is an end condition | ||
*/ | ||
public boolean isEnd() { | ||
switch (compareType) { | ||
|
@@ -360,9 +422,35 @@ public int getCompareType() { | |
* Get the referenced column. | ||
* | ||
* @return the column | ||
* @throws DbException if {@link #isCompoundColumns()} is {@code true} | ||
*/ | ||
public Column getColumn() { | ||
return column; | ||
if (!isCompoundColumns()) { | ||
return column; | ||
} | ||
throw DbException.getInternalError("The getColumn() method cannot be with multiple columns."); | ||
} | ||
|
||
/** | ||
* Get the referenced columns. | ||
* | ||
* @return the column array | ||
* @throws DbException if {@link #isCompoundColumns()} is {@code false} | ||
*/ | ||
public Column[] getColumns() { | ||
if (isCompoundColumns()) { | ||
return columns; | ||
} | ||
throw DbException.getInternalError("The getColumns() method cannot be with a single column."); | ||
} | ||
|
||
/** | ||
* Check if the expression contains multiple columns | ||
* | ||
* @return true if it contains multiple columns | ||
*/ | ||
public boolean isCompoundColumns() { | ||
return compoundColumns; | ||
} | ||
|
||
/** | ||
|
@@ -416,7 +504,13 @@ public boolean isEvaluatable() { | |
|
||
@Override | ||
public String toString() { | ||
StringBuilder builder = new StringBuilder("column=").append(column).append(", compareType="); | ||
StringBuilder builder = new StringBuilder(); | ||
if (!isCompoundColumns()) { | ||
builder.append("column=").append(column); | ||
} else { | ||
builder.append("columns=").append(columns); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed |
||
} | ||
builder.append(", compareType="); | ||
return compareTypeToString(builder, compareType) | ||
.append(", expression=").append(expression) | ||
.append(", expressionList=").append(expressionList) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done