Skip to content
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

Merged
merged 12 commits into from
Nov 27, 2023
30 changes: 28 additions & 2 deletions h2/src/main/org/h2/expression/condition/ConditionIn.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
package org.h2.expression.condition;

import java.util.ArrayList;
import java.util.List;

import org.h2.engine.SessionLocal;
import org.h2.expression.Expression;
import org.h2.expression.ExpressionColumn;
Expand Down Expand Up @@ -165,12 +167,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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!e.isEverything(visitor)) {
    return;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{}

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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);
Expand Down
170 changes: 132 additions & 38 deletions h2/src/main/org/h2/index/IndexCondition.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
*
Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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);
Expand Down Expand Up @@ -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(", ");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{}

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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:
Expand Down Expand Up @@ -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));
Expand All @@ -246,7 +301,7 @@ public String getSQL(int sqlFlags) {
if (expression != null) {
expression.getSQL(builder, sqlFlags, Expression.AUTO_PARENTHESES);
}
return builder.toString();
return builder;
}

/**
Expand All @@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{}

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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:
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Column[].toString() doesn't return anything useful here, use Column.writeColumns(builder, columns, TRACE_SQL_FLAGS) instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
Expand Down