提交 4c2a4489 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Store window data in SelectGroups

上级 64ff1b54
......@@ -8,7 +8,6 @@ package org.h2.command.dml;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import org.h2.api.ErrorCode;
import org.h2.api.Trigger;
......@@ -105,7 +104,8 @@ public class Select extends Query {
SelectGroups groupData;
private int havingIndex;
private boolean isGroupQuery, isGroupSortedQuery;
boolean isGroupQuery;
private boolean isGroupSortedQuery;
private boolean isWindowQuery;
private boolean isForUpdate, isForUpdateMvcc;
private double cost;
......@@ -177,8 +177,8 @@ public class Select extends Query {
return group;
}
public SelectGroups getGroupDataIfCurrent(boolean forAggregate) {
return groupData != null && (forAggregate || !isWindowQuery) && groupData.isCurrentGroup() ? groupData : null;
public SelectGroups getGroupDataIfCurrent(boolean window) {
return groupData != null && (window || groupData.isCurrentGroup()) ? groupData : null;
}
@Override
......@@ -357,10 +357,9 @@ public class Select extends Query {
private void queryWindow(int columnCount, LocalResult result, long offset, boolean quickOffset) {
if (groupData == null) {
groupData = new SelectGroups(session, expressions, groupIndex);
groupData = SelectGroups.getInstance(session, expressions, isGroupQuery, groupIndex);
}
groupData.reset();
HashMap<ValueArray, ArrayList<Row>> rows = new HashMap<>();
try {
int rowNumber = 0;
setCurrentRowNumber(0);
......@@ -369,13 +368,7 @@ public class Select extends Query {
setCurrentRowNumber(rowNumber + 1);
if (isConditionMet()) {
rowNumber++;
ValueArray key = groupData.nextSource();
ArrayList<Row> groupRows = rows.get(key);
if (groupRows == null) {
groupRows = Utils.newSmallArrayList();
rows.put(key, groupRows);
}
groupRows.add(topTableFilter.get());
groupData.nextSource();
updateAgg(columnCount);
if (sampleSize > 0 && rowNumber >= sampleSize) {
break;
......@@ -384,10 +377,7 @@ public class Select extends Query {
}
groupData.done();
for (ValueArray currentGroupsKey; (currentGroupsKey = groupData.next()) != null;) {
for (Row originalRow : rows.get(currentGroupsKey)) {
topTableFilter.set(originalRow);
offset = processGroupedRow(columnCount, result, offset, quickOffset, currentGroupsKey);
}
offset = processGroupedRow(columnCount, result, offset, quickOffset, currentGroupsKey);
}
} finally {
groupData.reset();
......@@ -396,7 +386,7 @@ public class Select extends Query {
private void queryGroup(int columnCount, LocalResult result, long offset, boolean quickOffset) {
if (groupData == null) {
groupData = new SelectGroups(session, expressions, groupIndex);
groupData = SelectGroups.getInstance(session, expressions, isGroupQuery, groupIndex);
}
groupData.reset();
try {
......@@ -1654,7 +1644,7 @@ public class Select extends Query {
LazyResultGroupSorted(Expression[] expressions, int columnCount) {
super(expressions, columnCount);
if (groupData == null) {
groupData = new SelectGroups(getSession(), Select.this.expressions, groupIndex);
groupData = SelectGroups.getInstance(getSession(), Select.this.expressions, isGroupQuery, groupIndex);
} else {
// TODO is this branch possible?
groupData.resetLazy();
......
......@@ -24,7 +24,7 @@ import org.h2.value.ValueArray;
* Call sequence:
* </p>
* <ul>
* <li>{@link #reset()} (not required before the first execution).</li>
* <li>{@link #reset()}.</li>
* <li>For each source row {@link #nextSource()} should be invoked.</li>
* <li>{@link #done()}.</li>
* <li>{@link #next()} is invoked inside a loop until it returns null.</li>
......@@ -39,51 +39,179 @@ import org.h2.value.ValueArray;
* can have one or more rows.</li>
* </ul>
*/
public final class SelectGroups {
public abstract class SelectGroups {
private final Session session;
private static final class Grouped extends SelectGroups {
private final ArrayList<Expression> expressions;
private final int[] groupIndex;
private final int[] groupIndex;
/**
* Map of group-by key to group-by expression data e.g. AggregateData
*/
private HashMap<ValueArray, Object[]> groupByData;
/**
* Key into groupByData that produces currentGroupByExprData. Not used
* in lazy mode.
*/
private ValueArray currentGroupsKey;
/**
* Cursor for {@link #next()} method.
*/
private Iterator<Entry<ValueArray, Object[]>> cursor;
/**
* The key for the default group.
*/
// Can be static, but TestClearReferences complains about it
private final ValueArray defaultGroup = ValueArray.get(new Value[0]);
Grouped(Session session, ArrayList<Expression> expressions, int[] groupIndex) {
super(session, expressions);
this.groupIndex = groupIndex;
}
@Override
public void reset() {
super.reset();
groupByData = new HashMap<>();
currentGroupsKey = null;
cursor = null;
}
@Override
public void nextSource() {
if (groupIndex == null) {
currentGroupsKey = 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);
}
currentGroupsKey = ValueArray.get(keyValues);
}
Object[] values = groupByData.get(currentGroupsKey);
if (values == null) {
values = new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())];
groupByData.put(currentGroupsKey, values);
}
currentGroupByExprData = values;
currentGroupRowId++;
}
@Override
void updateCurrentGroupExprData() {
// this can be null in lazy mode
if (currentGroupsKey != null) {
// since we changed the size of the array, update the object in
// the groups map
groupByData.put(currentGroupsKey, currentGroupByExprData);
}
}
@Override
public void done() {
if (groupIndex == null && groupByData.size() == 0) {
groupByData.put(defaultGroup,
new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())]);
}
cursor = groupByData.entrySet().iterator();
}
@Override
public ValueArray next() {
if (cursor.hasNext()) {
Map.Entry<ValueArray, Object[]> entry = cursor.next();
currentGroupByExprData = entry.getValue();
return entry.getKey();
}
return null;
}
@Override
public void resetLazy() {
super.resetLazy();
currentGroupsKey = null;
}
}
private static final class Plain extends SelectGroups {
private ArrayList<Object[]> rows;
/**
* Cursor for {@link #next()} method.
*/
private Iterator<Object[]> cursor;
Plain(Session session, ArrayList<Expression> expressions) {
super(session, expressions);
}
@Override
public void reset() {
super.reset();
rows = new ArrayList<>();
cursor = null;
}
@Override
public void nextSource() {
Object[] values = new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())];
rows.add(values);
currentGroupByExprData = values;
currentGroupRowId++;
}
@Override
void updateCurrentGroupExprData() {
rows.set(rows.size() - 1, currentGroupByExprData);
}
@Override
public void done() {
cursor = rows.iterator();
}
@Override
public ValueArray next() {
if (cursor.hasNext()) {
Object[] values = cursor.next();
currentGroupByExprData = values;
return ValueArray.get(new Value[0]);
}
return null;
}
}
final Session session;
final ArrayList<Expression> expressions;
/**
* The array of current group-by expression data e.g. AggregateData.
*/
private Object[] currentGroupByExprData;
Object[] currentGroupByExprData;
/**
* Maps an expression object to an index, to use in accessing the Object[]
* pointed to by groupByData.
*/
private final HashMap<Expression, Integer> exprToIndexInGroupByData = new HashMap<>();
final HashMap<Expression, Integer> exprToIndexInGroupByData = new HashMap<>();
/**
* Map of group-by key to group-by expression data e.g. AggregateData
* Maps an expression object to its data.
*/
private HashMap<ValueArray, Object[]> groupByData;
/**
* Key into groupByData that produces currentGroupByExprData. Not used in
* lazy mode.
*/
private ValueArray currentGroupsKey;
private final HashMap<Expression, Object> windowData = new HashMap<>();
/**
* The id of the current group.
*/
private int currentGroupRowId;
/**
* The key for the default group.
*/
// Can be static, but TestClearReferences complains about it
private ValueArray defaultGroup = ValueArray.get(new Value[0]);
/**
* Cursor for {@link #next()} method.
*/
private Iterator<Entry<ValueArray, Object[]>> cursor;
int currentGroupRowId;
/**
* Creates new instance of grouped data.
......@@ -92,13 +220,19 @@ public final class SelectGroups {
* the session
* @param expressions
* the expressions
* @param isGroupQuery
* is this query is a group query
* @param groupIndex
* the indexes of group expressions, or null
*/
public SelectGroups(Session session, ArrayList<Expression> expressions, int[] groupIndex) {
public static SelectGroups getInstance(Session session, ArrayList<Expression> expressions, boolean isGroupQuery,
int[] groupIndex) {
return isGroupQuery ? new Grouped(session, expressions, groupIndex) : new Plain(session, expressions);
}
SelectGroups(Session session, ArrayList<Expression> expressions) {
this.session = session;
this.expressions = expressions;
this.groupIndex = groupIndex;
}
/**
......@@ -110,8 +244,17 @@ public final class SelectGroups {
/**
* Get the group-by data for the current group and the passed in expression.
*
* @param expr
* expression
* @param window
* true if expression is a window expression
* @return expression data or null
*/
public Object getCurrentGroupExprData(Expression expr) {
public Object getCurrentGroupExprData(Expression expr, boolean window) {
if (window) {
return windowData.get(expr);
}
Integer index = exprToIndexInGroupByData.get(expr);
if (index == null) {
return null;
......@@ -121,8 +264,20 @@ public final class SelectGroups {
/**
* Set the group-by data for the current group and the passed in expression.
*
* @param expr
* expression
* @param object
* expression data to set
* @param window
* true if expression is a window expression
*/
public void setCurrentGroupExprData(Expression expr, Object obj) {
public void setCurrentGroupExprData(Expression expr, Object obj, boolean window) {
if (window) {
Object old = windowData.put(expr, obj);
assert old == null;
return;
}
Integer index = exprToIndexInGroupByData.get(expr);
if (index != null) {
assert currentGroupByExprData[index] == null;
......@@ -133,16 +288,13 @@ public final class SelectGroups {
exprToIndexInGroupByData.put(expr, index);
if (index >= currentGroupByExprData.length) {
currentGroupByExprData = Arrays.copyOf(currentGroupByExprData, currentGroupByExprData.length * 2);
// this can be null in lazy mode
if (currentGroupsKey != null) {
// since we changed the size of the array, update the object in
// the groups map
groupByData.put(currentGroupsKey, currentGroupByExprData);
}
updateCurrentGroupExprData();
}
currentGroupByExprData[index] = obj;
}
abstract void updateCurrentGroupExprData();
/**
* Returns identity of the current row. Used by aggregates to check whether
* they already processed this row or not.
......@@ -157,72 +309,34 @@ public final class SelectGroups {
* Resets this group data for reuse.
*/
public void reset() {
groupByData = new HashMap<>();
currentGroupByExprData = null;
currentGroupsKey = null;
exprToIndexInGroupByData.clear();
cursor = null;
windowData.clear();
}
/**
* Invoked for each source row to evaluate group key and setup all necessary
* data for aggregates.
*
* @return key of the current group
*/
public ValueArray nextSource() {
if (groupIndex == null) {
currentGroupsKey = 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);
}
currentGroupsKey = ValueArray.get(keyValues);
}
Object[] values = groupByData.get(currentGroupsKey);
if (values == null) {
values = new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())];
groupByData.put(currentGroupsKey, values);
}
currentGroupByExprData = values;
currentGroupRowId++;
return currentGroupsKey;
}
*/
public abstract void nextSource();
/**
* Invoked after all source rows are evaluated.
*/
public void done() {
if (groupIndex == null && groupByData.size() == 0) {
groupByData.put(defaultGroup, new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())]);
}
cursor = groupByData.entrySet().iterator();
}
public abstract void done();
/**
* Returns the key of the next group.
*
* @return the key of the next group, or null
*/
public ValueArray next() {
if (cursor.hasNext()) {
Map.Entry<ValueArray, Object[]> entry = cursor.next();
currentGroupByExprData = entry.getValue();
return entry.getKey();
}
return null;
}
public abstract ValueArray next();
/**
* Resets this group data for reuse in lazy mode.
*/
public void resetLazy() {
currentGroupByExprData = null;
currentGroupsKey = null;
}
/**
......
......@@ -164,9 +164,9 @@ public class ExpressionColumn extends Expression {
// this is a different level (the enclosing query)
return;
}
Value v = (Value) groupData.getCurrentGroupExprData(this);
Value v = (Value) groupData.getCurrentGroupExprData(this, false);
if (v == null) {
groupData.setCurrentGroupExprData(this, now);
groupData.setCurrentGroupExprData(this, now, false);
} else {
if (!database.areEqual(now, v)) {
throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getSQL());
......@@ -180,7 +180,7 @@ public class ExpressionColumn extends Expression {
if (select != null) {
SelectGroups groupData = select.getGroupDataIfCurrent(false);
if (groupData != null) {
Value v = (Value) groupData.getCurrentGroupExprData(this);
Value v = (Value) groupData.getCurrentGroupExprData(this, false);
if (v != null) {
return v;
}
......
......@@ -170,10 +170,14 @@ public class Aggregate extends AbstractAggregate {
/**
* Create a new aggregate object.
*
* @param type the aggregate type
* @param on the aggregated expression
* @param select the select statement
* @param distinct if distinct is used
* @param type
* the aggregate type
* @param on
* the aggregated expression
* @param select
* the select statement
* @param distinct
* if distinct is used
*/
public Aggregate(AggregateType type, Expression on, Select select, boolean distinct) {
this.type = type;
......@@ -229,8 +233,10 @@ public class Aggregate extends AbstractAggregate {
* Get the aggregate type for this name, or -1 if no aggregate has been
* found.
*
* @param name the aggregate function name
* @return null if no aggregate function has been found, or the aggregate type
* @param name
* the aggregate function name
* @return null if no aggregate function has been found, or the aggregate
* type
*/
public static AggregateType getAggregateType(String name) {
return AGGREGATES.get(name);
......@@ -239,7 +245,8 @@ public class Aggregate extends AbstractAggregate {
/**
* Set the order for ARRAY_AGG() or GROUP_CONCAT() aggregate.
*
* @param orderByList the order by list
* @param orderByList
* the order by list
*/
public void setOrderByList(ArrayList<SelectOrderBy> orderByList) {
this.orderByList = orderByList;
......@@ -248,7 +255,8 @@ public class Aggregate extends AbstractAggregate {
/**
* Set the separator for the GROUP_CONCAT() aggregate.
*
* @param separator the separator expression
* @param separator
* the separator expression
*/
public void setGroupConcatSeparator(Expression separator) {
this.groupConcatSeparator = separator;
......@@ -286,7 +294,7 @@ public class Aggregate extends AbstractAggregate {
// if (on != null) {
// on.updateAggregate();
// }
SelectGroups groupData = select.getGroupDataIfCurrent(true);
SelectGroups groupData = select.getGroupDataIfCurrent(over != null);
if (groupData == null) {
// this is a different level (the enclosing query)
return;
......@@ -299,6 +307,9 @@ public class Aggregate extends AbstractAggregate {
}
lastGroupRowId = groupRowId;
if (over != null) {
over.updateAggregate(session);
}
if (filterCondition != null) {
if (!filterCondition.getBooleanValue(session)) {
return;
......@@ -366,7 +377,7 @@ public class Aggregate extends AbstractAggregate {
DbException.throwInternalError("type=" + type);
}
}
SelectGroups groupData = select.getGroupDataIfCurrent(true);
SelectGroups groupData = select.getGroupDataIfCurrent(over != null);
if (groupData == null) {
throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getSQL());
}
......@@ -381,8 +392,7 @@ public class Aggregate extends AbstractAggregate {
sortWithOrderBy(array);
}
StatementBuilder buff = new StatementBuilder();
String sep = groupConcatSeparator == null ?
"," : groupConcatSeparator.getValue(session).getString();
String sep = groupConcatSeparator == null ? "," : groupConcatSeparator.getValue(session).getString();
for (Value val : array) {
String s;
if (val.getType() == Value.ARRAY) {
......@@ -431,10 +441,11 @@ public class Aggregate extends AbstractAggregate {
ValueArray key;
if (over != null && (key = over.getCurrentKey(session)) != null) {
@SuppressWarnings("unchecked")
ValueHashMap<AggregateData> map = (ValueHashMap<AggregateData>) groupData.getCurrentGroupExprData(this);
ValueHashMap<AggregateData> map = (ValueHashMap<AggregateData>) groupData.getCurrentGroupExprData(this,
true);
if (map == null) {
map = new ValueHashMap<>();
groupData.setCurrentGroupExprData(this, map);
groupData.setCurrentGroupExprData(this, map, true);
}
data = map.get(key);
if (data == null) {
......@@ -442,10 +453,10 @@ public class Aggregate extends AbstractAggregate {
map.put(key, data);
}
} else {
data = (AggregateData) groupData.getCurrentGroupExprData(this);
data = (AggregateData) groupData.getCurrentGroupExprData(this, over != null);
if (data == null) {
data = AggregateData.create(type);
groupData.setCurrentGroupExprData(this, data);
groupData.setCurrentGroupExprData(this, data, over != null);
}
}
return data;
......@@ -766,8 +777,7 @@ public class Aggregate extends AbstractAggregate {
if (on != null && !on.isEverything(visitor)) {
return false;
}
if (groupConcatSeparator != null &&
!groupConcatSeparator.isEverything(visitor)) {
if (groupConcatSeparator != null && !groupConcatSeparator.isEverything(visitor)) {
return false;
}
if (orderByList != null) {
......
......@@ -160,7 +160,7 @@ public class JavaAggregate extends AbstractAggregate {
@Override
public Value getValue(Session session) {
SelectGroups groupData = select.getGroupDataIfCurrent(true);
SelectGroups groupData = select.getGroupDataIfCurrent(over != null);
if (groupData == null) {
throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getSQL());
}
......@@ -201,7 +201,7 @@ public class JavaAggregate extends AbstractAggregate {
@Override
public void updateAggregate(Session session) {
SelectGroups groupData = select.getGroupDataIfCurrent(true);
SelectGroups groupData = select.getGroupDataIfCurrent(over != null);
if (groupData == null) {
// this is a different level (the enclosing query)
return;
......@@ -214,6 +214,9 @@ public class JavaAggregate extends AbstractAggregate {
}
lastGroupRowId = groupRowId;
if (over != null) {
over.updateAggregate(session);
}
if (filterCondition != null) {
if (!filterCondition.getBooleanValue(session)) {
return;
......@@ -253,13 +256,13 @@ public class JavaAggregate extends AbstractAggregate {
ValueArray key;
if (over != null && (key = over.getCurrentKey(session)) != null) {
@SuppressWarnings("unchecked")
ValueHashMap<Aggregate> map = (ValueHashMap<Aggregate>) groupData.getCurrentGroupExprData(this);
ValueHashMap<Aggregate> map = (ValueHashMap<Aggregate>) groupData.getCurrentGroupExprData(this, true);
if (map == null) {
if (ifExists) {
return null;
}
map = new ValueHashMap<>();
groupData.setCurrentGroupExprData(this, map);
groupData.setCurrentGroupExprData(this, map, true);
}
data = map.get(key);
if (data == null) {
......@@ -270,13 +273,13 @@ public class JavaAggregate extends AbstractAggregate {
map.put(key, data);
}
} else {
data = (Aggregate) groupData.getCurrentGroupExprData(this);
data = (Aggregate) groupData.getCurrentGroupExprData(this, over != null);
if (data == null) {
if (ifExists) {
return null;
}
data = getInstance();
groupData.setCurrentGroupExprData(this, data);
groupData.setCurrentGroupExprData(this, data, over != null);
}
}
return data;
......@@ -287,14 +290,14 @@ public class JavaAggregate extends AbstractAggregate {
ValueArray key;
if (over != null && (key = over.getCurrentKey(session)) != null) {
@SuppressWarnings("unchecked")
ValueHashMap<AggregateDataCollecting> map =
(ValueHashMap<AggregateDataCollecting>) groupData.getCurrentGroupExprData(this);
ValueHashMap<AggregateDataCollecting> map = (ValueHashMap<AggregateDataCollecting>) groupData
.getCurrentGroupExprData(this, true);
if (map == null) {
if (ifExists) {
return null;
}
map = new ValueHashMap<>();
groupData.setCurrentGroupExprData(this, map);
groupData.setCurrentGroupExprData(this, map, true);
}
data = map.get(key);
if (data == null) {
......@@ -305,13 +308,13 @@ public class JavaAggregate extends AbstractAggregate {
map.put(key, data);
}
} else {
data = (AggregateDataCollecting) groupData.getCurrentGroupExprData(this);
data = (AggregateDataCollecting) groupData.getCurrentGroupExprData(this, over != null);
if (data == null) {
if (ifExists) {
return null;
}
data = new AggregateDataCollecting();
groupData.setCurrentGroupExprData(this, data);
groupData.setCurrentGroupExprData(this, data, over != null);
}
}
return data;
......
......@@ -39,6 +39,7 @@ public final class Window {
* the column resolver
* @param level
* the subquery nesting level
* @see Expression#mapColumns(ColumnResolver, int)
*/
public void mapColumns(ColumnResolver resolver, int level) {
if (partitionBy != null) {
......@@ -56,6 +57,7 @@ public final class Window {
* the table filter
* @param value
* true if the table filter can return value
* @see Expression#setEvaluatable(TableFilter, boolean)
*/
public void setEvaluatable(TableFilter tableFilter, boolean value) {
if (partitionBy != null) {
......@@ -90,6 +92,7 @@ public final class Window {
* Returns SQL representation.
*
* @return SQL representation.
* @see Expression#getSQL()
*/
public String getSQL() {
if (partitionBy == null) {
......@@ -105,6 +108,21 @@ public final class Window {
return builder.append(')').toString();
}
/**
* Update an aggregate value.
*
* @param session
* the session
* @see Expression#updateAggregate(Session)
*/
public void updateAggregate(Session session) {
if (partitionBy != null) {
for (Expression expr : partitionBy) {
expr.updateAggregate(session);
}
}
}
@Override
public String toString() {
return getSQL();
......
......@@ -107,6 +107,17 @@ SELECT ARRAY_AGG(ID) OVER (PARTITION BY NAME), NAME FROM TEST;
> (4, 5, 6) c
> rows: 6
SELECT ARRAY_AGG(ID) FILTER (WHERE ID < 3 OR ID > 4) OVER (PARTITION BY NAME), NAME FROM TEST ORDER BY NAME;
> ARRAY_AGG(ID) FILTER (WHERE ((ID < 3) OR (ID > 4))) OVER (PARTITION BY NAME) NAME
> ---------------------------------------------------------------------------- ----
> (1, 2) a
> (1, 2) a
> null b
> (5, 6) c
> (5, 6) c
> (5, 6) c
> rows (ordered): 6
SELECT ARRAY_AGG(SUM(ID)) OVER () FROM TEST;
> exception FEATURE_NOT_SUPPORTED_1
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论