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 048ee8e7e85..a4392c13808 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
@@ -2360,7 +2360,13 @@ throws PermissionDeniedException, EXistException, XPathException
(s.getTest().getType() == Type.ATTRIBUTE && s.getAxis() == Constants.CHILD_AXIS))
// combines descendant-or-self::node()/attribute:*
s.setAxis(Constants.DESCENDANT_ATTRIBUTE_AXIS);
- else {
+ else if (s.getAxis() <= Constants.PRECEDING_SIBLING_AXIS) {
+ // Reverse axis: insert explicit descendant-or-self::node() step
+ LocationStep descStep = new LocationStep(context, Constants.DESCENDANT_SELF_AXIS, new TypeTest(Type.NODE));
+ descStep.setAbbreviated(true);
+ path.replaceLastExpression(descStep);
+ path.add(step);
+ } else {
s.setAxis(Constants.DESCENDANT_SELF_AXIS);
s.setAbbreviated(true);
}
@@ -2984,6 +2990,13 @@ throws PermissionDeniedException, EXistException, XPathException
rs.setAxis(Constants.DESCENDANT_AXIS);
} else if (rs.getAxis() == Constants.SELF_AXIS) {
rs.setAxis(Constants.DESCENDANT_SELF_AXIS);
+ } else if (rs.getAxis() <= Constants.PRECEDING_SIBLING_AXIS) {
+ // Reverse axis: cannot merge with descendant-or-self,
+ // insert explicit descendant-or-self::node() step before the reverse axis step
+ LocationStep descStep = new LocationStep(context, Constants.DESCENDANT_SELF_AXIS, new TypeTest(Type.NODE));
+ descStep.setAbbreviated(true);
+ path.replaceLastExpression(descStep);
+ path.add(rightStep);
} else {
rs.setAxis(Constants.DESCENDANT_SELF_AXIS);
rs.setAbbreviated(true);
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 e7fe9b350da..1e035a2ec18 100644
--- a/exist-core/src/test/java/org/exist/xquery/XPathQueryTest.java
+++ b/exist-core/src/test/java/org/exist/xquery/XPathQueryTest.java
@@ -808,6 +808,65 @@ public void precedingAxis() throws XMLDBException {
queryResource(service, "siblings.xml", "//a/n[. = '3']/preceding::s", 3);
}
+ /**
+ * Tests that // followed by a reverse axis correctly expands to
+ * /descendant-or-self::node()/ + reverse axis, rather than
+ * collapsing the reverse axis into descendant-or-self.
+ *
+ * @see #691
+ */
+ @Test
+ public void dslashWithReverseAxis() throws XMLDBException {
+ final String xml =
+ "" +
+ " " +
+ " 1" +
+ " 2" +
+ " " +
+ " " +
+ " 3" +
+ " 4" +
+ " " +
+ "";
+
+ final XQueryService service =
+ storeXMLStringAndGetQueryService("dslash_reverse.xml", xml);
+
+ // //preceding::b should produce the same count as the expanded form
+ queryAndAssert(service,
+ "let $d := doc('/db/test/dslash_reverse.xml') " +
+ "return count($d//preceding::b) eq count($d/descendant-or-self::node()/preceding::b)",
+ 1, "//preceding::b count should match expanded form");
+
+ // //ancestor::a should produce the same count as the expanded form
+ queryAndAssert(service,
+ "let $d := doc('/db/test/dslash_reverse.xml') " +
+ "return count($d//ancestor::a) eq count($d/descendant-or-self::node()/ancestor::a)",
+ 1, "//ancestor::a count should match expanded form");
+
+ // Note: //preceding-sibling::b skipped due to pre-existing NPE in
+ // NewArrayNodeSet.selectPrecedingSiblings when evaluating preceding-sibling
+ // on descendant-or-self::node() context (affects both abbreviated and expanded forms)
+
+ // //ancestor-or-self::a should produce the same count as the expanded form
+ queryAndAssert(service,
+ "let $d := doc('/db/test/dslash_reverse.xml') " +
+ "return count($d//ancestor-or-self::a) eq count($d/descendant-or-self::node()/ancestor-or-self::a)",
+ 1, "//ancestor-or-self::a count should match expanded form");
+
+ // //parent::a should produce the same count as the expanded form
+ queryAndAssert(service,
+ "let $d := doc('/db/test/dslash_reverse.xml') " +
+ "return count($d//parent::a) eq count($d/descendant-or-self::node()/parent::a)",
+ 1, "//parent::a count should match expanded form");
+
+ // Relative path: $node//preceding::b should match expanded form
+ queryAndAssert(service,
+ "let $node := doc('/db/test/dslash_reverse.xml')/root/a[2] " +
+ "return count($node//preceding::b) eq count($node/descendant-or-self::node()/preceding::b)",
+ 1, "$node//preceding::b count should match expanded form");
+ }
+
@Test
public void position() throws XMLDBException, IOException, SAXException {