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 f5f3cb0209a..345fadfa657 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
@@ -103,6 +103,10 @@ options {
return buf.toString();
}
+ public Exception getLastException() {
+ return (Exception) exceptions.get(exceptions.size() - 1);
+ }
+
public String getXQDoc() {
return lexer.getXQDoc();
}
@@ -802,7 +806,7 @@ initialClause throws XPathException
intermediateClause throws XPathException
:
- ( initialClause | whereClause | groupByClause | orderByClause )
+ ( initialClause | whereClause | groupByClause | orderByClause | countClause )
;
whereClause throws XPathException
@@ -810,6 +814,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 )*
@@ -2223,6 +2234,8 @@ reservedKeywords returns [String name]
|
"array" { name = "array"; }
|
+ "count" { name = "count"; }
+ |
"copy-namespaces" { name = "copy-namespaces"; }
|
"empty-sequence" { name = "empty-sequence"; }
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 e965f13c295..5c74f31de50 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
@@ -128,9 +128,9 @@ options {
private static class ForLetClause {
XQueryAST ast;
- String varName;
+ QName varName;
SequenceType sequenceType= null;
- String posVar= null;
+ QName posVar = null;
Expression inputSequence;
Expression action;
FLWORClause.ClauseType type = FLWORClause.ClauseType.FOR;
@@ -1398,7 +1398,11 @@ throws PermissionDeniedException, EXistException, XPathException
)?
step=expr[inputSequence]
{
- clause.varName= someVarName.getText();
+ try {
+ clause.varName = QName.parse(staticContext, someVarName.getText(), null);
+ } catch (final IllegalQNameException iqe) {
+ throw new XPathException(someVarName.getLine(), someVarName.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + someVarName.getText());
+ }
clause.inputSequence= inputSequence;
clauses.add(clause);
}
@@ -1449,7 +1453,11 @@ throws PermissionDeniedException, EXistException, XPathException
)?
step=expr[inputSequence]
{
- clause.varName= everyVarName.getText();
+ try {
+ clause.varName = QName.parse(staticContext, everyVarName.getText(), null);
+ } catch (final IllegalQNameException iqe) {
+ throw new XPathException(everyVarName.getLine(), everyVarName.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + everyVarName.getText());
+ }
clause.inputSequence= inputSequence;
clauses.add(clause);
}
@@ -1585,11 +1593,21 @@ throws PermissionDeniedException, EXistException, XPathException
)?
(
posVar:POSITIONAL_VAR
- { clause.posVar= posVar.getText(); }
+ {
+ try {
+ clause.posVar = QName.parse(staticContext, posVar.getText(), null);
+ } catch (final IllegalQNameException iqe) {
+ throw new XPathException(posVar.getLine(), posVar.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + posVar.getText());
+ }
+ }
)?
step=expr [inputSequence]
{
- clause.varName= varName.getText();
+ try {
+ clause.varName = QName.parse(staticContext, varName.getText(), null);
+ } catch (final IllegalQNameException iqe) {
+ throw new XPathException(varName.getLine(), varName.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + varName.getText());
+ }
clause.inputSequence= inputSequence;
clauses.add(clause);
}
@@ -1618,7 +1636,11 @@ throws PermissionDeniedException, EXistException, XPathException
)?
step=expr [inputSequence]
{
- clause.varName= letVarName.getText();
+ try {
+ clause.varName = QName.parse(staticContext, letVarName.getText(), null);
+ } catch (final IllegalQNameException iqe) {
+ throw new XPathException(letVarName.getLine(), letVarName.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + letVarName.getText());
+ }
clause.inputSequence= inputSequence;
clauses.add(clause);
}
@@ -1752,29 +1774,49 @@ throws PermissionDeniedException, EXistException, XPathException
clauses.add(clause);
}
)
- )+
- step=expr [(PathExpr) action]
- {
+ |
+ #(
+ co:"count"
+ countVarName:VARIABLE_BINDING
+ {
+ ForLetClause clause = new ForLetClause();
+ clause.ast = co;
+ try {
+ clause.varName = QName.parse(staticContext, countVarName.getText(), null);
+ } catch (final IllegalQNameException iqe) {
+ throw new XPathException(countVarName.getLine(), countVarName.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + countVarName.getText());
+ }
+ clause.type = FLWORClause.ClauseType.COUNT;
+ clause.inputSequence = null;
+ clauses.add(clause);
+ }
+ )
+ )+
+ step=expr [(PathExpr) action]
+ {
for (int i= clauses.size() - 1; i >= 0; i--) {
ForLetClause clause= (ForLetClause) clauses.get(i);
FLWORClause expr;
switch (clause.type) {
case LET:
- expr= new LetExpr(context);
+ expr = new LetExpr(context);
expr.setASTNode(expr_AST_in);
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 COUNT:
+ expr = new CountClause(context, clause.varName);
+ break;
+ default:
+ expr = new ForExpr(context, clause.allowEmpty);
+ break;
}
expr.setASTNode(clause.ast);
if (clause.type == FLWORClause.ClauseType.FOR || clause.type == FLWORClause.ClauseType.LET) {
diff --git a/exist-core/src/main/java/org/exist/xquery/AbstractFLWORClause.java b/exist-core/src/main/java/org/exist/xquery/AbstractFLWORClause.java
index 99775bc4c40..8e987e78f72 100644
--- a/exist-core/src/main/java/org/exist/xquery/AbstractFLWORClause.java
+++ b/exist-core/src/main/java/org/exist/xquery/AbstractFLWORClause.java
@@ -41,14 +41,10 @@ public AbstractFLWORClause(XQueryContext context) {
}
@Override
- public LocalVariable createVariable(final String name) throws XPathException {
- try {
- final LocalVariable var = new LocalVariable(QName.parse(context, name, null));
- firstVar = var;
- return var;
- } catch (final IllegalQNameException e) {
- throw new XPathException(this, ErrorCodes.XPST0081, "No namespace defined for prefix " + name);
- }
+ public LocalVariable createVariable(final QName name) throws XPathException {
+ final LocalVariable var = new LocalVariable(name);
+ firstVar = var;
+ return var;
}
@Override
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 2975f22c470..ed64ab74b5c 100644
--- a/exist-core/src/main/java/org/exist/xquery/BasicExpressionVisitor.java
+++ b/exist-core/src/main/java/org/exist/xquery/BasicExpressionVisitor.java
@@ -180,6 +180,11 @@ public void visitOrderByClause(final OrderByClause orderBy) {
// Nothing to do
}
+ @Override
+ public void visitCountClause(final CountClause count) {
+ // Nothing to do
+ }
+
@Override
public void visitGroupByClause(final GroupByClause groupBy) {
// Nothing to do
diff --git a/exist-core/src/main/java/org/exist/xquery/BindingExpression.java b/exist-core/src/main/java/org/exist/xquery/BindingExpression.java
index c75d6d1f151..57ea725e6c3 100644
--- a/exist-core/src/main/java/org/exist/xquery/BindingExpression.java
+++ b/exist-core/src/main/java/org/exist/xquery/BindingExpression.java
@@ -23,13 +23,14 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.exist.dom.QName;
import org.exist.dom.persistent.*;
import org.exist.numbering.NodeId;
import org.exist.storage.UpdateListener;
import org.exist.xquery.value.*;
/**
- * Abstract superclass for the variable binding expressions "for" and "let".
+ * Abstract superclass for the variable binding expressions "for", "let", and "count".
*
* @author Wolfgang Meier
*/
@@ -41,22 +42,20 @@ public abstract class BindingExpression extends AbstractFLWORClause implements R
protected final static SequenceType POSITIONAL_VAR_TYPE =
new SequenceType(Type.INTEGER, Cardinality.EXACTLY_ONE);
- protected String varName;
+ protected QName varName;
protected SequenceType sequenceType = null;
protected Expression inputSequence;
-
private ExprUpdateListener listener;
-
- public BindingExpression(XQueryContext context) {
+ public BindingExpression(final XQueryContext context) {
super(context);
}
- public void setVariable(String qname) {
- varName = qname;
+ public void setVariable(final QName varName) {
+ this.varName = varName;
}
- public String getVariable() {
+ public QName getVariable() {
return this.varName;
}
@@ -77,52 +76,45 @@ public Expression getInputSequence() {
return this.inputSequence;
}
- /* (non-Javadoc)
- * @see org.exist.xquery.Expression#analyze(org.exist.xquery.Expression, int)
- */
- public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
+ @Override
+ public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException {
unordered = (contextInfo.getFlags() & UNORDERED) > 0;
}
@Override
public Sequence postEval(Sequence seq) throws XPathException {
- if (returnExpr instanceof FLWORClause) {
- seq = ((FLWORClause)returnExpr).postEval(seq);
+ if (returnExpr instanceof FLWORClause flworClause) {
+ seq = flworClause.postEval(seq);
}
return super.postEval(seq);
}
- /* (non-Javadoc)
- * @see org.exist.xquery.Expression#preselect(org.exist.dom.persistent.DocumentSet, org.exist.xquery.StaticContext)
- */
- public DocumentSet preselect(DocumentSet in_docs) throws XPathException {
- return in_docs;
+ public DocumentSet preselect(final DocumentSet docs) throws XPathException {
+ return docs;
}
- /* (non-Javadoc)
- * @see org.exist.xquery.AbstractExpression#resetState()
- */
- public void resetState(boolean postOptimization) {
+ @Override
+ public void resetState(final boolean postOptimization) {
super.resetState(postOptimization);
inputSequence.resetState(postOptimization);
returnExpr.resetState(postOptimization);
}
- public final static void setContext(int contextId, Sequence seq) throws XPathException {
+ public static void setContext(final int contextId, final Sequence seq) throws XPathException {
if (seq instanceof VirtualNodeSet) {
((VirtualNodeSet)seq).setInPredicate(true);
((VirtualNodeSet)seq).setSelfIsContext();
} else {
- Item next;
- for (final SequenceIterator i = seq.unorderedIterator(); i.hasNext();) {
- next = i.nextItem();
- if (next instanceof NodeProxy)
- {((NodeProxy) next).addContextNode(contextId, (NodeProxy) next);}
+ for (final SequenceIterator i = seq.unorderedIterator(); i.hasNext(); ) {
+ final Item next = i.nextItem();
+ if (next instanceof NodeProxy) {
+ ((NodeProxy) next).addContextNode(contextId, (NodeProxy) next);
+ }
}
}
}
- public final static void clearContext(int contextId, Sequence seq) throws XPathException {
+ public final static void clearContext(final int contextId, final Sequence seq) throws XPathException {
if (seq != null && !(seq instanceof VirtualNodeSet)) {
seq.clearContext(contextId);
}
@@ -132,27 +124,29 @@ protected void registerUpdateListener(final Sequence sequence) {
if (listener == null) {
listener = new ExprUpdateListener(sequence);
context.registerUpdateListener(listener);
- } else
- {listener.setSequence(sequence);}
+ } else {
+ listener.setSequence(sequence);
+ }
}
private class ExprUpdateListener implements UpdateListener {
private Sequence sequence;
- public ExprUpdateListener(Sequence sequence) {
+ public ExprUpdateListener(final Sequence sequence) {
this.sequence = sequence;
}
- public void setSequence(Sequence sequence) {
+ public void setSequence(final Sequence sequence) {
this.sequence = sequence;
}
@Override
- public void documentUpdated(DocumentImpl document, int event) {
+ public void documentUpdated(final DocumentImpl document, final int event) {
+ // no-op
}
@Override
- public void nodeMoved(NodeId oldNodeId, NodeHandle newNode) {
+ public void nodeMoved(final NodeId oldNodeId, final NodeHandle newNode) {
sequence.nodeMoved(oldNodeId, newNode);
}
@@ -163,6 +157,7 @@ public void unsubscribe() {
@Override
public void debug() {
+ // no-op
}
}
@@ -178,11 +173,12 @@ public int returnsType() {
/* RewritableExpression API */
@Override
- public void replace(Expression oldExpr, Expression newExpr) {
- if (inputSequence == oldExpr)
- {inputSequence = newExpr;}
- else if (returnExpr == oldExpr)
- {returnExpr = newExpr;}
+ public void replace(final Expression oldExpr, final Expression newExpr) {
+ if (inputSequence == oldExpr) {
+ inputSequence = newExpr;
+ } else if (returnExpr == oldExpr) {
+ returnExpr = newExpr;
+ }
}
@Override
@@ -196,7 +192,8 @@ public Expression getFirst() {
}
@Override
- public void remove(Expression oldExpr) throws XPathException {
+ public void remove(final Expression oldExpr) throws XPathException {
+ // no-op
}
/* END RewritableExpression API */
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..4ea6dc4a7f1
--- /dev/null
+++ b/exist-core/src/main/java/org/exist/xquery/CountClause.java
@@ -0,0 +1,211 @@
+/*
+ * 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.IntegerValue;
+import org.exist.xquery.value.Item;
+import org.exist.xquery.value.Sequence;
+import org.exist.xquery.value.SequenceType;
+import org.exist.xquery.value.Type;
+import org.exist.xquery.value.ValueSequence;
+
+/**
+ * Implements a count clause inside a FLWOR expressions.
+ *
+ * @author Adam Retter
+ * @author Gabriele Tomassetti
+ */
+public class CountClause extends AbstractFLWORClause {
+
+ private static final SequenceType countVarType = new SequenceType(Type.INTEGER, Cardinality.EXACTLY_ONE);
+
+ final QName varName;
+
+ // the count itself
+ private long count = 0;
+ private int step = 1;
+
+ public CountClause(final XQueryContext context, final QName varName) {
+ super(context);
+ this.varName = varName;
+ }
+
+ @Override
+ public ClauseType getType() {
+ return ClauseType.COUNT;
+ }
+
+ public QName getVarName() {
+ return varName;
+ }
+
+ @Override
+ public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException {
+ contextInfo.setParent(this);
+ unordered = (contextInfo.getFlags() & UNORDERED) > 0;
+
+ // Save the local variable stack
+ final LocalVariable mark = context.markLocalVariables(false);
+ try {
+ final AnalyzeContextInfo varContextInfo = new AnalyzeContextInfo(contextInfo);
+
+ // Declare the count variable
+ final LocalVariable countVar = new LocalVariable(varName);
+ countVar.setSequenceType(countVarType);
+ countVar.setStaticType(varContextInfo.getStaticReturnType());
+ context.declareVariableBinding(countVar);
+
+ // analyze the return expression
+ final AnalyzeContextInfo newContextInfo = new AnalyzeContextInfo(contextInfo);
+ returnExpr.analyze(newContextInfo);
+
+ } finally {
+ // restore the local variable stack
+ context.popLocalVariables(mark);
+ }
+ }
+
+ @Override
+ public Sequence preEval(final Sequence seq) throws XPathException {
+ // determine whether to count down or up
+ this.step = hasPreviousOrderByDescending() ? -1 : 1;
+
+ // get the count start position
+ if (this.step == 1) {
+ this.count = 0;
+ } else {
+ this.count = seq.getItemCountLong() + 1;
+ }
+
+ return super.preEval(seq);
+ }
+
+ private boolean hasPreviousOrderByDescending() {
+ FLWORClause prev = getPreviousClause();
+ while (prev != null) {
+ switch (prev.getType()) {
+ case LET, GROUPBY, FOR -> {
+ return false;
+ }
+ case ORDERBY -> {
+ return isDescending(((OrderByClause) prev).getOrderSpecs());
+ }
+ default -> prev = prev.getPreviousClause();
+ }
+ }
+ return true;
+ }
+ private boolean isDescending(final OrderSpec[] orderSpecs) {
+ for (final OrderSpec orderSpec : orderSpecs) {
+ if ((orderSpec.getModifiers() & OrderSpec.DESCENDING_ORDER) == OrderSpec.DESCENDING_ORDER) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException {
+ if (context.getProfiler().isEnabled()) {
+ context.getProfiler().start(this);
+ context.getProfiler().message(this, Profiler.DEPENDENCIES,
+ "DEPENDENCIES", Dependency.getDependenciesName(this.getDependencies()));
+ if (contextSequence != null) {
+ context.getProfiler().message(this, Profiler.START_SEQUENCES,
+ "CONTEXT SEQUENCE", contextSequence);
+ }
+ if (contextItem != null) {
+ context.getProfiler().message(this, Profiler.START_SEQUENCES,
+ "CONTEXT ITEM", contextItem.toSequence());
+ }
+ }
+
+ context.expressionStart(this);
+
+ final Sequence resultSequence = new ValueSequence(unordered);
+
+ // update the count
+ count = count + step;
+
+ // Save the local variable stack
+ final LocalVariable mark = context.markLocalVariables(false);
+ try {
+
+ // Declare the count variable
+ final LocalVariable countVar = createVariable(varName);
+ countVar.setSequenceType(countVarType);
+ context.declareVariableBinding(countVar);
+
+ // set the binding for the count
+ countVar.setValue(new IntegerValue(count));
+
+ // eval the return expression on the window binding
+ resultSequence.addAll(returnExpr.eval(null, null));
+
+ // free resources
+ countVar.destroy(context, resultSequence);
+
+ } finally {
+ // restore the local variable stack
+ context.popLocalVariables(mark, resultSequence);
+ }
+
+ setActualReturnType(resultSequence.getItemType());
+
+ context.expressionEnd(this);
+ if (context.getProfiler().isEnabled()) {
+ context.getProfiler().end(this, "", resultSequence);
+ }
+
+ return resultSequence;
+ }
+
+ @Override
+ public Sequence postEval(Sequence seq) throws XPathException {
+ if (returnExpr instanceof FLWORClause flworClause) {
+ seq = flworClause.postEval(seq);
+ }
+ return super.postEval(seq);
+ }
+
+ @Override
+ public void dump(final ExpressionDumper dumper) {
+ dumper.display("count", this.getLine());
+ dumper.startIndent();
+ dumper.display(this.varName);
+ dumper.endIndent().nl();
+ }
+
+ public String toString() {
+ final StringBuilder result = new StringBuilder();
+ result.append("count ");
+ result.append("$").append(this.varName);
+ return result.toString();
+ }
+
+ @Override
+ public void accept(final ExpressionVisitor visitor) {
+ visitor.visitCountClause(this);
+ }
+}
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..343a4a2a60f 100644
--- a/exist-core/src/main/java/org/exist/xquery/ExpressionVisitor.java
+++ b/exist-core/src/main/java/org/exist/xquery/ExpressionVisitor.java
@@ -74,6 +74,8 @@ public interface ExpressionVisitor {
void visitLetExpression(LetExpr letExpr);
+ void visitCountClause(CountClause count);
+
void visitOrderByClause(OrderByClause orderBy);
void visitGroupByClause(GroupByClause groupBy);
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..60a665579f0 100644
--- a/exist-core/src/main/java/org/exist/xquery/FLWORClause.java
+++ b/exist-core/src/main/java/org/exist/xquery/FLWORClause.java
@@ -21,6 +21,7 @@
*/
package org.exist.xquery;
+import org.exist.dom.QName;
import org.exist.xquery.value.Sequence;
/**
@@ -31,7 +32,7 @@
public interface FLWORClause extends Expression {
enum ClauseType {
- FOR, LET, GROUPBY, ORDERBY, WHERE, SOME, EVERY
+ FOR, LET, GROUPBY, ORDERBY, WHERE, SOME, EVERY, COUNT
}
/**
@@ -102,7 +103,7 @@ enum ClauseType {
* @return a new local variable, registered in the context
* @throws XPathException if an error occurs whilst creating the variable
*/
- LocalVariable createVariable(String name) throws XPathException;
+ LocalVariable createVariable(QName name) throws XPathException;
/**
* Returns the first variable created by this FLWOR clause for reference
diff --git a/exist-core/src/main/java/org/exist/xquery/ForExpr.java b/exist-core/src/main/java/org/exist/xquery/ForExpr.java
index bb1fceec5ac..47a3f278e97 100644
--- a/exist-core/src/main/java/org/exist/xquery/ForExpr.java
+++ b/exist-core/src/main/java/org/exist/xquery/ForExpr.java
@@ -33,7 +33,7 @@
*/
public class ForExpr extends BindingExpression {
- private String positionalVariable = null;
+ private QName positionalVariable = null;
private boolean allowEmpty = false;
private boolean isOuterFor = true;
@@ -53,7 +53,7 @@ public ClauseType getType() {
*
* @param var the name of the variable to set
*/
- public void setPositionalVariable(String var) {
+ public void setPositionalVariable(final QName var) {
positionalVariable = var;
}
@@ -69,7 +69,7 @@ public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
final AnalyzeContextInfo varContextInfo = new AnalyzeContextInfo(contextInfo);
inputSequence.analyze(varContextInfo);
// Declare the iteration variable
- final LocalVariable inVar = new LocalVariable(QName.parse(context, varName, null));
+ final LocalVariable inVar = new LocalVariable(varName);
inVar.setSequenceType(sequenceType);
inVar.setStaticType(varContextInfo.getStaticReturnType());
context.declareVariableBinding(inVar);
@@ -80,7 +80,7 @@ public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
throw new XPathException(this, ErrorCodes.XQST0089,
"bound variable and positional variable have the same name");
}
- final LocalVariable posVar = new LocalVariable(QName.parse(context, positionalVariable, null));
+ final LocalVariable posVar = new LocalVariable(positionalVariable);
posVar.setSequenceType(POSITIONAL_VAR_TYPE);
posVar.setStaticType(Type.INTEGER);
context.declareVariableBinding(posVar);
@@ -89,8 +89,6 @@ public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
final AnalyzeContextInfo newContextInfo = new AnalyzeContextInfo(contextInfo);
newContextInfo.addFlag(SINGLE_STEP_EXECUTION);
returnExpr.analyze(newContextInfo);
- } catch (final QName.IllegalQNameException e) {
- throw new XPathException(this, ErrorCodes.XPST0081, "No namespace defined for prefix");
} finally {
// restore the local variable stack
context.popLocalVariables(mark);
@@ -135,7 +133,7 @@ public Sequence eval(Sequence contextSequence, Item contextItem)
// Declare positional variable
LocalVariable at = null;
if (positionalVariable != null) {
- at = new LocalVariable(QName.parse(context, positionalVariable, null));
+ at = new LocalVariable(positionalVariable);
at.setSequenceType(POSITIONAL_VAR_TYPE);
context.declareVariableBinding(at);
}
@@ -187,8 +185,6 @@ public Sequence eval(Sequence contextSequence, Item contextItem)
processItem(var, i.nextItem(), in, resultSequence, at, p);
}
}
- } catch (final QName.IllegalQNameException e) {
- throw new XPathException(this, ErrorCodes.XPST0081, "No namespace defined for prefix " + positionalVariable);
} finally {
// restore the local variable stack
context.popLocalVariables(mark, resultSequence);
diff --git a/exist-core/src/main/java/org/exist/xquery/GroupByClause.java b/exist-core/src/main/java/org/exist/xquery/GroupByClause.java
index 1d22c8c277f..5ea83ae2ff6 100644
--- a/exist-core/src/main/java/org/exist/xquery/GroupByClause.java
+++ b/exist-core/src/main/java/org/exist/xquery/GroupByClause.java
@@ -172,8 +172,8 @@ public Sequence postEval(final Sequence seq) throws XPathException {
context.popLocalVariables(mark, result);
}
- if (returnExpr instanceof FLWORClause) {
- result = ((FLWORClause) returnExpr).postEval(result);
+ if (returnExpr instanceof FLWORClause flworClause) {
+ result = flworClause.postEval(result);
}
result = super.postEval(result);
return result;
diff --git a/exist-core/src/main/java/org/exist/xquery/LetExpr.java b/exist-core/src/main/java/org/exist/xquery/LetExpr.java
index 05dae3376c6..0ba839b3fc8 100644
--- a/exist-core/src/main/java/org/exist/xquery/LetExpr.java
+++ b/exist-core/src/main/java/org/exist/xquery/LetExpr.java
@@ -53,7 +53,7 @@ public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException
final AnalyzeContextInfo varContextInfo = new AnalyzeContextInfo(contextInfo);
inputSequence.analyze(varContextInfo);
//Declare the iteration variable
- final LocalVariable inVar = new LocalVariable(QName.parse(context, varName, null));
+ final LocalVariable inVar = new LocalVariable(varName);
inVar.setSequenceType(sequenceType);
inVar.setStaticType(varContextInfo.getStaticReturnType());
context.declareVariableBinding(inVar);
@@ -61,8 +61,6 @@ public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException
context.setContextSequencePosition(0, null);
returnExpr.analyze(contextInfo);
- } catch (final QName.IllegalQNameException e) {
- throw new XPathException(this, ErrorCodes.XPST0081, "No namespace defined for prefix " + varName);
} finally {
// restore the local variable stack
context.popLocalVariables(mark);
diff --git a/exist-core/src/main/java/org/exist/xquery/OrderByClause.java b/exist-core/src/main/java/org/exist/xquery/OrderByClause.java
index 2abb6b7038f..84bd07c5a1e 100644
--- a/exist-core/src/main/java/org/exist/xquery/OrderByClause.java
+++ b/exist-core/src/main/java/org/exist/xquery/OrderByClause.java
@@ -95,8 +95,8 @@ public Sequence postEval(Sequence seq) throws XPathException {
orderedResult.sort();
Sequence result = orderedResult;
- if (getReturnExpression() instanceof FLWORClause) {
- result = ((FLWORClause) getReturnExpression()).postEval(result);
+ if (getReturnExpression() instanceof FLWORClause flworClause) {
+ result = flworClause.postEval(result);
}
return super.postEval(result);
}
@@ -112,6 +112,19 @@ public void dump(ExpressionDumper dumper) {
dumper.nl();
}
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("order by ");
+ for (int i = 0; i < orderSpecs.length; i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ builder.append(orderSpecs[i]);
+ }
+ return builder.toString();
+ }
+
@Override
public void accept(ExpressionVisitor visitor) {
visitor.visitOrderByClause(this);
diff --git a/exist-core/src/main/java/org/exist/xquery/QuantifiedExpression.java b/exist-core/src/main/java/org/exist/xquery/QuantifiedExpression.java
index 39d06b3f602..34950d32b97 100644
--- a/exist-core/src/main/java/org/exist/xquery/QuantifiedExpression.java
+++ b/exist-core/src/main/java/org/exist/xquery/QuantifiedExpression.java
@@ -21,7 +21,6 @@
*/
package org.exist.xquery;
-import org.exist.dom.QName;
import org.exist.xquery.util.ExpressionDumper;
import org.exist.xquery.value.BooleanValue;
import org.exist.xquery.value.Item;
@@ -63,13 +62,11 @@ public ClauseType getType() {
public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
final LocalVariable mark = context.markLocalVariables(false);
try {
- context.declareVariableBinding(new LocalVariable(QName.parse(context, varName, null)));
+ context.declareVariableBinding(new LocalVariable(varName));
contextInfo.setParent(this);
inputSequence.analyze(contextInfo);
returnExpr.analyze(contextInfo);
- } catch (final QName.IllegalQNameException e) {
- throw new XPathException(this, ErrorCodes.XPST0081, "No namespace defined for prefix " + varName);
} finally {
context.popLocalVariables(mark);
}
@@ -87,12 +84,7 @@ public Sequence eval(Sequence contextSequence, Item contextItem)
{context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT ITEM", contextItem.toSequence());}
}
- final LocalVariable var;
- try {
- var = new LocalVariable(QName.parse(context, varName, null));
- } catch (final QName.IllegalQNameException e) {
- throw new XPathException(this, ErrorCodes.XPST0081, "No namespace defined for prefix " + varName);
- }
+ final LocalVariable var = new LocalVariable(varName);
final Sequence inSeq = inputSequence.eval(contextSequence, contextItem);
if (sequenceType != null) {
diff --git a/exist-core/src/main/java/org/exist/xquery/WhereClause.java b/exist-core/src/main/java/org/exist/xquery/WhereClause.java
index 031838963d6..480781c9ce4 100644
--- a/exist-core/src/main/java/org/exist/xquery/WhereClause.java
+++ b/exist-core/src/main/java/org/exist/xquery/WhereClause.java
@@ -133,8 +133,8 @@ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathExc
@Override
public Sequence postEval(Sequence seq) throws XPathException {
fastTrack = false;
- if (returnExpr instanceof FLWORClause) {
- seq = ((FLWORClause) returnExpr).postEval(seq);
+ if (returnExpr instanceof FLWORClause flworClause) {
+ seq = flworClause.postEval(seq);
}
return super.postEval(seq);
}
diff --git a/exist-core/src/main/java/org/exist/xquery/XQuery.java b/exist-core/src/main/java/org/exist/xquery/XQuery.java
index 2009777436e..c3908470605 100644
--- a/exist-core/src/main/java/org/exist/xquery/XQuery.java
+++ b/exist-core/src/main/java/org/exist/xquery/XQuery.java
@@ -224,10 +224,18 @@ private CompiledXQuery compile(final XQueryContext context, final Reader reader,
} else {
parser.xpath();
}
-
- if(parser.foundErrors()) {
- LOG.debug(parser.getErrorMessage());
- throw new StaticXQueryException(context.getRootExpression(), parser.getErrorMessage());
+
+ if (parser.foundErrors()) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(parser.getErrorMessage());
+ }
+ final Exception lastException = parser.getLastException();
+ if (lastException != null && lastException instanceof XPathException) {
+ final XPathException xpe = (XPathException) lastException;
+ throw new StaticXQueryException(xpe.getColumn(), xpe.getLine(), parser.getErrorMessage(), xpe);
+ } else {
+ throw new StaticXQueryException(context.getRootExpression(), parser.getErrorMessage());
+ }
}
final AST ast = parser.getAST();
diff --git a/exist-core/src/main/java/org/exist/xquery/value/DoubleValue.java b/exist-core/src/main/java/org/exist/xquery/value/DoubleValue.java
index 47cf110194b..e888892da82 100644
--- a/exist-core/src/main/java/org/exist/xquery/value/DoubleValue.java
+++ b/exist-core/src/main/java/org/exist/xquery/value/DoubleValue.java
@@ -146,19 +146,25 @@ public boolean isPositive() {
@Override
protected @Nullable IntSupplier createComparisonWith(final NumericValue other) {
- if (other instanceof IntegerValue) {
- return () -> BigDecimal.valueOf(value).compareTo(new BigDecimal(((IntegerValue) other).value));
- }
- if (other instanceof DecimalValue) {
- return () -> BigDecimal.valueOf(value).compareTo(((DecimalValue) other).value);
- }
- if (other instanceof DoubleValue) {
- return () -> Double.compare(value, ((DoubleValue) other).value);
- }
- if (other instanceof FloatValue) {
- return () -> Double.compare(value, ((FloatValue) other).value);
- }
- return null;
+ final IntSupplier comparison;
+ if (isNaN()) {
+ comparison = () -> Constants.INFERIOR;
+ } else if (other.isNaN()) {
+ comparison = () -> Constants.SUPERIOR;
+ } else if (isInfinite() && other.isInfinite() && isPositive() == other.isPositive()) {
+ comparison = () -> Constants.EQUAL;
+ } else if (other instanceof IntegerValue iv) {
+ comparison = () -> BigDecimal.valueOf(value).compareTo(new BigDecimal(iv.value));
+ } else if (other instanceof DecimalValue dv) {
+ comparison = () -> BigDecimal.valueOf(value).compareTo(dv.value);
+ } else if (other instanceof DoubleValue dv) {
+ comparison = () -> Double.compare(value, dv.value);
+ } else if (other instanceof FloatValue fv) {
+ comparison = () -> Double.compare(value, fv.value);
+ } else {
+ comparison = null;
+ }
+ return comparison;
}
@Override
diff --git a/exist-core/src/main/java/org/exist/xquery/value/FloatValue.java b/exist-core/src/main/java/org/exist/xquery/value/FloatValue.java
index b9538b24fc3..1d4b7847c2c 100644
--- a/exist-core/src/main/java/org/exist/xquery/value/FloatValue.java
+++ b/exist-core/src/main/java/org/exist/xquery/value/FloatValue.java
@@ -150,7 +150,13 @@ public boolean isPositive() {
@Override
protected @Nullable IntSupplier createComparisonWith(final NumericValue other) {
final IntSupplier comparison;
- if (other instanceof IntegerValue) {
+ if (isNaN()) {
+ comparison = () -> Constants.INFERIOR;
+ } else if (other.isNaN()) {
+ comparison = () -> Constants.SUPERIOR;
+ } else if (isInfinite() && other.isInfinite() && isPositive() == other.isPositive()) {
+ comparison = () -> Constants.EQUAL;
+ } else if (other instanceof IntegerValue) {
comparison = () -> BigDecimal.valueOf(value).compareTo(new BigDecimal(((IntegerValue)other).value));
} else if (other instanceof DecimalValue) {
final BigDecimal promoted = new BigDecimal(Float.toString(value));
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..6798961fc92
--- /dev/null
+++ b/exist-core/src/test/java/org/exist/xquery/CountExpressionTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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 antlr.RecognitionException;
+import antlr.TokenStreamException;
+import org.exist.dom.QName;
+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.jupiter.api.Test;
+
+import java.io.StringReader;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * @author Adam Retter
+ * @author Gabriele Tomassetti
+ */
+public class CountExpressionTest {
+
+ @Test
+ public void countTest() throws RecognitionException, XPathException, TokenStreamException, QName.IllegalQNameException {
+ final 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" +
+ " ";
+
+ // parse the query into the internal syntax tree
+ final XQueryContext context = new XQueryContext();
+ final XQueryLexer lexer = new XQueryLexer(context, new StringReader(query));
+ final XQueryParser xparser = new XQueryParser(lexer);
+ xparser.xpath();
+ if (xparser.foundErrors()) {
+ fail(xparser.getErrorMessage());
+ return;
+ }
+
+ final XQueryAST ast = (XQueryAST) xparser.getAST();
+
+ final XQueryTreeParser treeParser = new XQueryTreeParser(context);
+ final 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(new QName("rank"), ((CountClause)((OrderByClause)(((ForExpr)expr.getFirst()).returnExpr)).returnExpr).varName);
+ }
+}
diff --git a/exist-core/src/test/xquery/count.xql b/exist-core/src/test/xquery/count.xql
index 14548feba94..ea2b9e33b13 100644
--- a/exist-core/src/test/xquery/count.xql
+++ b/exist-core/src/test/xquery/count.xql
@@ -22,43 +22,43 @@
xquery version "3.0";
(:~ Additional tests for the fn:count function :)
-module namespace count="http://exist-db.org/xquery/test/count";
+module namespace cnt="http://exist-db.org/xquery/test/count";
declare namespace test="http://exist-db.org/xquery/xqsuite";
import module namespace xmldb="http://exist-db.org/xquery/xmldb";
-declare variable $count:TEST_COLLECTION_NAME := "test-count";
-declare variable $count:TEST_COLLECTION := "/db/" || $count:TEST_COLLECTION_NAME;
-declare variable $count:COLLECTION1_NAME := "test-count-1";
-declare variable $count:COLLECTION2_NAME := "test-count-2";
-declare variable $count:COLLECTION1 := $count:TEST_COLLECTION || "/" || $count:COLLECTION1_NAME;
-declare variable $count:COLLECTION2 := $count:TEST_COLLECTION || "/" || $count:COLLECTION2_NAME;
+declare variable $cnt:TEST_COLLECTION_NAME := "test-count";
+declare variable $cnt:TEST_COLLECTION := "/db/" || $cnt:TEST_COLLECTION_NAME;
+declare variable $cnt:COLLECTION1_NAME := "test-count-1";
+declare variable $cnt:COLLECTION2_NAME := "test-count-2";
+declare variable $cnt:COLLECTION1 := $cnt:TEST_COLLECTION || "/" || $cnt:COLLECTION1_NAME;
+declare variable $cnt:COLLECTION2 := $cnt:TEST_COLLECTION || "/" || $cnt:COLLECTION2_NAME;
declare
%test:setUp
-function count:setup() {
- xmldb:create-collection("/db", $count:TEST_COLLECTION_NAME),
- xmldb:create-collection($count:TEST_COLLECTION, $count:COLLECTION1_NAME),
- xmldb:store($count:COLLECTION1, "test1.xml", ),
- xmldb:create-collection($count:TEST_COLLECTION, $count:COLLECTION2_NAME),
- xmldb:store($count:COLLECTION2, "test2xml", )
+function cnt:setup() {
+ xmldb:create-collection("/db", $cnt:TEST_COLLECTION_NAME),
+ xmldb:create-collection($cnt:TEST_COLLECTION, $cnt:COLLECTION1_NAME),
+ xmldb:store($cnt:COLLECTION1, "test1.xml", ),
+ xmldb:create-collection($cnt:TEST_COLLECTION, $cnt:COLLECTION2_NAME),
+ xmldb:store($cnt:COLLECTION2, "test2xml", )
};
declare
%test:tearDown
-function count:cleanup() {
- xmldb:remove($count:TEST_COLLECTION)
+function cnt:cleanup() {
+ xmldb:remove($cnt:TEST_COLLECTION)
};
declare
%test:assertEquals(1, 1)
-function count:arg-self-on-stored() {
- (collection($count:COLLECTION1)/*, collection($count:COLLECTION2)/*)/count(.)
+function cnt:arg-self-on-stored() {
+ (collection($cnt:COLLECTION1)/*, collection($cnt:COLLECTION2)/*)/count(.)
};
declare
%test:assertEquals(1, 1, 1)
-function count:arg-self-on-constructed() {
+function cnt:arg-self-on-constructed() {
(, , )/count(.)
};
diff --git a/exist-core/src/test/xquery/xquery3/count.xqm b/exist-core/src/test/xquery/xquery3/count.xqm
new file mode 100644
index 00000000000..d3a7d063bf6
--- /dev/null
+++ b/exist-core/src/test/xquery/xquery3/count.xqm
@@ -0,0 +1,262 @@
+(:
+ : 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
+ :)
+xquery version "3.0";
+
+module namespace ct = "http://exist-db.org/xquery/test/count";
+
+declare namespace test = "http://exist-db.org/xquery/xqsuite";
+
+
+declare
+ %test:assertEquals(
+ '1',
+ '2',
+ '3',
+ '4'
+ )
+function ct:simple() {
+ for $x in 1 to 4
+ count $index1
+ return
+ {$x}
+};
+
+declare
+ %test:assertEquals(
+ '1',
+ '2',
+ '3',
+ '4'
+ )
+function ct:order-ascending-index1-before() {
+ for $x in 1 to 4
+ order by $x ascending
+ count $index1
+ return
+ {$x}
+};
+
+declare
+ %test:assertEquals(
+ '1',
+ '2',
+ '3',
+ '4'
+ )
+function ct:order-ascending-index1-after() {
+ for $x in 1 to 4
+ count $index1
+ order by $x ascending
+ return
+ {$x}
+};
+
+declare
+ %test:assertEquals(
+ '4',
+ '3',
+ '2',
+ '1'
+ )
+function ct:order-descending-index1-before() {
+ for $x in 1 to 4
+ order by $x descending
+ count $index1
+ return
+ {$x}
+};
+
+declare
+ %test:assertEquals(
+ '4',
+ '3',
+ '2',
+ '1'
+ )
+function ct:order-descending-index1-after() {
+ for $x in 1 to 4
+ count $index1
+ order by $x descending
+ return
+ {$x}
+};
+
+declare
+%test:pending('Related to failing XQTS test prod-CountClause/count-009, see: https://github.com/eXist-db/exist/pull/4530#issue-1356325345')
+%test:assertEquals(
+ 'b',
+ 'a'
+ )
+function ct:order-alpha-ascending-indexes() {
+ for $x in ('a', 'b')
+ count $index1
+ let $remainder := $index1 mod 2
+ order by $remainder, $index1
+ count $index2
+ return
+ {$x}
+};
+
+declare
+ %test:assertEquals(
+ '1',
+ '2',
+ '3',
+ '4'
+ )
+function ct:order-ascending-indexes() {
+ for $x in 1 to 4
+ count $index1
+ order by $x ascending
+ count $index2
+ return
+ {$x}
+};
+
+declare
+ %test:assertEquals(
+ '4',
+ '3',
+ '2',
+ '1'
+ )
+function ct:order-descending-indexes() {
+ for $x in 1 to 4
+ count $index1
+ order by $x descending
+ count $index2
+ return
+ {$x}
+};
+
+declare
+ %test:pending('Related to failing XQTS test prod-CountClause/count-009, see: https://github.com/eXist-db/exist/pull/4530#issue-1356325345')
+ %test:assertEquals(
+ '- 3
',
+ '- 1
',
+ '- 4
',
+ '- 2
'
+ )
+function ct:order-non-linear-ascending-indexes() {
+ for $x in 1 to 4
+ count $index1
+ let $remainder := $index1 mod 3
+ order by $remainder ascending
+ count $index2
+ return
+ - {$x}
+};
+
+declare
+ %test:pending('Related to failing XQTS test prod-CountClause/count-009, see: https://github.com/eXist-db/exist/pull/4530#issue-1356325345')
+ %test:assertEquals(
+ '- 2
',
+ '- 1
',
+ '- 4
',
+ '- 3
'
+ )
+function ct:order-non-linear-descending-indexes() {
+ for $x in 1 to 4
+ count $index1
+ let $remainder := $index1 mod 3
+ order by $remainder descending
+ count $index2
+ return
+ - {$x}
+};
+
+declare
+ %test:pending('Related to failing XQTS test prod-CountClause/count-009, see: https://github.com/eXist-db/exist/pull/4530#issue-1356325345')
+ %test:assertEquals(
+ '- 21
',
+ '- 11
',
+ '- 22
',
+ '- 12
'
+ )
+function ct:order-ascending-indexes-two-keys() {
+ for $x in 1 to 2
+ for $y in 1 to 2
+ count $index1
+ let $remainder := $index1 mod 3
+ order by $remainder, $index1
+ count $index2
+ return
+ - {$x}{$y}
+};
+
+declare
+ %test:pending('Related to failing XQTS test prod-CountClause/count-009, see: https://github.com/eXist-db/exist/pull/4530#issue-1356325345')
+ %test:assertEquals(
+ '- 12
',
+ '- 22
',
+ '- 11
',
+ '- 21
'
+ )
+function ct:order-descending-indexes-two-keys() {
+ for $x in 1 to 2
+ for $y in 1 to 2
+ count $index1
+ let $remainder := $index1 mod 3
+ order by $remainder descending, $index1 descending
+ count $index2
+ return
+ - {$x}{$y}
+};
+
+declare
+ %test:pending('Related to failing XQTS test prod-CountClause/count-009, see: https://github.com/eXist-db/exist/pull/4530#issue-1356325345')
+ %test:assertEquals(
+ '- 21
',
+ '- 22
',
+ '- 11
',
+ '- 12
'
+ )
+function ct:order-ascending-descending-indexes-two-keys() {
+ for $x in 1 to 2
+ for $y in 1 to 2
+ count $index1
+ let $remainder := $index1 mod 3
+ order by $remainder ascending, $index1 descending
+ count $index2
+ return
+ - {$x}{$y}
+};
+
+
+declare
+ %test:pending('Related to failing XQTS test prod-CountClause/count-009, see: https://github.com/eXist-db/exist/pull/4530#issue-1356325345')
+ %test:assertEquals(
+ '- 12
',
+ '- 11
',
+ '- 22
',
+ '- 21
'
+ )
+function ct:order-descending-ascending-indexes-two-keys() {
+ for $x in 1 to 2
+ for $y in 1 to 2
+ count $index1
+ let $remainder := $index1 mod 3
+ order by $remainder descending, $index1 ascending
+ count $index2
+ return
+ - {$x}{$y}
+};
\ No newline at end of file