/*
 * Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.table;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;

import org.h2.api.ErrorCode;
import org.h2.command.Parser;
import org.h2.command.dml.AllColumnsForPlan;
import org.h2.command.dml.Select;
import org.h2.engine.Right;
import org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.expression.ExpressionColumn;
import org.h2.expression.condition.Comparison;
import org.h2.expression.condition.ConditionAndOr;
import org.h2.index.Index;
import org.h2.index.IndexCondition;
import org.h2.index.IndexCursor;
import org.h2.index.IndexLookupBatch;
import org.h2.index.ViewIndex;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
import org.h2.value.Value;
import org.h2.value.ValueLong;
import org.h2.value.ValueNull;

A table filter represents a table that is used in a query. There is one such object whenever a table (or view) is used in a query. For example the following query has 2 table filters: SELECT * FROM TEST T1, TEST T2.
/** * A table filter represents a table that is used in a query. There is one such * object whenever a table (or view) is used in a query. For example the * following query has 2 table filters: SELECT * FROM TEST T1, TEST T2. */
public class TableFilter implements ColumnResolver { private static final int BEFORE_FIRST = 0, FOUND = 1, AFTER_LAST = 2, NULL_ROW = 3;
Whether this is a direct or indirect (nested) outer join
/** * Whether this is a direct or indirect (nested) outer join */
protected boolean joinOuterIndirect; private Session session; private final Table table; private final Select select; private String alias; private Index index; private final IndexHints indexHints; private int[] masks; private int scanCount; private boolean evaluatable;
Batched join support.
/** * Batched join support. */
private JoinBatch joinBatch; private int joinFilterId = -1;
Indicates that this filter is used in the plan.
/** * Indicates that this filter is used in the plan. */
private boolean used;
The filter used to walk through the index.
/** * The filter used to walk through the index. */
private final IndexCursor cursor;
The index conditions used for direct index lookup (start or end).
/** * The index conditions used for direct index lookup (start or end). */
private final ArrayList<IndexCondition> indexConditions = Utils.newSmallArrayList();
Whether new window conditions should not be accepted.
/** * Whether new window conditions should not be accepted. */
private boolean doneWithIndexConditions;
Additional conditions that can't be used for index lookup, but for row filter for this table (ID=ID, NAME LIKE '%X%')
/** * Additional conditions that can't be used for index lookup, but for row * filter for this table (ID=ID, NAME LIKE '%X%') */
private Expression filterCondition;
The complete join condition.
/** * The complete join condition. */
private Expression joinCondition; private SearchRow currentSearchRow; private Row current; private int state;
The joined table (if there is one).
/** * The joined table (if there is one). */
private TableFilter join;
Whether this is an outer join.
/** * Whether this is an outer join. */
private boolean joinOuter;
The nested joined table (if there is one).
/** * The nested joined table (if there is one). */
private TableFilter nestedJoin; private ArrayList<Column> naturalJoinColumns; private boolean foundOne; private Expression fullCondition; private final int hashCode; private final int orderInFrom; private LinkedHashMap<Column, String> derivedColumnMap;
Create a new table filter object.
Params:
  • session – the session
  • table – the table from where to read data
  • alias – the alias name
  • rightsChecked – true if rights are already checked
  • select – the select statement
  • orderInFrom – original order number (index) of this table filter in
  • indexHints – the index hints to be used by the query planner
/** * Create a new table filter object. * * @param session the session * @param table the table from where to read data * @param alias the alias name * @param rightsChecked true if rights are already checked * @param select the select statement * @param orderInFrom original order number (index) of this table filter in * @param indexHints the index hints to be used by the query planner */
public TableFilter(Session session, Table table, String alias, boolean rightsChecked, Select select, int orderInFrom, IndexHints indexHints) { this.session = session; this.table = table; this.alias = alias; this.select = select; this.cursor = new IndexCursor(this); if (!rightsChecked) { session.getUser().checkRight(table, Right.SELECT); } hashCode = session.nextObjectId(); this.orderInFrom = orderInFrom; this.indexHints = indexHints; }
Get the order number (index) of this table filter in the "from" clause of the query.
Returns:the index (0, 1, 2,...)
/** * Get the order number (index) of this table filter in the "from" clause of * the query. * * @return the index (0, 1, 2,...) */
public int getOrderInFrom() { return orderInFrom; } public IndexCursor getIndexCursor() { return cursor; } @Override public Select getSelect() { return select; } public Table getTable() { return table; }
Lock the table. This will also lock joined tables.
Params:
  • s – the session
  • exclusive – true if an exclusive lock is required
  • forceLockEvenInMvcc – lock even in the MVCC mode
/** * Lock the table. This will also lock joined tables. * * @param s the session * @param exclusive true if an exclusive lock is required * @param forceLockEvenInMvcc lock even in the MVCC mode */
public void lock(Session s, boolean exclusive, boolean forceLockEvenInMvcc) { table.lock(s, exclusive, forceLockEvenInMvcc); if (join != null) { join.lock(s, exclusive, forceLockEvenInMvcc); } }
Get the best plan item (index, cost) to use for the current join order.
Params:
  • s – the session
  • filters – all joined table filters
  • filter – the current table filter index
  • allColumnsSet – the set of all columns
Returns:the best plan item
/** * Get the best plan item (index, cost) to use for the current join * order. * * @param s the session * @param filters all joined table filters * @param filter the current table filter index * @param allColumnsSet the set of all columns * @return the best plan item */
public PlanItem getBestPlanItem(Session s, TableFilter[] filters, int filter, AllColumnsForPlan allColumnsSet) { PlanItem item1 = null; SortOrder sortOrder = null; if (select != null) { sortOrder = select.getSortOrder(); } if (indexConditions.isEmpty()) { item1 = new PlanItem(); item1.setIndex(table.getScanIndex(s, null, filters, filter, sortOrder, allColumnsSet)); item1.cost = item1.getIndex().getCost(s, null, filters, filter, sortOrder, allColumnsSet); } int len = table.getColumns().length; int[] masks = new int[len]; for (IndexCondition condition : indexConditions) { if (condition.isEvaluatable()) { if (condition.isAlwaysFalse()) { masks = null; break; } int id = condition.getColumn().getColumnId(); if (id >= 0) { masks[id] |= condition.getMask(indexConditions); } } } PlanItem item = table.getBestPlanItem(s, masks, filters, filter, sortOrder, allColumnsSet); item.setMasks(masks); // The more index conditions, the earlier the table. // This is to ensure joins without indexes run quickly: // x (x.a=10); y (x.b=y.b) - see issue 113 item.cost -= item.cost * indexConditions.size() / 100 / (filter + 1); if (item1 != null && item1.cost < item.cost) { item = item1; } if (nestedJoin != null) { setEvaluatable(true); item.setNestedJoinPlan(nestedJoin.getBestPlanItem(s, filters, filter, allColumnsSet)); // TODO optimizer: calculate cost of a join: should use separate // expected row number and lookup cost item.cost += item.cost * item.getNestedJoinPlan().cost; } if (join != null) { setEvaluatable(true); do { filter++; } while (filters[filter] != join); item.setJoinPlan(join.getBestPlanItem(s, filters, filter, allColumnsSet)); // TODO optimizer: calculate cost of a join: should use separate // expected row number and lookup cost item.cost += item.cost * item.getJoinPlan().cost; } return item; }
Set what plan item (index, cost, masks) to use.
Params:
  • item – the plan item
/** * Set what plan item (index, cost, masks) to use. * * @param item the plan item */
public void setPlanItem(PlanItem item) { if (item == null) { // invalid plan, most likely because a column wasn't found // this will result in an exception later on return; } setIndex(item.getIndex()); masks = item.getMasks(); if (nestedJoin != null) { if (item.getNestedJoinPlan() != null) { nestedJoin.setPlanItem(item.getNestedJoinPlan()); } else { nestedJoin.setScanIndexes(); } } if (join != null) { if (item.getJoinPlan() != null) { join.setPlanItem(item.getJoinPlan()); } else { join.setScanIndexes(); } } }
Set all missing indexes to scan indexes recursively.
/** * Set all missing indexes to scan indexes recursively. */
private void setScanIndexes() { if (index == null) { setIndex(table.getScanIndex(session)); } if (join != null) { join.setScanIndexes(); } if (nestedJoin != null) { nestedJoin.setScanIndexes(); } }
Prepare reading rows. This method will remove all index conditions that can not be used, and optimize the conditions.
/** * Prepare reading rows. This method will remove all index conditions that * can not be used, and optimize the conditions. */
public void prepare() { // forget all unused index conditions // the indexConditions list may be modified here for (int i = 0; i < indexConditions.size(); i++) { IndexCondition condition = indexConditions.get(i); if (!condition.isAlwaysFalse()) { Column col = condition.getColumn(); if (col.getColumnId() >= 0) { if (index.getColumnIndex(col) < 0) { indexConditions.remove(i); i--; } } } } if (nestedJoin != null) { if (nestedJoin == this) { DbException.throwInternalError("self join"); } nestedJoin.prepare(); } if (join != null) { if (join == this) { DbException.throwInternalError("self join"); } join.prepare(); } if (filterCondition != null) { filterCondition = filterCondition.optimize(session); } if (joinCondition != null) { joinCondition = joinCondition.optimize(session); } }
Start the query. This will reset the scan counts.
Params:
  • s – the session
/** * Start the query. This will reset the scan counts. * * @param s the session */
public void startQuery(Session s) { this.session = s; scanCount = 0; if (nestedJoin != null) { nestedJoin.startQuery(s); } if (join != null) { join.startQuery(s); } }
Reset to the current position.
/** * Reset to the current position. */
public void reset() { if (joinBatch != null && joinFilterId == 0) { // reset join batch only on top table filter joinBatch.reset(true); return; } if (nestedJoin != null) { nestedJoin.reset(); } if (join != null) { join.reset(); } state = BEFORE_FIRST; foundOne = false; } private boolean isAlwaysTopTableFilter(int filter) { if (filter != 0) { return false; } // check if we are at the top table filters all the way up SubQueryInfo info = session.getSubQueryInfo(); while (true) { if (info == null) { return true; } if (info.getFilter() != 0) { return false; } info = info.getUpper(); } }
Attempt to initialize batched join.
Params:
  • jb – join batch if it is already created
  • filters – the table filters
  • filter – the filter index (0, 1,...)
Returns:join batch if query runs over index which supports batched lookups, null otherwise
/** * Attempt to initialize batched join. * * @param jb join batch if it is already created * @param filters the table filters * @param filter the filter index (0, 1,...) * @return join batch if query runs over index which supports batched * lookups, {@code null} otherwise */
public JoinBatch prepareJoinBatch(JoinBatch jb, TableFilter[] filters, int filter) { assert filters[filter] == this; joinBatch = null; joinFilterId = -1; if (getTable().isView()) { session.pushSubQueryInfo(masks, filters, filter, select.getSortOrder()); try { ((ViewIndex) index).getQuery().prepareJoinBatch(); } finally { session.popSubQueryInfo(); } } // For globally top table filter we don't need to create lookup batch, // because currently it will not be used (this will be shown in // ViewIndex.getPlanSQL()). Probably later on it will make sense to // create it to better support X IN (...) conditions, but this needs to // be implemented separately. If isAlwaysTopTableFilter is false then we // either not a top table filter or top table filter in a sub-query, // which in turn is not top in outer query, thus we need to enable // batching here to allow outer query run batched join against this // sub-query. IndexLookupBatch lookupBatch = null; if (jb == null && select != null && !isAlwaysTopTableFilter(filter)) { lookupBatch = index.createLookupBatch(filters, filter); if (lookupBatch != null) { jb = new JoinBatch(filter + 1, join); } } if (jb != null) { if (nestedJoin != null) { throw DbException.throwInternalError(); } joinBatch = jb; joinFilterId = filter; if (lookupBatch == null && !isAlwaysTopTableFilter(filter)) { // createLookupBatch will be called at most once because jb can // be created only if lookupBatch is already not null from the // call above. lookupBatch = index.createLookupBatch(filters, filter); if (lookupBatch == null) { // the index does not support lookup batching, need to fake // it because we are not top lookupBatch = JoinBatch.createFakeIndexLookupBatch(this); } } jb.register(this, lookupBatch); } return jb; } public int getJoinFilterId() { return joinFilterId; } public JoinBatch getJoinBatch() { return joinBatch; }
Check if there are more rows to read.
Returns:true if there are
/** * Check if there are more rows to read. * * @return true if there are */
public boolean next() { if (joinBatch != null) { // will happen only on topTableFilter since joinBatch.next() does // not call join.next() return joinBatch.next(); } if (state == AFTER_LAST) { return false; } else if (state == BEFORE_FIRST) { cursor.find(session, indexConditions); if (!cursor.isAlwaysFalse()) { if (nestedJoin != null) { nestedJoin.reset(); } if (join != null) { join.reset(); } } } else { // state == FOUND || NULL_ROW // the last row was ok - try next row of the join if (join != null && join.next()) { return true; } } while (true) { // go to the next row if (state == NULL_ROW) { break; } if (cursor.isAlwaysFalse()) { state = AFTER_LAST; } else if (nestedJoin != null) { if (state == BEFORE_FIRST) { state = FOUND; } } else { if ((++scanCount & 4095) == 0) { checkTimeout(); } if (cursor.next()) { currentSearchRow = cursor.getSearchRow(); current = null; state = FOUND; } else { state = AFTER_LAST; } } if (nestedJoin != null && state == FOUND) { if (!nestedJoin.next()) { state = AFTER_LAST; if (joinOuter && !foundOne) { // possibly null row } else { continue; } } } // if no more rows found, try the null row (for outer joins only) if (state == AFTER_LAST) { if (joinOuter && !foundOne) { setNullRow(); } else { break; } } if (!isOk(filterCondition)) { continue; } boolean joinConditionOk = isOk(joinCondition); if (state == FOUND) { if (joinConditionOk) { foundOne = true; } else { continue; } } if (join != null) { join.reset(); if (!join.next()) { continue; } } // check if it's ok if (state == NULL_ROW || joinConditionOk) { return true; } } state = AFTER_LAST; return false; }
Set the state of this and all nested tables to the NULL row.
/** * Set the state of this and all nested tables to the NULL row. */
protected void setNullRow() { state = NULL_ROW; current = table.getNullRow(); currentSearchRow = current; if (nestedJoin != null) { nestedJoin.visit(new TableFilterVisitor() { @Override public void accept(TableFilter f) { f.setNullRow(); } }); } } private void checkTimeout() { session.checkCanceled(); }
Whether the current value of the condition is true, or there is no condition.
Params:
  • condition – the condition (null for no condition)
Returns:true if yes
/** * Whether the current value of the condition is true, or there is no * condition. * * @param condition the condition (null for no condition) * @return true if yes */
boolean isOk(Expression condition) { return condition == null || condition.getBooleanValue(session); }
Get the current row.
Returns:the current row, or null
/** * Get the current row. * * @return the current row, or null */
public Row get() { if (current == null && currentSearchRow != null) { current = cursor.get(); } return current; }
Set the current row.
Params:
  • current – the current row
/** * Set the current row. * * @param current the current row */
public void set(Row current) { this.current = current; this.currentSearchRow = current; }
Get the table alias name. If no alias is specified, the table name is returned.
Returns:the alias name
/** * Get the table alias name. If no alias is specified, the table name is * returned. * * @return the alias name */
@Override public String getTableAlias() { if (alias != null) { return alias; } return table.getName(); }
Add an index condition.
Params:
  • condition – the index condition
/** * Add an index condition. * * @param condition the index condition */
public void addIndexCondition(IndexCondition condition) { if (!doneWithIndexConditions) { indexConditions.add(condition); } }
Used to reject all additional index conditions.
/** * Used to reject all additional index conditions. */
public void doneWithIndexConditions() { this.doneWithIndexConditions = true; }
Add a filter condition.
Params:
  • condition – the condition
  • isJoin – if this is in fact a join condition
/** * Add a filter condition. * * @param condition the condition * @param isJoin if this is in fact a join condition */
public void addFilterCondition(Expression condition, boolean isJoin) { if (isJoin) { if (joinCondition == null) { joinCondition = condition; } else { joinCondition = new ConditionAndOr(ConditionAndOr.AND, joinCondition, condition); } } else { if (filterCondition == null) { filterCondition = condition; } else { filterCondition = new ConditionAndOr(ConditionAndOr.AND, filterCondition, condition); } } }
Add a joined table.
Params:
  • filter – the joined table filter
  • outer – if this is an outer join
  • on – the join condition
/** * Add a joined table. * * @param filter the joined table filter * @param outer if this is an outer join * @param on the join condition */
public void addJoin(TableFilter filter, boolean outer, Expression on) { if (on != null) { on.mapColumns(this, 0, Expression.MAP_INITIAL); TableFilterVisitor visitor = new MapColumnsVisitor(on); visit(visitor); filter.visit(visitor); } if (join == null) { join = filter; filter.joinOuter = outer; if (outer) { filter.visit(new JOIVisitor()); } if (on != null) { filter.mapAndAddFilter(on); } } else { join.addJoin(filter, outer, on); } }
Set a nested joined table.
Params:
  • filter – the joined table filter
/** * Set a nested joined table. * * @param filter the joined table filter */
public void setNestedJoin(TableFilter filter) { nestedJoin = filter; }
Map the columns and add the join condition.
Params:
  • on – the condition
/** * Map the columns and add the join condition. * * @param on the condition */
public void mapAndAddFilter(Expression on) { on.mapColumns(this, 0, Expression.MAP_INITIAL); addFilterCondition(on, true); if (nestedJoin != null) { on.mapColumns(nestedJoin, 0, Expression.MAP_INITIAL); } if (join != null) { join.mapAndAddFilter(on); } }
Create the index conditions for this filter if needed.
/** * Create the index conditions for this filter if needed. */
public void createIndexConditions() { if (joinCondition != null) { joinCondition = joinCondition.optimize(session); joinCondition.createIndexConditions(session, this); if (nestedJoin != null) { joinCondition.createIndexConditions(session, nestedJoin); } } if (join != null) { join.createIndexConditions(); } if (nestedJoin != null) { nestedJoin.createIndexConditions(); } } public TableFilter getJoin() { return join; }
Whether this is an outer joined table.
Returns:true if it is
/** * Whether this is an outer joined table. * * @return true if it is */
public boolean isJoinOuter() { return joinOuter; }
Whether this is indirectly an outer joined table (nested within an inner join).
Returns:true if it is
/** * Whether this is indirectly an outer joined table (nested within an inner * join). * * @return true if it is */
public boolean isJoinOuterIndirect() { return joinOuterIndirect; }
Get the query execution plan text to use for this table filter and append it to the specified builder.
Params:
  • builder – string builder to append to
  • isJoin – if this is a joined table
  • alwaysQuote – quote all identifiers
Returns:the specified builder
/** * Get the query execution plan text to use for this table filter and append * it to the specified builder. * * @param builder string builder to append to * @param isJoin if this is a joined table * @param alwaysQuote quote all identifiers * @return the specified builder */
public StringBuilder getPlanSQL(StringBuilder builder, boolean isJoin, boolean alwaysQuote) { if (isJoin) { if (joinOuter) { builder.append("LEFT OUTER JOIN "); } else { builder.append("INNER JOIN "); } } if (nestedJoin != null) { StringBuilder buffNested = new StringBuilder(); TableFilter n = nestedJoin; do { n.getPlanSQL(buffNested, n != nestedJoin, alwaysQuote).append('\n'); n = n.getJoin(); } while (n != null); String nested = buffNested.toString(); boolean enclose = !nested.startsWith("("); if (enclose) { builder.append("(\n"); } StringUtils.indent(builder, nested, 4, false); if (enclose) { builder.append(')'); } if (isJoin) { builder.append(" ON "); if (joinCondition == null) { // need to have a ON expression, // otherwise the nesting is unclear builder.append("1=1"); } else { joinCondition.getUnenclosedSQL(builder, alwaysQuote); } } return builder; } if (table.isView() && ((TableView) table).isRecursive()) { table.getSchema().getSQL(builder, alwaysQuote).append('.'); Parser.quoteIdentifier(builder, table.getName(), alwaysQuote); } else { table.getSQL(builder, alwaysQuote); } if (table.isView() && ((TableView) table).isInvalid()) { throw DbException.get(ErrorCode.VIEW_IS_INVALID_2, table.getName(), "not compiled"); } if (alias != null) { builder.append(' '); Parser.quoteIdentifier(builder, alias, alwaysQuote); if (derivedColumnMap != null) { builder.append('('); boolean f = false; for (String name : derivedColumnMap.values()) { if (f) { builder.append(", "); } f = true; Parser.quoteIdentifier(builder, name, alwaysQuote); } builder.append(')'); } } if (indexHints != null) { builder.append(" USE INDEX ("); boolean first = true; for (String index : indexHints.getAllowedIndexes()) { if (!first) { builder.append(", "); } else { first = false; } Parser.quoteIdentifier(builder, index, alwaysQuote); } builder.append(")"); } if (index != null) { builder.append('\n'); StringBuilder planBuilder = new StringBuilder(); if (joinBatch != null) { IndexLookupBatch lookupBatch = joinBatch.getLookupBatch(joinFilterId); if (lookupBatch == null) { if (joinFilterId != 0) { throw DbException.throwInternalError(Integer.toString(joinFilterId)); } } else { planBuilder.append("batched:").append(lookupBatch.getPlanSQL()).append(' '); } } planBuilder.append(index.getPlanSQL()); if (!indexConditions.isEmpty()) { planBuilder.append(": "); for (int i = 0, size = indexConditions.size(); i < size; i++) { if (i > 0) { planBuilder.append("\n AND "); } planBuilder.append(indexConditions.get(i).getSQL(false)); } } String plan = StringUtils.quoteRemarkSQL(planBuilder.toString()); planBuilder.setLength(0); planBuilder.append("/* ").append(plan); if (plan.indexOf('\n') >= 0) { planBuilder.append('\n'); } StringUtils.indent(builder, planBuilder.append(" */").toString(), 4, false); } if (isJoin) { builder.append("\n ON "); if (joinCondition == null) { // need to have a ON expression, otherwise the nesting is // unclear builder.append("1=1"); } else { joinCondition.getUnenclosedSQL(builder, alwaysQuote); } } if (filterCondition != null) { builder.append('\n'); String condition = StringUtils.unEnclose(filterCondition.getSQL(false)); condition = "/* WHERE " + StringUtils.quoteRemarkSQL(condition) + "\n*/"; StringUtils.indent(builder, condition, 4, false); } if (scanCount > 0) { builder.append("\n /* scanCount: ").append(scanCount).append(" */"); } return builder; }
Remove all index conditions that are not used by the current index.
/** * Remove all index conditions that are not used by the current index. */
void removeUnusableIndexConditions() { // the indexConditions list may be modified here for (int i = 0; i < indexConditions.size(); i++) { IndexCondition cond = indexConditions.get(i); if (!cond.isEvaluatable()) { indexConditions.remove(i--); } } } public int[] getMasks() { return masks; } public ArrayList<IndexCondition> getIndexConditions() { return indexConditions; } public Index getIndex() { return index; } public void setIndex(Index index) { this.index = index; cursor.setIndex(index); } public void setUsed(boolean used) { this.used = used; } public boolean isUsed() { return used; }
Set the session of this table filter.
Params:
  • session – the new session
/** * Set the session of this table filter. * * @param session the new session */
void setSession(Session session) { this.session = session; }
Remove the joined table
/** * Remove the joined table */
public void removeJoin() { this.join = null; } public Expression getJoinCondition() { return joinCondition; }
Remove the join condition.
/** * Remove the join condition. */
public void removeJoinCondition() { this.joinCondition = null; } public Expression getFilterCondition() { return filterCondition; }
Remove the filter condition.
/** * Remove the filter condition. */
public void removeFilterCondition() { this.filterCondition = null; } public void setFullCondition(Expression condition) { this.fullCondition = condition; if (join != null) { join.setFullCondition(condition); } }
Optimize the full condition. This will add the full condition to the filter condition.
Params:
  • fromOuterJoin – if this method was called from an outer joined table
/** * Optimize the full condition. This will add the full condition to the * filter condition. * * @param fromOuterJoin if this method was called from an outer joined table */
void optimizeFullCondition(boolean fromOuterJoin) { if (fullCondition != null) { fullCondition.addFilterConditions(this, fromOuterJoin || joinOuter); if (nestedJoin != null) { nestedJoin.optimizeFullCondition(fromOuterJoin || joinOuter); } if (join != null) { join.optimizeFullCondition(fromOuterJoin || joinOuter); } } }
Update the filter and join conditions of this and all joined tables with the information that the given table filter and all nested filter can now return rows or not.
Params:
  • filter – the table filter
  • b – the new flag
/** * Update the filter and join conditions of this and all joined tables with * the information that the given table filter and all nested filter can now * return rows or not. * * @param filter the table filter * @param b the new flag */
public void setEvaluatable(TableFilter filter, boolean b) { filter.setEvaluatable(b); if (filterCondition != null) { filterCondition.setEvaluatable(filter, b); } if (joinCondition != null) { joinCondition.setEvaluatable(filter, b); } if (nestedJoin != null) { // don't enable / disable the nested join filters // if enabling a filter in a joined filter if (this == filter) { nestedJoin.setEvaluatable(nestedJoin, b); } } if (join != null) { join.setEvaluatable(filter, b); } } public void setEvaluatable(boolean evaluatable) { this.evaluatable = evaluatable; } @Override public String getSchemaName() { return table.getSchema().getName(); } @Override public Column[] getColumns() { return table.getColumns(); } @Override public String getDerivedColumnName(Column column) { HashMap<Column, String> map = derivedColumnMap; return map != null ? map.get(column) : null; }
Get the system columns that this table understands. This is used for compatibility with other databases. The columns are only returned if the current mode supports system columns.
Returns:the system columns
/** * Get the system columns that this table understands. This is used for * compatibility with other databases. The columns are only returned if the * current mode supports system columns. * * @return the system columns */
@Override public Column[] getSystemColumns() { if (!session.getDatabase().getMode().systemColumns) { return null; } Column[] sys = new Column[3]; sys[0] = new Column("oid", Value.INT); sys[0].setTable(table, 0); sys[1] = new Column("ctid", Value.STRING); sys[1].setTable(table, 0); sys[2] = new Column("CTID", Value.STRING); sys[2].setTable(table, 0); return sys; } @Override public Column getRowIdColumn() { return table.getRowIdColumn(); } @Override public Value getValue(Column column) { if (joinBatch != null) { return joinBatch.getValue(joinFilterId, column); } if (currentSearchRow == null) { return null; } int columnId = column.getColumnId(); if (columnId == -1) { return ValueLong.get(currentSearchRow.getKey()); } if (current == null) { Value v = currentSearchRow.getValue(columnId); if (v != null) { return v; } current = cursor.get(); if (current == null) { return ValueNull.INSTANCE; } } return current.getValue(columnId); } @Override public TableFilter getTableFilter() { return this; } public void setAlias(String alias) { this.alias = alias; }
Set derived column list.
Params:
  • derivedColumnNames – names of derived columns
/** * Set derived column list. * * @param derivedColumnNames names of derived columns */
public void setDerivedColumns(ArrayList<String> derivedColumnNames) { Column[] columns = getColumns(); int count = columns.length; if (count != derivedColumnNames.size()) { throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); } LinkedHashMap<Column, String> map = new LinkedHashMap<>(); for (int i = 0; i < count; i++) { String alias = derivedColumnNames.get(i); for (int j = 0; j < i; j++) { if (alias.equals(derivedColumnNames.get(j))) { throw DbException.get(ErrorCode.DUPLICATE_COLUMN_NAME_1, alias); } } map.put(columns[i], alias); } this.derivedColumnMap = map; } @Override public Expression optimize(ExpressionColumn expressionColumn, Column column) { return expressionColumn; } @Override public String toString() { return alias != null ? alias : table.toString(); }
Add a column to the natural join key column list.
Params:
  • c – the column to add
/** * Add a column to the natural join key column list. * * @param c the column to add */
public void addNaturalJoinColumn(Column c) { if (naturalJoinColumns == null) { naturalJoinColumns = Utils.newSmallArrayList(); } naturalJoinColumns.add(c); }
Check if the given column is a natural join column.
Params:
  • c – the column to check
Returns:true if this is a joined natural join column
/** * Check if the given column is a natural join column. * * @param c the column to check * @return true if this is a joined natural join column */
public boolean isNaturalJoinColumn(Column c) { return naturalJoinColumns != null && naturalJoinColumns.contains(c); } @Override public int hashCode() { return hashCode; }
Are there any index conditions that involve IN(...).
Returns:whether there are IN(...) comparisons
/** * Are there any index conditions that involve IN(...). * * @return whether there are IN(...) comparisons */
public boolean hasInComparisons() { for (IndexCondition cond : indexConditions) { int compareType = cond.getCompareType(); if (compareType == Comparison.IN_QUERY || compareType == Comparison.IN_LIST) { return true; } } return false; }
Add the current row to the array, if there is a current row.
Params:
  • rows – the rows to lock
/** * Add the current row to the array, if there is a current row. * * @param rows the rows to lock */
public void lockRowAdd(ArrayList<Row> rows) { if (state == FOUND) { rows.add(get()); } } public TableFilter getNestedJoin() { return nestedJoin; }
Visit this and all joined or nested table filters.
Params:
  • visitor – the visitor
/** * Visit this and all joined or nested table filters. * * @param visitor the visitor */
public void visit(TableFilterVisitor visitor) { TableFilter f = this; do { visitor.accept(f); TableFilter n = f.nestedJoin; if (n != null) { n.visit(visitor); } f = f.join; } while (f != null); } public boolean isEvaluatable() { return evaluatable; } public Session getSession() { return session; } public IndexHints getIndexHints() { return indexHints; }
A visitor for table filters.
/** * A visitor for table filters. */
public interface TableFilterVisitor {
This method is called for each nested or joined table filter.
Params:
  • f – the filter
/** * This method is called for each nested or joined table filter. * * @param f the filter */
void accept(TableFilter f); }
A visitor that maps columns.
/** * A visitor that maps columns. */
private static final class MapColumnsVisitor implements TableFilterVisitor { private final Expression on; MapColumnsVisitor(Expression on) { this.on = on; } @Override public void accept(TableFilter f) { on.mapColumns(f, 0, Expression.MAP_INITIAL); } }
A visitor that sets joinOuterIndirect to true.
/** * A visitor that sets joinOuterIndirect to true. */
private static final class JOIVisitor implements TableFilterVisitor { JOIVisitor() { } @Override public void accept(TableFilter f) { f.joinOuterIndirect = true; } } }