Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2004-2014 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.command.dml;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import org.h2.api.ErrorCode;
import org.h2.api.Trigger;
import org.h2.command.CommandInterface;
import org.h2.engine.Constants;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.engine.SysProperties;
import org.h2.expression.Comparison;
import org.h2.expression.ConditionAndOr;
import org.h2.expression.Expression;
import org.h2.expression.ExpressionColumn;
import org.h2.expression.ExpressionVisitor;
import org.h2.expression.Parameter;
import org.h2.expression.Wildcard;
import org.h2.index.Cursor;
import org.h2.index.Index;
import org.h2.index.IndexCondition;
import org.h2.index.IndexType;
import org.h2.message.DbException;
import org.h2.result.LocalResult;
import org.h2.result.ResultInterface;
import org.h2.result.ResultTarget;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.ColumnResolver;
import org.h2.table.IndexColumn;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.New;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
import org.h2.util.ValueHashMap;
import org.h2.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueNull;
/**
* This class represents a simple SELECT statement.
*
* For each select statement,
* visibleColumnCount <= distinctColumnCount <= expressionCount.
* The expression list count could include ORDER BY and GROUP BY expressions
* that are not in the select list.
*
* The call sequence is init(), mapColumns() if it's a subquery, prepare().
*
* @author Thomas Mueller
* @author Joel Turkel (Group sorted query)
*/
public class Select extends Query {
private TableFilter topTableFilter;
private final ArrayList filters = New.arrayList();
private final ArrayList topFilters = New.arrayList();
private ArrayList expressions;
private Expression[] expressionArray;
private Expression having;
private Expression condition;
private int visibleColumnCount, distinctColumnCount;
private ArrayList orderList;
private ArrayList group;
private int[] groupIndex;
private boolean[] groupByExpression;
private HashMap currentGroup;
private int havingIndex;
private boolean isGroupQuery, isGroupSortedQuery;
private boolean isForUpdate, isForUpdateMvcc;
private double cost;
private boolean isQuickAggregateQuery, isDistinctQuery;
private boolean isPrepared, checkInit;
private boolean sortUsingIndex;
private SortOrder sort;
private int currentGroupRowId;
public Select(Session session) {
super(session);
}
/**
* Add a table to the query.
*
* @param filter the table to add
* @param isTop if the table can be the first table in the query plan
*/
public void addTableFilter(TableFilter filter, boolean isTop) {
// Oracle doesn't check on duplicate aliases
// String alias = filter.getAlias();
// if (filterNames.contains(alias)) {
// throw Message.getSQLException(
// ErrorCode.DUPLICATE_TABLE_ALIAS, alias);
// }
// filterNames.add(alias);
filters.add(filter);
if (isTop) {
topFilters.add(filter);
}
}
public ArrayList getTopFilters() {
return topFilters;
}
public void setExpressions(ArrayList expressions) {
this.expressions = expressions;
}
/**
* Called if this query contains aggregate functions.
*/
public void setGroupQuery() {
isGroupQuery = true;
}
public void setGroupBy(ArrayList group) {
this.group = group;
}
public ArrayList getGroupBy() {
return group;
}
public HashMap getCurrentGroup() {
return currentGroup;
}
public int getCurrentGroupRowId() {
return currentGroupRowId;
}
@Override
public void setOrder(ArrayList order) {
orderList = order;
}
/**
* Add a condition to the list of conditions.
*
* @param cond the condition to add
*/
public void addCondition(Expression cond) {
if (condition == null) {
condition = cond;
} else {
condition = new ConditionAndOr(ConditionAndOr.AND, cond, condition);
}
}
private void queryGroupSorted(int columnCount, ResultTarget result) {
int rowNumber = 0;
setCurrentRowNumber(0);
currentGroup = null;
Value[] previousKeyValues = null;
while (topTableFilter.next()) {
setCurrentRowNumber(rowNumber + 1);
if (condition == null ||
Boolean.TRUE.equals(condition.getBooleanValue(session))) {
rowNumber++;
Value[] keyValues = new Value[groupIndex.length];
// update group
for (int i = 0; i < groupIndex.length; i++) {
int idx = groupIndex[i];
Expression expr = expressions.get(idx);
keyValues[i] = expr.getValue(session);
}
if (previousKeyValues == null) {
previousKeyValues = keyValues;
currentGroup = New.hashMap();
} else if (!Arrays.equals(previousKeyValues, keyValues)) {
addGroupSortedRow(previousKeyValues, columnCount, result);
previousKeyValues = keyValues;
currentGroup = New.hashMap();
}
currentGroupRowId++;
for (int i = 0; i < columnCount; i++) {
if (groupByExpression == null || !groupByExpression[i]) {
Expression expr = expressions.get(i);
expr.updateAggregate(session);
}
}
}
}
if (previousKeyValues != null) {
addGroupSortedRow(previousKeyValues, columnCount, result);
}
}
private void addGroupSortedRow(Value[] keyValues, int columnCount,
ResultTarget result) {
Value[] row = new Value[columnCount];
for (int j = 0; groupIndex != null && j < groupIndex.length; j++) {
row[groupIndex[j]] = keyValues[j];
}
for (int j = 0; j < columnCount; j++) {
if (groupByExpression != null && groupByExpression[j]) {
continue;
}
Expression expr = expressions.get(j);
row[j] = expr.getValue(session);
}
if (isHavingNullOrFalse(row)) {
return;
}
row = keepOnlyDistinct(row, columnCount);
result.addRow(row);
}
private Value[] keepOnlyDistinct(Value[] row, int columnCount) {
if (columnCount == distinctColumnCount) {
return row;
}
// remove columns so that 'distinct' can filter duplicate rows
Value[] r2 = new Value[distinctColumnCount];
System.arraycopy(row, 0, r2, 0, distinctColumnCount);
return r2;
}
private boolean isHavingNullOrFalse(Value[] row) {
if (havingIndex >= 0) {
Value v = row[havingIndex];
if (v == ValueNull.INSTANCE) {
return true;
}
if (!Boolean.TRUE.equals(v.getBoolean())) {
return true;
}
}
return false;
}
private Index getGroupSortedIndex() {
if (groupIndex == null || groupByExpression == null) {
return null;
}
ArrayList indexes = topTableFilter.getTable().getIndexes();
if (indexes != null) {
for (int i = 0, size = indexes.size(); i < size; i++) {
Index index = indexes.get(i);
if (index.getIndexType().isScan()) {
continue;
}
if (index.getIndexType().isHash()) {
// does not allow scanning entries
continue;
}
if (isGroupSortedIndex(topTableFilter, index)) {
return index;
}
}
}
return null;
}
private boolean isGroupSortedIndex(TableFilter tableFilter, Index index) {
// check that all the GROUP BY expressions are part of the index
Column[] indexColumns = index.getColumns();
// also check that the first columns in the index are grouped
boolean[] grouped = new boolean[indexColumns.length];
outerLoop:
for (int i = 0, size = expressions.size(); i < size; i++) {
if (!groupByExpression[i]) {
continue;
}
Expression expr = expressions.get(i).getNonAliasExpression();
if (!(expr instanceof ExpressionColumn)) {
return false;
}
ExpressionColumn exprCol = (ExpressionColumn) expr;
for (int j = 0; j < indexColumns.length; ++j) {
if (tableFilter == exprCol.getTableFilter()) {
if (indexColumns[j].equals(exprCol.getColumn())) {
grouped[j] = true;
continue outerLoop;
}
}
}
// We didn't find a matching index column
// for one group by expression
return false;
}
// check that the first columns in the index are grouped
// good: index(a, b, c); group by b, a
// bad: index(a, b, c); group by a, c
for (int i = 1; i < grouped.length; i++) {
if (!grouped[i - 1] && grouped[i]) {
return false;
}
}
return true;
}
private int getGroupByExpressionCount() {
if (groupByExpression == null) {
return 0;
}
int count = 0;
for (boolean b : groupByExpression) {
if (b) {
++count;
}
}
return count;
}
private void queryGroup(int columnCount, LocalResult result) {
ValueHashMap> groups =
ValueHashMap.newInstance();
int rowNumber = 0;
setCurrentRowNumber(0);
currentGroup = null;
ValueArray defaultGroup = ValueArray.get(new Value[0]);
int sampleSize = getSampleSizeValue(session);
while (topTableFilter.next()) {
setCurrentRowNumber(rowNumber + 1);
if (condition == null ||
Boolean.TRUE.equals(condition.getBooleanValue(session))) {
Value key;
rowNumber++;
if (groupIndex == null) {
key = defaultGroup;
} else {
Value[] keyValues = new Value[groupIndex.length];
// update group
for (int i = 0; i < groupIndex.length; i++) {
int idx = groupIndex[i];
Expression expr = expressions.get(idx);
keyValues[i] = expr.getValue(session);
}
key = ValueArray.get(keyValues);
}
HashMap values = groups.get(key);
if (values == null) {
values = new HashMap();
groups.put(key, values);
}
currentGroup = values;
currentGroupRowId++;
int len = columnCount;
for (int i = 0; i < len; i++) {
if (groupByExpression == null || !groupByExpression[i]) {
Expression expr = expressions.get(i);
expr.updateAggregate(session);
}
}
if (sampleSize > 0 && rowNumber >= sampleSize) {
break;
}
}
}
if (groupIndex == null && groups.size() == 0) {
groups.put(defaultGroup, new HashMap());
}
ArrayList keys = groups.keys();
for (Value v : keys) {
ValueArray key = (ValueArray) v;
currentGroup = groups.get(key);
Value[] keyValues = key.getList();
Value[] row = new Value[columnCount];
for (int j = 0; groupIndex != null && j < groupIndex.length; j++) {
row[groupIndex[j]] = keyValues[j];
}
for (int j = 0; j < columnCount; j++) {
if (groupByExpression != null && groupByExpression[j]) {
continue;
}
Expression expr = expressions.get(j);
row[j] = expr.getValue(session);
}
if (isHavingNullOrFalse(row)) {
continue;
}
row = keepOnlyDistinct(row, columnCount);
result.addRow(row);
}
}
/**
* Get the index that matches the ORDER BY list, if one exists. This is to
* avoid running a separate ORDER BY if an index can be used. This is
* specially important for large result sets, if only the first few rows are
* important (LIMIT is used)
*
* @return the index if one is found
*/
private Index getSortIndex() {
if (sort == null) {
return null;
}
ArrayList sortColumns = New.arrayList();
for (int idx : sort.getQueryColumnIndexes()) {
if (idx < 0 || idx >= expressions.size()) {
throw DbException.getInvalidValueException("ORDER BY", idx + 1);
}
Expression expr = expressions.get(idx);
expr = expr.getNonAliasExpression();
if (expr.isConstant()) {
continue;
}
if (!(expr instanceof ExpressionColumn)) {
return null;
}
ExpressionColumn exprCol = (ExpressionColumn) expr;
if (exprCol.getTableFilter() != topTableFilter) {
return null;
}
sortColumns.add(exprCol.getColumn());
}
Column[] sortCols = sortColumns.toArray(new Column[sortColumns.size()]);
int[] sortTypes = sort.getSortTypes();
if (sortCols.length == 0) {
// sort just on constants - can use scan index
return topTableFilter.getTable().getScanIndex(session);
}
ArrayList list = topTableFilter.getTable().getIndexes();
if (list != null) {
for (int i = 0, size = list.size(); i < size; i++) {
Index index = list.get(i);
if (index.getCreateSQL() == null) {
// can't use the scan index
continue;
}
if (index.getIndexType().isHash()) {
continue;
}
IndexColumn[] indexCols = index.getIndexColumns();
if (indexCols.length < sortCols.length) {
continue;
}
boolean ok = true;
for (int j = 0; j < sortCols.length; j++) {
// the index and the sort order must start
// with the exact same columns
IndexColumn idxCol = indexCols[j];
Column sortCol = sortCols[j];
boolean implicitSortColumn = false;
if (idxCol.column != sortCol) {
implicitSortColumn = isSortColumnImplicit(
topTableFilter, idxCol.column);
if (!implicitSortColumn) {
ok = false;
break;
}
}
if (!implicitSortColumn && idxCol.sortType != sortTypes[j]) {
// NULL FIRST for ascending and NULLS LAST
// for descending would actually match the default
ok = false;
break;
}
}
if (ok) {
return index;
}
}
}
if (sortCols.length == 1 && sortCols[0].getColumnId() == -1) {
// special case: order by _ROWID_
Index index = topTableFilter.getTable().getScanIndex(session);
if (index.isRowIdIndex()) {
return index;
}
}
return null;
}
/**
* Validates the cases where ORDER BY clause do not contains all indexed
* columns, but the related index path still would be valid for such search.
* Sometimes, the absence of a column in the ORDER BY clause does not alter
* the expected final result, and an index sorted scan could still be used.
*