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

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

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

91.62
/exist-core/src/main/java/org/exist/xquery/functions/fn/LoadXQueryModule.java
1
/*
2
 * Elemental
3
 * Copyright (C) 2024, Evolved Binary Ltd
4
 *
5
 * admin@evolvedbinary.com
6
 * https://www.evolvedbinary.com | https://www.elemental.xyz
7
 *
8
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 *
24
 * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
25
 *       The original license header is included below.
26
 *
27
 * =====================================================================
28
 *
29
 * eXist-db Open Source Native XML Database
30
 * Copyright (C) 2001 The eXist-db Authors
31
 *
32
 * info@exist-db.org
33
 * http://www.exist-db.org
34
 *
35
 * This library is free software; you can redistribute it and/or
36
 * modify it under the terms of the GNU Lesser General Public
37
 * License as published by the Free Software Foundation; either
38
 * version 2.1 of the License, or (at your option) any later version.
39
 *
40
 * This library is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43
 * Lesser General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Lesser General Public
46
 * License along with this library; if not, write to the Free Software
47
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
48
 */
49
package org.exist.xquery.functions.fn;
50

51
import com.evolvedbinary.j8fu.function.ConsumerE;
52
import io.lacuna.bifurcan.IEntry;
53
import io.lacuna.bifurcan.IMap;
54
import io.lacuna.bifurcan.Map;
55
import io.lacuna.bifurcan.Maps;
56
import org.exist.dom.QName;
57
import org.exist.xquery.*;
58
import org.exist.xquery.Module;
59
import org.exist.xquery.functions.map.AbstractMapType;
60
import org.exist.xquery.functions.map.MapType;
61
import org.exist.xquery.parser.XQueryAST;
62
import org.exist.xquery.value.*;
63

64
import java.util.*;
65

66
import static org.exist.xquery.functions.map.MapType.newLinearMap;
67

68
/**
69
 * Implements fn:load-xquery-module. Creates a temporary context for the imported module, so the
70
 * current XQuery execution context is not polluted.
71
 *
72
 * eXist does currently not support setting external variables in a library module or defining a context
73
 * sequence for variables. The "context-item" and "variables" options are thus ignored.
74
 *
75
 * @author Wolfgang
76
 */
