Unverified 提交 a092ef8b authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov 提交者: GitHub

Merge pull request #1664 from katzyn/window

Allow any expressions in window frames
......@@ -2709,7 +2709,7 @@ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE TIES
UNBOUNDED PRECEDING|value PRECEDING|CURRENT ROW
","
A window frame preceding clause.
If value is specified it should be non-negative value or parameter.
If value is specified it should not be negative.
","
UNBOUNDED PRECEDING
1 PRECEDING
......@@ -2721,7 +2721,7 @@ UNBOUNDED PRECEDING|value PRECEDING|CURRENT ROW
|value FOLLOWING|UNBOUNDED FOLLOWING
","
A window frame bound clause.
If value is specified it should be non-negative value or parameter.
If value is specified it should not be negative.
","
UNBOUNDED PRECEDING
UNBOUNDED FOLLOWING
......
......@@ -21,6 +21,8 @@ Change Log
<h2>Next Version (unreleased)</h2>
<ul>
<li>PR #1664: Allow any expressions in window frames
</li>
<li>Issue #1576: H2 Console should not display precision and scale for data types that don't have them
</li>
<li>PR #1662: Fix Alter Table Drop Column In View when table name is wrapped by Double Quotes
......
......@@ -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,13 @@ public abstract class AbstractAggregate extends DataAnalysisOperation {
aggregateFastPartition(session, result, ordered, rowIdColumn, grouped);
return;
}
if (frame.getExclusion() == WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
boolean variableBounds = frame.isVariableBounds();
if (variableBounds) {
variableBounds = checkVariableBounds(frame, ordered);
}
if (variableBounds) {
grouped = false;
} else if (frame.getExclusion() == WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
WindowFrameBound following = frame.getFollowing();
boolean unboundedFollowing = following != null
&& following.getType() == WindowFrameBoundType.UNBOUNDED_FOLLOWING;
......@@ -128,6 +134,31 @@ public abstract class AbstractAggregate extends DataAnalysisOperation {
}
}
private static boolean checkVariableBounds(WindowFrame frame, ArrayList<Value[]> ordered) {
int size = ordered.size();
WindowFrameBound bound = frame.getStarting();
if (bound.isVariable()) {
int offset = bound.getExpressionIndex();
Value v = ordered.get(0)[offset];
for (int i = 1; i < size; i++) {
if (!v.equals(ordered.get(i)[offset])) {
return true;
}
}
}
bound = frame.getFollowing();
if (bound != null && bound.isVariable()) {
int offset = bound.getExpressionIndex();
Value v = ordered.get(0)[offset];
for (int i = 1; i < size; i++) {
if (!v.equals(ordered.get(i)[offset])) {
return true;
}
}
}
return false;
}
private void aggregateFastPartition(Session session, HashMap<Integer, Value> result, ArrayList<Value[]> ordered,
int rowIdColumn, boolean grouped) {
Object aggregateData = createAggregateData();
......
......@@ -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,25 @@ 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 index = getNumExpressions();
if (orderBy != null) {
index += orderBy.size();
}
int n = 0;
WindowFrameBound bound = frame.getStarting();
if (bound.isVariable()) {
bound.setExpressionIndex(index);
n++;
}
bound = frame.getFollowing();
if (bound != null && bound.isVariable()) {
bound.setExpressionIndex(index + n);
n++;
}
numFrameExpressions = n;
}
}
return this;
}
......@@ -200,9 +227,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 +252,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 +280,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 +303,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) {
......@@ -374,22 +419,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 +469,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 +495,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
......
......@@ -12,10 +12,12 @@ import java.util.NoSuchElementException;
import org.h2.engine.Session;
import org.h2.expression.BinaryOperation;
import org.h2.expression.Expression;
import org.h2.expression.BinaryOperation.OpType;
import org.h2.expression.ValueExpression;
import org.h2.message.DbException;
import org.h2.result.SortOrder;
import org.h2.table.ColumnResolver;
import org.h2.value.Value;
/**
......@@ -204,7 +206,6 @@ public final class WindowFrame {
* index of the current row
* @param reverse
* whether iterator should iterate in reverse order
*
* @return iterator
*/
public static Iterator<Value[]> iterator(Window over, Session session, ArrayList<Value[]> orderedRows,
......@@ -286,8 +287,9 @@ public final class WindowFrame {
return offset;
}
private static int getIntOffset(WindowFrameBound bound, Session session) {
int value = bound.getValue().getValue(session).getInt();
private static int getIntOffset(WindowFrameBound bound, Value[] values, Session session) {
Value v = bound.isVariable() ? values[bound.getExpressionIndex()] : bound.getValue().getValue(session);
int value = v.getInt();
if (value < 0) {
throw DbException.getInvalidValueException("unsigned", value);
}
......@@ -301,13 +303,14 @@ public final class WindowFrame {
Value[] row = orderedRows.get(currentRow);
Value[] newRow = row.clone();
newRow[sortIndex] = new BinaryOperation(opType, //
ValueExpression.get(row[sortIndex]), ValueExpression.get(getValueOffset(bound, session))) //
ValueExpression.get(row[sortIndex]),
ValueExpression.get(getValueOffset(bound, orderedRows.get(currentRow), session))) //
.optimize(session).getValue(session);
return newRow;
}
private static Value getValueOffset(WindowFrameBound bound, Session session) {
Value value = bound.getValue().getValue(session);
private static Value getValueOffset(WindowFrameBound bound, Value[] values, Session session) {
Value value = bound.isVariable() ? values[bound.getExpressionIndex()] : bound.getValue().getValue(session);
if (value.getSignum() < 0) {
throw DbException.getInvalidValueException("unsigned", value.getTraceSQL());
}
......@@ -385,6 +388,68 @@ public final class WindowFrame {
&& s.compareTo(f) <= 0;
}
/**
* Check if bounds of this frame has variable expressions. This method may
* be used only after {@link #optimize(Session)} invocation.
*
* @return if bounds of this frame has variable expressions
*/
public boolean isVariableBounds() {
if (starting.isVariable()) {
return true;
}
if (following != null && following.isVariable()) {
return true;
}
return false;
}
/**
* 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) {
starting.mapColumns(resolver, level, state);
if (following != null) {
following.mapColumns(resolver, level, state);
}
}
/**
* Try to optimize bound expressions.
*
* @param session
* the session
*/
void optimize(Session session) {
starting.optimize(session);
if (following != null) {
following.optimize(session);
}
}
/**
* Update an aggregate value.
*
* @param session
* the session
* @param stage
* select stage
* @see Expression#updateAggregate(Session, int)
*/
void updateAggregate(Session session, int stage) {
starting.updateAggregate(session, stage);
if (following != null) {
following.updateAggregate(session, stage);
}
}
/**
* Returns iterator.
*
......@@ -491,12 +556,12 @@ public final class WindowFrame {
case PRECEDING:
switch (units) {
case ROWS: {
int value = getIntOffset(bound, session);
int value = getIntOffset(bound, orderedRows.get(currentRow), session);
index = value > currentRow ? -1 : currentRow - value;
break;
}
case GROUPS: {
int value = getIntOffset(bound, session);
int value = getIntOffset(bound, orderedRows.get(currentRow), session);
if (!forFollowing) {
index = toGroupStart(orderedRows, sortOrder, currentRow, 0);
while (value > 0 && index > 0) {
......@@ -566,13 +631,13 @@ public final class WindowFrame {
case FOLLOWING:
switch (units) {
case ROWS: {
int value = getIntOffset(bound, session);
int value = getIntOffset(bound, orderedRows.get(currentRow), session);
int rem = last - currentRow;
index = value > rem ? size : currentRow + value;
break;
}
case GROUPS: {
int value = getIntOffset(bound, session);
int value = getIntOffset(bound, orderedRows.get(currentRow), session);
if (forFollowing) {
index = toGroupEnd(orderedRows, sortOrder, currentRow, last);
while (value > 0 && index < last) {
......
......@@ -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,11 @@ public class WindowFrameBound {
private final WindowFrameBoundType type;
private final Expression value;
private Expression value;
private boolean isVariable;
private int expressionIndex = -1;
/**
* Creates new instance of window frame bound.
......@@ -51,6 +57,81 @@ 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;
}
/**
* Returns the index of preserved expression.
*
* @return the index of preserved expression, or -1
*/
public int getExpressionIndex() {
return expressionIndex;
}
/**
* Sets the index of preserved expression.
*
* @param expressionIndex
* the index to set
*/
void setExpressionIndex(int expressionIndex) {
this.expressionIndex = expressionIndex;
}
/**
* 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.
*
......
......@@ -596,5 +596,43 @@ 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
SELECT ID, VALUE,
ARRAY_AGG(ID ORDER BY ID) OVER
(PARTITION BY VALUE ORDER BY ID ROWS BETWEEN VALUE / 3 PRECEDING AND VALUE / 3 FOLLOWING) A,
ARRAY_AGG(ID ORDER BY ID) OVER
(PARTITION BY VALUE ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND VALUE / 3 FOLLOWING) AP,
ARRAY_AGG(ID ORDER BY ID) OVER
(PARTITION BY VALUE ORDER BY ID ROWS BETWEEN VALUE / 3 PRECEDING AND UNBOUNDED FOLLOWING) AF
FROM TEST;
> ID VALUE A AP AF
> -- ----- ------ ------ ------
> 1 1 [1] [1] [1, 2]
> 2 1 [2] [1, 2] [2]
> 3 2 [3] [3] [3, 4]
> 4 2 [4] [3, 4] [4]
> 5 3 [5, 6] [5, 6] [5, 6]
> 6 3 [5, 6] [5, 6] [5, 6]
> 7 4 [7, 8] [7, 8] [7, 8]
> 8 4 [7, 8] [7, 8] [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
......@@ -230,3 +252,13 @@ SELECT I, X, LAST_VALUE(I) OVER (ORDER BY X) L FROM VALUES (1, 1), (2, 1), (3, 2
> 4 2 4
> 5 3 5
> rows: 5
SELECT A, MAX(B) M, FIRST_VALUE(A) OVER (ORDER BY A ROWS BETWEEN MAX(B) - 1 FOLLOWING AND UNBOUNDED FOLLOWING) F
FROM VALUES (1, 1), (1, 1), (2, 1), (2, 2), (3, 1) V(A, B)
GROUP BY A;
> A M F
> - - -
> 1 1 1
> 2 2 3
> 3 1 3
> rows: 3
......@@ -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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论