• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eXist-db / exist / 41072693145

30 Oct 2026 02:33PM UTC coverage: 58.179% (-0.02%) from 58.197%
41072693145

push

github

web-flow
Merge pull request #6887 from duncdrum/bugfix/locale-xquery-functions

[bugfix] Make XQuery functions independent of the JVM default locale

32502 of 61726 branches covered (52.66%)

Branch coverage included in aggregate %.

24 of 35 new or added lines in 7 files covered. (68.57%)

62 existing lines in 8 files now uncovered.

85568 of 141218 relevant lines covered (60.59%)

2.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.96
/exist-core/src/main/java/org/exist/xquery/ForExpr.java
1
/*
2
 * eXist-db Open Source Native XML Database
3
 * Copyright (C) 2001 The eXist-db Authors
4
 *
5
 * info@exist-db.org
6
 * http://www.exist-db.org
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with this library; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21
 */
22
package org.exist.xquery;
23

24
import org.exist.dom.QName;
25
import org.exist.dom.persistent.NodeSet;
26
import org.exist.xquery.util.ExpressionDumper;
27
import org.exist.xquery.value.*;
28

29
import javax.annotation.Nullable;
30
import java.util.HashSet;
31
import java.util.Set;
32

33
/**
34
 * Represents an XQuery "for" expression.
35
 * 
36
 * @author <a href="mailto:wolfgang@exist-db.org">Wolfgang Meier</a>
37
 */
38
public class ForExpr extends BindingExpression {
39

40
    private QName positionalVariable = null;
3 ✔
41
    private boolean allowEmpty = false;
3 ✔
42
    private boolean isOuterFor = true;
3 ✔
43

44
    public ForExpr(XQueryContext context, boolean allowingEmpty) {
45
        super(context);
3 ✔
46
        this.allowEmpty = allowingEmpty;
3 ✔
47
    }
1 ✔
48

49
    @Override
50
    public ClauseType getType() {
51
        return ClauseType.FOR;
2 ✔
52
    }
53

54
    /**
55
     * A "for" expression may have an optional positional variable whose
56
     * QName can be set via this method.
57
     * 
58
     * @param variable the name of the variable to set
59
     */
60
    public void setPositionalVariable(final QName variable) {
61
        positionalVariable = variable;
3 ✔
62
    }
1 ✔
63

64
    /**
65
     * Get the name of the optional positional variable.
66
     *
67
     * @return the name of the positional variable, or null if there is none.
68
     */
69
    @Nullable QName getPositionalVariable() {
70
        return positionalVariable;
3 ✔
71
    }
72

73
    /**
74
     * Determine if this "for" clause starts a chain of {@link FLWORClause#preEval(Sequence)} calls,
75
     * i.e. it is not itself pre-evaluated by a preceding "for" clause.
76
     *
77
     * @return true if this is the outer "for" clause.
78
     */
79
    boolean isOuterFor() {
80
        return isOuterFor;
3 ✔
81
    }
82

83
        /* (non-Javadoc)
84
     * @see org.exist.xquery.Expression#analyze(org.exist.xquery.Expression)
85
     */
86
    public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
87
        super.analyze(contextInfo);
3 ✔
88
        // Save the local variable stack
89
        final LocalVariable mark = context.markLocalVariables(false);
5 ✔
90
        try {
91
            contextInfo.setParent(this);
3 ✔
92
            final AnalyzeContextInfo varContextInfo = new AnalyzeContextInfo(contextInfo);
5 ✔
93
            inputSequence.analyze(varContextInfo);
4 ✔
94
            // Declare the iteration variable
95
            final LocalVariable inVar = new LocalVariable(varName);
6 ✔
96
            inVar.setSequenceType(sequenceType);
4 ✔
97
            inVar.setStaticType(varContextInfo.getStaticReturnType());
4 ✔
98
            context.declareVariableBinding(inVar);
5 ✔
99
            // Declare positional variable
100
            if (positionalVariable != null) {
3 ✔
101
                final LocalVariable posVar = new LocalVariable(positionalVariable);
6 ✔
102
                posVar.setSequenceType(POSITIONAL_VAR_TYPE);
3 ✔
103
                posVar.setStaticType(Type.INTEGER);
3 ✔
104
                context.declareVariableBinding(posVar);
5 ✔
105
            }
106

107
            final AnalyzeContextInfo newContextInfo = new AnalyzeContextInfo(contextInfo);
5 ✔
108
            newContextInfo.addFlag(SINGLE_STEP_EXECUTION);
3 ✔
109
            returnExpr.analyze(newContextInfo);
4 ✔
110
        } finally {
1 ✔
111
            // restore the local variable stack
112
            context.popLocalVariables(mark);
4 ✔
113
        }
114
    }
1 ✔
115

116
    /**
117
     * This implementation tries to process the "where" clause in advance, i.e. in one single
118
     * step. This is possible if the input sequence is a node set and the where expression
119
     * has no dependencies on other variables than those declared in this "for" statement.
120
     * 
121
     * @see org.exist.xquery.Expression#eval(Sequence, Item)
122
     */
