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..70558367bea 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 @@ -804,7 +804,7 @@ initialClause throws XPathException intermediateClause throws XPathException : - ( initialClause | whereClause | groupByClause | orderByClause ) + ( initialClause | whereClause | groupByClause | orderByClause | countClause ) ; whereClause throws XPathException @@ -812,6 +812,13 @@ whereClause throws XPathException "where"^ exprSingle ; +countClause throws XPathException +{ String varName; } +: + "count"^ DOLLAR! varName=varName! + { #countClause = #(#countClause, #[VARIABLE_BINDING, varName]); } + ; + forClause throws XPathException : "for"^ inVarBinding ( COMMA! inVarBinding )* @@ -2224,6 +2231,8 @@ reservedKeywords returns [String name] "map" { name = "map"; } | "array" { name = "array"; } + | + "count" { name = "count"; } ; /** 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..f49d9253216 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 @@ -1709,6 +1709,19 @@ throws PermissionDeniedException, EXistException, XPathException clauses.add(clause); } ) + | + #( + co:"count" + countVarName:VARIABLE_BINDING + { + ForLetClause clause = new ForLetClause(); + clause.ast = co; + clause.varName = countVarName.getText(); + clause.type = FLWORClause.ClauseType.COUNT; + clause.inputSequence = null; + clauses.add(clause); + } + ) )+ step=expr [(PathExpr) action] { @@ -1728,6 +1741,9 @@ throws PermissionDeniedException, EXistException, XPathException case WHERE: expr = new WhereClause(context, new DebuggableExpression(clause.inputSequence)); break; + case COUNT: + expr = new CountClause(context, clause.varName); + break; default: expr= new ForExpr(context, clause.allowEmpty); break; diff --git a/exist-core/src/main/java/org/exist/xquery/CountClause.java b/exist-core/src/main/java/org/exist/xquery/CountClause.java new file mode 100644 index 00000000000..828b13dc73b --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/CountClause.java @@ -0,0 +1,52 @@ + +package org.exist.xquery; + +import org.exist.dom.persistent.*; +import org.exist.xquery.util.ExpressionDumper; +import org.exist.xquery.value.Item; +import org.exist.xquery.value.Sequence; +import org.exist.xquery.value.Type; + +/** + * Implements a count clause inside a FLWOR expressions. + * + * @author + */ +public class CountClause extends AbstractFLWORClause { + + protected String varName; + + public CountClause(XQueryContext context, String countName) { + super(context); + this.varName = countName; + } + + @Override + public ClauseType getType() { + return ClauseType.COUNT; + } + + public String getVarName() { + return varName; + } + + @Override + public void analyze(AnalyzeContextInfo contextInfo) throws XPathException + { + + } + + @Override + public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException + { + return null; + } + + @Override + public void dump(ExpressionDumper dumper) { + dumper.display("count", this.getLine()); + dumper.startIndent(); + dumper.display(this.varName); + dumper.endIndent().nl(); + } +} \ No newline at end of file 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..7cb5305717c 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, COUNT } /** diff --git a/exist-core/src/test/java/org/exist/xquery/CountExpressionTest.java b/exist-core/src/test/java/org/exist/xquery/CountExpressionTest.java new file mode 100644 index 00000000000..0e6baa4d31f --- /dev/null +++ b/exist-core/src/test/java/org/exist/xquery/CountExpressionTest.java @@ -0,0 +1,71 @@ +package org.exist.xquery; + +import antlr.RecognitionException; +import antlr.TokenStreamException; +import antlr.collections.AST; +import org.exist.EXistException; +import org.exist.storage.BrokerPool; +import org.exist.storage.DBBroker; +import org.exist.test.ExistEmbeddedServer; +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 java.io.StringReader; + +import static org.junit.Assert.*; + +public class CountExpressionTest +{ + + @ClassRule + public static final ExistEmbeddedServer existEmbeddedServer = new ExistEmbeddedServer(true, true); + + @Test + public void countTest() throws EXistException, RecognitionException, XPathException, TokenStreamException + { + String query = "xquery version \"3.1\";\n" + + "for $p in $products\n" + + "order by $p/sales descending\n" + + "count $rank\n" + + "where $rank <= 3\n" + + "return\n" + + " \n" + + " {$p/name, $p/sales}\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; + } + + // count keyword + assertEquals(XQueryParser.LITERAL_count, ast.getNextSibling().getFirstChild().getNextSibling().getNextSibling().getType()); + // rank variable binding + assertEquals(XQueryParser.VARIABLE_BINDING, ast.getNextSibling().getFirstChild().getNextSibling().getNextSibling().getFirstChild().getType()); + assertTrue(((ForExpr)expr.getFirst()).returnExpr instanceof OrderByClause); + assertTrue(((OrderByClause)(((ForExpr)expr.getFirst()).returnExpr)).returnExpr instanceof CountClause); + assertEquals("rank", ((CountClause)((OrderByClause)(((ForExpr)expr.getFirst()).returnExpr)).returnExpr).varName); + } + } +}