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..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 @@ -180,6 +180,11 @@ imaginaryTokenDefinitions PRAGMA GTEQ SEQUENCE + TUMBLING_WINDOW + CURRENT_ITEM + PREVIOUS_ITEM + NEXT_ITEM + WINDOW_VARS ; // === XPointer === @@ -692,7 +697,7 @@ expr throws XPathException exprSingle throws XPathException : - ( ( "for" | "let" ) DOLLAR ) => flworExpr + ( ( "for" | "let" ) ("tumbling" | "sliding" | DOLLAR ) ) => flworExpr | ( "try" LCURLY ) => tryCatchExpr | ( ( "some" | "every" ) DOLLAR ) => quantifiedExpr | ( "if" LPAREN ) => ifExpr @@ -799,7 +804,9 @@ flworExpr throws XPathException initialClause throws XPathException : - ( forClause | letClause ) + ( ( "for" DOLLAR ) => forClause + | ( "for" ( "tumbling" | "sliding" ) ) => windowClause + | letClause ) ; intermediateClause throws XPathException @@ -822,6 +829,11 @@ letClause throws XPathException "let"^ letVarBinding ( COMMA! letVarBinding )* ; +windowClause throws XPathException +: + "for"! ("tumbling"|"sliding") "window"^ inVarBinding windowStartCondition ( windowEndCondition )? + ; + inVarBinding throws XPathException { String varName; } : @@ -846,6 +858,37 @@ allowingEmpty "allowing"! "empty" ; +windowStartCondition throws XPathException +: + "start"^ windowVars "when" exprSingle +; + +windowEndCondition throws XPathException +: + ( "only" )? "end"^ windowVars "when" exprSingle +; + +windowVars throws XPathException +{ String currentItemName = null, previousItemName = null, nextItemName = null; String varName = null; } +: + ( DOLLAR! currentItemName=eqName! )? + ( "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 (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; + } +; + letVarBinding throws XPathException { String varName; } : @@ -2224,6 +2267,22 @@ reservedKeywords returns [String name] "map" { name = "map"; } | "array" { name = "array"; } + | + "tumbling" { name = "tumbling"; } + | + "window" { name = "window"; } + | + "start" { name = "start"; } + | + "end" { name = "end"; } + | + "only" { name = "only"; } + | + "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 d1bea32bfdc..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 @@ -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,188 @@ throws PermissionDeniedException, EXistException, XPathException ) )+ ) + | + #( + wc:"window" + { + ForLetClause clause= new ForLetClause(); + clause.type = FLWORClause.ClauseType.WINDOW; + clause.windowConditions = new ArrayList(2); + } + ( + "tumbling" + { + clause.windowType = WindowExpr.WindowType.TUMBLING_WINDOW; + } + | + "sliding" + { + clause.windowType = WindowExpr.WindowType.SLIDING_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 + 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 + 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, endPreviousItem.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 +1904,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 +1938,7 @@ throws PermissionDeniedException, EXistException, XPathException } ((GroupByClause)expr).setGroupSpecs(specs); } - } + } if (!(action instanceof FLWORClause)) expr.setReturnExpression(new DebuggableExpression(action)); else { 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..28a5712ea6b --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/WindowCondition.java @@ -0,0 +1,73 @@ +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(); + } + + 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; + } +} 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..c3b3ae2f240 --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/WindowExpr.java @@ -0,0 +1,138 @@ +/* + * 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, + SLIDING_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 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..27bc9f8db4d --- /dev/null +++ b/exist-core/src/test/java/org/exist/xquery/WindowClauseTest.java @@ -0,0 +1,535 @@ +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()); + } + } + + @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()); + } + } +}