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