Skip to content

Commit

Permalink
Merge pull request eXist-db#4530 from evolvedbinary/feature/count-exp…
Browse files Browse the repository at this point in the history
…ression

Support for Count Expressions
  • Loading branch information
line-o authored Dec 11, 2023
2 parents 6feea2c + 643be29 commit 1029a51
Show file tree
Hide file tree
Showing 20 changed files with 772 additions and 139 deletions.
15 changes: 14 additions & 1 deletion exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ options {
return buf.toString();
}
public Exception getLastException() {
return (Exception) exceptions.get(exceptions.size() - 1);
}
public String getXQDoc() {
return lexer.getXQDoc();
}
Expand Down Expand Up @@ -802,14 +806,21 @@ initialClause throws XPathException
intermediateClause throws XPathException
:
( initialClause | whereClause | groupByClause | orderByClause )
( initialClause | whereClause | groupByClause | orderByClause | countClause )
;
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 )*
Expand Down Expand Up @@ -2223,6 +2234,8 @@ reservedKeywords returns [String name]
|
"array" { name = "array"; }
|
"count" { name = "count"; }
|
"copy-namespaces" { name = "copy-namespaces"; }
|
"empty-sequence" { name = "empty-sequence"; }
Expand Down
86 changes: 64 additions & 22 deletions exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
81 changes: 39 additions & 42 deletions exist-core/src/main/java/org/exist/xquery/BindingExpression.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="mailto:[email protected]">Wolfgang Meier</a>
*/
Expand All @@ -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;
}

Expand All @@ -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);
}
Expand All @@ -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);
}

Expand All @@ -163,6 +157,7 @@ public void unsubscribe() {

@Override
public void debug() {
// no-op
}
}

Expand All @@ -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
Expand All @@ -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 */
Expand Down
Loading

0 comments on commit 1029a51

Please sign in to comment.