123
    public Sequence eval(Sequence contextSequence, Item contextItem)
124
            throws XPathException {
125
        if (context.getProfiler().isEnabled()) {
5 !
126
            context.getProfiler().start(this);
×
127
            context.getProfiler().message(this, Profiler.DEPENDENCIES,
×
128
                "DEPENDENCIES", Dependency.getDependenciesName(this.getDependencies()));
×
129
            if (contextSequence != null)
×
130
                {context.getProfiler().message(this, Profiler.START_SEQUENCES,
×
131
                "CONTEXT SEQUENCE", contextSequence);}
×
132
            if (contextItem != null)
×
133
                {context.getProfiler().message(this, Profiler.START_SEQUENCES,
×
134
                "CONTEXT ITEM", contextItem.toSequence());}
×
135
        }
136
        context.expressionStart(this);
4 ✔
137
        LocalVariable var;
138
        Sequence in;
139
        // Save the local variable stack
140
        LocalVariable mark = context.markLocalVariables(false);
5 ✔
141
        Sequence resultSequence = new ValueSequence(unordered);
6 ✔
142
        try {
143
            // Evaluate the "in" expression
144
            in = inputSequence.eval(contextSequence, null);
6 ✔
145
            clearContext(getExpressionId(), in);
4 ✔
146
            // Declare the iteration variable
147
            var = createVariable(varName);
5 ✔
148
            var.setSequenceType(sequenceType);
4 ✔
149
            context.declareVariableBinding(var);
5 ✔
150
            registerUpdateListener(in);
3 ✔
151
            // Declare positional variable
152
            LocalVariable at = null;
2 ✔
153
            if (positionalVariable != null) {
3 ✔
154
                at = new LocalVariable(positionalVariable);
6 ✔
155
                at.setSequenceType(POSITIONAL_VAR_TYPE);
3 ✔
156
                context.declareVariableBinding(at);
5 ✔
157
            }
158
            // Assign the whole input sequence to the bound variable.
159
            // This is required if we process the "where" or "order by" clause
160
            // in one step.
161
            var.setValue(in);
3 ✔
162
            // Save the current context document set to the variable as a hint
163
            // for path expressions occurring in the "return" clause.
164
            if (in instanceof NodeSet) {
3 ✔
165
                var.setContextDocs(in.getDocumentSet());
4 ✔
166
            } else {
1 ✔
167
                var.setContextDocs(null);
3 ✔
168
            }
169
            // See if we can process the "where" clause in a single step (instead of
170
            // calling the where expression for each item in the input sequence)
171
            // This is possible if the input sequence is a node set and has no
172
            // dependencies on the current context item.
173
            if (isOuterFor) {
3 ✔
174
                if (returnExpr instanceof WhereClause clause1) {
9 ✔
175
                    if (at == null) {
2 !
176
                        in = clause1.preEval(in);
4 ✔
177
                    }
178
                } else if (returnExpr instanceof FLWORClause clause) {
10 ✔
179
                    in = clause.preEval(in);
4 ✔
180
                }
181
            }
182

183
            final IntegerValue atVal = new IntegerValue(this, 1);
6 ✔
184
            if (positionalVariable != null) {
3 ✔
185
                at.setValue(atVal);
3 ✔
186
            }
187
            //Type.EMPTY is *not* a subtype of other types ;
188
            //the tests below would fail without this prior cardinality check
189
            if (in.isEmpty() && sequenceType != null &&
6 !
190
                    !sequenceType.getCardinality().isSuperCardinalityOrEqualOf(Cardinality.EMPTY_SEQUENCE)) {
×
191
                throw new XPathException(this, ErrorCodes.XPTY0004,
×
192
                        "Invalid cardinality for variable $" + varName +
×
193
                                ". Expected " + sequenceType.getCardinality().getHumanDescription() +
×
194
                                ", got " + in.getCardinality().getHumanDescription());
×
195
            }
196

197
            // Loop through each variable binding
198
            int p = 0;
2 ✔
199
            if (in.isEmpty() && allowEmpty) {
6 ✔
200
                processItem(var, AtomicValue.EMPTY_VALUE, Sequence.EMPTY_SEQUENCE, resultSequence, at, p);
8 ✔
201
            } else {
1 ✔
202
                for (final SequenceIterator i = in.iterate(); i.hasNext(); p++) {
8 ✔
203
                    processItem(var, i.nextItem(), in, resultSequence, at, p);
9 ✔
204
                }
205
            }
206
        } finally {
1 ✔
207
            // restore the local variable stack 
208
            context.popLocalVariables(mark, resultSequence);
5 ✔
209
        }
210

211
        clearContext(getExpressionId(), in);
4 ✔
212
        setActualReturnType(resultSequence.getItemType());
4 ✔
213

214
        if (callPostEval()) {
3 ✔
215
            resultSequence = postEval(resultSequence);
4 ✔
216
        }
217

218
        context.expressionEnd(this);
4 ✔
219
        if (context.getProfiler().isEnabled())
5 !
220
            {context.getProfiler().end(this, "", resultSequence);}
×
221
        return resultSequence;
2 ✔
222
    }
223

