From 1a105299e4c49df48e2a2c0970586f43460f4e89 Mon Sep 17 00:00:00 2001 From: Adam Retter Date: Thu, 18 Dec 2025 15:00:40 +0100 Subject: [PATCH 01/13] [bugfix] Raise error code XPTY0019 when an Atomic Value cannot be converted to a Sequence of Nodes --- .../src/main/java/org/exist/xquery/value/AtomicValue.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exist-core/src/main/java/org/exist/xquery/value/AtomicValue.java b/exist-core/src/main/java/org/exist/xquery/value/AtomicValue.java index f9e927600d..fdbab0fd64 100644 --- a/exist-core/src/main/java/org/exist/xquery/value/AtomicValue.java +++ b/exist-core/src/main/java/org/exist/xquery/value/AtomicValue.java @@ -230,14 +230,14 @@ public NodeSet toNodeSet() throws XPathException { if (!effectiveBooleanValue()) return NodeSet.EMPTY_SET; */ - throw new XPathException(getExpression(), + throw new XPathException(getExpression(), ErrorCodes.W3CErrorCode.XPTY0019.getErrorCode(), "cannot convert " + Type.getTypeName(getType()) + "('" + getStringValue() + "')" + " to a node set"); } @Override public MemoryNodeSet toMemNodeSet() throws XPathException { - throw new XPathException(getExpression(), + throw new XPathException(getExpression(), ErrorCodes.W3CErrorCode.XPTY0019.getErrorCode(), "cannot convert " + Type.getTypeName(getType()) + "('" + getStringValue() + "')" + " to a node set"); } From d9828ca0b370c7d77fea694ebd3d870ca8e807cf Mon Sep 17 00:00:00 2001 From: Adam Retter Date: Thu, 18 Dec 2025 17:34:07 +0100 Subject: [PATCH 02/13] [bugfix] Fix an issue with //following::node(), //preceding::node(), //function(following::node()), and //function(preceding::node()) whereby these were incorrectly parsed and flattened to just a descendant-or-self::node() lookup, resulting in incorrect results during evaluation. --- exist-core/pom.xml | 4 +- .../org/exist/xquery/parser/XQueryTree.g | 30 ++++++++---- .../main/java/org/exist/xquery/PathExpr.java | 34 +++++++++++++ .../java/org/exist/xquery/XPathQueryTest.java | 48 +++++++++++++++++++ extensions/indexes/lucene/pom.xml | 2 + ...arenthesizedLocationStep_ftquery_Tests.xml | 26 +++++++++- 6 files changed, 134 insertions(+), 10 deletions(-) diff --git a/exist-core/pom.xml b/exist-core/pom.xml index f5e8137632..6308bbcc5e 100644 --- a/exist-core/pom.xml +++ b/exist-core/pom.xml @@ -1193,6 +1193,7 @@ src/test/java/org/exist/xquery/NodeTypeTest.java src/main/java/org/exist/xquery/Optimizer.java src/main/java/org/exist/xquery/Option.java + src/main/java/org/exist/xquery/PathExpr.java src/main/java/org/exist/xquery/PerformanceStats.java src/test/java/org/exist/xquery/RestBinariesTest.java src/test/java/org/exist/xquery/StoredModuleTest.java @@ -1974,6 +1975,7 @@ src/test/java/org/exist/xquery/NodeTypeTest.java src/main/java/org/exist/xquery/Optimizer.java src/main/java/org/exist/xquery/Option.java + src/main/java/org/exist/xquery/PathExpr.java src/main/java/org/exist/xquery/PerformanceStats.java src/test/java/org/exist/xquery/RestBinariesTest.java src/test/java/org/exist/xquery/StoredModuleTest.java @@ -2715,4 +2717,4 @@ The BaseX Team. The original license statement is also included below.]]> - \ No newline at end of file + diff --git a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g index 84768536ed..b3d8589af3 100644 --- a/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g +++ b/exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g @@ -2801,19 +2801,33 @@ throws PermissionDeniedException, EXistException, XPathException rs.setAxis(Constants.DESCENDANT_AXIS); } else if (rs.getAxis() == Constants.SELF_AXIS) { rs.setAxis(Constants.DESCENDANT_SELF_AXIS); - } else { + } else if (rs.getAxis() == Constants.CHILD_AXIS || rs.getAxis() == Constants.UNKNOWN_AXIS) { + // For CHILD_AXIS or UNKNOWN_AXIS, change to descendant-or-self rs.setAxis(Constants.DESCENDANT_SELF_AXIS); rs.setAbbreviated(true); + } else { + // For other explicit axes (following, preceding, ancestor, etc.) + // insert a separate descendant-or-self::node() step before this step + final LocationStep dsStep = new LocationStep(context, Constants.DESCENDANT_SELF_AXIS, new AnyNodeTest()); + path.insertBeforeLast(dsStep); } } else { - rightStep.setPrimaryAxis(Constants.DESCENDANT_SELF_AXIS); - if(rightStep instanceof VariableReference) { - rightStep = new SimpleStep(context, Constants.DESCENDANT_SELF_AXIS, rightStep); - path.replaceLastExpression(rightStep); - } else if (rightStep instanceof FilteredExpression) - ((FilteredExpression)rightStep).setAbbreviated(true); - + if (rightStep instanceof Function) { + // For non-LocationStep expressions (function calls, etc.) + // insert a separate descendant-or-self::node() step before this step + final LocationStep dsStep = new LocationStep(context, Constants.DESCENDANT_SELF_AXIS, new AnyNodeTest()); + path.insertBeforeLast(dsStep); + } else { + rightStep.setPrimaryAxis(Constants.DESCENDANT_SELF_AXIS); + if(rightStep instanceof VariableReference) { + // VariableReference needs special handling + rightStep = new SimpleStep(context, Constants.DESCENDANT_SELF_AXIS, rightStep); + path.replaceLastExpression(rightStep); + } else if (rightStep instanceof FilteredExpression) { + ((FilteredExpression)rightStep).setAbbreviated(true); + } + } } } )? diff --git a/exist-core/src/main/java/org/exist/xquery/PathExpr.java b/exist-core/src/main/java/org/exist/xquery/PathExpr.java index 818a30c5a2..83bfcf5344 100644 --- a/exist-core/src/main/java/org/exist/xquery/PathExpr.java +++ b/exist-core/src/main/java/org/exist/xquery/PathExpr.java @@ -1,4 +1,28 @@ /* + * Elemental + * Copyright (C) 2024, Evolved Binary Ltd + * + * admin@evolvedbinary.com + * https://www.evolvedbinary.com | https://www.elemental.xyz + * + * 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; version 2.1. + * + * 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 + * + * NOTE: Parts of this file contain code from 'The eXist-db Authors'. + * The original license header is included below. + * + * ===================================================================== + * * eXist-db Open Source Native XML Database * Copyright (C) 2001 The eXist-db Authors * @@ -498,6 +522,16 @@ public void replaceLastExpression(final Expression s) { steps.set(steps.size() - 1, s); } + /** + * Insert an expression before the last expression in the path. + * + * @param expression the expression to insert + */ + public void insertBeforeLast(final Expression expression) { + final int idx = steps.isEmpty() ? 0 : steps.size() - 1; + steps.add(idx, expression); + } + public String getLiteralValue() { if (steps.size() == 0) { return ""; diff --git a/exist-core/src/test/java/org/exist/xquery/XPathQueryTest.java b/exist-core/src/test/java/org/exist/xquery/XPathQueryTest.java index e9ce117cdf..63efdbfc81 100644 --- a/exist-core/src/test/java/org/exist/xquery/XPathQueryTest.java +++ b/exist-core/src/test/java/org/exist/xquery/XPathQueryTest.java @@ -820,6 +820,54 @@ public void followingAxis() throws XMLDBException, IOException, SAXException { result = queryResource(service, "siblings.xml", "//a/s[. = 'B']/following::s[2]", 1); assertThat(result.getResource(0).getContent().toString(), CompareMatcher.isIdenticalTo("C")); + + String query = "declare variable $i := \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " ;\n" + + "\n" + + "root($i)//following::node()"; + + result = service.query(query); + assertEquals(5, result.getSize()); + assertThat(result.getResource(0).getContent().toString(), CompareMatcher.isIdenticalTo("")); + assertThat(result.getResource(1).getContent().toString(), CompareMatcher.isIdenticalTo("")); + assertThat(result.getResource(2).getContent().toString(), CompareMatcher.isIdenticalTo("")); + assertThat(result.getResource(3).getContent().toString(), CompareMatcher.isIdenticalTo("")); + assertThat(result.getResource(4).getContent().toString(), CompareMatcher.isIdenticalTo("")); + + query = "declare variable $i := \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " ;\n" + + "\n" + + "root($i)//count(following::node())"; + + result = service.query(query); + assertEquals(7, result.getSize()); + assertEquals("0", result.getResource(0).getContent()); + assertEquals("5", result.getResource(1).getContent()); + assertEquals("4", result.getResource(2).getContent()); + assertEquals("0", result.getResource(3).getContent()); + assertEquals("0", result.getResource(4).getContent()); + assertEquals("0", result.getResource(5).getContent()); + assertEquals("0", result.getResource(6).getContent()); } @Test diff --git a/extensions/indexes/lucene/pom.xml b/extensions/indexes/lucene/pom.xml index 1690949457..aa39dc7752 100644 --- a/extensions/indexes/lucene/pom.xml +++ b/extensions/indexes/lucene/pom.xml @@ -240,6 +240,7 @@ pom.xml src/test/resources-filtered/conf.xml src/test/resources/log4j2.xml + src/test/xquery/lucene/parenthesizedLocationStep_ftquery_Tests.xml src/main/java/org/exist/indexing/lucene/AbstractFieldConfig.java src/main/java/org/exist/indexing/lucene/AbstractTextExtractor.java src/main/java/org/exist/indexing/lucene/AnalyzerConfig.java @@ -279,6 +280,7 @@ src/test/resources-filtered/conf.xml src/test/resources/log4j2.xml src/test/xquery/lucene/fields.xqm + src/test/xquery/lucene/parenthesizedLocationStep_ftquery_Tests.xml src/main/java/org/exist/indexing/lucene/AbstractFieldConfig.java src/main/java/org/exist/indexing/lucene/AbstractTextExtractor.java src/main/java/org/exist/indexing/lucene/AnalyzerConfig.java diff --git a/extensions/indexes/lucene/src/test/xquery/lucene/parenthesizedLocationStep_ftquery_Tests.xml b/extensions/indexes/lucene/src/test/xquery/lucene/parenthesizedLocationStep_ftquery_Tests.xml index 1b87d08c8c..0d417ef601 100644 --- a/extensions/indexes/lucene/src/test/xquery/lucene/parenthesizedLocationStep_ftquery_Tests.xml +++ b/extensions/indexes/lucene/src/test/xquery/lucene/parenthesizedLocationStep_ftquery_Tests.xml @@ -1,5 +1,29 @@