From 4e211665ba5d1a57800298016eeb290a450729ed Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Sun, 10 May 2026 00:08:33 -0400 Subject: [PATCH 1/2] [bugfix] Preserve in-scope namespaces when copying XQueryContext The XQueryContext copy constructor copied staticNamespaces (prolog "declare namespace" entries) but dropped inScopeNamespaces. The tree parser uses the copy as its lookup context (XQueryTree.g:94), so any prefix a host application registered via declareInScopeNamespace before compilation -- the API the XQTS runner uses to apply each test's ... declarations -- was invisible to QName.parse() during tree-walk and produced spurious XPST0081 errors. Closes 23 XQ 3.1 XQTS failures across op-intersect, op-union, and op-except (the fn-{intersect,union,except}-node-args tests using the "atomic" environment), plus partial relief for prod-* tests that go through the same code path. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../java/org/exist/xquery/XQueryContext.java | 10 ++ .../xquery/InScopeNamespaceCompileTest.java | 100 ++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 exist-core/src/test/java/org/exist/xquery/InScopeNamespaceCompileTest.java diff --git a/exist-core/src/main/java/org/exist/xquery/XQueryContext.java b/exist-core/src/main/java/org/exist/xquery/XQueryContext.java index 9f5e0bbf7ab..0f46b11b8a7 100644 --- a/exist-core/src/main/java/org/exist/xquery/XQueryContext.java +++ b/exist-core/src/main/java/org/exist/xquery/XQueryContext.java @@ -509,6 +509,16 @@ public XQueryContext(final XQueryContext copyFrom) { LOG.warn("Failed to copy namespace declaration for prefix '{}'", prefix, ex); } } + + // Copy in-scope namespaces too. Hosts (e.g. the XQTS runner) register + // environment-supplied prefixes via declareInScopeNamespace before + // compilation, and the tree parser's QName.parse() consults this map + // when resolving path-step names. Dropping it produced spurious + // XPST0081 errors during compile. + for (final Map.Entry entry : copyFrom.inScopeNamespaces.entrySet()) { + inScopeNamespaces.put(entry.getKey(), entry.getValue()); + inScopePrefixes.put(entry.getValue(), entry.getKey()); + } } diff --git a/exist-core/src/test/java/org/exist/xquery/InScopeNamespaceCompileTest.java b/exist-core/src/test/java/org/exist/xquery/InScopeNamespaceCompileTest.java new file mode 100644 index 00000000000..1f3e1f28c81 --- /dev/null +++ b/exist-core/src/test/java/org/exist/xquery/InScopeNamespaceCompileTest.java @@ -0,0 +1,100 @@ +/* + * eXist-db Open Source Native XML Database + * Copyright (C) 2001 The eXist-db Authors + * + * info@exist-db.org + * http://www.exist-db.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package org.exist.xquery; + +import java.io.StringReader; + +import org.exist.EXistException; +import org.exist.security.PermissionDeniedException; +import org.exist.storage.BrokerPool; +import org.exist.test.ExistEmbeddedServer; +import org.exist.xquery.parser.XQueryLexer; +import org.exist.xquery.parser.XQueryParser; +import org.exist.xquery.parser.XQueryTreeParser; + +import antlr.collections.AST; + +import org.junit.ClassRule; +import org.junit.Test; + +/** + * Verifies that namespaces registered on the static context via + * {@link XQueryContext#declareInScopeNamespace(String, String)} before + * compilation are visible to path-step QName resolution. The XQTS runner + * relies on this to apply <environment>/<namespace> declarations + * (e.g. the "atomic" environment) without rewriting test queries. + * + *

Regression test for set-operator XPST0081 failures discovered in the + * XQ 3.1 develop gap analysis (op-intersect, op-union, op-except). + */ +public class InScopeNamespaceCompileTest { + + @ClassRule + public static final ExistEmbeddedServer server = new ExistEmbeddedServer(true, true); + + private static final String ATOMIC_NS = "http://www.w3.org/XQueryTest"; + + private void compileWithAtomicPrefix(final String query) throws Exception { + final BrokerPool pool = BrokerPool.getInstance(); + final XQueryContext context = new XQueryContext(pool); + context.declareInScopeNamespace("atomic", ATOMIC_NS); + + final XQueryLexer lexer = new XQueryLexer(context, new StringReader(query)); + final XQueryParser xparser = new XQueryParser(lexer); + xparser.xpath(); + if (xparser.foundErrors()) { + throw new AssertionError(xparser.getErrorMessage()); + } + final AST ast = xparser.getAST(); + final XQueryTreeParser treeParser = new XQueryTreeParser(context); + final PathExpr expr = new PathExpr(context); + treeParser.xpath(ast, expr); + if (treeParser.foundErrors()) { + throw new AssertionError(treeParser.getErrorMessage()); + } + } + + @Test + public void intersectResolvesPrefix() + throws EXistException, PermissionDeniedException, Exception { + // mirrors XQTS fn-intersect-node-args-016 + compileWithAtomicPrefix("(/atomic:root/atomic:integer) intersect (/atomic:root/atomic:integer)"); + } + + @Test + public void unionResolvesPrefix() throws Exception { + // mirrors XQTS fn-union-node-args-016 + compileWithAtomicPrefix("(/atomic:root/atomic:integer) union (/atomic:root/atomic:integer)"); + } + + @Test + public void exceptResolvesPrefix() throws Exception { + // mirrors XQTS fn-except-node-args-016 + compileWithAtomicPrefix("(/atomic:root/atomic:integer) except (/atomic:root/atomic:integer)"); + } + + @Test + public void plainPathResolvesPrefix() throws Exception { + // baseline: a plain path expression must work too + compileWithAtomicPrefix("/atomic:root/atomic:integer"); + } +} From 5a91572f5dd7bb90d82d454970c5fc41245c5d8f Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Sun, 10 May 2026 11:46:07 -0400 Subject: [PATCH 2/2] [refactor] XQueryContext: shorten copy-in-scope-namespaces comment per line-o Apply line-o's exact suggested wording from the PR #6329 review thread. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/main/java/org/exist/xquery/XQueryContext.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/exist-core/src/main/java/org/exist/xquery/XQueryContext.java b/exist-core/src/main/java/org/exist/xquery/XQueryContext.java index 0f46b11b8a7..9eac6a95e1d 100644 --- a/exist-core/src/main/java/org/exist/xquery/XQueryContext.java +++ b/exist-core/src/main/java/org/exist/xquery/XQueryContext.java @@ -510,11 +510,8 @@ public XQueryContext(final XQueryContext copyFrom) { } } - // Copy in-scope namespaces too. Hosts (e.g. the XQTS runner) register - // environment-supplied prefixes via declareInScopeNamespace before - // compilation, and the tree parser's QName.parse() consults this map - // when resolving path-step names. Dropping it produced spurious - // XPST0081 errors during compile. + // Copy in-scope namespaces registered via declareInScopeNamespace. Otherwise QName.parse() + // will raise error XPST0081 when resolving path-step names in any of those. for (final Map.Entry entry : copyFrom.inScopeNamespaces.entrySet()) { inScopeNamespaces.put(entry.getKey(), entry.getValue()); inScopePrefixes.put(entry.getValue(), entry.getKey());