224
    private void processItem(LocalVariable var, Item contextItem, Sequence in, Sequence resultSequence, LocalVariable
225
            at, int p) throws XPathException {
226
        context.proceed(this);
4 ✔
227
        context.setContextSequencePosition(p, in);
5 ✔
228
        if (positionalVariable != null) {
3 ✔
229
            final int position = contextItem == AtomicValue.EMPTY_VALUE ? 0 : p + 1;
9 ✔
230
            at.setValue(new IntegerValue(this, position));
8 ✔
231
        }
232
        final Sequence contextSequence = contextItem.toSequence();
3 ✔
233
        // set variable value to current item
234
        var.setValue(contextSequence);
3 ✔
235
        var.checkType();
2 ✔
236
        //Reset the context position
237
        context.setContextSequencePosition(0, null);
5 ✔
238

239
        final Sequence returnExprResult;
240
        if (returnExpr instanceof OrderByClause) {
4 ✔
241
            returnExprResult = returnExpr.eval(contextSequence, null);
6 ✔
242
        } else {
1 ✔
243
            returnExprResult = returnExpr.eval(null, null);
6 ✔
244
        }
245
        resultSequence.addAll(returnExprResult);
3 ✔
246

247
        // free resources
248
        var.destroy(context, resultSequence);
5 ✔
249
    }
1 ✔
250

251
    private boolean callPostEval() {
252
        FLWORClause prev = getPreviousClause();
3 ✔
253
        while (prev != null) {
3 ✔
254
            switch (prev.getType()) {
6 ✔
255
                case LET:
256
                case FOR:
257
                    return false;
2 ✔
258
                case ORDERBY:
259
                case GROUPBY:
260
                    return true;
2 ✔
261
            }
262
            prev = prev.getPreviousClause();
3 ✔
263
        }
264
        return true;
2 ✔
265
    }
266

267
    @Override
268
    public Sequence preEval(Sequence seq) throws XPathException {
269
        // if preEval gets called, we know we're inside another FOR
270
        isOuterFor = false;
3 ✔
271
        return super.preEval(seq);
4 ✔
272
    }
273

274
    @Override
275
    public void dump(ExpressionDumper dumper) {
UNCOV
276
        dumper.display("for ", line);
×
UNCOV
277
        dumper.startIndent();
×
UNCOV
278
        dumper.display("$").display(varName);
×
UNCOV
279
        if (sequenceType != null) {
×
280
            dumper.display(" as ").display(sequenceType);
×
281
        }
UNCOV
282
        if (allowEmpty) {
×
283
            dumper.display(" allowing empty ");
×
284
        }
UNCOV
285
        if (positionalVariable != null)
×
286
            {dumper.display(" at ").display(positionalVariable);}
×
UNCOV
287
        dumper.display(" in ");
×
UNCOV
288
        inputSequence.dump(dumper);
×
UNCOV
289
        dumper.endIndent().nl();
×
290
        //TODO : QuantifiedExpr
UNCOV
291
        if (returnExpr instanceof LetExpr)
×
292
            {dumper.display(" ", returnExpr.getLine());}
×
293
        else
UNCOV
294
            {dumper.display("return", returnExpr.getLine());} 
×
UNCOV
295
        dumper.startIndent();
×
UNCOV
296
        returnExpr.dump(dumper);
×
UNCOV
297
        dumper.endIndent().nl();
×
UNCOV
298
    }
×
299

300
    @Override
301
    public String toString() {
302
        final StringBuilder result = new StringBuilder();
×
303
        result.append("for ");
×
304
        result.append("$").append(varName);
×
305
        if (sequenceType != null)
×
306
            {result.append(" as ").append(sequenceType);}
×
307
        if (allowEmpty) {
×
308
            result.append(" allowing empty ");
×
309
        }
310
        if (positionalVariable != null) {
×
311
            result.append(" at ").append(positionalVariable);
×
312
        }
313
        result.append(" in ");
×
314
        result.append(inputSequence.toString());
×
315
        result.append(" ");
×
316
        //TODO : QuantifiedExpr
317
        if (returnExpr instanceof LetExpr)
×
318
            {result.append(" ");}
×
319
        else
320
            {result.append("return ");}
×
321
        result.append(returnExpr.toString());
×
322
        return result.toString();
×
323
    }
324

325
    @Override
326
    public void accept(final ExpressionVisitor visitor) {
327
        visitor.visitForExpression(this);
3 ✔
328
    }
1 ✔
329

330
    @Override
331
    public Set<QName> getTupleStreamVariables() {
332
        final Set<QName> variables = new HashSet<>();
4 ✔
333
        if (positionalVariable != null) {
3 ✔
334
            variables.add(positionalVariable);
5 ✔
335
        }
336

337
        final QName variable = getVariable();
3 ✔
338
        if (variable != null) {
2 !
339
            variables.add(variable);
4 ✔
340
        }
341

342
        final LocalVariable startVar = getStartVariable();
3 ✔
343
        if (startVar != null) {
2 !
344
            variables.add(startVar.getQName());
×
345
        }
346

347
        return variables;
2 ✔
348
    }
349
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc