提交 8863b2db authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Add WINDOW clause support

上级 3b4620a2
......@@ -488,7 +488,7 @@ ALL, CHECK, CONSTRAINT, CROSS, CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP, DI
EXISTS, FALSE, FETCH, FOR, FOREIGN, FROM, FULL, GROUP, HAVING, INNER, INTERSECT, INTERSECTS,
IS, JOIN, LIKE, LIMIT, LOCALTIME, LOCALTIMESTAMP, MINUS, NATURAL, NOT, NULL, OFFSET, ON, ORDER,
PRIMARY, ROWNUM, SELECT, SYSDATE, SYSTIME, SYSTIMESTAMP, TODAY, TOP, TRUE, UNION, UNIQUE, WHERE,
WITH
WINDOW, WITH
</code>
</p><p>
Certain words of this list are keywords because they are functions that can be used without '()' for compatibility,
......
......@@ -166,7 +166,7 @@
90133=Cannot change the setting {0} when the database is already open
90134=Access to the class {0} is denied
90135=The database is open in exclusive mode; can not open additional connections
90136=Unsupported outer join condition: {0}
90136=Window not found: {0}
90137=Can only assign to a variable, not to: {0}
90138=Invalid database name: {0}
90139=The public static Java method was not found: {0}
......
......@@ -1926,6 +1926,16 @@ public class ErrorCode {
*/
public static final int DATABASE_IS_IN_EXCLUSIVE_MODE = 90135;
/**
* The error with code <code>90136</code> is thrown when
* trying to reference a window that does not exist.
* Example:
* <pre>
* SELECT LEAD(X) OVER W FROM TEST;
* </pre>
*/
public static final int WINDOW_NOT_FOUND_1 = 90136;
/**
* The error with code <code>90137</code> is thrown when
* trying to assign a value to something that is not a variable.
......@@ -2009,7 +2019,7 @@ public class ErrorCode {
public static final int AUTHENTICATOR_NOT_AVAILABLE = 90144;
// next are 90136, 90145
// next is 90145
private ErrorCode() {
// utility class
......
......@@ -44,6 +44,7 @@ import static org.h2.util.ParserUtil.TRUE;
import static org.h2.util.ParserUtil.UNION;
import static org.h2.util.ParserUtil.UNIQUE;
import static org.h2.util.ParserUtil.WHERE;
import static org.h2.util.ParserUtil.WINDOW;
import static org.h2.util.ParserUtil.WITH;
import java.math.BigDecimal;
......@@ -482,6 +483,8 @@ public class Parser {
"UNIQUE",
// WHERE
"WHERE",
// WINDOW
"WINDOW",
// WITH
"WITH",
// PARAMETER
......@@ -2628,6 +2631,17 @@ public class Parser {
Expression condition = readExpression();
command.setHaving(condition);
}
if (readIf(WINDOW)) {
do {
int index = parseIndex;
String name = readAliasIdentifier();
read("AS");
Window w = readWindowSpecification();
if (!currentSelect.addWindow(name, w)) {
throw DbException.getSyntaxError(sqlCommand, index, "unique identifier");
}
} while (readIf(COMMA));
}
command.setParameterList(parameters);
currentSelect = oldSelect;
setSQL(command, "SELECT", start);
......@@ -3049,7 +3063,7 @@ public class Parser {
}
Window over = null;
if (readIf("OVER")) {
over = readWindowSpecification();
over = readWindowNameOrSpecification();
aggregate.setOverCondition(over);
currentSelect.setWindowQuery();
} else if (!isAggregate) {
......@@ -3059,8 +3073,24 @@ public class Parser {
}
}
private Window readWindowNameOrSpecification() {
return isToken(OPEN_PAREN) ? readWindowSpecification() : new Window(readAliasIdentifier(), null, null, null);
}
private Window readWindowSpecification() {
read(OPEN_PAREN);
String parent = null;
if (currentTokenType == IDENTIFIER) {
String token = currentToken;
if (currentTokenQuoted || ( //
!equalsToken(token, "PARTITION") //
&& !equalsToken(token, "ROWS") //
&& !equalsToken(token, "RANGE") //
&& !equalsToken(token, "GROUPS"))) {
parent = token;
read();
}
}
ArrayList<Expression> partitionBy = null;
if (readIf("PARTITION")) {
read("BY");
......@@ -3077,7 +3107,7 @@ public class Parser {
}
WindowFrame frame = readWindowFrame();
read(CLOSE_PAREN);
return new Window(partitionBy, orderBy, frame);
return new Window(parent, partitionBy, orderBy, frame);
}
private WindowFrame readWindowFrame() {
......
......@@ -8,6 +8,7 @@ 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;
......@@ -25,6 +26,7 @@ import org.h2.expression.ExpressionVisitor;
import org.h2.expression.Parameter;
import org.h2.expression.Wildcard;
import org.h2.expression.aggregate.Aggregate;
import org.h2.expression.aggregate.Window;
import org.h2.index.Cursor;
import org.h2.index.Index;
import org.h2.index.IndexType;
......@@ -116,6 +118,8 @@ public class Select extends Query {
private boolean isGroupWindowStage2;
private HashMap<String, Window> windows;
public Select(Session session) {
super(session);
}
......@@ -214,6 +218,30 @@ public class Select extends Query {
return distinct || distinctExpressions != null;
}
/**
* Adds a named window definition.
*
* @param name name
* @param window window definition
* @return true if a new definition was added, false if old definition was replaced
*/
public boolean addWindow(String name, Window window) {
if (windows == null) {
windows = new HashMap<>();
}
return windows.put(name, window) == null;
}
/**
* Returns a window with specified name, or null.
*
* @param name name of the window
* @return the window with specified name, or null
*/
public Window getWindow(String name) {
return windows != null ? windows.get(name) : null;
}
/**
* Add a condition to the list of conditions.
*
......
......@@ -7,9 +7,11 @@ package org.h2.expression.aggregate;
import java.util.ArrayList;
import org.h2.api.ErrorCode;
import org.h2.command.dml.SelectOrderBy;
import org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.message.DbException;
import org.h2.result.SortOrder;
import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter;
......@@ -22,11 +24,13 @@ import org.h2.value.ValueArray;
*/
public final class Window {
private final ArrayList<Expression> partitionBy;
private ArrayList<Expression> partitionBy;
private final ArrayList<SelectOrderBy> orderBy;
private ArrayList<SelectOrderBy> orderBy;
private final WindowFrame frame;
private WindowFrame frame;
private String parent;
/**
* @param builder
......@@ -54,6 +58,8 @@ public final class Window {
/**
* Creates a new instance of window clause.
*
* @param parent
* name of the parent window
* @param partitionBy
* PARTITION BY clause, or null
* @param orderBy
......@@ -61,7 +67,9 @@ public final class Window {
* @param frame
* window frame clause
*/
public Window(ArrayList<Expression> partitionBy, ArrayList<SelectOrderBy> orderBy, WindowFrame frame) {
public Window(String parent, ArrayList<Expression> partitionBy, ArrayList<SelectOrderBy> orderBy,
WindowFrame frame) {
this.parent = parent;
this.partitionBy = partitionBy;
this.orderBy = orderBy;
this.frame = frame;
......@@ -77,6 +85,7 @@ public final class Window {
* @see Expression#mapColumns(ColumnResolver, int)
*/
public void mapColumns(ColumnResolver resolver, int level) {
resolveWindows(resolver);
if (partitionBy != null) {
for (Expression e : partitionBy) {
e.mapColumns(resolver, level);
......@@ -89,6 +98,26 @@ public final class Window {
}
}
private void resolveWindows(ColumnResolver resolver) {
if (parent != null) {
Window p = resolver.getSelect().getWindow(parent);
if (p == null) {
throw DbException.get(ErrorCode.WINDOW_NOT_FOUND_1, parent);
}
p.resolveWindows(resolver);
if (partitionBy == null) {
partitionBy = p.partitionBy;
}
if (orderBy == null) {
orderBy = p.orderBy;
}
if (frame == null) {
frame = p.frame;
}
parent = null;
}
}
/**
* Try to optimize the window conditions.
*
......
......@@ -1549,7 +1549,7 @@ public class JdbcDatabaseMetaData extends TraceObject implements
* HAVING, INNER, INTERSECT, INTERSECTS, IS, JOIN, LIKE, LIMIT, LOCALTIME,
* LOCALTIMESTAMP, MINUS, NATURAL, NOT, NULL, OFFSET, ON, ORDER, PRIMARY, ROWNUM,
* SELECT, SYSDATE, SYSTIME, SYSTIMESTAMP, TODAY, TOP, TRUE, UNION, UNIQUE, WHERE,
* WITH
* WINDOW, WITH
* </pre>
*
* @return a list of additional the keywords
......
......@@ -562,6 +562,7 @@ public class DbException extends RuntimeException {
case CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS:
case TRANSACTION_NOT_FOUND_1:
case AGGREGATE_NOT_FOUND_1:
case WINDOW_NOT_FOUND_1:
case CAN_ONLY_ASSIGN_TO_VARIABLE_1:
case PUBLIC_STATIC_JAVA_METHOD_NOT_FOUND_1:
case JAVA_OBJECT_SERIALIZER_CHANGE_WITH_DATA_TABLE:
......
......@@ -166,7 +166,7 @@
90133=Nelze změnit nastavení {0}, pokud je již databáze otevřena
90134=Přístup ke třídě {0} byl odepřen
90135=Databáze je spuštěna ve vyhrazeném režimu; nelze otevřít další spojení
90136=Nepodporovaná podmínka vnějšího spojení: {0}
90136=#Window not found: {0}
90137=Lze přiřadit pouze proměnné, nikoli: {0}
90138=Neplatný název databáze: {0}
90139=Nenalezena veřejná statická Java metoda: {0}
......
......@@ -166,7 +166,7 @@
90133=Kann das Setting {0} nicht ändern wenn die Datenbank bereits geöffnet ist
90134=Der Zugriff auf die Klasse {0} ist nicht erlaubt
90135=Die Datenbank befindet sich im Exclusiv Modus; es können keine zusätzlichen Verbindungen geöffnet werden
90136=Diese Outer Join Bedingung wird nicht unterstützt: {0}
90136=#Window not found: {0}
90137=Werte können nur einer Variablen zugewiesen werden, nicht an: {0}
90138=Ungültiger Datenbank Name: {0}
90139=Die (public static) Java Funktion wurde nicht gefunden: {0}
......
......@@ -166,7 +166,7 @@
90133=Cannot change the setting {0} when the database is already open
90134=Access to the class {0} is denied
90135=The database is open in exclusive mode; can not open additional connections
90136=Unsupported outer join condition: {0}
90136=Window not found: {0}
90137=Can only assign to a variable, not to: {0}
90138=Invalid database name: {0}
90139=The public static Java method was not found: {0}
......
......@@ -166,7 +166,7 @@
90133=No puede cambiar el setting {0} cuando la base de datos esta abierta
90134=Acceso denegado a la clase {0}
90135=La base de datos esta abierta en modo EXCLUSIVO; no puede abrir conexiones adicionales
90136=Condición No soportada en Outer join : {0}
90136=#Window not found: {0}
90137=Solo puede asignarse a una variable, no a: {0}
90138=Nombre de base de datos Invalido: {0}
90139=El metodo Java (publico y estatico) : {0} no fue encontrado
......
......@@ -166,7 +166,7 @@
90133=Impossible de changer le paramétrage {0} lorsque la base de données est déjà ouverte
90134=L''accès à la classe {0} est interdit
90135=La base de données est ouverte en mode exclusif; impossible d''ouvrir des connexions additionnelles
90136=Condition de jointure extérieure non prise en charge: {0}
90136=#Window not found: {0}
90137=Peut seulement être assigné à une variable, pas à: {0}
90138=Nom de la base de données invalide: {0}
90139=La méthode Java public static n''a pas été trouvée: {0}
......
......@@ -166,7 +166,7 @@
90133=データベースオープン中には、設定 {0} を変更できません
90134=クラス {0} へのアクセスが拒否されました
90135=データベースは排他モードでオープンされています; 接続を追加することはできません
90136=未サポートの外部結合条件: {0}
90136=#Window not found: {0}
90137=割り当ては変数にのみ可能です。{0} にはできません
90138=不正なデータベース名: {0}
90139=public staticであるJavaメソッドが見つかりません: {0}
......
......@@ -166,7 +166,7 @@
90133=Nie można zmienić ustawienia {0} gdy baza danych jest otwarta
90134=Dostęp do klasy {0} jest zabroniony
90135=Baza danych jest otwarta w trybie wyłączności, nie można otworzyć dodatkowych połączeń
90136=Nieobsługiwany warunek złączenia zewnętrznego: {0}
90136=#Window not found: {0}
90137=Można przypisywać tylko do zmiennych, nie do: {0}
90138=Nieprawidłowa nazwa bazy danych: {0}
90139=Publiczna, statyczna metoda Java nie znaleziona: {0}
......
......@@ -166,7 +166,7 @@
90133=#Cannot change the setting {0} when the database is already open
90134=#Access to the class {0} is denied
90135=#The database is open in exclusive mode; can not open additional connections
90136=#Unsupported outer join condition: {0}
90136=#Window not found: {0}
90137=#Can only assign to a variable, not to: {0}
90138=#Invalid database name: {0}
90139=#The public static Java method was not found: {0}
......
......@@ -166,7 +166,7 @@
90133=Невозможно изменить опцию {0}, когда база данных уже открыта
90134=Доступ к классу {0} запрещен
90135=База данных открыта в эксклюзивном режиме, открыть дополнительные соединения невозможно
90136=Данное условие не поддерживается в OUTER JOIN : {0}
90136=Окно не найдено: {0}
90137=Присваивать значения возможно только переменным, но не: {0}
90138=Недопустимое имя базы данных: {0}
90139=public static Java метод не найден: {0}
......
......@@ -166,7 +166,7 @@
90133=Nemôžem zmeniť nastavenie {0} keď už je databáza otvorená
90134=Prístup k triede {0} odoprený
90135=Databáza je otvorená vo výhradnom (exclusive) móde; nemôžem na ňu otvoriť ďalšie pripojenia
90136=Nepodporovaná "outer join" podmienka: {0}
90136=#Window not found: {0}
90137=Môžete priradiť len do premennej, nie do: {0}
90138=Nesprávne meno databázy: {0}
90139=Verejná statická Java metóda nebola nájdená: {0}
......
......@@ -166,7 +166,7 @@
90133=数据库有已启动的时候不允许更改设置{0}
90134=访问 {0}类时被拒绝
90135=数据库运行在独占模式(exclusive mode); 不能打开额外的连接
90136=不支持的外连接条件: {0}
90136=#Window not found: {0}
90137=只能赋值到一个变量,而不是: {0}
90138=无效数据库名称: {0}
90139=找不到公用Java静态方法: {0}
......
......@@ -207,10 +207,15 @@ public class ParserUtil {
*/
public static final int WHERE = UNIQUE + 1;
/**
* The token "WINDOW".
*/
public static final int WINDOW = WHERE + 1;
/**
* The token "WITH".
*/
public static final int WITH = WHERE + 1;
public static final int WITH = WINDOW + 1;
private static final int UPPER_OR_OTHER_LETTER =
1 << Character.UPPERCASE_LETTER
......@@ -426,10 +431,12 @@ public class ParserUtil {
}
return IDENTIFIER;
case 'W':
if ("WITH".equals(s)) {
return WITH;
} else if ("WHERE".equals(s)) {
if ("WHERE".equals(s)) {
return WHERE;
} else if ("WINDOW".equals(s)) {
return WINDOW;
} else if ("WITH".equals(s)) {
return WITH;
}
return IDENTIFIER;
default:
......
......@@ -110,6 +110,7 @@ public class TestScript extends TestDb {
testScript("altertable-fk.sql");
testScript("default-and-on_update.sql");
testScript("query-optimisations.sql");
testScript("window.sql");
String decimal2;
if (SysProperties.BIG_DECIMAL_IS_DECIMAL) {
decimal2 = "decimal_decimal";
......
-- 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
--
CREATE TABLE TEST(ID INT, R INT, CATEGORY INT);
> ok
INSERT INTO TEST VALUES
(1, 4, 1),
(2, 3, 1),
(3, 2, 2),
(4, 1, 2);
> update count: 4
SELECT *, ROW_NUMBER() OVER W FROM TEST;
> exception WINDOW_NOT_FOUND_1
SELECT * FROM TEST WINDOW W AS W1, W1 AS ();
> exception SYNTAX_ERROR_2
SELECT *, ROW_NUMBER() OVER W1, ROW_NUMBER() OVER W2 FROM TEST
WINDOW W1 AS (W2 ORDER BY ID), W2 AS (PARTITION BY CATEGORY ORDER BY ID DESC);
> ID R CATEGORY ROW_NUMBER() OVER (PARTITION BY CATEGORY ORDER BY ID) ROW_NUMBER() OVER (PARTITION BY CATEGORY ORDER BY ID DESC)
> -- - -------- ----------------------------------------------------- ----------------------------------------------------------
> 1 4 1 1 2
> 2 3 1 2 1
> 3 2 2 1 2
> 4 1 2 2 1
> rows (ordered): 4
SELECT *, LAST_VALUE(ID) OVER W FROM TEST
WINDOW W AS (PARTITION BY CATEGORY ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW);
> ID R CATEGORY LAST_VALUE(ID) OVER (PARTITION BY CATEGORY ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW)
> -- - -------- -------------------------------------------------------------------------------------------------------------------------------------
> 1 4 1 2
> 2 3 1 1
> 3 2 2 4
> 4 1 2 3
> rows (ordered): 4
DROP TABLE TEST;
> ok
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论