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 20308296806..3109d357158 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
@@ -2989,14 +2989,22 @@ throws PermissionDeniedException, EXistException, XPathException
rs.setAbbreviated(true);
}
- } else {
+ } else if (rightStep instanceof VariableReference) {
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);
-
+ rightStep = new SimpleStep(context, Constants.DESCENDANT_SELF_AXIS, rightStep);
+ path.replaceLastExpression(rightStep);
+ } else if (rightStep instanceof FilteredExpression) {
+ rightStep.setPrimaryAxis(Constants.DESCENDANT_SELF_AXIS);
+ ((FilteredExpression)rightStep).setAbbreviated(true);
+ } else {
+ // For other non-LocationStep expressions (e.g., PathExpr wrapping
+ // parenthesized expressions like //(@x) or //(a | b)), insert an
+ // explicit descendant-or-self::node() step. We must NOT call
+ // setPrimaryAxis here because it would corrupt inner axes (e.g.,
+ // overwriting an attribute axis in //(@x)).
+ LocationStep descStep = new LocationStep(context, Constants.DESCENDANT_SELF_AXIS, new TypeTest(Type.NODE));
+ path.replaceLastExpression(descStep);
+ path.add(rightStep);
}
}
)?
diff --git a/exist-core/src/test/java/org/exist/xquery/DescendantOrSelfWithNonLocationStepTest.java b/exist-core/src/test/java/org/exist/xquery/DescendantOrSelfWithNonLocationStepTest.java
new file mode 100644
index 00000000000..ec3803e2695
--- /dev/null
+++ b/exist-core/src/test/java/org/exist/xquery/DescendantOrSelfWithNonLocationStepTest.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 org.exist.test.ExistXmldbEmbeddedServer;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.xmldb.api.base.ResourceSet;
+import org.xmldb.api.base.XMLDBException;
+import org.xmldb.api.modules.XQueryService;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests for // (descendant-or-self) with non-LocationStep expressions.
+ *
+ * The XPath abbreviation // expands to /descendant-or-self::node()/. When
+ * the right-hand side is a LocationStep, the tree walker can merge the axis.
+ * When it is a non-LocationStep expression (parenthesized expression, variable
+ * reference, filtered expression), we must insert an explicit
+ * descendant-or-self::node() step instead.
+ */
+public class DescendantOrSelfWithNonLocationStepTest {
+
+ @ClassRule
+ public static final ExistXmldbEmbeddedServer existEmbeddedServer =
+ new ExistXmldbEmbeddedServer(false, true, true);
+
+ private ResourceSet execute(final String xquery) throws XMLDBException {
+ final XQueryService xqs = existEmbeddedServer.getRoot().getService(XQueryService.class);
+ return xqs.query(xquery);
+ }
+
+ @Test
+ public void parenthesizedAttribute() throws XMLDBException {
+ // //(@x) should find all @x attributes at any depth
+ final ResourceSet result = execute(
+ "let $doc := \n" +
+ "return count($doc//(@x))");
+ assertEquals("3", result.getResource(0).getContent().toString());
+ }
+
+ @Test
+ public void parenthesizedAttributeUnion() throws XMLDBException {
+ // //(@x | @y) should find all @x and @y attributes at any depth
+ final ResourceSet result = execute(
+ "let $doc := \n" +
+ "return count($doc//(@x | @y))");
+ assertEquals("4", result.getResource(0).getContent().toString());
+ }
+
+ @Test
+ public void parenthesizedElementUnion() throws XMLDBException {
+ // //(b | c) should find elements at any depth, including direct children
+ final ResourceSet result = execute(
+ "let $doc := \n" +
+ "return count($doc//(b | c))");
+ assertEquals("3", result.getResource(0).getContent().toString());
+ }
+
+ @Test
+ public void parenthesizedUnionWithFollowingAxis() throws XMLDBException {
+ // //(north | near-south)/preceding-sibling::comment() should work
+ final ResourceSet result = execute(
+ "let $doc :=\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ "return count($doc//(north | near-south)/preceding-sibling::comment())");
+ assertEquals("3", result.getResource(0).getContent().toString());
+ }
+}