77
public class LoadXQueryModule extends BasicFunction {
78

79
    public final static FunctionSignature LOAD_XQUERY_MODULE_1 = new FunctionSignature(
1✔
80
            new QName("load-xquery-module", Function.BUILTIN_FUNCTION_NS, FnModule.PREFIX),
1✔
81
            "Provides access to the public functions and global variables of a dynamically-loaded XQuery library module.",
1✔
82
            new SequenceType[] {
1✔
83
                    new FunctionParameterSequenceType("module-uri", Type.STRING,
1✔
84
                            Cardinality.EXACTLY_ONE, "The target namespace of the module")
1✔
85
            },
86
            new FunctionReturnSequenceType(
1✔
87
                    Type.MAP_ITEM,
1✔
88
                    Cardinality.EXACTLY_ONE,
1✔
89
                    "a map with two entries: 1) 'variables': a map with one entry for each public global variable declared in " +
1✔
90
                            "the library module. The key of the entry is the name of the variable, as an xs:QName value; the " +
91
                            "associated value is the value of the variable; 2) 'functions': a map which contains one " +
92
                            "entry for each public function declared in the library module, except that when two functions have " +
93
                            "the same name (but different arity), they share the same entry. The key of the entry is the name of the " +
94
                            "function(s), as an xs:QName value; the associated value is a map A. This map (A) contains one entry for each " +
95
                            "function with the given name; its key is the arity of the function, as an xs:integer value, and its associated " +
96
                            "value is the function itself, as a function item. The function can be invoked using the rules for dynamic " +
97
                            "function invocation.")
98
    );
99

100
    public final static FunctionSignature LOAD_XQUERY_MODULE_2 = new FunctionSignature(
1✔
101
            new QName("load-xquery-module", Function.BUILTIN_FUNCTION_NS, FnModule.PREFIX),
1✔
102
            "Provides access to the public functions and global variables of a dynamically-loaded XQuery library module.",
1✔
103
            new SequenceType[] {
1✔
104
                    new FunctionParameterSequenceType("module-uri", Type.STRING,
1✔
105
                            Cardinality.EXACTLY_ONE, "The target namespace of the module"),
1✔
106
                    new FunctionParameterSequenceType("options", Type.MAP_ITEM,
1✔
107
                            Cardinality.EXACTLY_ONE, "Options for loading the module")
1✔
108
            },
109
            new FunctionReturnSequenceType(
1✔
110
                    Type.MAP_ITEM,
1✔
111
                    Cardinality.EXACTLY_ONE,
1✔
112
                    "a map with two entries: 1) 'variables': a map with one entry for each public global variable declared in " +
1✔
113
                            "the library module. The key of the entry is the name of the variable, as an xs:QName value; the " +
114
                            "associated value is the value of the variable; 2) 'functions': a map which contains one " +
115
                            "entry for each public function declared in the library module, except that when two functions have " +
116
                            "the same name (but different arity), they share the same entry. The key of the entry is the name of the " +
117
                            "function(s), as an xs:QName value; the associated value is a map A. This map (A) contains one entry for each " +
118
                            "function with the given name; its key is the arity of the function, as an xs:integer value, and its associated " +
119
                            "value is the function itself, as a function item. The function can be invoked using the rules for dynamic " +
120
                            "function invocation.")
121
    );
122

123
    public final static StringValue OPTIONS_LOCATION_HINTS = new StringValue("location-hints");
1✔
124
    public final static StringValue OPTIONS_XQUERY_VERSION = new StringValue("xquery-version");
1✔
125
    public final static StringValue OPTIONS_VARIABLES = new StringValue("variables");
1✔
126
    public final static StringValue OPTIONS_CONTEXT_ITEM = new StringValue("context-item");
1✔
127
    public final static StringValue OPTIONS_VENDOR = new StringValue("vendor-options");
1✔
128

129
    public final static StringValue RESULT_FUNCTIONS = new StringValue("functions");
1✔
130
    public final static StringValue RESULT_VARIABLES = new StringValue("variables");
1✔
131

132
    public LoadXQueryModule(XQueryContext context, FunctionSignature signature) {
133
        super(context, signature);
1✔
134
    }
1✔
135

136
    @Override
137
    public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException {
138
        final String targetNamespace = args[0].getStringValue();
1✔
139
        if (targetNamespace.isEmpty()) {
1!
140
            throw new XPathException(this, ErrorCodes.FOQM0001, "Target namespace must be a string with length > 0");
×
141
        }
142
        AnyURIValue[] locationHints = null;
1✔
143
        String xqVersion = getXQueryVersion(context.getXQueryVersion());
1✔
144
        AbstractMapType externalVars = new MapType(this, context);
1✔
145
        Sequence contextItem = Sequence.EMPTY_SEQUENCE;
1✔
146

147
        // evaluate options
148
        if (getArgumentCount() == 2) {
1✔
149
            final AbstractMapType map = (AbstractMapType) args[1].itemAt(0);
1✔
150
            final Sequence locationHintsOption = map.get(OPTIONS_LOCATION_HINTS);
1✔
151
            locationHints = new AnyURIValue[locationHintsOption.getItemCount()];
1✔
152
            for (int i = 0; i < locationHints.length; i++) {
1✔
153
                locationHints[i] = (AnyURIValue) locationHintsOption.itemAt(i).convertTo(Type.ANY_URI);
1✔
154
            }
155

156
            final Sequence versions = map.get(OPTIONS_XQUERY_VERSION);
1✔
157
            if (!versions.isEmpty()) {
1✔
158
                xqVersion = versions.itemAt(0).getStringValue();
1✔
159
            }
160

161
            final Sequence vars = map.get(OPTIONS_VARIABLES);
1✔
162
            if (!vars.isEmpty()) {
1✔
163
                if (vars.hasOne() && vars.itemAt(0).getType() == Type.MAP_ITEM) {
1!
164
                    externalVars = (AbstractMapType) vars.itemAt(0);
1✔
165
                } else {
1✔
166
                    throw new XPathException(this, ErrorCodes.XPTY0004, "Option 'variables' must be a map");
×
167
                }
168
            }
169
            contextItem = map.get(OPTIONS_CONTEXT_ITEM);
1✔
170
            if (contextItem.getItemCount() > 1) {
1!
171
                throw new XPathException(this, ErrorCodes.XPTY0004, "Option 'context-item' must contain zero or one " +
×
172
                        "items");
173
            }
174
        }
175

176
        // create temporary context so main context is not polluted
177
        final XQueryContext tempContext = new XQueryContext(context.getBroker().getBrokerPool(), context.getProfiler());
1✔
178
        try {
179
            tempContext.setWatchDog(context.getWatchDog());
1✔
180
            tempContext.setModuleLoadPath(context.getModuleLoadPath());
1✔
181
            setExternalVars(externalVars, tempContext::declareGlobalVariable);
1✔
182
            tempContext.prepareForExecution();
1✔
183

184
            Module[] loadedModules = null;
1✔
185
            try {
186
                loadedModules = tempContext.importModule(targetNamespace, null, locationHints);
1✔
187

188
            } catch (final XPathException e) {
1✔
189
                if (e.getErrorCode() == ErrorCodes.XQST0059) {
1!
190
                    // importModule may throw exception if no location is given and module cannot be resolved
191
                    throw new XPathException(this, ErrorCodes.FOQM0002, "Module with URI " + targetNamespace + " not found");
1✔
192
                }
193
                throw new XPathException(this, ErrorCodes.FOQM0003, "Error found when importing module: " + e.getMessage());
×
194
            }
195

196
            // not found, raise error
197
            if (loadedModules == null || loadedModules.length == 0) {
1!
198
                throw new XPathException(this, ErrorCodes.FOQM0002, "Module with URI " + targetNamespace + " not found");
×
199
            }
200

201
            if (!xqVersion.equals(getXQueryVersion(tempContext.getXQueryVersion()))) {
1✔
202
                throw new XPathException(this, ErrorCodes.FOQM0003, "Imported module has wrong XQuery version: " +
1✔
203
                        getXQueryVersion(tempContext.getXQueryVersion()));
1✔
204
            }
205

206
            final IMap<AtomicValue, Sequence> variables = newLinearMap(null);
1✔
207
            final IMap<AtomicValue, IMap<AtomicValue, Sequence>> functions = newLinearMap(null);
1✔
208

209
            for (final Module loadedModule : loadedModules) {
1✔
210
                loadedModule.setContextItem(contextItem);
1✔
211
                setExternalVars(externalVars, loadedModule::declareVariable);
1✔
212
                if (!loadedModule.isInternalModule()) {
1✔
213
                    // ensure variable declarations in the imported module are analyzed.
214
                    // unlike when using a normal import statement, this is not done automatically
215
                    ((ExternalModule) loadedModule).analyzeGlobalVars();
1✔
216
                }
217

218
                getModuleVariables(loadedModule, variables);
1✔
219
                getModuleFunctions(loadedModule, tempContext, functions);
1✔
220
            }
221

222
            final IMap<AtomicValue, Sequence> result = Map.from(io.lacuna.bifurcan.List.of(
1✔
223
                    new Maps.Entry<>(RESULT_FUNCTIONS, new MapType(this, context, functions.mapValues((k, v) -> (Sequence) new MapType(this, context, v.forked(), Type.INTEGER)).forked(), Type.QNAME)),
1✔
224
                    new Maps.Entry<>(RESULT_VARIABLES, new MapType(this, context, variables.forked(), Type.QNAME))
1✔
225
            ));
226

227
            return new MapType(this, context, result, Type.STRING);
1✔
228
        } finally {
229
            context.addImportedContext(tempContext);
1✔
230
        }
231
    }
232

233
    private void getModuleVariables(final Module module, final IMap<AtomicValue, Sequence> variables) throws XPathException {
234
        for (final Iterator<QName> i = module.getGlobalVariables(); i.hasNext(); ) {
1✔
235
            final QName name = i.next();
1✔
236
            try {
237
                final Variable var = module.resolveVariable(name);
1✔
238
                variables.put(new QNameValue(context, name), var.getValue());
1✔
239
            } catch (final XPathException e) {
1✔
240
                throw new XPathException(this, ErrorCodes.FOQM0005, "Incorrect type for external variable " + name);
1✔
241
            }
242
        }
243
    }
1✔
244

245
    private void getModuleFunctions(final Module module, final XQueryContext tempContext, final IMap<AtomicValue, IMap<AtomicValue, Sequence>> functions) throws XPathException {
246
        final ValueSequence functionSeq = new ValueSequence();
1✔
247
        addFunctionRefsFromModule(this, tempContext, functionSeq, module);
1✔
248
        for (final SequenceIterator i = functionSeq.iterate(); i.hasNext(); ) {
1✔
249
            final FunctionReference ref = (FunctionReference) i.nextItem();
1✔
250
            final FunctionSignature signature = ref.getSignature();
1✔
251
            final QNameValue qn = new QNameValue(context, signature.getName());
1✔
252
            IMap<AtomicValue, Sequence> entry = functions.get(qn, null);
1✔
253
            if (entry == null) {
1✔
254
                entry = newLinearMap(null);
1✔
255
                functions.put(qn, entry);
1✔
256
            }
257
            entry.put(new IntegerValue(signature.getArgumentCount()), ref);
1✔
258
        }
259
    }
1✔
260

261
    private void setExternalVars(final AbstractMapType externalVars, final ConsumerE<Variable, XPathException> setter)
262
            throws XPathException {
263
        for (final IEntry<AtomicValue, Sequence> entry: externalVars) {
1✔
264
            if (!Type.subTypeOf(entry.key().getType(), Type.QNAME)) {
1!
265
                throw new XPathException(this, ErrorCodes.XPTY0004, "name of external variable must be a qname: " +
×
266
                        entry.key());
×
267
            }
268

269
            final Variable var = new VariableImpl(((QNameValue) entry.key()).getQName());
1✔
270
            var.setValue(entry.value());
1✔
271
            setter.accept(var);
1✔
272
        }
273
    }
1✔
274

275
    public static void addFunctionRefsFromModule(final Expression parent, final XQueryContext tempContext,
276
            final ValueSequence resultSeq, final Module module) throws XPathException {
277
        final FunctionSignature[] signatures = module.listFunctions();
1✔
278
        for (final FunctionSignature signature : signatures) {
1✔
279
            if (!signature.isPrivate()) {
1✔
280
                if (module.isInternalModule()) {
1✔
281
                    int arity;
282
                    if (signature.isVariadic()) {
1✔
283
                        arity = signature.getArgumentTypes().length;
1✔
284
                    }
1✔
285
                    else {
286
                        arity = signature.getArgumentCount();
1✔
287
                    }
288
                    final FunctionDef def = ((InternalModule)module).getFunctionDef(signature.getName(), arity);
1✔
289
                    final XQueryAST ast = new XQueryAST();
1✔
290
                    ast.setLine(parent.getLine());
1✔
291
                    ast.setColumn(parent.getColumn());
1✔
292
                    final List<Expression> args = new ArrayList<>(arity);
1✔
293
                    for (int i = 0; i < arity; i++) {
1✔
294
                        args.add(new Function.Placeholder(tempContext));
1✔
295
                    }
296
                    final Function fn = Function.createFunction(tempContext, ast, module, def);
1✔
297
                    fn.setArguments(args);
1✔
298
                    final InternalFunctionCall call = new InternalFunctionCall(fn);
1✔
299
                    final FunctionCall ref = FunctionFactory.wrap(tempContext, call);
1✔
300
                    resultSeq.addAll(new FunctionReference(ref));
1✔
301
                } else {
1✔
302
                    final UserDefinedFunction func = ((ExternalModule) module).getFunction(signature.getName(), signature.getArgumentCount(), tempContext);
1✔
303
                    // could be null if private function
304
                    if (func != null) {
1!
305
                        // create function reference
306
                        final FunctionCall funcCall = new FunctionCall(tempContext, func);
1✔
307
                        funcCall.setLocation(parent.getLine(), parent.getColumn());
1✔
308
                        resultSeq.add(new FunctionReference(funcCall));
1✔
309
                    }
310
                }
311
            }
312
        }
313
    }
1✔
314

315
    private static String getXQueryVersion(final int version) {
316
        return String.valueOf(version / 10) + '.' + version % 10;
1✔
317
    }
318
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc