Skip to content

Commit dc0b21c

Browse files
fn:function-lookup: find functions from all modules of the executing query (closes #2641) (#2644)
1 parent 7b2fd71 commit dc0b21c

File tree

4 files changed

+36
-24
lines changed

4 files changed

+36
-24
lines changed

basex-core/src/main/java/org/basex/query/func/Functions.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ public static Expr item(final QNm qnm, final int arity, final boolean runtime,
179179
}
180180

181181
// user-defined function
182-
final StaticFunc sf = qc.functions.get(info.sc(), name, arity);
182+
final StaticFunc sf = qc.functions.get(info.sc(), name, arity, runtime);
183183
if(sf != null) {
184184
final Expr func = item(sf, fb, qc);
185185
if(sf.updating) qc.updating();
@@ -215,7 +215,7 @@ private static QNm funcName(final QNm name, final int arity, final InputInfo inf
215215
final QueryContext qc) {
216216

217217
final StaticContext sc = info.sc();
218-
if(name.hasURI() || qc.functions.get(sc, name, arity) != null) return name;
218+
if(name.hasURI() || qc.functions.get(sc, name, arity, false) != null) return name;
219219
return new QNm(name.local(), sc.funcNS != null ? sc.funcNS : FN_URI);
220220
}
221221

@@ -326,7 +326,7 @@ private static StaticFuncCall staticCall(final QNm name, final FuncBuilder fb,
326326
}
327327

328328
final StaticFuncCall call = new StaticFuncCall(name, fb.args(), fb.keywords, fb.info);
329-
qc.functions.setFunc(call, qc);
329+
qc.functions.setFunc(call, fb.runtime, qc);
330330
return call;
331331
}
332332

basex-core/src/main/java/org/basex/query/func/StaticFuncs.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public StaticFunc declare(final StaticContext sc, final QNm name, final Params p
5656

5757
final byte[] modUri = Token.eq(name.uri(), FN_URI) ? FN_URI : QNm.uri(sc.module);
5858
final StaticFunc sf = new StaticFunc(name, params, expr, anns, vs, info, doc);
59-
if(get(sc, name, sf.min, sf.arity()) != null) throw DUPLFUNC_X.get(info, name);
59+
if(get(sc, name, sf.min, sf.arity(), false) != null) throw DUPLFUNC_X.get(info, name);
6060
funcsByModule.computeIfAbsent(modUri, QNmMap::new).computeIfAbsent(name, ArrayList::new).
6161
add(sf);
6262
return sf;
@@ -76,14 +76,16 @@ public Expr newRef(final QuerySupplier<Expr> resolve) {
7676
/**
7777
* Assigns a function to a static function call.
7878
* @param call name function name
79+
* @param useDynamicContext {@code true} if function lookup should include the dynamic context
7980
* @param qc query context
8081
* @throws QueryException query exception
8182
*/
82-
void setFunc(final StaticFuncCall call, final QueryContext qc) throws QueryException {
83+
void setFunc(final StaticFuncCall call, final boolean useDynamicContext, final QueryContext qc)
84+
throws QueryException {
8385
final InputInfo info = call.info();
8486
final QNm name = call.name;
8587
final int arity = call.arity();
86-
final StaticFunc func = get(info.sc(), name, arity);
88+
final StaticFunc func = get(info.sc(), name, arity, useDynamicContext);
8789
if(func != null) {
8890
if(func.expr == null) throw FUNCNOIMPL_X.get(func.info, func.name.prefixString());
8991
call.setFunc(func);
@@ -128,10 +130,12 @@ public void compileAll(final CompileContext cc) {
128130
* @param sc static context
129131
* @param qname function name
130132
* @param arity function arity
133+
* @param useDynamicContext {@code true} if function lookup should include the dynamic context
131134
* @return function if found, {@code null} otherwise
132135
*/
133-
public StaticFunc get(final StaticContext sc, final QNm qname, final int arity) {
134-
return get(sc, qname, arity, arity);
136+
public StaticFunc get(final StaticContext sc, final QNm qname, final int arity,
137+
final boolean useDynamicContext) {
138+
return get(sc, qname, arity, arity, useDynamicContext);
135139
}
136140

137141
/**
@@ -140,13 +144,15 @@ public StaticFunc get(final StaticContext sc, final QNm qname, final int arity)
140144
* @param qname function name
141145
* @param min minimum function arity
142146
* @param max maximum function arity
147+
* @param useDynamicContext {@code true} if function lookup should include the dynamic context
143148
* @return function if found, {@code null} otherwise
144149
*/
145-
private StaticFunc get(final StaticContext sc, final QNm qname, final int min, final int max) {
150+
private StaticFunc get(final StaticContext sc, final QNm qname, final int min, final int max,
151+
final boolean useDynamicContext) {
146152
final byte[] funcUri = qname.uri();
147153
final byte[] modUri = Token.eq(funcUri, FN_URI) ? FN_URI : QNm.uri(sc.module);
148154
StaticFunc func = get(modUri, qname, min, max);
149-
if(func == null && sc.imports.contains(funcUri)) {
155+
if(func == null && (useDynamicContext || sc.imports.contains(funcUri))) {
150156
func = get(funcUri, qname, min, max);
151157
if(func != null && func.anns.contains(Annotation.PRIVATE)) func = null;
152158
}

basex-core/src/main/java/org/basex/query/func/inspect/InspectFunction.java

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.basex.query.func.inspect;
22

33
import org.basex.query.*;
4-
import org.basex.query.ann.*;
54
import org.basex.query.func.*;
65
import org.basex.query.value.item.*;
76
import org.basex.query.value.node.*;
@@ -22,16 +21,7 @@ public FNode item(final QueryContext qc, final InputInfo ii) throws QueryExcepti
2221
StaticFunc func = null;
2322
if(name != null) {
2423
final int arity = function.arity();
25-
func = qc.functions.get(ii.sc(), name, arity);
26-
if(func == null) {
27-
for(final StaticFunc sf : qc.functions) {
28-
if(!sf.annotations().contains(Annotation.PRIVATE) && sf.funcName().eq(name)
29-
&& sf.minArity() <= arity && sf.arity() >= arity) {
30-
func = sf;
31-
break;
32-
}
33-
}
34-
}
24+
func = qc.functions.get(ii.sc(), name, arity, true);
3525
}
3626
return new PlainDoc(qc, info).function(name, func, function.funcType(), function.annotations());
3727
}

basex-core/src/test/java/org/basex/query/ModuleTest.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,10 +243,10 @@ public final class ModuleTest extends SandboxTest {
243243
+ "};\n"
244244
+ "declare variable $c:hello := 'can you see me now';");
245245

246-
// function is not visible to fn:function-lookup (not in dynamically known function definitions)
246+
// function is visible to fn:function-lookup even when not in static context
247247
query("import module namespace b = 'b' at '" + b.path() + "';\n"
248-
+ "fn:function-lookup(#Q{c}hello, 0)", "");
249-
// function is still visible to inspect:functions
248+
+ "fn:function-lookup(#Q{c}hello, 0)()", "can you see me now");
249+
// function is visible to inspect:functions
250250
query("import module namespace b = 'b' at '" + b.path() + "';\n"
251251
+ "inspect:functions()", "Q{c}hello#0");
252252

@@ -260,4 +260,20 @@ public final class ModuleTest extends SandboxTest {
260260
+ "declare namespace c = 'c';\n"
261261
+ "$c:hello", QueryError.INVISIBLEVAR_X);
262262
}
263+
264+
/** Tests fn:function-lookup from within a library module for a module imported elsewhere. */
265+
@Test public void gh2641() {
266+
final IOFile sandbox = sandbox();
267+
final IOFile a = new IOFile(sandbox, "a.xqm");
268+
final IOFile b = new IOFile(sandbox, "b.xqm");
269+
write(a, "module namespace a = 'A';\n"
270+
+ "declare function a:lookup() {\n"
271+
+ " function-lookup(QName('B', 'test'), 0)\n"
272+
+ "};");
273+
write(b, "module namespace b = 'B';\n"
274+
+ "declare function b:test() {};");
275+
query("import module namespace a = 'A' at '" + a.path() + "';\n"
276+
+ "import module namespace b = 'B' at '" + b.path() + "';\n"
277+
+ "exists(a:lookup())", true);
278+
}
263279
}

0 commit comments

Comments
 (0)