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

Add support for both preceding or both following bounds

上级 2170322c
...@@ -2551,7 +2551,7 @@ They also may require a lot of memory for large queries. ...@@ -2551,7 +2551,7 @@ They also may require a lot of memory for large queries.
"Other Grammar","Window frame"," "Other Grammar","Window frame","
ROWS|RANGE|GROUP ROWS|RANGE|GROUP
{windowFramePreceding|BETWEEN windowFramePreceding AND windowFrameFollowing} {windowFramePreceding|BETWEEN windowFrameBound AND windowFrameBound}
[EXCLUDE {CURRENT ROW|GROUP|TIES|NO OTHERS}] [EXCLUDE {CURRENT ROW|GROUP|TIES|NO OTHERS}]
"," ","
A window frame clause. A window frame clause.
...@@ -2571,12 +2571,14 @@ UNBOUNDED PRECEDING ...@@ -2571,12 +2571,14 @@ UNBOUNDED PRECEDING
CURRENT ROW CURRENT ROW
" "
"Other Grammar","Window frame following"," "Other Grammar","Window frame bound","
UNBOUNDED FOLLOWING|value FOLLOWING|CURRENT ROW UNBOUNDED PRECEDING|value PRECEDING|CURRENT ROW
|value FOLLOWING|UNBOUNDED FOLLOWING
"," ","
A window frame following clause. A window frame bound clause.
If value is specified it should be non-negative value or parameter. If value is specified it should be non-negative value or parameter.
"," ","
UNBOUNDED PRECEDING
UNBOUNDED FOLLOWING UNBOUNDED FOLLOWING
1 FOLLOWING 1 FOLLOWING
CURRENT ROW CURRENT ROW
......
...@@ -177,7 +177,7 @@ import org.h2.expression.aggregate.JavaAggregate; ...@@ -177,7 +177,7 @@ import org.h2.expression.aggregate.JavaAggregate;
import org.h2.expression.aggregate.Window; import org.h2.expression.aggregate.Window;
import org.h2.expression.aggregate.WindowFrame; import org.h2.expression.aggregate.WindowFrame;
import org.h2.expression.aggregate.WindowFrameBound; import org.h2.expression.aggregate.WindowFrameBound;
import org.h2.expression.aggregate.WindowFrameBound.WindowFrameBoundType; import org.h2.expression.aggregate.WindowFrameBoundType;
import org.h2.expression.aggregate.WindowFrameExclusion; import org.h2.expression.aggregate.WindowFrameExclusion;
import org.h2.expression.aggregate.WindowFrameUnits; import org.h2.expression.aggregate.WindowFrameUnits;
import org.h2.expression.aggregate.WindowFunction; import org.h2.expression.aggregate.WindowFunction;
...@@ -3105,9 +3105,9 @@ public class Parser { ...@@ -3105,9 +3105,9 @@ public class Parser {
} }
WindowFrameBound starting, following; WindowFrameBound starting, following;
if (readIf("BETWEEN")) { if (readIf("BETWEEN")) {
starting = readWindowFrameStarting(); starting = readWindowFrameRange();
read("AND"); read("AND");
following = readWindowFrameFollowing(); following = readWindowFrameRange();
} else { } else {
starting = readWindowFrameStarting(); starting = readWindowFrameStarting();
following = null; following = null;
...@@ -3132,7 +3132,7 @@ public class Parser { ...@@ -3132,7 +3132,7 @@ public class Parser {
private WindowFrameBound readWindowFrameStarting() { private WindowFrameBound readWindowFrameStarting() {
if (readIf("UNBOUNDED")) { if (readIf("UNBOUNDED")) {
read("PRECEDING"); read("PRECEDING");
return new WindowFrameBound(WindowFrameBoundType.UNBOUNDED, null); return new WindowFrameBound(WindowFrameBoundType.UNBOUNDED_PRECEDING, null);
} }
if (readIf("CURRENT")) { if (readIf("CURRENT")) {
read("ROW"); read("ROW");
...@@ -3140,21 +3140,27 @@ public class Parser { ...@@ -3140,21 +3140,27 @@ public class Parser {
} }
Expression value = readValueOrParameter(); Expression value = readValueOrParameter();
read("PRECEDING"); read("PRECEDING");
return new WindowFrameBound(WindowFrameBoundType.VALUE, value); return new WindowFrameBound(WindowFrameBoundType.PRECEDING, value);
} }
private WindowFrameBound readWindowFrameFollowing() { private WindowFrameBound readWindowFrameRange() {
if (readIf("UNBOUNDED")) { if (readIf("UNBOUNDED")) {
if (readIf("PRECEDING")) {
return new WindowFrameBound(WindowFrameBoundType.UNBOUNDED_PRECEDING, null);
}
read("FOLLOWING"); read("FOLLOWING");
return new WindowFrameBound(WindowFrameBoundType.UNBOUNDED, null); return new WindowFrameBound(WindowFrameBoundType.UNBOUNDED_FOLLOWING, null);
} }
if (readIf("CURRENT")) { if (readIf("CURRENT")) {
read("ROW"); read("ROW");
return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, null); return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, null);
} }
Expression value = readValueOrParameter(); Expression value = readValueOrParameter();
if (readIf("PRECEDING")) {
return new WindowFrameBound(WindowFrameBoundType.PRECEDING, value);
}
read("FOLLOWING"); read("FOLLOWING");
return new WindowFrameBound(WindowFrameBoundType.VALUE, value); return new WindowFrameBound(WindowFrameBoundType.FOLLOWING, value);
} }
private Expression readValueOrParameter() { private Expression readValueOrParameter() {
......
...@@ -10,7 +10,6 @@ import java.util.ArrayList; ...@@ -10,7 +10,6 @@ import java.util.ArrayList;
import org.h2.command.dml.SelectOrderBy; import org.h2.command.dml.SelectOrderBy;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.expression.Expression; import org.h2.expression.Expression;
import org.h2.expression.aggregate.WindowFrameBound.WindowFrameBoundType;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
import org.h2.table.ColumnResolver; import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
...@@ -66,8 +65,9 @@ public final class Window { ...@@ -66,8 +65,9 @@ public final class Window {
this.partitionBy = partitionBy; this.partitionBy = partitionBy;
this.orderBy = orderBy; this.orderBy = orderBy;
if (frame == null) { if (frame == null) {
frame = new WindowFrame(WindowFrameUnits.RANGE, new WindowFrameBound(WindowFrameBoundType.UNBOUNDED, null), frame = new WindowFrame(WindowFrameUnits.RANGE,
null, WindowFrameExclusion.EXCLUDE_NO_OTHERS); new WindowFrameBound(WindowFrameBoundType.UNBOUNDED_PRECEDING, null), null,
WindowFrameExclusion.EXCLUDE_NO_OTHERS);
} }
this.frame = frame; this.frame = frame;
} }
......
...@@ -11,11 +11,11 @@ import java.util.Collections; ...@@ -11,11 +11,11 @@ import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import org.h2.api.ErrorCode;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.expression.BinaryOperation; import org.h2.expression.BinaryOperation;
import org.h2.expression.BinaryOperation.OpType; import org.h2.expression.BinaryOperation.OpType;
import org.h2.expression.ValueExpression; import org.h2.expression.ValueExpression;
import org.h2.expression.aggregate.WindowFrameBound.WindowFrameBoundType;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
import org.h2.value.Value; import org.h2.value.Value;
...@@ -235,7 +235,7 @@ public final class WindowFrame { ...@@ -235,7 +235,7 @@ public final class WindowFrame {
* @return whether window frame specification can be omitted * @return whether window frame specification can be omitted
*/ */
public boolean isDefault() { public boolean isDefault() {
return starting.getType() == WindowFrameBoundType.UNBOUNDED && following == null return starting.getType() == WindowFrameBoundType.UNBOUNDED_PRECEDING && following == null
&& exclusion == WindowFrameExclusion.EXCLUDE_NO_OTHERS; && exclusion == WindowFrameExclusion.EXCLUDE_NO_OTHERS;
} }
...@@ -246,8 +246,8 @@ public final class WindowFrame { ...@@ -246,8 +246,8 @@ public final class WindowFrame {
* @return whether window frame specification contains all rows in partition * @return whether window frame specification contains all rows in partition
*/ */
public boolean isFullPartition() { public boolean isFullPartition() {
return starting.getType() == WindowFrameBoundType.UNBOUNDED && following != null return starting.getType() == WindowFrameBoundType.UNBOUNDED_PRECEDING && following != null
&& following.getType() == WindowFrameBoundType.UNBOUNDED && following.getType() == WindowFrameBoundType.UNBOUNDED_FOLLOWING
&& exclusion == WindowFrameExclusion.EXCLUDE_NO_OTHERS; && exclusion == WindowFrameExclusion.EXCLUDE_NO_OTHERS;
} }
...@@ -269,46 +269,69 @@ public final class WindowFrame { ...@@ -269,46 +269,69 @@ public final class WindowFrame {
*/ */
public Iterator<Value[]> iterator(Session session, ArrayList<Value[]> orderedRows, SortOrder sortOrder, public Iterator<Value[]> iterator(Session session, ArrayList<Value[]> orderedRows, SortOrder sortOrder,
int currentRow, boolean reverse) { int currentRow, boolean reverse) {
int startIndex = getIndex(session, orderedRows, sortOrder, currentRow, starting);
int endIndex = following != null ? getIndex(session, orderedRows, sortOrder, currentRow, following)
: currentRow;
if (endIndex < startIndex) {
throw DbException.get(ErrorCode.SYNTAX_ERROR_1, getSQL());
}
int size = orderedRows.size(); int size = orderedRows.size();
WindowFrameBound bound = starting; if (startIndex >= size || endIndex < 0) {
int startIndex; return Collections.emptyIterator();
switch (bound.getType()) { }
case UNBOUNDED: if (startIndex < 0) {
startIndex = 0; startIndex = 0;
}
if (endIndex >= size) {
endIndex = size - 1;
}
if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
return complexIterator(orderedRows, sortOrder, currentRow, startIndex, endIndex, reverse);
}
return reverse ? new PlainReverseItr(orderedRows, startIndex, endIndex)
: new PlainItr(orderedRows, startIndex, endIndex);
}
private int getIndex(Session session, ArrayList<Value[]> orderedRows, SortOrder sortOrder, int currentRow,
WindowFrameBound bound) {
int size = orderedRows.size();
int last = size - 1;
int index;
switch (bound.getType()) {
case UNBOUNDED_PRECEDING:
index = 0;
break; break;
case CURRENT_ROW: case PRECEDING:
startIndex = currentRow;
break;
case VALUE:
switch (units) { switch (units) {
case ROWS: { case ROWS: {
int value = getIntOffset(bound, session); int value = getIntOffset(bound, session);
startIndex = value > currentRow ? 0 : currentRow - value; index = value > currentRow ? -1 : currentRow - value;
break; break;
} }
case GROUPS: { case GROUPS: {
int value = getIntOffset(bound, session); int value = getIntOffset(bound, session);
startIndex = toGroupStart(orderedRows, sortOrder, currentRow, 0); index = toGroupStart(orderedRows, sortOrder, currentRow, 0);
while (value > 0 && startIndex > 0) { while (value > 0 && index > 0) {
value--; value--;
startIndex = toGroupStart(orderedRows, sortOrder, startIndex - 1, 0); index = toGroupStart(orderedRows, sortOrder, index - 1, 0);
}
if (value > 0) {
index = -1;
} }
break; break;
} }
case RANGE: { case RANGE: {
startIndex = currentRow; index = currentRow;
int index = sortOrder.getQueryColumnIndexes()[0]; int sortIndex = sortOrder.getQueryColumnIndexes()[0];
if ((sortOrder.getSortTypes()[0] & SortOrder.DESCENDING) != 0) { if ((sortOrder.getSortTypes()[0] & SortOrder.DESCENDING) != 0) {
Value c = plus(session, orderedRows, currentRow, bound, index); Value c = plus(session, orderedRows, currentRow, bound, sortIndex);
while (startIndex > 0 while (index > 0 && session.getDatabase().compare(c, orderedRows.get(index - 1)[sortIndex]) >= 0) {
&& session.getDatabase().compare(c, orderedRows.get(startIndex - 1)[index]) >= 0) { index--;
startIndex--;
} }
} else { } else {
Value c = minus(session, orderedRows, currentRow, bound, index); Value c = minus(session, orderedRows, currentRow, bound, sortIndex);
while (startIndex > 0 while (index > 0 && session.getDatabase().compare(c, orderedRows.get(index - 1)[sortIndex]) <= 0) {
&& session.getDatabase().compare(c, orderedRows.get(startIndex - 1)[index]) <= 0) { index--;
startIndex--;
} }
} }
break; break;
...@@ -317,70 +340,58 @@ public final class WindowFrame { ...@@ -317,70 +340,58 @@ public final class WindowFrame {
throw DbException.getUnsupportedException("units=" + units); throw DbException.getUnsupportedException("units=" + units);
} }
break; break;
default: case CURRENT_ROW:
throw DbException.getUnsupportedException("window frame bound type=" + bound.getType()); index = currentRow;
} break;
bound = following; case FOLLOWING:
int endIndex; switch (units) {
if (bound == null) { case ROWS: {
endIndex = currentRow; int value = getIntOffset(bound, session);
} else { int rem = last - currentRow;
int last = size - 1; index = value > rem ? size : currentRow + value;
switch (bound.getType()) {
case UNBOUNDED:
endIndex = last;
break;
case CURRENT_ROW:
endIndex = currentRow;
break; break;
case VALUE: }
switch (units) { case GROUPS: {
case ROWS: { int value = getIntOffset(bound, session);
int value = getIntOffset(bound, session); index = toGroupEnd(orderedRows, sortOrder, currentRow, last);
int rem = last - currentRow; while (value > 0 && index < last) {
endIndex = value > rem ? last : currentRow + value; value--;
break; index = toGroupEnd(orderedRows, sortOrder, index + 1, last);
} }
case GROUPS: { if (value > 0) {
int value = getIntOffset(bound, session); index = size;
endIndex = toGroupEnd(orderedRows, sortOrder, currentRow, last);
while (value > 0 && endIndex < last) {
value--;
endIndex = toGroupEnd(orderedRows, sortOrder, endIndex + 1, last);
}
break;
} }
case RANGE: { break;
endIndex = currentRow; }
int index = sortOrder.getQueryColumnIndexes()[0]; case RANGE: {
if ((sortOrder.getSortTypes()[0] & SortOrder.DESCENDING) != 0) { index = currentRow;
Value c = minus(session, orderedRows, currentRow, bound, index); int sortIndex = sortOrder.getQueryColumnIndexes()[0];
while (endIndex < last if ((sortOrder.getSortTypes()[0] & SortOrder.DESCENDING) != 0) {
&& session.getDatabase().compare(c, orderedRows.get(endIndex + 1)[index]) <= 0) { Value c = minus(session, orderedRows, currentRow, bound, sortIndex);
endIndex++; while (index < last
} && session.getDatabase().compare(c, orderedRows.get(index + 1)[sortIndex]) <= 0) {
} else { index++;
Value c = plus(session, orderedRows, currentRow, bound, index); }
while (endIndex < last } else {
&& session.getDatabase().compare(c, orderedRows.get(endIndex + 1)[index]) >= 0) { Value c = plus(session, orderedRows, currentRow, bound, sortIndex);
endIndex++; while (index < last
} && session.getDatabase().compare(c, orderedRows.get(index + 1)[sortIndex]) >= 0) {
index++;
} }
break;
}
default:
throw DbException.getUnsupportedException("units=" + units);
} }
break; break;
}
default: default:
throw DbException.getUnsupportedException("window frame bound type=" + bound.getType()); throw DbException.getUnsupportedException("units=" + units);
} }
break;
case UNBOUNDED_FOLLOWING:
index = last;
break;
default:
throw DbException.getUnsupportedException("window frame bound type=" + bound.getType());
} }
if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) { return index;
return complexIterator(orderedRows, sortOrder, currentRow, startIndex, endIndex, reverse);
}
return reverse ? new PlainReverseItr(orderedRows, startIndex, endIndex)
: new PlainItr(orderedRows, startIndex, endIndex);
} }
private Iterator<Value[]> complexIterator(ArrayList<Value[]> orderedRows, SortOrder sortOrder, int currentRow, private Iterator<Value[]> complexIterator(ArrayList<Value[]> orderedRows, SortOrder sortOrder, int currentRow,
......
...@@ -6,35 +6,12 @@ ...@@ -6,35 +6,12 @@
package org.h2.expression.aggregate; package org.h2.expression.aggregate;
import org.h2.expression.Expression; import org.h2.expression.Expression;
import org.h2.message.DbException;
/** /**
* Window frame bound. * Window frame bound.
*/ */
public class WindowFrameBound { public class WindowFrameBound {
/**
* Window frame bound type.
*/
public enum WindowFrameBoundType {
/**
* UNBOUNDED PRECEDING or UNBOUNDED FOLLOWING clause.
*/
UNBOUNDED,
/**
* CURRENT_ROW clause.
*/
CURRENT_ROW,
/**
* PRECEDING or FOLLOWING clause.
*/
VALUE;
}
private final WindowFrameBoundType type; private final WindowFrameBoundType type;
private final Expression value; private final Expression value;
...@@ -49,7 +26,7 @@ public class WindowFrameBound { ...@@ -49,7 +26,7 @@ public class WindowFrameBound {
*/ */
public WindowFrameBound(WindowFrameBoundType type, Expression value) { public WindowFrameBound(WindowFrameBoundType type, Expression value) {
this.type = type; this.type = type;
if (type == WindowFrameBoundType.VALUE) { if (type == WindowFrameBoundType.PRECEDING || type == WindowFrameBoundType.FOLLOWING) {
this.value = value; this.value = value;
} else { } else {
this.value = null; this.value = null;
...@@ -84,16 +61,10 @@ public class WindowFrameBound { ...@@ -84,16 +61,10 @@ public class WindowFrameBound {
* @see Expression#getSQL() * @see Expression#getSQL()
*/ */
public String getSQL(boolean following) { public String getSQL(boolean following) {
switch (type) { if (type == WindowFrameBoundType.PRECEDING || type == WindowFrameBoundType.FOLLOWING) {
case UNBOUNDED: return value.getSQL() + ' ' + type.getSQL();
return following ? "UNBOUNDED FOLLOWING" : "UNBOUNDED PRECEDING";
case CURRENT_ROW:
return "CURRENT ROW";
case VALUE:
return value.getSQL() + (following ? " FOLLOWING" : " PRECEDING");
default:
throw DbException.throwInternalError("type=" + type);
} }
return type.getSQL();
} }
} }
/*
* Copyright 2004-2018 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.expression.aggregate;
/**
* Window frame bound type.
*/
public enum WindowFrameBoundType {
/**
* UNBOUNDED PRECEDING clause.
*/
UNBOUNDED_PRECEDING("UNBOUNDED PRECEDING"),
/**
* PRECEDING clause.
*/
PRECEDING("PRECEDING"),
/**
* CURRENT_ROW clause.
*/
CURRENT_ROW("CURRENT_ROW"),
/**
* FOLLOWING clause.
*/
FOLLOWING("FOLLOWING"),
/**
* UNBOUNDED FOLLOWING clause.
*/
UNBOUNDED_FOLLOWING("UNBOUNDED FOLLOWING");
private final String sql;
private WindowFrameBoundType(String sql) {
this.sql = sql;
}
/**
* Returns SQL representation.
*
* @return SQL representation.
* @see org.h2.expression.Expression#getSQL()
*/
public String getSQL() {
return sql;
}
}
...@@ -314,5 +314,17 @@ SELECT *, ...@@ -314,5 +314,17 @@ SELECT *,
SELECT *, ARRAY_AGG(ID) OVER (ORDER BY VALUE ROWS -1 PRECEDING) FROM TEST; SELECT *, ARRAY_AGG(ID) OVER (ORDER BY VALUE ROWS -1 PRECEDING) FROM TEST;
> exception INVALID_VALUE_2 > exception INVALID_VALUE_2
SELECT *, ARRAY_AGG(ID) OVER (ORDER BY ID ROWS BETWEEN 2 PRECEDING AND 1 PRECEDING) FROM TEST FETCH FIRST 4 ROWS ONLY;
> ID VALUE ARRAY_AGG(ID) OVER (ORDER BY ID ROWS BETWEEN 2 PRECEDING AND 1 PRECEDING)
> -- ----- -------------------------------------------------------------------------
> 1 1 null
> 2 1 (1)
> 3 5 (1, 2)
> 4 8 (2, 3)
> rows (ordered): 4
SELECT *, ARRAY_AGG(ID) OVER (ORDER BY ID RANGE BETWEEN CURRENT ROW AND 1 PRECEDING) FROM TEST;
> exception SYNTAX_ERROR_1
DROP TABLE TEST; DROP TABLE TEST;
> ok > ok
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论