提交 aa3c98ba authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Allow any expressions in window frames

上级 f27c631e
......@@ -3294,7 +3294,7 @@ public class Parser {
read(ROW);
return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, null);
}
Expression value = readValueOrParameter();
Expression value = readExpression();
read("PRECEDING");
return new WindowFrameBound(WindowFrameBoundType.PRECEDING, value);
}
......@@ -3311,7 +3311,7 @@ public class Parser {
read(ROW);
return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, null);
}
Expression value = readValueOrParameter();
Expression value = readExpression();
if (readIf("PRECEDING")) {
return new WindowFrameBound(WindowFrameBoundType.PRECEDING, value);
}
......@@ -3319,16 +3319,6 @@ public class Parser {
return new WindowFrameBound(WindowFrameBoundType.FOLLOWING, value);
}
private Expression readValueOrParameter() {
int index = parseIndex;
Expression value = readExpression();
if (!(value instanceof ValueExpression) && !(value instanceof Parameter)) {
parseIndex = index;
throw getSyntaxError();
}
return value;
}
private AggregateType getAggregateType(String name) {
if (!identifiersToUpper) {
// if not yet converted to uppercase, do it now
......
......@@ -98,7 +98,9 @@ public abstract class AbstractAggregate extends DataAnalysisOperation {
aggregateFastPartition(session, result, ordered, rowIdColumn, grouped);
return;
}
if (frame.getExclusion() == WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
if (frame.isVariableBounds()) {
grouped = false;
} else if (frame.getExclusion() == WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
WindowFrameBound following = frame.getFollowing();
boolean unboundedFollowing = following != null
&& following.getType() == WindowFrameBoundType.UNBOUNDED_FOLLOWING;
......@@ -117,10 +119,11 @@ public abstract class AbstractAggregate extends DataAnalysisOperation {
}
// All other types of frames (slow)
int size = ordered.size();
int frameParametersOffset = getWindowFrameParametersOffset();
for (int i = 0; i < size;) {
Object aggregateData = createAggregateData();
for (Iterator<Value[]> iter = WindowFrame.iterator(over, session, ordered, getOverOrderBySort(), i,
false); iter.hasNext();) {
for (Iterator<Value[]> iter = WindowFrame.iterator(over, session, ordered, getOverOrderBySort(),
frameParametersOffset, i, false); iter.hasNext();) {
updateFromExpressions(session, aggregateData, iter.next());
}
Value r = getAggregatedValue(session, aggregateData);
......
......@@ -59,14 +59,19 @@ public abstract class DataAnalysisOperation extends Expression {
*/
protected SortOrder overOrderBySort;
private int numFrameExpressions;
private int lastGroupRowId;
/**
* Create sort order.
*
* @param session database session
* @param orderBy array of order by expressions
* @param offset index offset
* @param session
* database session
* @param orderBy
* array of order by expressions
* @param offset
* index offset
* @return the SortOrder
*/
protected static SortOrder createOrder(Session session, ArrayList<SelectOrderBy> orderBy, int offset) {
......@@ -131,9 +136,12 @@ public abstract class DataAnalysisOperation extends Expression {
/**
* Map the columns of the resolver to expression columns.
*
* @param resolver the column resolver
* @param level the subquery nesting level
* @param innerState one of the Expression MAP_IN_* values
* @param resolver
* the column resolver
* @param level
* the subquery nesting level
* @param innerState
* one of the Expression MAP_IN_* values
*/
protected void mapColumnsAnalysis(ColumnResolver resolver, int level, int innerState) {
if (over != null) {
......@@ -151,6 +159,18 @@ public abstract class DataAnalysisOperation extends Expression {
} else if (!isAggregate()) {
overOrderBySort = new SortOrder(session.getDatabase(), new int[getNumExpressions()], new int[0], null);
}
WindowFrame frame = over.getWindowFrame();
if (frame != null) {
int n = 0;
if (frame.getStarting().isVariable()) {
n++;
}
WindowFrameBound following = frame.getFollowing();
if (following != null && following.isVariable()) {
n++;
}
numFrameExpressions = n;
}
}
return this;
}
......@@ -200,9 +220,12 @@ public abstract class DataAnalysisOperation extends Expression {
/**
* Update a row of an aggregate.
*
* @param session the database session
* @param groupData data for the aggregate group
* @param groupRowId row id of group
* @param session
* the database session
* @param groupData
* data for the aggregate group
* @param groupRowId
* row id of group
*/
protected abstract void updateAggregate(Session session, SelectGroups groupData, int groupRowId);
......@@ -222,12 +245,21 @@ public abstract class DataAnalysisOperation extends Expression {
}
/**
* Returns the number of expressions, excluding FILTER and OVER clauses.
* Returns the number of expressions, excluding OVER clause.
*
* @return the number of expressions
*/
protected abstract int getNumExpressions();
/**
* Returns the number of window frame expressions.
*
* @return the number of window frame expressions
*/
private int getNumFrameExpressions() {
return numFrameExpressions;
}
/**
* Stores current values of expressions into the specified array.
*
......@@ -241,9 +273,12 @@ public abstract class DataAnalysisOperation extends Expression {
/**
* Get the aggregate data for a window clause.
*
* @param session database session
* @param groupData aggregate group data
* @param forOrderBy true if this is for ORDER BY
* @param session
* database session
* @param groupData
* aggregate group data
* @param forOrderBy
* true if this is for ORDER BY
* @return the aggregate data object, specific to each kind of aggregate.
*/
protected Object getWindowData(Session session, SelectGroups groupData, boolean forOrderBy) {
......@@ -261,9 +296,12 @@ public abstract class DataAnalysisOperation extends Expression {
/**
* Get the aggregate group data object from the collector object.
* @param groupData the collector object
* @param ifExists if true, return null if object not found,
* if false, return new object if nothing found
*
* @param groupData
* the collector object
* @param ifExists
* if true, return null if object not found, if false, return new
* object if nothing found
* @return group data object
*/
protected Object getGroupData(SelectGroups groupData, boolean ifExists) {
......@@ -320,6 +358,20 @@ public abstract class DataAnalysisOperation extends Expression {
: getWindowResult(session, groupData);
}
/**
* Returns offset of window frame parameters.
*
* @return offset of window frame parameters
*/
protected final int getWindowFrameParametersOffset() {
int frameParametersOffset = getNumExpressions();
ArrayList<SelectOrderBy> orderBy = over.getOrderBy();
if (orderBy != null) {
frameParametersOffset += orderBy.size();
}
return frameParametersOffset;
}
/**
* Returns result of this window function or window aggregate. This method
* is not used for plain aggregates.
......@@ -374,22 +426,38 @@ public abstract class DataAnalysisOperation extends Expression {
/**
* Update a row of an ordered aggregate.
*
* @param session the database session
* @param groupData data for the aggregate group
* @param groupRowId row id of group
* @param orderBy list of order by expressions
* @param session
* the database session
* @param groupData
* data for the aggregate group
* @param groupRowId
* row id of group
* @param orderBy
* list of order by expressions
*/
protected void updateOrderedAggregate(Session session, SelectGroups groupData, int groupRowId,
ArrayList<SelectOrderBy> orderBy) {
int ne = getNumExpressions();
int size = orderBy != null ? orderBy.size() : 0;
Value[] array = new Value[ne + size + 1];
int frameSize = getNumFrameExpressions();
Value[] array = new Value[ne + size + frameSize + 1];
rememberExpressions(session, array);
for (int i = 0; i < size; i++) {
@SuppressWarnings("null")
SelectOrderBy o = orderBy.get(i);
array[ne++] = o.expression.getValue(session);
}
if (frameSize > 0) {
WindowFrame frame = over.getWindowFrame();
WindowFrameBound bound = frame.getStarting();
if (bound.isVariable()) {
array[ne++] = bound.getValue().getValue(session);
}
bound = frame.getFollowing();
if (bound != null && bound.isVariable()) {
array[ne++] = bound.getValue().getValue(session);
}
}
array[ne] = ValueInt.get(groupRowId);
@SuppressWarnings("unchecked")
ArrayList<Value[]> data = (ArrayList<Value[]>) getWindowData(session, groupData, true);
......@@ -408,6 +476,7 @@ public abstract class DataAnalysisOperation extends Expression {
rowIdColumn += orderBy.size();
Collections.sort(orderedData, overOrderBySort);
}
rowIdColumn += getNumFrameExpressions();
getOrderedResultLoop(session, result, orderedData, rowIdColumn);
partition.setOrderedResult(result);
}
......@@ -433,7 +502,8 @@ public abstract class DataAnalysisOperation extends Expression {
/**
* Used to create SQL for the OVER and FILTER clauses.
*
* @param builder string builder
* @param builder
* string builder
* @return the builder object
*/
protected StringBuilder appendTailConditions(StringBuilder builder) {
......
......@@ -66,7 +66,7 @@ public final class Window {
* @param orderBy
* ORDER BY clause, or null
* @param frame
* window frame clause
* window frame clause, or null
*/
public Window(String parent, ArrayList<Expression> partitionBy, ArrayList<SelectOrderBy> orderBy,
WindowFrame frame) {
......@@ -97,6 +97,9 @@ public final class Window {
o.expression.mapColumns(resolver, level, Expression.MAP_IN_WINDOW);
}
}
if (frame != null) {
frame.mapColumns(resolver, level, Expression.MAP_IN_WINDOW);
}
}
private void resolveWindows(ColumnResolver resolver) {
......@@ -136,6 +139,9 @@ public final class Window {
o.expression = o.expression.optimize(session);
}
}
if (frame != null) {
frame.optimize(session);
}
}
/**
......@@ -253,6 +259,9 @@ public final class Window {
o.expression.updateAggregate(session, stage);
}
}
if (frame != null) {
frame.updateAggregate(session, stage);
}
}
@Override
......
......@@ -5,7 +5,9 @@
*/
package org.h2.expression.analysis;
import org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.table.ColumnResolver;
/**
* Window frame bound.
......@@ -14,7 +16,9 @@ public class WindowFrameBound {
private final WindowFrameBoundType type;
private final Expression value;
private Expression value;
private boolean isVariable;
/**
* Creates new instance of window frame bound.
......@@ -51,6 +55,62 @@ public class WindowFrameBound {
return value;
}
/**
* Returns whether bound is defined with a variable. This method may be used
* only after {@link #optimize(Session)} invocation.
*
* @return whether bound is defined with a variable
*/
public boolean isVariable() {
return isVariable;
}
/**
* Map the columns of the resolver to expression columns.
*
* @param resolver
* the column resolver
* @param level
* the subquery nesting level
* @param state
* current state for nesting checks
*/
void mapColumns(ColumnResolver resolver, int level, int state) {
if (value != null) {
value.mapColumns(resolver, level, state);
}
}
/**
* Try to optimize bound expression.
*
* @param session
* the session
*/
void optimize(Session session) {
if (value != null) {
value = value.optimize(session);
if (!value.isConstant()) {
isVariable = true;
}
}
}
/**
* Update an aggregate value.
*
* @param session
* the session
* @param stage
* select stage
* @see Expression#updateAggregate(Session, int)
*/
void updateAggregate(Session session, int stage) {
if (value != null) {
value.updateAggregate(session, stage);
}
}
/**
* Appends SQL representation to the specified builder.
*
......
......@@ -349,18 +349,19 @@ public class WindowFunction extends DataAnalysisOperation {
private void getNth(Session session, HashMap<Integer, Value> result, ArrayList<Value[]> ordered, int rowIdColumn) {
int size = ordered.size();
int frameParametersOffset = getWindowFrameParametersOffset();
for (int i = 0; i < size; i++) {
Value[] row = ordered.get(i);
int rowId = row[rowIdColumn].getInt();
Value v;
switch (type) {
case FIRST_VALUE:
v = getNthValue(WindowFrame.iterator(over, session, ordered, getOverOrderBySort(), i, false), 0,
ignoreNulls);
v = getNthValue(WindowFrame.iterator(over, session, ordered, getOverOrderBySort(),
frameParametersOffset, i, false), 0, ignoreNulls);
break;
case LAST_VALUE:
v = getNthValue(WindowFrame.iterator(over, session, ordered, getOverOrderBySort(), i, true), 0,
ignoreNulls);
v = getNthValue(WindowFrame.iterator(over, session, ordered, getOverOrderBySort(),
frameParametersOffset, i, true), 0, ignoreNulls);
break;
case NTH_VALUE: {
int n = row[1].getInt();
......@@ -368,8 +369,8 @@ public class WindowFunction extends DataAnalysisOperation {
throw DbException.getInvalidValueException("nth row", n);
}
n--;
Iterator<Value[]> iter = WindowFrame.iterator(over, session, ordered, getOverOrderBySort(), i,
fromLast);
Iterator<Value[]> iter = WindowFrame.iterator(over, session, ordered, getOverOrderBySort(),
frameParametersOffset, i, fromLast);
v = getNthValue(iter, n, ignoreNulls);
break;
}
......
......@@ -596,5 +596,23 @@ SELECT ID, VALUE,
> 8 4 [1, 2, 3, 4, 5, 6, 7, 8] [8] [1, 2, 3, 4, 5, 6, 7, 8] [7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [7, 8]
> rows: 8
SELECT ID, VALUE,
ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND VALUE FOLLOWING) RG,
ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID RANGE BETWEEN VALUE PRECEDING AND UNBOUNDED FOLLOWING) RGR,
ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND VALUE FOLLOWING) R,
ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID ROWS BETWEEN VALUE PRECEDING AND UNBOUNDED FOLLOWING) RR
FROM TEST;
> ID VALUE RG RGR R RR
> -- ----- ------------------------ ------------------------ ------------------------ ------------------------
> 1 1 [1, 2] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2] [1, 2, 3, 4, 5, 6, 7, 8]
> 2 1 [1, 2, 3] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2, 3] [1, 2, 3, 4, 5, 6, 7, 8]
> 3 2 [1, 2, 3, 4, 5] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5] [1, 2, 3, 4, 5, 6, 7, 8]
> 4 2 [1, 2, 3, 4, 5, 6] [2, 3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6] [2, 3, 4, 5, 6, 7, 8]
> 5 3 [1, 2, 3, 4, 5, 6, 7, 8] [2, 3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [2, 3, 4, 5, 6, 7, 8]
> 6 3 [1, 2, 3, 4, 5, 6, 7, 8] [3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [3, 4, 5, 6, 7, 8]
> 7 4 [1, 2, 3, 4, 5, 6, 7, 8] [3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [3, 4, 5, 6, 7, 8]
> 8 4 [1, 2, 3, 4, 5, 6, 7, 8] [4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [4, 5, 6, 7, 8]
> rows: 8
DROP TABLE TEST;
> ok
......@@ -218,6 +218,28 @@ SELECT ID, CATEGORY,
> 13 4 1 1 1 2 3 4
> rows (ordered): 13
SELECT ID, CATEGORY,
FIRST_VALUE(ID) OVER (ORDER BY ID ROWS BETWEEN CATEGORY FOLLOWING AND UNBOUNDED FOLLOWING) F,
LAST_VALUE(ID) OVER (ORDER BY ID ROWS BETWEEN CURRENT ROW AND CATEGORY FOLLOWING) L,
NTH_VALUE(ID, 2) OVER (ORDER BY ID ROWS BETWEEN CATEGORY FOLLOWING AND UNBOUNDED FOLLOWING) N
FROM TEST ORDER BY ID;
> ID CATEGORY F L N
> -- -------- ---- -- ----
> 1 1 2 2 3
> 2 1 3 3 4
> 3 1 4 4 5
> 4 1 5 5 6
> 5 1 6 6 7
> 6 1 7 7 8
> 7 2 9 9 10
> 8 2 10 10 11
> 9 3 12 12 13
> 10 3 13 13 null
> 11 3 null 13 null
> 12 4 null 13 null
> 13 4 null 13 null
> rows (ordered): 13
DROP TABLE TEST;
> ok
......
......@@ -805,4 +805,4 @@ queryparser tokenized freeze factorings recompilation unenclosed rfe dsync
econd irst bcef ordinality nord unnest
analyst occupation distributive josaph aor engineer sajeewa isuru randil kevin doctor businessman artist ashan
corrupts splitted disruption unintentional octets preconditions predicates subq objectweb insn opcodes
preserves masking holder unboxing avert iae transformed subtle reevaluate exclusions subclause ftbl
preserves masking holder unboxing avert iae transformed subtle reevaluate exclusions subclause ftbl rgr
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论