Skip to content

Commit 1029a51

Browse files
authored
Merge pull request #4530 from evolvedbinary/feature/count-expression
Support for Count Expressions
2 parents 6feea2c + 643be29 commit 1029a51

File tree

20 files changed

+772
-139
lines changed

20 files changed

+772
-139
lines changed

exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ options {
103103
return buf.toString();
104104
}
105105
106+
public Exception getLastException() {
107+
return (Exception) exceptions.get(exceptions.size() - 1);
108+
}
109+
106110
public String getXQDoc() {
107111
return lexer.getXQDoc();
108112
}
@@ -802,14 +806,21 @@ initialClause throws XPathException
802806
803807
intermediateClause throws XPathException
804808
:
805-
( initialClause | whereClause | groupByClause | orderByClause )
809+
( initialClause | whereClause | groupByClause | orderByClause | countClause )
806810
;
807811
808812
whereClause throws XPathException
809813
:
810814
"where"^ exprSingle
811815
;
812816
817+
countClause throws XPathException
818+
{ String varName; }
819+
:
820+
"count"^ DOLLAR! varName=varName!
821+
{ #countClause = #(#countClause, #[VARIABLE_BINDING, varName]); }
822+
;
823+
813824
forClause throws XPathException
814825
:
815826
"for"^ inVarBinding ( COMMA! inVarBinding )*
@@ -2223,6 +2234,8 @@ reservedKeywords returns [String name]
22232234
|
22242235
"array" { name = "array"; }
22252236
|
2237+
"count" { name = "count"; }
2238+
|
22262239
"copy-namespaces" { name = "copy-namespaces"; }
22272240
|
22282241
"empty-sequence" { name = "empty-sequence"; }

exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,9 @@ options {
128128

129129
private static class ForLetClause {
130130
XQueryAST ast;
131-
String varName;
131+
QName varName;
132132
SequenceType sequenceType= null;
133-
String posVar= null;
133+
QName posVar = null;
134134
Expression inputSequence;
135135
Expression action;
136136
FLWORClause.ClauseType type = FLWORClause.ClauseType.FOR;
@@ -1398,7 +1398,11 @@ throws PermissionDeniedException, EXistException, XPathException
13981398
)?
13991399
step=expr[inputSequence]
14001400
{
1401-
clause.varName= someVarName.getText();
1401+
try {
1402+
clause.varName = QName.parse(staticContext, someVarName.getText(), null);
1403+
} catch (final IllegalQNameException iqe) {
1404+
throw new XPathException(someVarName.getLine(), someVarName.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + someVarName.getText());
1405+
}
14021406
clause.inputSequence= inputSequence;
14031407
clauses.add(clause);
14041408
}
@@ -1449,7 +1453,11 @@ throws PermissionDeniedException, EXistException, XPathException
14491453
)?
14501454
step=expr[inputSequence]
14511455
{
1452-
clause.varName= everyVarName.getText();
1456+
try {
1457+
clause.varName = QName.parse(staticContext, everyVarName.getText(), null);
1458+
} catch (final IllegalQNameException iqe) {
1459+
throw new XPathException(everyVarName.getLine(), everyVarName.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + everyVarName.getText());
1460+
}
14531461
clause.inputSequence= inputSequence;
14541462
clauses.add(clause);
14551463
}
@@ -1585,11 +1593,21 @@ throws PermissionDeniedException, EXistException, XPathException
15851593
)?
15861594
(
15871595
posVar:POSITIONAL_VAR
1588-
{ clause.posVar= posVar.getText(); }
1596+
{
1597+
try {
1598+
clause.posVar = QName.parse(staticContext, posVar.getText(), null);
1599+
} catch (final IllegalQNameException iqe) {
1600+
throw new XPathException(posVar.getLine(), posVar.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + posVar.getText());
1601+
}
1602+
}
15891603
)?
15901604
step=expr [inputSequence]
15911605
{
1592-
clause.varName= varName.getText();
1606+
try {
1607+
clause.varName = QName.parse(staticContext, varName.getText(), null);
1608+
} catch (final IllegalQNameException iqe) {
1609+
throw new XPathException(varName.getLine(), varName.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + varName.getText());
1610+
}
15931611
clause.inputSequence= inputSequence;
15941612
clauses.add(clause);
15951613
}
@@ -1618,7 +1636,11 @@ throws PermissionDeniedException, EXistException, XPathException
16181636
)?
16191637
step=expr [inputSequence]
16201638
{
1621-
clause.varName= letVarName.getText();
1639+
try {
1640+
clause.varName = QName.parse(staticContext, letVarName.getText(), null);
1641+
} catch (final IllegalQNameException iqe) {
1642+
throw new XPathException(letVarName.getLine(), letVarName.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + letVarName.getText());
1643+
}
16221644
clause.inputSequence= inputSequence;
16231645
clauses.add(clause);
16241646
}
@@ -1752,29 +1774,49 @@ throws PermissionDeniedException, EXistException, XPathException
17521774
clauses.add(clause);
17531775
}
17541776
)
1755-
)+
1756-
step=expr [(PathExpr) action]
1757-
{
1777+
|
1778+
#(
1779+
co:"count"
1780+
countVarName:VARIABLE_BINDING
1781+
{
1782+
ForLetClause clause = new ForLetClause();
1783+
clause.ast = co;
1784+
try {
1785+
clause.varName = QName.parse(staticContext, countVarName.getText(), null);
1786+
} catch (final IllegalQNameException iqe) {
1787+
throw new XPathException(countVarName.getLine(), countVarName.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + countVarName.getText());
1788+
}
1789+
clause.type = FLWORClause.ClauseType.COUNT;
1790+
clause.inputSequence = null;
1791+
clauses.add(clause);
1792+
}
1793+
)
1794+
)+
1795+
step=expr [(PathExpr) action]
1796+
{
17581797
for (int i= clauses.size() - 1; i >= 0; i--) {
17591798
ForLetClause clause= (ForLetClause) clauses.get(i);
17601799
FLWORClause expr;
17611800
switch (clause.type) {
17621801
case LET:
1763-
expr= new LetExpr(context);
1802+
expr = new LetExpr(context);
17641803
expr.setASTNode(expr_AST_in);
17651804
break;
17661805
case GROUPBY:
1767-
expr = new GroupByClause(context);
1768-
break;
1769-
case ORDERBY:
1770-
expr = new OrderByClause(context, clause.orderSpecs);
1771-
break;
1772-
case WHERE:
1773-
expr = new WhereClause(context, new DebuggableExpression(clause.inputSequence));
1774-
break;
1775-
default:
1776-
expr= new ForExpr(context, clause.allowEmpty);
1777-
break;
1806+
expr = new GroupByClause(context);
1807+
break;
1808+
case ORDERBY:
1809+
expr = new OrderByClause(context, clause.orderSpecs);
1810+
break;
1811+
case WHERE:
1812+
expr = new WhereClause(context, new DebuggableExpression(clause.inputSequence));
1813+
break;
1814+
case COUNT:
1815+
expr = new CountClause(context, clause.varName);
1816+
break;
1817+
default:
1818+
expr = new ForExpr(context, clause.allowEmpty);
1819+
break;
17781820
}
17791821
expr.setASTNode(clause.ast);
17801822
if (clause.type == FLWORClause.ClauseType.FOR || clause.type == FLWORClause.ClauseType.LET) {

exist-core/src/main/java/org/exist/xquery/AbstractFLWORClause.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,10 @@ public AbstractFLWORClause(XQueryContext context) {
4141
}
4242

4343
@Override
44-
public LocalVariable createVariable(final String name) throws XPathException {
45-
try {
46-
final LocalVariable var = new LocalVariable(QName.parse(context, name, null));
47-
firstVar = var;
48-
return var;
49-
} catch (final IllegalQNameException e) {
50-
throw new XPathException(this, ErrorCodes.XPST0081, "No namespace defined for prefix " + name);
51-
}
44+
public LocalVariable createVariable(final QName name) throws XPathException {
45+
final LocalVariable var = new LocalVariable(name);
46+
firstVar = var;
47+
return var;
5248
}
5349

5450
@Override

exist-core/src/main/java/org/exist/xquery/BasicExpressionVisitor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ public void visitOrderByClause(final OrderByClause orderBy) {
180180
// Nothing to do
181181
}
182182

183+
@Override
184+
public void visitCountClause(final CountClause count) {
185+
// Nothing to do
186+
}
187+
183188
@Override
184189
public void visitGroupByClause(final GroupByClause groupBy) {
185190
// Nothing to do

exist-core/src/main/java/org/exist/xquery/BindingExpression.java

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@
2323

2424
import org.apache.logging.log4j.LogManager;
2525
import org.apache.logging.log4j.Logger;
26+
import org.exist.dom.QName;
2627
import org.exist.dom.persistent.*;
2728
import org.exist.numbering.NodeId;
2829
import org.exist.storage.UpdateListener;
2930
import org.exist.xquery.value.*;
3031

3132
/**
32-
* Abstract superclass for the variable binding expressions "for" and "let".
33+
* Abstract superclass for the variable binding expressions "for", "let", and "count".
3334
*
3435
* @author <a href="mailto:[email protected]">Wolfgang Meier</a>
3536
*/
@@ -41,22 +42,20 @@ public abstract class BindingExpression extends AbstractFLWORClause implements R
4142
protected final static SequenceType POSITIONAL_VAR_TYPE =
4243
new SequenceType(Type.INTEGER, Cardinality.EXACTLY_ONE);
4344

44-
protected String varName;
45+
protected QName varName;
4546
protected SequenceType sequenceType = null;
4647
protected Expression inputSequence;
47-
4848
private ExprUpdateListener listener;
4949

50-
51-
public BindingExpression(XQueryContext context) {
50+
public BindingExpression(final XQueryContext context) {
5251
super(context);
5352
}
5453

55-
public void setVariable(String qname) {
56-
varName = qname;
54+
public void setVariable(final QName varName) {
55+
this.varName = varName;
5756
}
5857

59-
public String getVariable() {
58+
public QName getVariable() {
6059
return this.varName;
6160
}
6261

@@ -77,52 +76,45 @@ public Expression getInputSequence() {
7776
return this.inputSequence;
7877
}
7978

80-
/* (non-Javadoc)
81-
* @see org.exist.xquery.Expression#analyze(org.exist.xquery.Expression, int)
82-
*/
83-
public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
79+
@Override
80+
public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException {
8481
unordered = (contextInfo.getFlags() & UNORDERED) > 0;
8582
}
8683

8784
@Override
8885
public Sequence postEval(Sequence seq) throws XPathException {
89-
if (returnExpr instanceof FLWORClause) {
90-
seq = ((FLWORClause)returnExpr).postEval(seq);
86+
if (returnExpr instanceof FLWORClause flworClause) {
87+
seq = flworClause.postEval(seq);
9188
}
9289
return super.postEval(seq);
9390
}
9491

95-
/* (non-Javadoc)
96-
* @see org.exist.xquery.Expression#preselect(org.exist.dom.persistent.DocumentSet, org.exist.xquery.StaticContext)
97-
*/
98-
public DocumentSet preselect(DocumentSet in_docs) throws XPathException {
99-
return in_docs;
92+
public DocumentSet preselect(final DocumentSet docs) throws XPathException {
93+
return docs;
10094
}
10195

102-
/* (non-Javadoc)
103-
* @see org.exist.xquery.AbstractExpression#resetState()
104-
*/
105-
public void resetState(boolean postOptimization) {
96+
@Override
97+
public void resetState(final boolean postOptimization) {
10698
super.resetState(postOptimization);
10799
inputSequence.resetState(postOptimization);
108100
returnExpr.resetState(postOptimization);
109101
}
110102

111-
public final static void setContext(int contextId, Sequence seq) throws XPathException {
103+
public static void setContext(final int contextId, final Sequence seq) throws XPathException {
112104
if (seq instanceof VirtualNodeSet) {
113105
((VirtualNodeSet)seq).setInPredicate(true);
114106
((VirtualNodeSet)seq).setSelfIsContext();
115107
} else {
116-
Item next;
117-
for (final SequenceIterator i = seq.unorderedIterator(); i.hasNext();) {
118-
next = i.nextItem();
119-
if (next instanceof NodeProxy)
120-
{((NodeProxy) next).addContextNode(contextId, (NodeProxy) next);}
108+
for (final SequenceIterator i = seq.unorderedIterator(); i.hasNext(); ) {
109+
final Item next = i.nextItem();
110+
if (next instanceof NodeProxy) {
111+
((NodeProxy) next).addContextNode(contextId, (NodeProxy) next);
112+
}
121113
}
122114
}
123115
}
124116

125-
public final static void clearContext(int contextId, Sequence seq) throws XPathException {
117+
public final static void clearContext(final int contextId, final Sequence seq) throws XPathException {
126118
if (seq != null && !(seq instanceof VirtualNodeSet)) {
127119
seq.clearContext(contextId);
128120
}
@@ -132,27 +124,29 @@ protected void registerUpdateListener(final Sequence sequence) {
132124
if (listener == null) {
133125
listener = new ExprUpdateListener(sequence);
134126
context.registerUpdateListener(listener);
135-
} else
136-
{listener.setSequence(sequence);}
127+
} else {
128+
listener.setSequence(sequence);
129+
}
137130
}
138131

139132
private class ExprUpdateListener implements UpdateListener {
140133
private Sequence sequence;
141134

142-
public ExprUpdateListener(Sequence sequence) {
135+
public ExprUpdateListener(final Sequence sequence) {
143136
this.sequence = sequence;
144137
}
145138

146-
public void setSequence(Sequence sequence) {
139+
public void setSequence(final Sequence sequence) {
147140
this.sequence = sequence;
148141
}
149142

150143
@Override
151-
public void documentUpdated(DocumentImpl document, int event) {
144+
public void documentUpdated(final DocumentImpl document, final int event) {
145+
// no-op
152146
}
153147

154148
@Override
155-
public void nodeMoved(NodeId oldNodeId, NodeHandle newNode) {
149+
public void nodeMoved(final NodeId oldNodeId, final NodeHandle newNode) {
156150
sequence.nodeMoved(oldNodeId, newNode);
157151
}
158152

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

164158
@Override
165159
public void debug() {
160+
// no-op
166161
}
167162
}
168163

@@ -178,11 +173,12 @@ public int returnsType() {
178173
/* RewritableExpression API */
179174

180175
@Override
181-
public void replace(Expression oldExpr, Expression newExpr) {
182-
if (inputSequence == oldExpr)
183-
{inputSequence = newExpr;}
184-
else if (returnExpr == oldExpr)
185-
{returnExpr = newExpr;}
176+
public void replace(final Expression oldExpr, final Expression newExpr) {
177+
if (inputSequence == oldExpr) {
178+
inputSequence = newExpr;
179+
} else if (returnExpr == oldExpr) {
180+
returnExpr = newExpr;
181+
}
186182
}
187183

188184
@Override
@@ -196,7 +192,8 @@ public Expression getFirst() {
196192
}
197193

198194
@Override
199-
public void remove(Expression oldExpr) throws XPathException {
195+
public void remove(final Expression oldExpr) throws XPathException {
196+
// no-op
200197
}
201198

202199
/* END RewritableExpression API */

0 commit comments

Comments
 (0)