From 6d5496da7bf2a96a589909335373f8623cbac6ea Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Tue, 28 Jun 2022 17:25:50 +0200 Subject: [PATCH 01/13] Add Tumbling Window basic example --- .../xquery/TumblingWindowClauseTest.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java diff --git a/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java b/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java new file mode 100644 index 00000000000..3591d73c34a --- /dev/null +++ b/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java @@ -0,0 +1,48 @@ +package org.exist.xquery; + +import antlr.RecognitionException; +import antlr.TokenStreamException; +import antlr.collections.AST; +import org.exist.EXistException; +import org.exist.security.PermissionDeniedException; +import org.exist.storage.BrokerPool; +import org.exist.storage.DBBroker; +import org.exist.test.ExistEmbeddedServer; +import org.exist.util.LockException; +import org.exist.xquery.parser.XQueryLexer; +import org.exist.xquery.parser.XQueryParser; +import org.exist.xquery.parser.XQueryTreeParser; +import org.exist.xquery.value.Sequence; +import org.junit.ClassRule; +import org.junit.Test; +import org.xml.sax.SAXException; +import java.io.IOException; +import java.io.StringReader; + +import static org.junit.Assert.*; + +public class TumblingWindowClauseTest { + + @ClassRule + public static final ExistEmbeddedServer existEmbeddedServer = new ExistEmbeddedServer(true, true); + + @Test + public void query() throws EXistException, PermissionDeniedException, IOException, SAXException, LockException, RecognitionException, XPathException, TokenStreamException + { + String query = + "xquery version \"3.0\";\n" + + "for tumbling window \n" + + "return

1

"; + + // parse the query into the internal syntax tree + XQueryLexer lexer = new XQueryLexer(new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + AST ast = xparser.getAST(); + } +} From cbb3c827ad2a0cbbd2b90e80e1f6ea14f3f9751b Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Tue, 28 Jun 2022 17:30:32 +0200 Subject: [PATCH 02/13] Add basic version of windowClause --- .../main/antlr/org/exist/xquery/parser/XQuery.g | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index 30ec8b2ae3f..701c7519a91 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -180,6 +180,7 @@ imaginaryTokenDefinitions PRAGMA GTEQ SEQUENCE + TUMBLING_WINDOW ; // === XPointer === @@ -692,7 +693,7 @@ expr throws XPathException exprSingle throws XPathException : - ( ( "for" | "let" ) DOLLAR ) => flworExpr + ( ( "for" | "let" ) ("tumbling" | DOLLAR ) ) => flworExpr | ( "try" LCURLY ) => tryCatchExpr | ( ( "some" | "every" ) DOLLAR ) => quantifiedExpr | ( "if" LPAREN ) => ifExpr @@ -799,7 +800,9 @@ flworExpr throws XPathException initialClause throws XPathException : - ( forClause | letClause ) + ( ( "for" DOLLAR ) => forClause + | ("for" "tumbling" ) => windowClause + | letClause ) ; intermediateClause throws XPathException @@ -822,6 +825,12 @@ letClause throws XPathException "let"^ letVarBinding ( COMMA! letVarBinding )* ; +windowClause throws XPathException +: + "for"^ "tumbling"! "window"! + { #windowClause= #([TUMBLING_WINDOW, "tumbling window"], #windowClause); } + ; + inVarBinding throws XPathException { String varName; } : @@ -2224,6 +2233,10 @@ reservedKeywords returns [String name] "map" { name = "map"; } | "array" { name = "array"; } + | + "tumbling" { name = "tumbling"; } + | + "window" { name = "window"; } ; /** From cce8445804507da468935a7054b11a8407e8d692 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 29 Jun 2022 12:03:15 +0200 Subject: [PATCH 03/13] Add support for start condition for tumbling windowClause --- .../antlr/org/exist/xquery/parser/XQuery.g | 27 +++++++++++++++++-- .../xquery/TumblingWindowClauseTest.java | 6 ++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index 701c7519a91..2bdd998b381 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -801,7 +801,7 @@ flworExpr throws XPathException initialClause throws XPathException : ( ( "for" DOLLAR ) => forClause - | ("for" "tumbling" ) => windowClause + | ( "for" "tumbling" ) => windowClause | letClause ) ; @@ -827,7 +827,7 @@ letClause throws XPathException windowClause throws XPathException : - "for"^ "tumbling"! "window"! + "for"^ "tumbling" "window" inVarBinding windowStartCondition { #windowClause= #([TUMBLING_WINDOW, "tumbling window"], #windowClause); } ; @@ -855,6 +855,19 @@ allowingEmpty "allowing"! "empty" ; +windowStartCondition throws XPathException +: + "start" windowVars "when" exprSingle +; + +windowVars throws XPathException +{ String currentItem = null, previousItem = null, nextItem = null; } +: + ( DOLLAR! currentItem=eqName )? ( positionalVar )? + ( "previous"! DOLLAR! previousItem=eqName )? + ( "next"! DOLLAR! nextItem=eqName )? +; + letVarBinding throws XPathException { String varName; } : @@ -2237,6 +2250,16 @@ reservedKeywords returns [String name] "tumbling" { name = "tumbling"; } | "window" { name = "window"; } + | + "start" { name = "start"; } + | + "end" { name = "end"; } + | + "only" { name = "only"; } + | + "previous" { name = "previous"; } + | + "next" { name = "next"; } ; /** diff --git a/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java b/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java index 3591d73c34a..a9cd8cc7f0a 100644 --- a/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java +++ b/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java @@ -29,9 +29,9 @@ public class TumblingWindowClauseTest { @Test public void query() throws EXistException, PermissionDeniedException, IOException, SAXException, LockException, RecognitionException, XPathException, TokenStreamException { - String query = - "xquery version \"3.0\";\n" + - "for tumbling window \n" + + String query = "xquery version \"3.0\";\n" + + "for tumbling window $w in (2, 4, 6, 8, 10)\n" + + "start at $s when fn:true()\n" + "return

1

"; // parse the query into the internal syntax tree From b8c8f0b9c708a90643459a6f9edf7d41fd2485e2 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 29 Jun 2022 12:12:01 +0200 Subject: [PATCH 04/13] Add support for windowEndCondition --- exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g | 7 ++++++- .../java/org/exist/xquery/TumblingWindowClauseTest.java | 7 ++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index 2bdd998b381..70d9bb6d0f1 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -827,7 +827,7 @@ letClause throws XPathException windowClause throws XPathException : - "for"^ "tumbling" "window" inVarBinding windowStartCondition + "for"^ "tumbling" "window" inVarBinding windowStartCondition ( windowEndCondition )? { #windowClause= #([TUMBLING_WINDOW, "tumbling window"], #windowClause); } ; @@ -860,6 +860,11 @@ windowStartCondition throws XPathException "start" windowVars "when" exprSingle ; +windowEndCondition throws XPathException +: + ( "only" )? "end" windowVars "when" exprSingle +; + windowVars throws XPathException { String currentItem = null, previousItem = null, nextItem = null; } : diff --git a/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java b/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java index a9cd8cc7f0a..b169987b37c 100644 --- a/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java +++ b/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java @@ -30,9 +30,10 @@ public class TumblingWindowClauseTest { public void query() throws EXistException, PermissionDeniedException, IOException, SAXException, LockException, RecognitionException, XPathException, TokenStreamException { String query = "xquery version \"3.0\";\n" + - "for tumbling window $w in (2, 4, 6, 8, 10)\n" + - "start at $s when fn:true()\n" + - "return

1

"; + "for tumbling window $w in (2, 4, 6, 8, 10, 12, 14)\n" + + " start at $s when fn:true()\n" + + " only end at $e when $e - $s eq 2\n" + + "return { $w }"; // parse the query into the internal syntax tree XQueryLexer lexer = new XQueryLexer(new StringReader(query)); From cfa16310578eb6ab63d638df75cf8d35fd090b21 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 4 Jul 2022 14:02:58 +0200 Subject: [PATCH 05/13] Add WindowExpr, WindowCondition to support WindowClause --- .../exist/xquery/BasicExpressionVisitor.java | 5 + .../xquery/DefaultExpressionVisitor.java | 5 + .../org/exist/xquery/ExpressionVisitor.java | 2 + .../java/org/exist/xquery/FLWORClause.java | 2 +- .../org/exist/xquery/WindowCondition.java | 48 ++++++ .../java/org/exist/xquery/WindowExpr.java | 137 ++++++++++++++++++ 6 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 exist-core/src/main/java/org/exist/xquery/WindowCondition.java create mode 100644 exist-core/src/main/java/org/exist/xquery/WindowExpr.java diff --git a/exist-core/src/main/java/org/exist/xquery/BasicExpressionVisitor.java b/exist-core/src/main/java/org/exist/xquery/BasicExpressionVisitor.java index 2260aa2596f..595933440a4 100644 --- a/exist-core/src/main/java/org/exist/xquery/BasicExpressionVisitor.java +++ b/exist-core/src/main/java/org/exist/xquery/BasicExpressionVisitor.java @@ -173,6 +173,11 @@ public void visitLetExpression(LetExpr letExpr) { //Nothing to do } + @Override + public void visitWindowExpression(WindowExpr letExpr) { + //Nothing to do + } + @Override public void visitOrderByClause(OrderByClause orderBy) { // Nothing to do diff --git a/exist-core/src/main/java/org/exist/xquery/DefaultExpressionVisitor.java b/exist-core/src/main/java/org/exist/xquery/DefaultExpressionVisitor.java index ff768b7315d..c03f6e1474e 100644 --- a/exist-core/src/main/java/org/exist/xquery/DefaultExpressionVisitor.java +++ b/exist-core/src/main/java/org/exist/xquery/DefaultExpressionVisitor.java @@ -69,6 +69,11 @@ public void visitLetExpression(LetExpr letExpr) { letExpr.getReturnExpression().accept(this); } + public void visitWindowExpression(WindowExpr windowExpr) { + windowExpr.getInputSequence().accept(this); + windowExpr.getReturnExpression().accept(this); + } + @Override public void visitOrderByClause(OrderByClause orderBy) { for (OrderSpec spec: orderBy.getOrderSpecs()) { diff --git a/exist-core/src/main/java/org/exist/xquery/ExpressionVisitor.java b/exist-core/src/main/java/org/exist/xquery/ExpressionVisitor.java index b3e2ff04321..fee8214c535 100644 --- a/exist-core/src/main/java/org/exist/xquery/ExpressionVisitor.java +++ b/exist-core/src/main/java/org/exist/xquery/ExpressionVisitor.java @@ -103,4 +103,6 @@ public interface ExpressionVisitor { void visitVariableDeclaration(VariableDeclaration decl); void visitSimpleMapOperator(OpSimpleMap simpleMap); + + void visitWindowExpression(WindowExpr windowExpr); } diff --git a/exist-core/src/main/java/org/exist/xquery/FLWORClause.java b/exist-core/src/main/java/org/exist/xquery/FLWORClause.java index 93623749530..e032d460949 100644 --- a/exist-core/src/main/java/org/exist/xquery/FLWORClause.java +++ b/exist-core/src/main/java/org/exist/xquery/FLWORClause.java @@ -31,7 +31,7 @@ public interface FLWORClause extends Expression { enum ClauseType { - FOR, LET, GROUPBY, ORDERBY, WHERE, SOME, EVERY + FOR, LET, GROUPBY, ORDERBY, WHERE, SOME, EVERY, WINDOW } /** diff --git a/exist-core/src/main/java/org/exist/xquery/WindowCondition.java b/exist-core/src/main/java/org/exist/xquery/WindowCondition.java new file mode 100644 index 00000000000..e4e8e336f21 --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/WindowCondition.java @@ -0,0 +1,48 @@ +package org.exist.xquery; + +import com.ibm.icu.text.Collator; +import org.exist.dom.QName; +import org.exist.xquery.parser.XQueryAST; +import org.exist.xquery.util.ExpressionDumper; + +public class WindowCondition +{ + @SuppressWarnings("unused") + private final XQueryContext context; + private Collator collator; + private Expression whenExpression; + private String posVar = null; + private QName currentItem = null; + private QName previousItem = null; + private QName nextItem = null; + private boolean only = false; + + public WindowCondition(XQueryContext context, Expression whenExpr, + QName current, QName previous, QName next, String posVar, boolean only) { + this.whenExpression = whenExpr; + this.context = context; + this.currentItem = current; + this.previousItem = previous; + this.nextItem = next; + this.posVar = posVar; + this.only = only; + this.collator = context.getDefaultCollator(); + } + + public void setCollator(String collation) throws XPathException { + this.collator = context.getCollator(collation); + } + + public Collator getCollator() { + return this.collator; + } + + @Override + public String toString() { + return this.only ? "only " : "" + + "current " + this.currentItem + " at " + this.posVar + + " previous " + this.previousItem + + " next " + this.nextItem + + " when " + this.whenExpression.toString(); + } +} diff --git a/exist-core/src/main/java/org/exist/xquery/WindowExpr.java b/exist-core/src/main/java/org/exist/xquery/WindowExpr.java new file mode 100644 index 00000000000..c609911f96c --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/WindowExpr.java @@ -0,0 +1,137 @@ +/* + * eXist-db Open Source Native XML Database + * Copyright (C) 2001 The eXist-db Authors + * + * info@exist-db.org + * http://www.exist-db.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package org.exist.xquery; + +import org.exist.dom.QName; +import org.exist.xquery.util.ExpressionDumper; +import org.exist.xquery.value.*; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Implements an XQuery let-expression. + * + * @author Wolfgang Meier + */ +public class WindowExpr extends BindingExpression { + + public enum WindowType { + TUMBLING_WINDOW + } + + //private Expression inputSequence = null; + private WindowCondition startWindowCondition = null; + private WindowCondition endWindowCondition = null; + + private WindowType windowType = WindowType.TUMBLING_WINDOW; + + public WindowExpr(XQueryContext context, WindowType type, WindowCondition start, WindowCondition end) { + super(context); + //this.inputSequence = inputSequence; + this.windowType = type; + this.startWindowCondition = start; + this.endWindowCondition = end; + } + + @Override + public ClauseType getType() { + return ClauseType.WINDOW; + } + + public WindowType getWindowType() { return this.windowType; } + + public WindowCondition getStartWindowCondition() + { + return startWindowCondition; + } + + public WindowCondition getEndWindowCondition() + { + return endWindowCondition; + } + + @Override + public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException { + super.analyze(contextInfo); + // TODO + } + + public Sequence eval(Sequence contextSequence, Item contextItem) + throws XPathException { + // TODO + + Sequence resultSequence = null; + return resultSequence; + } + + /* (non-Javadoc) + * @see org.exist.xquery.Expression#dump(org.exist.xquery.util.ExpressionDumper) + */ + public void dump(ExpressionDumper dumper) { + dumper.display(this.getWindowType() == WindowType.TUMBLING_WINDOW ? "tumbling window " : "sliding window ", line); + dumper.startIndent(); + dumper.display("$").display(varName); + if (sequenceType != null) { + dumper.display(" as ").display(sequenceType); + } + dumper.display(" in "); + inputSequence.dump(dumper); + dumper.endIndent().nl(); + //TODO : QuantifiedExpr + if (returnExpr instanceof LetExpr) + {dumper.display(" ", returnExpr.getLine());} + else + {dumper.display("return", returnExpr.getLine());} + dumper.startIndent(); + returnExpr.dump(dumper); + dumper.endIndent().nl(); + } + + public String toString() { + final StringBuilder result = new StringBuilder(); + result.append(this.getWindowType() == WindowType.TUMBLING_WINDOW ? "tumbling window " : "sliding window "); + result.append("$").append(varName); + if (sequenceType != null) + {result.append(" as ").append(sequenceType);} + result.append(" in "); + result.append(inputSequence.toString()); + result.append(" "); + result.append("start " + startWindowCondition.toString()); + result.append(" end " + endWindowCondition.toString()); + //TODO : QuantifiedExpr + if (returnExpr instanceof LetExpr) + {result.append(" ");} + else + {result.append("return ");} + result.append(returnExpr.toString()); + return result.toString(); + } + + public void accept(ExpressionVisitor visitor) { + visitor.visitWindowExpression(this); + } + + @Override + public boolean allowMixedNodesInReturn() { + return true; + } +} \ No newline at end of file From 067561131826a8dfc029f5dcf3612607c0581e4e Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 4 Jul 2022 14:03:41 +0200 Subject: [PATCH 06/13] Improve parsing of Tumbling Window --- .../antlr/org/exist/xquery/parser/XQuery.g | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index 70d9bb6d0f1..6009f38fa77 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -181,6 +181,10 @@ imaginaryTokenDefinitions GTEQ SEQUENCE TUMBLING_WINDOW + CURRENT_ITEM + PREVIOUS_ITEM + NEXT_ITEM + WINDOW_VARS ; // === XPointer === @@ -827,8 +831,7 @@ letClause throws XPathException windowClause throws XPathException : - "for"^ "tumbling" "window" inVarBinding windowStartCondition ( windowEndCondition )? - { #windowClause= #([TUMBLING_WINDOW, "tumbling window"], #windowClause); } + "for"! "tumbling"^ "window"! inVarBinding windowStartCondition ( windowEndCondition )? ; inVarBinding throws XPathException @@ -857,20 +860,32 @@ allowingEmpty windowStartCondition throws XPathException : - "start" windowVars "when" exprSingle + "start"^ windowVars "when" exprSingle ; windowEndCondition throws XPathException : - ( "only" )? "end" windowVars "when" exprSingle + ( "only" )? "end"^ windowVars "when" exprSingle ; windowVars throws XPathException -{ String currentItem = null, previousItem = null, nextItem = null; } +{ String currentItemName = null, previousItemName = null, nextItemName = null; } : - ( DOLLAR! currentItem=eqName )? ( positionalVar )? - ( "previous"! DOLLAR! previousItem=eqName )? - ( "next"! DOLLAR! nextItem=eqName )? + ( DOLLAR! currentItemName=eqName! )? + ( sp:positionalVar )? + ( "previous"! DOLLAR! previousItemName=eqName! )? + ( "next"! DOLLAR! nextItemName=eqName! )? + { + windowVars_AST = (org.exist.xquery.parser.XQueryAST)astFactory.create(WINDOW_VARS); + if (currentItemName != null) + windowVars_AST.addChild(astFactory.create(CURRENT_ITEM,currentItemName)); + if (sp_AST != null) + windowVars_AST.addChild(sp_AST); + if (previousItemName != null) + windowVars_AST.addChild(astFactory.create(PREVIOUS_ITEM,previousItemName)); + if (nextItemName != null) + windowVars_AST.addChild(astFactory.create(NEXT_ITEM,nextItemName)); + } ; letVarBinding throws XPathException From d506872daabf42a11b73d3a83dc2f3460129f9ae Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 4 Jul 2022 14:04:25 +0200 Subject: [PATCH 07/13] Add handling of intermediate AST for Tumbling Window --- .../org/exist/xquery/parser/XQueryTree.g | 202 ++++++++++++++++-- 1 file changed, 189 insertions(+), 13 deletions(-) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g index d1bea32bfdc..3f55e473615 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g @@ -136,6 +136,8 @@ options { FLWORClause.ClauseType type = FLWORClause.ClauseType.FOR; List groupSpecs = null; List orderSpecs = null; + List windowConditions = null; + WindowExpr.WindowType windowType = null; boolean allowEmpty = false; } @@ -1589,6 +1591,176 @@ throws PermissionDeniedException, EXistException, XPathException ) )+ ) + | + #( + tw:"tumbling" + { + ForLetClause clause= new ForLetClause(); + clause.type = FLWORClause.ClauseType.WINDOW; + clause.windowConditions = new ArrayList(2); + clause.windowType = WindowExpr.WindowType.TUMBLING_WINDOW; + } + // invarBinding + ( + #( + windowWarName:VARIABLE_BINDING + { + clause.ast = windowWarName; + PathExpr inputSequence= new PathExpr(context); + } + ( + #( + "as" + { clause.sequenceType= new SequenceType(); } + sequenceType [clause.sequenceType] + ) + )? + step=expr [inputSequence] + { + clause.varName= windowWarName.getText(); + clause.inputSequence= inputSequence; + clauses.add(clause); + } + ) + ) + // windowStartCondition + #( + "start" + { + PathExpr whenExpr = new PathExpr(context); + QName currentItemName = null; + QName previousItemName = null; + QName nextItemName = null; + String windowStartPosVar = null; + } + ( + // WINDOW_VARS + ( + currentItem:CURRENT_ITEM + { + if (currentItem != null && currentItem.getText() != null) { + try { + currentItemName = QName.parse(staticContext, currentItem.getText()); + } catch (final IllegalQNameException iqe) { + throw new XPathException(currentItem.getLine(), currentItem.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + currentItem.getText()); + } + } + } + )? + ( + startPosVar:POSITIONAL_VAR + { + windowStartPosVar = startPosVar.getText(); + } + )? + ( + previousItem:PREVIOUS_ITEM + { + if (previousItem != null && previousItem.getText() != null) { + try { + previousItemName= QName.parse(staticContext, previousItem.getText()); + } catch (final IllegalQNameException iqe) { + throw new XPathException(previousItem.getLine(), previousItem.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + previousItem.getText()); + } + } + } + )? + ( + nextItem:NEXT_ITEM + { + if (nextItem != null && nextItem.getText() != null) { + try { + nextItemName = QName.parse(staticContext, nextItem.getText()); + } catch (final IllegalQNameException iqe) { + throw new XPathException(nextItem.getLine(), nextItem.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + nextItem.getText()); + } + } + } + )? + ) + "when" + step=expr [whenExpr] + { + WindowCondition windowCondition = new WindowCondition( + context, whenExpr, currentItemName, previousItemName, nextItemName, windowStartPosVar, false + ); + clause.windowConditions.add(windowCondition); + } + ) + // windowEndCondition + ( + { + PathExpr endWhenExpr = new PathExpr(context); + QName endCurrentItemName = null; + QName endPreviousItemName = null; + QName endNextItemName = null; + String windowEndPosVar = null; + Boolean only = false; + } + #( + "end" + ( + "only" + { + only = true; + } + )? + ( + // WINDOW_VARS + ( + endCurrentItem:CURRENT_ITEM + { + if (endCurrentItem != null && endCurrentItem.getText() != null) { + try { + endCurrentItemName = QName.parse(staticContext, endCurrentItem.getText()); + } catch (final IllegalQNameException iqe) { + throw new XPathException(endCurrentItem.getLine(), endCurrentItem.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + endCurrentItem.getText()); + } + } + } + )? + ( + endPosVar:POSITIONAL_VAR + { + windowEndPosVar = endPosVar.getText(); + } + )? + ( + endPreviousItem:PREVIOUS_ITEM + { + if (endPreviousItem != null && endPreviousItem.getText() != null) { + try { + endPreviousItemName= QName.parse(staticContext, previousItem.getText()); + } catch (final IllegalQNameException iqe) { + throw new XPathException(endPreviousItem.getLine(), endPreviousItem.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + endPreviousItem.getText()); + } + } + } + )? + ( + endNextItem:NEXT_ITEM + { + if (endNextItem != null && endNextItem.getText() != null) { + try { + endNextItemName = QName.parse(staticContext, endNextItem.getText()); + } catch (final IllegalQNameException iqe) { + throw new XPathException(endNextItem.getLine(), endNextItem.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + endNextItem.getText()); + } + } + } + )? + ) + "when" + step=expr [endWhenExpr] + { + WindowCondition endWindowCondition = new WindowCondition( + context, endWhenExpr, endCurrentItemName, endPreviousItemName, endNextItemName, windowEndPosVar, only + ); + clause.windowConditions.add(endWindowCondition); + } + ) + )? + ) | // XQuery 3.0 group by clause #( @@ -1720,20 +1892,24 @@ throws PermissionDeniedException, EXistException, XPathException expr= new LetExpr(context); break; case GROUPBY: - expr = new GroupByClause(context); - break; - case ORDERBY: - expr = new OrderByClause(context, clause.orderSpecs); - break; - case WHERE: - expr = new WhereClause(context, new DebuggableExpression(clause.inputSequence)); - break; - default: - expr= new ForExpr(context, clause.allowEmpty); - break; + expr = new GroupByClause(context); + break; + case ORDERBY: + expr = new OrderByClause(context, clause.orderSpecs); + break; + case WHERE: + expr = new WhereClause(context, new DebuggableExpression(clause.inputSequence)); + break; + case WINDOW: + expr = new WindowExpr(context, clause.windowType, clause.windowConditions.get(0), clause.windowConditions.size() > 1 ? clause.windowConditions.get(1) : null); + break; + default: + expr= new ForExpr(context, clause.allowEmpty); + break; } expr.setASTNode(clause.ast); - if (clause.type == FLWORClause.ClauseType.FOR || clause.type == FLWORClause.ClauseType.LET) { + if (clause.type == FLWORClause.ClauseType.FOR || clause.type == FLWORClause.ClauseType.LET + || clause.type == FLWORClause.ClauseType.WINDOW) { final BindingExpression bind = (BindingExpression)expr; bind.setVariable(clause.varName); bind.setSequenceType(clause.sequenceType); @@ -1750,7 +1926,7 @@ throws PermissionDeniedException, EXistException, XPathException } ((GroupByClause)expr).setGroupSpecs(specs); } - } + } if (!(action instanceof FLWORClause)) expr.setReturnExpression(new DebuggableExpression(action)); else { From 867ec2096ce93545da4f7dfac2d9870bfdfeaec6 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Mon, 4 Jul 2022 14:05:01 +0200 Subject: [PATCH 08/13] Improve testing for Tumbling Window --- .../xquery/TumblingWindowClauseTest.java | 106 ++++++++++++++++-- 1 file changed, 96 insertions(+), 10 deletions(-) diff --git a/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java b/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java index b169987b37c..9d894cc6523 100644 --- a/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java +++ b/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java @@ -9,15 +9,18 @@ import org.exist.storage.DBBroker; import org.exist.test.ExistEmbeddedServer; import org.exist.util.LockException; +import org.exist.xquery.parser.XQueryAST; import org.exist.xquery.parser.XQueryLexer; import org.exist.xquery.parser.XQueryParser; import org.exist.xquery.parser.XQueryTreeParser; import org.exist.xquery.value.Sequence; +import org.junit.Assert; import org.junit.ClassRule; import org.junit.Test; import org.xml.sax.SAXException; import java.io.IOException; import java.io.StringReader; +import java.util.Optional; import static org.junit.Assert.*; @@ -27,23 +30,106 @@ public class TumblingWindowClauseTest { public static final ExistEmbeddedServer existEmbeddedServer = new ExistEmbeddedServer(true, true); @Test - public void query() throws EXistException, PermissionDeniedException, IOException, SAXException, LockException, RecognitionException, XPathException, TokenStreamException + public void simpleWindowConditions() throws EXistException, PermissionDeniedException, IOException, SAXException, LockException, RecognitionException, XPathException, TokenStreamException { - String query = "xquery version \"3.0\";\n" + + String query = "xquery version \"3.1\";\n" + "for tumbling window $w in (2, 4, 6, 8, 10, 12, 14)\n" + " start at $s when fn:true()\n" + " only end at $e when $e - $s eq 2\n" + "return { $w }"; - // parse the query into the internal syntax tree - XQueryLexer lexer = new XQueryLexer(new StringReader(query)); - XQueryParser xparser = new XQueryParser(lexer); - xparser.xpath(); - if (xparser.foundErrors()) { - fail(xparser.getErrorMessage()); - return; + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); + } + } + + @Test + public void complexWindowCondition() throws EXistException, PermissionDeniedException, IOException, SAXException, LockException, RecognitionException, XPathException, TokenStreamException + { + String query = "xquery version \"3.1\";\n" + + "for tumbling window $w in (2, 4, 6, 8, 10, 12, 14)\n" + + " start $first next $second when $first/price < $second/price\n" + + " end $last next $beyond when $last/price > $beyond/price\n" + + "return { $w }"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); } + } + + @Test + public void noEndWindowCondition() throws EXistException, PermissionDeniedException, IOException, SAXException, LockException, RecognitionException, XPathException, TokenStreamException + { + String query = "xquery version \"3.1\";\n" + + "for tumbling window $w in (2, 4, 6, 8, 10, 12, 14)\n" + + " start $first next $second when $first/price < $second/price\n" + + "return { $w }"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); - AST ast = xparser.getAST(); + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); + } } } From 161eb47babca985f42ba5aa9678068c560903bda Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 6 Jul 2022 08:58:34 +0200 Subject: [PATCH 09/13] Add support for Sliding WIndow Expression --- .../antlr/org/exist/xquery/parser/XQuery.g | 8 +- .../org/exist/xquery/parser/XQueryTree.g | 14 +- .../java/org/exist/xquery/WindowExpr.java | 3 +- .../xquery/TumblingWindowClauseTest.java | 135 ------------------ 4 files changed, 19 insertions(+), 141 deletions(-) delete mode 100644 exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index 6009f38fa77..16ebeb47849 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -697,7 +697,7 @@ expr throws XPathException exprSingle throws XPathException : - ( ( "for" | "let" ) ("tumbling" | DOLLAR ) ) => flworExpr + ( ( "for" | "let" ) ("tumbling" | "sliding" | DOLLAR ) ) => flworExpr | ( "try" LCURLY ) => tryCatchExpr | ( ( "some" | "every" ) DOLLAR ) => quantifiedExpr | ( "if" LPAREN ) => ifExpr @@ -805,7 +805,7 @@ flworExpr throws XPathException initialClause throws XPathException : ( ( "for" DOLLAR ) => forClause - | ( "for" "tumbling" ) => windowClause + | ( "for" ( "tumbling" | "sliding" ) ) => windowClause | letClause ) ; @@ -831,7 +831,7 @@ letClause throws XPathException windowClause throws XPathException : - "for"! "tumbling"^ "window"! inVarBinding windowStartCondition ( windowEndCondition )? + "for"! ("tumbling"|"sliding") "window"^ inVarBinding windowStartCondition ( windowEndCondition )? ; inVarBinding throws XPathException @@ -2280,6 +2280,8 @@ reservedKeywords returns [String name] "previous" { name = "previous"; } | "next" { name = "next"; } + | + "sliding" { name = "sliding"; } ; /** diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g index 3f55e473615..6ae9b932362 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g @@ -1593,13 +1593,23 @@ throws PermissionDeniedException, EXistException, XPathException ) | #( - tw:"tumbling" + wc:"window" { ForLetClause clause= new ForLetClause(); clause.type = FLWORClause.ClauseType.WINDOW; clause.windowConditions = new ArrayList(2); - clause.windowType = WindowExpr.WindowType.TUMBLING_WINDOW; } + ( + "tumbling" + { + clause.windowType = WindowExpr.WindowType.TUMBLING_WINDOW; + } + | + "sliding" + { + clause.windowType = WindowExpr.WindowType.SLIDING_WINDOW; + } + )? // invarBinding ( #( diff --git a/exist-core/src/main/java/org/exist/xquery/WindowExpr.java b/exist-core/src/main/java/org/exist/xquery/WindowExpr.java index c609911f96c..c3b3ae2f240 100644 --- a/exist-core/src/main/java/org/exist/xquery/WindowExpr.java +++ b/exist-core/src/main/java/org/exist/xquery/WindowExpr.java @@ -35,7 +35,8 @@ public class WindowExpr extends BindingExpression { public enum WindowType { - TUMBLING_WINDOW + TUMBLING_WINDOW, + SLIDING_WINDOW } //private Expression inputSequence = null; diff --git a/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java b/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java deleted file mode 100644 index 9d894cc6523..00000000000 --- a/exist-core/src/test/java/org/exist/xquery/TumblingWindowClauseTest.java +++ /dev/null @@ -1,135 +0,0 @@ -package org.exist.xquery; - -import antlr.RecognitionException; -import antlr.TokenStreamException; -import antlr.collections.AST; -import org.exist.EXistException; -import org.exist.security.PermissionDeniedException; -import org.exist.storage.BrokerPool; -import org.exist.storage.DBBroker; -import org.exist.test.ExistEmbeddedServer; -import org.exist.util.LockException; -import org.exist.xquery.parser.XQueryAST; -import org.exist.xquery.parser.XQueryLexer; -import org.exist.xquery.parser.XQueryParser; -import org.exist.xquery.parser.XQueryTreeParser; -import org.exist.xquery.value.Sequence; -import org.junit.Assert; -import org.junit.ClassRule; -import org.junit.Test; -import org.xml.sax.SAXException; -import java.io.IOException; -import java.io.StringReader; -import java.util.Optional; - -import static org.junit.Assert.*; - -public class TumblingWindowClauseTest { - - @ClassRule - public static final ExistEmbeddedServer existEmbeddedServer = new ExistEmbeddedServer(true, true); - - @Test - public void simpleWindowConditions() throws EXistException, PermissionDeniedException, IOException, SAXException, LockException, RecognitionException, XPathException, TokenStreamException - { - String query = "xquery version \"3.1\";\n" + - "for tumbling window $w in (2, 4, 6, 8, 10, 12, 14)\n" + - " start at $s when fn:true()\n" + - " only end at $e when $e - $s eq 2\n" + - "return { $w }"; - - BrokerPool pool = BrokerPool.getInstance(); - try(final DBBroker broker = pool.getBroker()) { - // parse the query into the internal syntax tree - XQueryContext context = new XQueryContext(broker.getBrokerPool()); - XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); - XQueryParser xparser = new XQueryParser(lexer); - xparser.xpath(); - if (xparser.foundErrors()) { - fail(xparser.getErrorMessage()); - return; - } - - XQueryAST ast = (XQueryAST) xparser.getAST(); - - XQueryTreeParser treeParser = new XQueryTreeParser(context); - PathExpr expr = new PathExpr(context); - treeParser.xpath(ast, expr); - if (treeParser.foundErrors()) { - fail(treeParser.getErrorMessage()); - return; - } - - assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); - } - } - - @Test - public void complexWindowCondition() throws EXistException, PermissionDeniedException, IOException, SAXException, LockException, RecognitionException, XPathException, TokenStreamException - { - String query = "xquery version \"3.1\";\n" + - "for tumbling window $w in (2, 4, 6, 8, 10, 12, 14)\n" + - " start $first next $second when $first/price < $second/price\n" + - " end $last next $beyond when $last/price > $beyond/price\n" + - "return { $w }"; - - BrokerPool pool = BrokerPool.getInstance(); - try(final DBBroker broker = pool.getBroker()) { - // parse the query into the internal syntax tree - XQueryContext context = new XQueryContext(broker.getBrokerPool()); - XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); - XQueryParser xparser = new XQueryParser(lexer); - xparser.xpath(); - if (xparser.foundErrors()) { - fail(xparser.getErrorMessage()); - return; - } - - XQueryAST ast = (XQueryAST) xparser.getAST(); - - XQueryTreeParser treeParser = new XQueryTreeParser(context); - PathExpr expr = new PathExpr(context); - treeParser.xpath(ast, expr); - if (treeParser.foundErrors()) { - fail(treeParser.getErrorMessage()); - return; - } - - assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); - } - } - - @Test - public void noEndWindowCondition() throws EXistException, PermissionDeniedException, IOException, SAXException, LockException, RecognitionException, XPathException, TokenStreamException - { - String query = "xquery version \"3.1\";\n" + - "for tumbling window $w in (2, 4, 6, 8, 10, 12, 14)\n" + - " start $first next $second when $first/price < $second/price\n" + - "return { $w }"; - - BrokerPool pool = BrokerPool.getInstance(); - try(final DBBroker broker = pool.getBroker()) { - // parse the query into the internal syntax tree - XQueryContext context = new XQueryContext(broker.getBrokerPool()); - XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); - XQueryParser xparser = new XQueryParser(lexer); - xparser.xpath(); - if (xparser.foundErrors()) { - fail(xparser.getErrorMessage()); - return; - } - - XQueryAST ast = (XQueryAST) xparser.getAST(); - - XQueryTreeParser treeParser = new XQueryTreeParser(context); - PathExpr expr = new PathExpr(context); - treeParser.xpath(ast, expr); - if (treeParser.foundErrors()) { - fail(treeParser.getErrorMessage()); - return; - } - - assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); - } - } -} From 125df84e725ebf1cd44383dd09a269d85805a474 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 6 Jul 2022 08:59:00 +0200 Subject: [PATCH 10/13] Add test for Sliding Window Expression --- .../org/exist/xquery/WindowClauseTest.java | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 exist-core/src/test/java/org/exist/xquery/WindowClauseTest.java diff --git a/exist-core/src/test/java/org/exist/xquery/WindowClauseTest.java b/exist-core/src/test/java/org/exist/xquery/WindowClauseTest.java new file mode 100644 index 00000000000..7b0e8345ccd --- /dev/null +++ b/exist-core/src/test/java/org/exist/xquery/WindowClauseTest.java @@ -0,0 +1,168 @@ +package org.exist.xquery; + +import antlr.RecognitionException; +import antlr.TokenStreamException; +import org.exist.EXistException; +import org.exist.security.PermissionDeniedException; +import org.exist.storage.BrokerPool; +import org.exist.storage.DBBroker; +import org.exist.test.ExistEmbeddedServer; +import org.exist.util.LockException; +import org.exist.xquery.parser.XQueryAST; +import org.exist.xquery.parser.XQueryLexer; +import org.exist.xquery.parser.XQueryParser; +import org.exist.xquery.parser.XQueryTreeParser; +import org.junit.ClassRule; +import org.junit.Test; +import org.xml.sax.SAXException; +import java.io.IOException; +import java.io.StringReader; + +import static org.junit.Assert.*; + +public class WindowClauseTest +{ + + @ClassRule + public static final ExistEmbeddedServer existEmbeddedServer = new ExistEmbeddedServer(true, true); + + @Test + public void simpleWindowConditions() throws EXistException, RecognitionException, XPathException, TokenStreamException + { + String query = "xquery version \"3.1\";\n" + + "for tumbling window $w in (2, 4, 6, 8, 10, 12, 14)\n" + + " start at $s when fn:true()\n" + + " only end at $e when $e - $s eq 2\n" + + "return { $w }"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); + } + } + + @Test + public void complexWindowCondition() throws EXistException, RecognitionException, XPathException, TokenStreamException + { + String query = "xquery version \"3.1\";\n" + + "for tumbling window $w in (2, 4, 6, 8, 10, 12, 14)\n" + + " start $first next $second when $first/price < $second/price\n" + + " end $last next $beyond when $last/price > $beyond/price\n" + + "return { $w }"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); + } + } + + @Test + public void noEndWindowCondition() throws EXistException, RecognitionException, XPathException, TokenStreamException + { + String query = "xquery version \"3.1\";\n" + + "for tumbling window $w in (2, 4, 6, 8, 10, 12, 14)\n" + + " start $first next $second when $first/price < $second/price\n" + + "return { $w }"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); + } + } + + @Test + public void slidingWindowClause() throws EXistException, RecognitionException, XPathException, TokenStreamException + { + String query = "xquery version \"3.1\";\n" + + "for sliding window $w in (2, 4, 6, 8, 10, 12, 14)\n" + + " start at $s when fn:true()\n" + + " only end at $e when $e - $s eq 2\n" + + "return { $w }"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); + assertEquals(WindowExpr.WindowType.SLIDING_WINDOW, ((WindowExpr) expr.getFirst()).getWindowType()); + } + } +} From 5f814efe3c4ccd8711cd10729f4f53c32783a7d0 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 20 Jul 2022 14:59:03 +0200 Subject: [PATCH 11/13] Fix bug in handling current item when positional var is present --- .../src/main/antlr/org/exist/xquery/parser/XQuery.g | 9 +++++---- .../src/main/antlr/org/exist/xquery/parser/XQueryTree.g | 8 +++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g index 16ebeb47849..1bc4c444581 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g @@ -869,22 +869,23 @@ windowEndCondition throws XPathException ; windowVars throws XPathException -{ String currentItemName = null, previousItemName = null, nextItemName = null; } +{ String currentItemName = null, previousItemName = null, nextItemName = null; String varName = null; } : ( DOLLAR! currentItemName=eqName! )? - ( sp:positionalVar )? + ( "at"! DOLLAR! varName=varName)? ( "previous"! DOLLAR! previousItemName=eqName! )? ( "next"! DOLLAR! nextItemName=eqName! )? { windowVars_AST = (org.exist.xquery.parser.XQueryAST)astFactory.create(WINDOW_VARS); if (currentItemName != null) windowVars_AST.addChild(astFactory.create(CURRENT_ITEM,currentItemName)); - if (sp_AST != null) - windowVars_AST.addChild(sp_AST); + if (varName != null) + windowVars_AST.addChild(astFactory.create(POSITIONAL_VAR,varName)); if (previousItemName != null) windowVars_AST.addChild(astFactory.create(PREVIOUS_ITEM,previousItemName)); if (nextItemName != null) windowVars_AST.addChild(astFactory.create(NEXT_ITEM,nextItemName)); + currentAST.root = (org.exist.xquery.parser.XQueryAST) windowVars_AST; } ; diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g index 6ae9b932362..5f782c85ecd 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g @@ -1643,8 +1643,9 @@ throws PermissionDeniedException, EXistException, XPathException QName nextItemName = null; String windowStartPosVar = null; } - ( + #( // WINDOW_VARS + WINDOW_VARS ( currentItem:CURRENT_ITEM { @@ -1715,8 +1716,9 @@ throws PermissionDeniedException, EXistException, XPathException only = true; } )? - ( + #( // WINDOW_VARS + WINDOW_VARS ( endCurrentItem:CURRENT_ITEM { @@ -1740,7 +1742,7 @@ throws PermissionDeniedException, EXistException, XPathException { if (endPreviousItem != null && endPreviousItem.getText() != null) { try { - endPreviousItemName= QName.parse(staticContext, previousItem.getText()); + endPreviousItemName= QName.parse(staticContext, endPreviousItem.getText()); } catch (final IllegalQNameException iqe) { throw new XPathException(endPreviousItem.getLine(), endPreviousItem.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + endPreviousItem.getText()); } From adea709c1bed8fe20d80775a9b6a78189c62bb92 Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 20 Jul 2022 15:03:08 +0200 Subject: [PATCH 12/13] Add property getters to WindowCondition --- .../org/exist/xquery/WindowCondition.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/exist-core/src/main/java/org/exist/xquery/WindowCondition.java b/exist-core/src/main/java/org/exist/xquery/WindowCondition.java index e4e8e336f21..28a5712ea6b 100644 --- a/exist-core/src/main/java/org/exist/xquery/WindowCondition.java +++ b/exist-core/src/main/java/org/exist/xquery/WindowCondition.java @@ -45,4 +45,29 @@ public String toString() { + " next " + this.nextItem + " when " + this.whenExpression.toString(); } + + public QName getCurrentItem() + { + return currentItem; + } + + public QName getNextItem() + { + return nextItem; + } + + public QName getPreviousItem() + { + return previousItem; + } + + public String getPosVar() + { + return posVar; + } + + public boolean getOnly() + { + return only; + } } From 26bd8a7e4535dcf439f8daaab8c3b89e5d83baab Mon Sep 17 00:00:00 2001 From: Gabriele Tomassetti Date: Wed, 20 Jul 2022 15:03:27 +0200 Subject: [PATCH 13/13] Add more examples from reference --- .../org/exist/xquery/WindowClauseTest.java | 367 ++++++++++++++++++ 1 file changed, 367 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/WindowClauseTest.java b/exist-core/src/test/java/org/exist/xquery/WindowClauseTest.java index 7b0e8345ccd..27bc9f8db4d 100644 --- a/exist-core/src/test/java/org/exist/xquery/WindowClauseTest.java +++ b/exist-core/src/test/java/org/exist/xquery/WindowClauseTest.java @@ -165,4 +165,371 @@ public void slidingWindowClause() throws EXistException, RecognitionException, X assertEquals(WindowExpr.WindowType.SLIDING_WINDOW, ((WindowExpr) expr.getFirst()).getWindowType()); } } + + @Test + public void allWindowsVars() throws EXistException, RecognitionException, XPathException, TokenStreamException + { + String query = "xquery version \"3.1\";\n" + + "for tumbling window $w in (2, 4, 6, 8, 10, 12, 14)\n" + + "start $first at $s previous $start-previous next $start-next when fn:true()\n" + + "only end $last at $e previous $end-previous next $end-next when $e - $s eq 2\n" + + "return\n" + + " {$first, $last}\n"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); + assertEquals("first", ((WindowExpr) expr.getFirst()).getStartWindowCondition().getCurrentItem().getStringValue()); + assertEquals("s", ((WindowExpr) expr.getFirst()).getStartWindowCondition().getPosVar()); + assertEquals("start-previous", ((WindowExpr) expr.getFirst()).getStartWindowCondition().getPreviousItem().getStringValue()); + assertEquals("start-next", ((WindowExpr) expr.getFirst()).getStartWindowCondition().getNextItem().getStringValue()); + assertEquals("last", ((WindowExpr) expr.getFirst()).getEndWindowCondition().getCurrentItem().getStringValue()); + assertEquals("e", ((WindowExpr) expr.getFirst()).getEndWindowCondition().getPosVar()); + assertEquals("end-previous", ((WindowExpr) expr.getFirst()).getEndWindowCondition().getPreviousItem().getStringValue()); + assertEquals("end-next", ((WindowExpr) expr.getFirst()).getEndWindowCondition().getNextItem().getStringValue()); + assertEquals(true, ((WindowExpr) expr.getFirst()).getEndWindowCondition().getOnly()); + } + } + + @Test + public void tumblingWindowAllWindowVarsNoOnly() throws EXistException, RecognitionException, XPathException, TokenStreamException + { + String query = "xquery version \"3.1\";\n" + + "for tumbling window $w in (2, 4, 6, 8, 10)\n" + + " start $s at $spos previous $sprev next $snext when true() \n" + + " end $e at $epos previous $eprev next $enext when true()\n" + + "return\n" + + " {$first, $last}\n"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); + assertEquals(WindowExpr.WindowType.TUMBLING_WINDOW, ((WindowExpr) expr.getFirst()).getWindowType()); + assertEquals("s", ((WindowExpr) expr.getFirst()).getStartWindowCondition().getCurrentItem().getStringValue()); + assertEquals("spos", ((WindowExpr) expr.getFirst()).getStartWindowCondition().getPosVar()); + assertEquals("sprev", ((WindowExpr) expr.getFirst()).getStartWindowCondition().getPreviousItem().getStringValue()); + assertEquals("snext", ((WindowExpr) expr.getFirst()).getStartWindowCondition().getNextItem().getStringValue()); + assertEquals("e", ((WindowExpr) expr.getFirst()).getEndWindowCondition().getCurrentItem().getStringValue()); + assertEquals("epos", ((WindowExpr) expr.getFirst()).getEndWindowCondition().getPosVar()); + assertEquals("eprev", ((WindowExpr) expr.getFirst()).getEndWindowCondition().getPreviousItem().getStringValue()); + assertEquals("enext", ((WindowExpr) expr.getFirst()).getEndWindowCondition().getNextItem().getStringValue()); + assertEquals(false, ((WindowExpr) expr.getFirst()).getEndWindowCondition().getOnly()); + } + } + + @Test + public void tumblingWindowAvgReturn() throws EXistException, RecognitionException, XPathException, TokenStreamException + { + String query = "xquery version \"3.1\";\n" + + "for tumbling window $w in (2, 4, 6, 8, 10, 12, 14)\n" + + " start at $s when fn:true()\n" + + " only end at $e when $e - $s eq 2\n" + + "return avg($w)"; + + BrokerPool pool = BrokerPool.getInstance(); + try(final DBBroker broker = pool.getBroker()) { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getCurrentItem()); + assertEquals("s", ((WindowExpr) expr.getFirst()).getStartWindowCondition().getPosVar()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getPreviousItem()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getNextItem()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getEndWindowCondition().getCurrentItem()); + assertEquals("e", ((WindowExpr) expr.getFirst()).getEndWindowCondition().getPosVar()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getEndWindowCondition().getPreviousItem()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getEndWindowCondition().getNextItem()); + assertEquals(true, ((WindowExpr) expr.getFirst()).getEndWindowCondition().getOnly()); + } + } + + @Test + public void tumblingWindowNoEndWindowConditionPositional() throws EXistException, RecognitionException, XPathException, TokenStreamException + { + String query = "xquery version \"3.1\";\n" + + "for tumbling window $w in (2, 4, 6, 8, 10, 12, 14)\n" + + " start at $s when $s mod 3 = 1\n" + + "return { $w }"; + + BrokerPool pool = BrokerPool.getInstance(); + try (final DBBroker broker = pool.getBroker()) + { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) + { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) + { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getCurrentItem()); + assertEquals("s", ((WindowExpr) expr.getFirst()).getStartWindowCondition().getPosVar()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getPreviousItem()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getNextItem()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getEndWindowCondition()); + } + } + + @Test + public void tumblingWindowNoEndWindowConditionCurrentItem() throws EXistException, RecognitionException, XPathException, TokenStreamException + { + String query = "xquery version \"3.1\";\n" + + "for tumbling window $w in (2, 4, 6, 8, 10, 12, 14)\n" + + " start $first when $first mod 3 = 0\n" + + "return { $w }"; + + BrokerPool pool = BrokerPool.getInstance(); + try (final DBBroker broker = pool.getBroker()) + { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) + { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) + { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); + assertEquals("first", ((WindowExpr) expr.getFirst()).getStartWindowCondition().getCurrentItem().getStringValue()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getPosVar()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getPreviousItem()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getNextItem()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getEndWindowCondition()); + } + } + + @Test + public void slidingWindowAvgReturn() throws EXistException, RecognitionException, XPathException, TokenStreamException + { + String query = "xquery version \"3.1\";\n" + + "for sliding window $w in (2, 4, 6, 8, 10, 12, 14)\n" + + " start at $s when fn:true()\n" + + " only end at $e when $e - $s eq 2\n" + + "return avg($w)"; + + BrokerPool pool = BrokerPool.getInstance(); + try (final DBBroker broker = pool.getBroker()) + { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) + { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) + { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); + assertEquals(WindowExpr.WindowType.SLIDING_WINDOW, ((WindowExpr) expr.getFirst()).getWindowType()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getCurrentItem()); + assertEquals("s", ((WindowExpr) expr.getFirst()).getStartWindowCondition().getPosVar()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getPreviousItem()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getNextItem()); + assertEquals("e", ((WindowExpr) expr.getFirst()).getEndWindowCondition().getPosVar()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getEndWindowCondition().getPreviousItem()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getEndWindowCondition().getNextItem()); + assertEquals(true, ((WindowExpr) expr.getFirst()).getEndWindowCondition().getOnly()); + } + } + + @Test + public void slidingWindowEndWithoutOnly() throws EXistException, RecognitionException, XPathException, TokenStreamException + { + String query = "xquery version \"3.1\";\n" + + "for sliding window $w in (2, 4, 6, 8, 10, 12, 14)\n" + + " start at $s when fn:true()\n" + + " end at $e when $e - $s eq 2\n" + + "return { $w }"; + + BrokerPool pool = BrokerPool.getInstance(); + try (final DBBroker broker = pool.getBroker()) + { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) + { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) + { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); + assertEquals(WindowExpr.WindowType.SLIDING_WINDOW, ((WindowExpr) expr.getFirst()).getWindowType()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getCurrentItem()); + assertEquals("s", ((WindowExpr) expr.getFirst()).getStartWindowCondition().getPosVar()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getPreviousItem()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getNextItem()); + assertEquals("e", ((WindowExpr) expr.getFirst()).getEndWindowCondition().getPosVar()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getEndWindowCondition().getPreviousItem()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getEndWindowCondition().getNextItem()); + assertEquals(false, ((WindowExpr) expr.getFirst()).getEndWindowCondition().getOnly()); + } + } + + @Test + public void tumblingWindowRunUp() throws EXistException, RecognitionException, XPathException, TokenStreamException + { + String query = "xquery version \"3.1\";\n" + + "for tumbling window $w in $closings\n" + + " start $first next $second when $first/price < $second/price\n" + + " end $last next $beyond when $last/price > $beyond/price\n" + + "return\n" + + " \n" + + " {fn:data($first/date)}\n" + + " {fn:data($first/price)}\n" + + " {fn:data($last/date)}\n" + + " {fn:data($last/price)}\n" + + " "; + + BrokerPool pool = BrokerPool.getInstance(); + try (final DBBroker broker = pool.getBroker()) + { + // parse the query into the internal syntax tree + XQueryContext context = new XQueryContext(broker.getBrokerPool()); + XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) + { + fail(xparser.getErrorMessage()); + return; + } + + XQueryAST ast = (XQueryAST) xparser.getAST(); + + XQueryTreeParser treeParser = new XQueryTreeParser(context); + PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) + { + fail(treeParser.getErrorMessage()); + return; + } + + assertTrue("Expression should be of type WindowExpr", expr.getFirst() instanceof WindowExpr); + assertEquals(WindowExpr.WindowType.TUMBLING_WINDOW, ((WindowExpr) expr.getFirst()).getWindowType()); + assertEquals("first", ((WindowExpr) expr.getFirst()).getStartWindowCondition().getCurrentItem().getStringValue()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getPosVar()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getStartWindowCondition().getPreviousItem()); + assertEquals("second", ((WindowExpr) expr.getFirst()).getStartWindowCondition().getNextItem().getStringValue()); + assertEquals("last", ((WindowExpr) expr.getFirst()).getEndWindowCondition().getCurrentItem().getStringValue()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getEndWindowCondition().getPosVar()); + assertEquals(null, ((WindowExpr) expr.getFirst()).getEndWindowCondition().getPreviousItem()); + assertEquals("beyond", ((WindowExpr) expr.getFirst()).getEndWindowCondition().getNextItem().getStringValue()); + assertEquals(false, ((WindowExpr) expr.getFirst()).getEndWindowCondition().getOnly()); + } + } }