Skip to content

Commit dfe03ed

Browse files
authored
Merge pull request #4120 from evolvedbinary/hotfix/module-lazy-variable-analysis
Ensure variables are analyzed before evaluated when called from a module
2 parents 650e6d0 + 750f139 commit dfe03ed

File tree

9 files changed

+162
-13
lines changed

9 files changed

+162
-13
lines changed

exist-core/src/main/java/org/exist/interpreter/Context.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.exist.storage.lock.LockedDocumentMap;
4444
import org.exist.util.hashtable.NamePool;
4545
import org.exist.xmldb.XmldbURI;
46+
import org.exist.xquery.AnalyzeContextInfo;
4647
import org.exist.xquery.Expression;
4748
import org.exist.xquery.FunctionCall;
4849
import org.exist.xquery.FunctionSignature;
@@ -507,8 +508,22 @@ public interface Context {
507508
/**
508509
* Try to resolve a variable.
509510
*
511+
* @param contextInfo contextual information
510512
* @param qname the qualified name of the variable
513+
*
514+
* @return the declared Variable object
515+
*
516+
* @throws XPathException if the variable is unknown
517+
*/
518+
Variable resolveVariable(@Nullable AnalyzeContextInfo contextInfo, QName qname) throws XPathException;
519+
520+
/**
521+
* Try to resolve a variable.
522+
*
523+
* @param qname the qualified name of the variable
524+
*
511525
* @return the declared Variable object
526+
*
512527
* @throws XPathException if the variable is unknown
513528
*/
514529
Variable resolveVariable(QName qname) throws XPathException;
@@ -867,4 +882,4 @@ public interface Context {
867882

868883
void runCleanupTasks(final Predicate<Object> predicate);
869884

870-
}
885+
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import org.exist.dom.QName;
2727
import org.exist.xquery.value.Sequence;
2828

29+
import javax.annotation.Nullable;
30+
2931
/**
3032
* Abstract base class for an {@link org.exist.xquery.InternalModule}.
3133
* Functions are defined in an array of {@link org.exist.xquery.FunctionDef}, which
@@ -213,7 +215,12 @@ public Variable declareVariable(final Variable var) {
213215
}
214216

215217
@Override
216-
public Variable resolveVariable(final QName qname) throws XPathException {
218+
@Nullable public Variable resolveVariable(final QName qname) throws XPathException {
219+
return resolveVariable(null, qname);
220+
}
221+
222+
@Override
223+
@Nullable public Variable resolveVariable(@Nullable final AnalyzeContextInfo contextInfo, final QName qname) throws XPathException {
217224
return mGlobalVariables.get(qname);
218225
}
219226

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

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import org.exist.xquery.value.Sequence;
2929
import org.exist.storage.DBBroker;
3030

31+
import javax.annotation.Nullable;
32+
3133
/**
3234
* Default implementation of an {@link org.exist.xquery.ExternalModule}.
3335
*
@@ -215,13 +217,26 @@ public boolean isVarDeclared(QName qname) {
215217
return mStaticVariables.get(qname) != null;
216218
}
217219

218-
/* (non-Javadoc)
219-
* @see org.exist.xquery.Module#resolveVariable(org.exist.dom.QName)
220-
*/
221-
public Variable resolveVariable(QName qname) throws XPathException {
220+
@Override
221+
@Nullable public Variable resolveVariable(final QName qname) throws XPathException {
222+
return resolveVariable(null, qname);
223+
}
224+
225+
@Override
226+
@Nullable public Variable resolveVariable(@Nullable final AnalyzeContextInfo contextInfo, final QName qname) throws XPathException {
222227
final VariableDeclaration decl = mGlobalVariables.get(qname);
223228
Variable var = mStaticVariables.get(qname);
224229
if (isReady && decl != null && (var == null || var.getValue() == null)) {
230+
231+
// Make sure Analyze has been called, see - https://github.com/eXist-db/exist/issues/4096
232+
final AnalyzeContextInfo declContextInfo;
233+
if (contextInfo != null) {
234+
declContextInfo = new AnalyzeContextInfo(contextInfo);
235+
} else {
236+
declContextInfo = new AnalyzeContextInfo();
237+
}
238+
decl.analyze(declContextInfo);
239+
225240
decl.eval(getContext().getContextItem());
226241
var = mStaticVariables.get(qname);
227242
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathExc
175175
try {
176176
seq[i] = getArgument(i).eval(contextSequence, contextItem);
177177
if(varDeps != null && varDeps[i] != null) {
178-
final Variable var = varDeps[i].getVariable();
178+
final Variable var = varDeps[i].getVariable(null);
179179
if(var != null) {
180180
contextDocs[i] = var.getContextDocs();
181181
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public interface Module {
9898
*/
9999
@Nullable Iterator<FunctionSignature> getSignaturesForFunction(QName qname);
100100

101+
@Nullable Variable resolveVariable(@Nullable AnalyzeContextInfo contextInfo, QName qname) throws XPathException;
101102
@Nullable Variable resolveVariable(QName qname) throws XPathException;
102103

103104
public Variable declareVariable(QName qname, Object value) throws XPathException;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ public Sequence eval(Sequence contextSequence, final Item contextItem) throws XP
217217
DocumentSet contextDocs = null;
218218
Expression expr = steps.get(0);
219219
if (expr instanceof VariableReference) {
220-
final Variable var = ((VariableReference) expr).getVariable();
220+
final Variable var = ((VariableReference) expr).getVariable(new AnalyzeContextInfo(parent, 0));
221221
//TOUNDERSTAND : how null could be possible here ? -pb
222222
if (var != null) {
223223
contextDocs = var.getContextDocs();

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import org.exist.xquery.value.Sequence;
2929
import org.exist.xquery.value.Type;
3030

31+
import javax.annotation.Nullable;
32+
3133
/**
3234
* Represents a reference to an in-scope variable.
3335
*
@@ -52,7 +54,7 @@ public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException
5254
this.parent = contextInfo.getParent();
5355
Variable var = null;
5456
try {
55-
var = getVariable();
57+
var = getVariable(contextInfo);
5658
} catch (final XPathException e) {
5759
// ignore: variable might not be known yet
5860
return;
@@ -84,7 +86,7 @@ public Sequence eval(final Sequence contextSequence, final Item contextItem) thr
8486
"CONTEXT ITEM", contextItem.toSequence());
8587
}
8688
}
87-
final Variable var = getVariable();
89+
final Variable var = getVariable(new AnalyzeContextInfo(parent, 0));
8890
if (var == null) {
8991
throw new XPathException(this, ErrorCodes.XPDY0002, "variable '$" + qname + "' is not set.");
9092
}
@@ -99,9 +101,9 @@ public Sequence eval(final Sequence contextSequence, final Item contextItem) thr
99101
return result;
100102
}
101103

102-
protected Variable getVariable() throws XPathException {
104+
protected Variable getVariable(@Nullable final AnalyzeContextInfo contextInfo) throws XPathException {
103105
try {
104-
return context.resolveVariable(qname);
106+
return context.resolveVariable(contextInfo, qname);
105107
} catch (final XPathException e) {
106108
e.setLocation(line, column);
107109
throw e;

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1869,6 +1869,11 @@ public Variable resolveVariable(final String name) throws XPathException {
18691869

18701870
@Override
18711871
public Variable resolveVariable(final QName qname) throws XPathException {
1872+
return resolveVariable(null, qname);
1873+
}
1874+
1875+
@Override
1876+
public Variable resolveVariable(@Nullable final AnalyzeContextInfo contextInfo, final QName qname) throws XPathException {
18721877
// check if the variable is declared local
18731878
Variable var = resolveLocalVariable(qname);
18741879

@@ -1878,7 +1883,7 @@ public Variable resolveVariable(final QName qname) throws XPathException {
18781883

18791884
if (modules != null) {
18801885
for (final Module module : modules) {
1881-
var = module.resolveVariable(qname);
1886+
var = module.resolveVariable(contextInfo, qname);
18821887
if (var != null) {
18831888
break;
18841889
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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.TestUtils;
25+
import org.exist.test.ExistXmldbEmbeddedServer;
26+
import org.exist.xmldb.EXistResource;
27+
import org.exist.xmldb.XmldbURI;
28+
import org.junit.BeforeClass;
29+
import org.junit.ClassRule;
30+
import org.junit.Test;
31+
import org.xmldb.api.DatabaseManager;
32+
import org.xmldb.api.base.Collection;
33+
import org.xmldb.api.base.ResourceSet;
34+
import org.xmldb.api.base.XMLDBException;
35+
import org.xmldb.api.modules.BinaryResource;
36+
import org.xmldb.api.modules.CollectionManagementService;
37+
import org.xmldb.api.modules.XMLResource;
38+
39+
import static org.junit.Assert.assertEquals;
40+
import static org.junit.Assert.assertNotNull;
41+
42+
public class VariablesTest {
43+
44+
@ClassRule
45+
public static final ExistXmldbEmbeddedServer existEmbeddedServer = new ExistXmldbEmbeddedServer(false, true, true);
46+
47+
private final static String MODULE =
48+
"module namespace mod1 = \"http://mod1\";\n" +
49+
"\n" +
50+
"declare variable $mod1:OPEN_GRAPH as map(xs:string, function(*)) := map {\n" +
51+
" \"og:title\" : function($node, $model) {\n" +
52+
" <meta property=\"og:title\" content=\"{map:get($mod1:PUBLICATIONS, 'some-id')}\"/>\n" +
53+
" }\n" +
54+
"};\n" +
55+
"\n" +
56+
"declare variable $mod1:PUBLICATIONS := map {\n" +
57+
" \"open-graph\" : map:merge((\n" +
58+
" $mod1:OPEN_GRAPH,\n" +
59+
" map {\n" +
60+
" \"og:image\" : function($node, $model) {\n" +
61+
" <meta property=\"og:image\" content=\"https://some/uri/some/image.png\"/>\n" +
62+
" }\n" +
63+
" }\n" +
64+
" ))\n" +
65+
"};";
66+
67+
@BeforeClass
68+
public static void setup() throws XMLDBException {
69+
final Collection c = createCollection("variables-test");
70+
writeModule(c, "mod1.xqm", MODULE);
71+
}
72+
73+
@Test
74+
public void callModule() throws XMLDBException {
75+
final String query =
76+
"import module namespace mod1 = \"http://mod1\" at \"xmldb:exist:///db/variables-test/mod1.xqm\";\n" +
77+
"$mod1:PUBLICATIONS(\"open-graph\")";
78+
79+
final ResourceSet rs = existEmbeddedServer.executeQuery(query);
80+
assertEquals(1, rs.getSize());
81+
assertEquals(XMLResource.RESOURCE_TYPE, rs.getResource(0).getResourceType());
82+
}
83+
84+
private static Collection createCollection(final String collectionName) throws XMLDBException {
85+
Collection collection = existEmbeddedServer.getRoot().getChildCollection(collectionName);
86+
final CollectionManagementService cmService = (CollectionManagementService) existEmbeddedServer.getRoot().getService("CollectionManagementService", "1.0");
87+
if (collection == null) {
88+
//cmService.removeCollection(collectionName);
89+
cmService.createCollection(collectionName);
90+
}
91+
92+
collection = DatabaseManager.getCollection(XmldbURI.LOCAL_DB + "/" + collectionName, TestUtils.ADMIN_DB_USER, TestUtils.ADMIN_DB_PWD);
93+
assertNotNull(collection);
94+
return collection;
95+
}
96+
97+
private static void writeModule(final Collection collection, final String modulename, final String module) throws XMLDBException {
98+
final BinaryResource res = (BinaryResource) collection.createResource(modulename, "BinaryResource");
99+
((EXistResource) res).setMimeType("application/xquery");
100+
res.setContent(module.getBytes());
101+
collection.storeResource(res);
102+
collection.close();
103+
}
104+
}

0 commit comments

Comments
 (0)