diff --git a/exist-core/pom.xml b/exist-core/pom.xml
index f5e8137632..e2165d3675 100644
--- a/exist-core/pom.xml
+++ b/exist-core/pom.xml
@@ -776,6 +776,7 @@
src/test/resources/log4j2.xml
src/test/resources/standalone-webapp/WEB-INF/web.xml
src/main/xjb/rest-api.xjb
+ src/test/xquery/parenthesizedLocationStep.xml
src/test/xquery/tail-recursion.xml
src/test/xquery/maps/maps.xqm
src/test/xquery/numbers/format-numbers.xql
@@ -888,6 +889,7 @@
src/main/java/org/exist/dom/persistent/DocumentSet.java
src/main/java/org/exist/dom/persistent/DocumentTypeImpl.java
src/main/java/org/exist/dom/persistent/ElementImpl.java
+ src/main/java/org/exist/dom/persistent/EmptyNodeSet.java
src/main/java/org/exist/dom/persistent/LockToken.java
src/main/java/org/exist/dom/persistent/Match.java
src/main/java/org/exist/dom/persistent/NewArrayNodeSet.java
@@ -950,6 +952,7 @@
src/main/java/org/exist/management/impl/JMXAgent.java
src/main/java/org/exist/management/impl/SanityReport.java
src/main/java/org/exist/numbering/DLN.java
+ src/main/java/org/exist/numbering/DLNBase.java
src/main/java/org/exist/numbering/DLNFactory.java
src/test/java/org/exist/numbering/DLNStorageTest.java
src/main/java/org/exist/numbering/NodeId.java
@@ -1179,12 +1182,14 @@
src/test/java/org/exist/xquery/EmbeddedBinariesTest.java
src/test/java/org/exist/xquery/EmbeddedBinariesTest.java.java
src/main/java/org/exist/xquery/ErrorCodes.java
+ src/main/java/org/exist/xquery/Except.java
src/main/java/org/exist/xquery/ExternalModuleImpl.java
src/main/java/org/exist/xquery/FilteredExpression.java
src/test/java/org/exist/xquery/ForwardReferenceTest.java
src/main/java/org/exist/xquery/Function.java
src/main/java/org/exist/xquery/FunctionFactory.java
src/test/java/org/exist/xquery/InternalModuleTest.java
+ src/main/java/org/exist/xquery/Intersect.java
src/test/java/org/exist/xquery/LexerTest.java
src/main/java/org/exist/xquery/LocationStep.java
src/main/java/org/exist/xquery/Module.java
@@ -1193,6 +1198,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
@@ -1269,6 +1275,7 @@
src/main/java/org/exist/xquery/functions/fn/FunInsertBefore.java
src/main/java/org/exist/xquery/functions/fn/FunIRIToURI.java
src/main/java/org/exist/xquery/functions/fn/FunLang.java
+ src/test/java/org/exist/xquery/functions/fn/FunLangTest.java
src/main/java/org/exist/xquery/functions/fn/FunLast.java
src/main/java/org/exist/xquery/functions/fn/FunLocalName.java
src/main/java/org/exist/xquery/functions/fn/FunMax.java
@@ -1409,6 +1416,7 @@
src/main/java/org/exist/xquery/value/GYearMonthValue.java
src/main/java/org/exist/xquery/value/GYearValue.java
src/main/java/org/exist/xquery/value/IntegerValue.java
+ src/main/java/org/exist/xquery/value/MemoryNodeSet.java
src/main/java/org/exist/xquery/value/QNameValue.java
src/main/java/org/exist/xquery/value/SequenceType.java
src/main/java/org/exist/xquery/value/StringValue.java
@@ -1416,6 +1424,7 @@
src/main/java/org/exist/xquery/value/TimeUtils.java
src/main/java/org/exist/xquery/value/TimeValue.java
src/main/java/org/exist/xquery/value/Type.java
+ src/main/java/org/exist/xquery/value/ValueSequence.java
src/test/java/org/exist/xquery/value/YearMonthDurationTest.java
src/main/java/org/exist/xquery/value/YearMonthDurationValue.java
src/main/java/org/exist/xslt/EXistURIResolver.java
@@ -1460,6 +1469,7 @@
src/test/resources/standalone-webapp/WEB-INF/web.xml
src/main/xjb/rest-api.xjb
src/test/xquery/binary-value.xqm
+ src/test/xquery/parenthesizedLocationStep.xml
src/test/xquery/pi.xqm
src/test/xquery/tail-recursion.xml
src/test/xquery/maps/maps.xqm
@@ -1583,6 +1593,7 @@
src/main/java/org/exist/dom/persistent/DocumentSet.java
src/main/java/org/exist/dom/persistent/DocumentTypeImpl.java
src/main/java/org/exist/dom/persistent/ElementImpl.java
+ src/main/java/org/exist/dom/persistent/EmptyNodeSet.java
src/main/java/org/exist/dom/persistent/LockToken.java
src/main/java/org/exist/dom/persistent/Match.java
src/main/java/org/exist/dom/persistent/NewArrayNodeSet.java
@@ -1649,6 +1660,7 @@
src/main/java/org/exist/mediatype/MediaTypeService.java
src/main/java/org/exist/mediatype/MediaTypeUtil.java
src/main/java/org/exist/numbering/DLN.java
+ src/main/java/org/exist/numbering/DLNBase.java
src/main/java/org/exist/numbering/DLNFactory.java
src/test/java/org/exist/numbering/DLNStorageTest.java
src/main/java/org/exist/numbering/NodeId.java
@@ -1953,6 +1965,7 @@
src/test/java/org/exist/xquery/EmbeddedBinariesTest.java
src/test/java/org/exist/xquery/EmbeddedBinariesTest.java.java
src/main/java/org/exist/xquery/ErrorCodes.java
+ src/main/java/org/exist/xquery/Except.java
src/main/java/org/exist/xquery/ExternalModuleImpl.java
src/main/java/org/exist/xquery/FilteredExpression.java
src/test/java/org/exist/xquery/ForwardReferenceTest.java
@@ -1962,6 +1975,7 @@
src/test/java/org/exist/xquery/ImportFromPkgTest.java
src/test/java/org/exist/xquery/ImportModuleTest.java
src/test/java/org/exist/xquery/InternalModuleTest.java
+ src/main/java/org/exist/xquery/Intersect.java
src/main/java/org/exist/xquery/JavaBinding.java
src/test/resources-filtered/org/exist/xquery/JavaBindingTest.conf.xml
src/test/java/org/exist/xquery/JavaBindingTest.java
@@ -1974,6 +1988,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
@@ -2054,6 +2069,7 @@
src/main/java/org/exist/xquery/functions/fn/FunInsertBefore.java
src/main/java/org/exist/xquery/functions/fn/FunIRIToURI.java
src/main/java/org/exist/xquery/functions/fn/FunLang.java
+ src/test/java/org/exist/xquery/functions/fn/FunLangTest.java
src/main/java/org/exist/xquery/functions/fn/FunLast.java
src/main/java/org/exist/xquery/functions/fn/FunLocalName.java
src/main/java/org/exist/xquery/functions/fn/FunMax.java
@@ -2217,6 +2233,7 @@
src/main/java/org/exist/xquery/value/GYearValue.java
src/main/java/org/exist/xquery/value/IntegerValue.java
src/main/java/org/exist/xquery/value/ItemComparator.java
+ src/main/java/org/exist/xquery/value/MemoryNodeSet.java
src/main/java/org/exist/xquery/value/QNameValue.java
src/main/java/org/exist/xquery/value/SequenceComparator.java
src/main/java/org/exist/xquery/value/SequenceType.java
@@ -2228,6 +2245,7 @@
src/main/java/org/exist/xquery/value/TimeUtils.java
src/main/java/org/exist/xquery/value/TimeValue.java
src/main/java/org/exist/xquery/value/Type.java
+ src/main/java/org/exist/xquery/value/ValueSequence.java
src/test/java/org/exist/xquery/value/YearMonthDurationTest.java
src/main/java/org/exist/xquery/value/YearMonthDurationValue.java
src/main/java/org/exist/xslt/EXistURIResolver.java
@@ -2715,4 +2733,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..01756fab32 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,38 @@ 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 {
+ if (rightStep.getPrimaryAxis() == Constants.ATTRIBUTE_AXIS) {
+ rightStep.setPrimaryAxis(Constants.DESCENDANT_ATTRIBUTE_AXIS);
+ } 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/dom/persistent/EmptyNodeSet.java b/exist-core/src/main/java/org/exist/dom/persistent/EmptyNodeSet.java
index 8fe090e118..451ae11896 100644
--- a/exist-core/src/main/java/org/exist/dom/persistent/EmptyNodeSet.java
+++ b/exist-core/src/main/java/org/exist/dom/persistent/EmptyNodeSet.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
*
@@ -67,10 +91,12 @@ public boolean hasOne() {
@Override
public void add(final NodeProxy proxy) {
+ throw new IllegalStateException("Cannot add a NodeProxy to an EmptyNodeSet because it is immutable");
}
@Override
public void addAll(final NodeSet other) {
+ throw new IllegalStateException("Cannot add a NodeSet to an EmptyNodeSet because it is immutable");
}
@Override
diff --git a/exist-core/src/main/java/org/exist/numbering/DLNBase.java b/exist-core/src/main/java/org/exist/numbering/DLNBase.java
index 9a382f2166..2bfbc50100 100644
--- a/exist-core/src/main/java/org/exist/numbering/DLNBase.java
+++ b/exist-core/src/main/java/org/exist/numbering/DLNBase.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
*
@@ -495,7 +519,14 @@ public boolean equals(final Object o) {
return Arrays.equals(bits, other.bits);
}
-// public int compareTo(final DLNBase other) {
+ @Override
+ public int hashCode() {
+ int result = Arrays.hashCode(bits);
+ result = 31 * result + bitIndex;
+ return result;
+ }
+
+ // public int compareTo(final DLNBase other) {
// if (other == null)
// return 1;
// final int a1len = bits.length;
diff --git a/exist-core/src/main/java/org/exist/xquery/Except.java b/exist-core/src/main/java/org/exist/xquery/Except.java
index a3f9103419..d7d3de32e5 100644
--- a/exist-core/src/main/java/org/exist/xquery/Except.java
+++ b/exist-core/src/main/java/org/exist/xquery/Except.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
*
@@ -22,14 +46,11 @@
package org.exist.xquery;
import java.util.Set;
-import java.util.TreeSet;
-import org.exist.xquery.value.Item;
-import org.exist.xquery.value.ItemComparator;
-import org.exist.xquery.value.Sequence;
-import org.exist.xquery.value.SequenceIterator;
-import org.exist.xquery.value.Type;
-import org.exist.xquery.value.ValueSequence;
+import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet;
+import org.exist.xquery.value.*;
+
+import javax.annotation.Nullable;
/**
* @author Wolfgang Meier
@@ -57,18 +78,29 @@ public Sequence combine(final Sequence ls, final Sequence rs) throws XPathExcept
if (ls.isPersistentSet() && rs.isPersistentSet()) {
result = ls.toNodeSet().except(rs.toNodeSet());
} else {
- result = new ValueSequence();
- final Set- set = new TreeSet<>(new ItemComparator());
+ @Nullable Sequence values = null;
+ @Nullable Set
- set = null;
for (final SequenceIterator i = rs.unorderedIterator(); i.hasNext(); ) {
+ if (set == null) {
+ set = new ObjectAVLTreeSet<>(ItemComparator.WITHOUT_COLLATOR);
+ }
set.add(i.nextItem());
}
for (final SequenceIterator i = ls.unorderedIterator(); i.hasNext(); ) {
final Item next = i.nextItem();
- if (!set.contains(next)) {
- result.add(next);
+ if (set == null || !set.contains(next)) {
+ if (values == null) {
+ values = new ValueSequence();
+ }
+ values.add(next);
}
}
- result.removeDuplicates();
+ if (values != null) {
+ values.removeDuplicates();
+ result = values;
+ } else {
+ result = Sequence.EMPTY_SEQUENCE;
+ }
}
}
diff --git a/exist-core/src/main/java/org/exist/xquery/Intersect.java b/exist-core/src/main/java/org/exist/xquery/Intersect.java
index ea58bd7f1b..dc141dba23 100644
--- a/exist-core/src/main/java/org/exist/xquery/Intersect.java
+++ b/exist-core/src/main/java/org/exist/xquery/Intersect.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
*
@@ -21,10 +45,11 @@
*/
package org.exist.xquery;
+import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet;
import org.exist.xquery.value.*;
+import javax.annotation.Nullable;
import java.util.Set;
-import java.util.TreeSet;
/**
* @author Wolfgang Meier
@@ -47,18 +72,29 @@ public Sequence combine(final Sequence ls, final Sequence rs) throws XPathExcept
if (ls.isPersistentSet() && rs.isPersistentSet()) {
result = ls.toNodeSet().intersection(rs.toNodeSet());
} else {
- result = new ValueSequence(true);
- final Set
- set = new TreeSet<>(new ItemComparator());
+ @Nullable Sequence values = null;
+ @Nullable Set
- set = null;
for (final SequenceIterator i = ls.unorderedIterator(); i.hasNext(); ) {
+ if (set == null) {
+ set = new ObjectAVLTreeSet<>(ItemComparator.WITHOUT_COLLATOR);
+ }
set.add(i.nextItem());
}
for (final SequenceIterator i = rs.unorderedIterator(); i.hasNext(); ) {
final Item next = i.nextItem();
- if (set.contains(next)) {
- result.add(next);
+ if (set != null && set.contains(next)) {
+ if (values == null) {
+ values = new ValueSequence(true);
+ }
+ values.add(next);
}
}
- result.removeDuplicates();
+ if (values != null) {
+ values.removeDuplicates();
+ result = values;
+ } else {
+ result = Sequence.EMPTY_SEQUENCE;
+ }
}
}
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/main/java/org/exist/xquery/Union.java b/exist-core/src/main/java/org/exist/xquery/Union.java
index 24594e3189..75590fd228 100644
--- a/exist-core/src/main/java/org/exist/xquery/Union.java
+++ b/exist-core/src/main/java/org/exist/xquery/Union.java
@@ -59,7 +59,6 @@ public Sequence combine(final Sequence ls, final Sequence rs) throws XPathExcept
final ValueSequence values = new ValueSequence(true);
values.addAll(ls);
values.addAll(rs);
- values.sortInDocumentOrder();
values.removeDuplicates();
result = values;
}
diff --git a/exist-core/src/main/java/org/exist/xquery/value/ArrayListValueSequence.java b/exist-core/src/main/java/org/exist/xquery/value/ArrayListValueSequence.java
index d00c7836d9..9f5c4e17e8 100644
--- a/exist-core/src/main/java/org/exist/xquery/value/ArrayListValueSequence.java
+++ b/exist-core/src/main/java/org/exist/xquery/value/ArrayListValueSequence.java
@@ -267,7 +267,7 @@ public void removeDuplicates() {
final List
- newValues = new ArrayList<>(values.size());
int newType = Type.ANY_TYPE;
- final ItemComparator itemComparator = new ItemComparator();
+ final ItemComparator itemComparator = ItemComparator.WITHOUT_COLLATOR;
for (int i = 0; i < values.size(); i++) {
final Item value = values.get(i);
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");
}
diff --git a/exist-core/src/main/java/org/exist/xquery/value/AtomicValueComparator.java b/exist-core/src/main/java/org/exist/xquery/value/AtomicValueComparator.java
index 92236c7a10..66ea393f14 100644
--- a/exist-core/src/main/java/org/exist/xquery/value/AtomicValueComparator.java
+++ b/exist-core/src/main/java/org/exist/xquery/value/AtomicValueComparator.java
@@ -48,11 +48,13 @@
@ThreadSafe
public class AtomicValueComparator implements Comparator {
- protected static final Logger LOG = LogManager.getLogger(RESTServer.class);
+ protected static final Logger LOG = LogManager.getLogger(AtomicValueComparator.class);
+
+ public static AtomicValueComparator WITHOUT_COLLATOR = new AtomicValueComparator();
private final @Nullable Collator collator;
- public AtomicValueComparator() {
+ private AtomicValueComparator() {
this(null);
}
diff --git a/exist-core/src/main/java/org/exist/xquery/value/ItemComparator.java b/exist-core/src/main/java/org/exist/xquery/value/ItemComparator.java
index b2377a5290..d8ac15fde3 100644
--- a/exist-core/src/main/java/org/exist/xquery/value/ItemComparator.java
+++ b/exist-core/src/main/java/org/exist/xquery/value/ItemComparator.java
@@ -56,6 +56,8 @@ public class ItemComparator implements Comparator
- {
@Nullable private final Collator collator;
@Nullable private AtomicValueComparator atomicValueComparator = null;
+ public static ItemComparator WITHOUT_COLLATOR = new ItemComparator();
+
public ItemComparator() {
this(null);
}
@@ -70,7 +72,11 @@ public int compare(final Item n1, final Item n2) {
return Constants.INFERIOR;
} else if (n1 instanceof AtomicValue && n2 instanceof AtomicValue) {
if (atomicValueComparator == null) {
- atomicValueComparator = new AtomicValueComparator(collator);
+ if (collator == null) {
+ atomicValueComparator = AtomicValueComparator.WITHOUT_COLLATOR;
+ } else {
+ atomicValueComparator = new AtomicValueComparator(collator);
+ }
}
return atomicValueComparator.compare((AtomicValue)n1, (AtomicValue)n2);
} else if (n1 instanceof Comparable) {
diff --git a/exist-core/src/main/java/org/exist/xquery/value/MemoryNodeSet.java b/exist-core/src/main/java/org/exist/xquery/value/MemoryNodeSet.java
index a25f6259ad..e6e3297a3d 100644
--- a/exist-core/src/main/java/org/exist/xquery/value/MemoryNodeSet.java
+++ b/exist-core/src/main/java/org/exist/xquery/value/MemoryNodeSet.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
*
@@ -28,7 +52,7 @@
public interface MemoryNodeSet extends Sequence {
- MemoryNodeSet EMPTY = new ValueSequence(1);
+ MemoryNodeSet EMPTY = new ValueSequence(0);
Sequence getAttributes(NodeTest test) throws XPathException;
diff --git a/exist-core/src/main/java/org/exist/xquery/value/SequenceComparator.java b/exist-core/src/main/java/org/exist/xquery/value/SequenceComparator.java
index bd810846c5..b339944390 100644
--- a/exist-core/src/main/java/org/exist/xquery/value/SequenceComparator.java
+++ b/exist-core/src/main/java/org/exist/xquery/value/SequenceComparator.java
@@ -83,7 +83,11 @@ public int compare(final Sequence o1, final Sequence o2) {
for (int i = 0; i < o1Count; i++) {
if (itemComparator == null) {
- itemComparator = new ItemComparator(collator);
+ if (collator == null) {
+ itemComparator = ItemComparator.WITHOUT_COLLATOR;
+ } else {
+ itemComparator = new ItemComparator(collator);
+ }
}
final Item i1 = o1.itemAt(i);
diff --git a/exist-core/src/main/java/org/exist/xquery/value/ValueSequence.java b/exist-core/src/main/java/org/exist/xquery/value/ValueSequence.java
index e990712ca6..8073a8546a 100644
--- a/exist-core/src/main/java/org/exist/xquery/value/ValueSequence.java
+++ b/exist-core/src/main/java/org/exist/xquery/value/ValueSequence.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
*
@@ -22,6 +46,8 @@
package org.exist.xquery.value;
import com.evolvedbinary.j8fu.function.FunctionE;
+import it.unimi.dsi.fastutil.objects.Object2ObjectRBTreeMap;
+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.collections.Collection;
@@ -238,9 +264,10 @@ public NodeSet toNodeSet() throws XPathException {
if (size == UNSET_SIZE) {
return NodeSet.EMPTY_SET;
}
+
// for this method to work, all items have to be nodes
if (itemType != Type.ANY_TYPE && Type.subTypeOf(itemType, Type.NODE)) {
- final NodeSet set = new NewArrayNodeSet();
+ @Nullable NodeSet set = null;
for (int i = 0; i <= size; i++) {
NodeValue v = (NodeValue) values[i];
if (v.getImplementationType() != NodeValue.PERSISTENT_NODE) {
@@ -295,16 +322,29 @@ public NodeSet toNodeSet() throws XPathException {
}
}
}
+ if (set == null) {
+ set = new NewArrayNodeSet(size + 1 - i);
+ }
set.add((NodeProxy) values[i]);
}
} else {
+ if (set == null) {
+ set = new NewArrayNodeSet(size + 1 - i);
+ }
set.add((NodeProxy) v);
}
}
+
+ if (set == null) {
+ set = NodeSet.EMPTY_SET;
+ }
+
if (holderVar != null) {
holderVar.setValue(set);
}
+
return set;
+
} else {
throw new XPathException((Expression) null, "Type error: the sequence cannot be converted into" +
" a node set. Item type is " + Type.getTypeName(itemType));
@@ -316,10 +356,12 @@ public MemoryNodeSet toMemNodeSet() throws XPathException {
if (size == UNSET_SIZE) {
return MemoryNodeSet.EMPTY;
}
+
if (itemType == Type.ANY_TYPE || !Type.subTypeOf(itemType, Type.NODE)) {
throw new XPathException((Expression) null, "Type error: the sequence cannot be converted into" +
" a node set. Item type is " + Type.getTypeName(itemType));
}
+
for (int i = 0; i <= size; i++) {
final NodeValue v = (NodeValue) values[i];
if (v.getImplementationType() == NodeValue.PERSISTENT_NODE) {
@@ -327,6 +369,7 @@ public MemoryNodeSet toMemNodeSet() throws XPathException {
" a MemoryNodeSet. It contains nodes from stored resources.");
}
}
+
expand();
inMemNodeSet = true;
return this;
@@ -336,15 +379,18 @@ public boolean isInMemorySet() {
if (size == UNSET_SIZE) {
return true;
}
+
if (itemType == Type.ANY_TYPE || !Type.subTypeOf(itemType, Type.NODE)) {
return false;
}
+
for (int i = 0; i <= size; i++) {
final NodeValue v = (NodeValue) values[i];
if (v.getImplementationType() == NodeValue.PERSISTENT_NODE) {
return false;
}
}
+
return true;
}
@@ -353,6 +399,7 @@ public boolean isPersistentSet() {
if (size == UNSET_SIZE) {
return true;
}
+
if (itemType != Type.ANY_TYPE && Type.subTypeOf(itemType, Type.NODE)) {
for (int i = 0; i <= size; i++) {
final NodeValue v = (NodeValue) values[i];
@@ -362,6 +409,7 @@ public boolean isPersistentSet() {
}
return true;
}
+
return false;
}
@@ -371,17 +419,23 @@ public boolean isPersistentSet() {
* Expand those references to get a pure in-memory DOM tree.
*/
private void expand() {
- final Set docs = new HashSet<>();
+ @Nullable Set docs = null;
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
final DocumentImpl ownerDoc = node.getNodeType() == Node.DOCUMENT_NODE ? (DocumentImpl) node : node.getOwnerDocument();
if (ownerDoc.hasReferenceNodes()) {
+ if (docs == null) {
+ docs = new ObjectOpenHashSet<>(size + 1 - i);
+ }
docs.add(ownerDoc);
}
}
- for (final DocumentImpl doc : docs) {
- doc.expand();
+
+ if (docs != null) {
+ for (final DocumentImpl doc : docs) {
+ doc.expand();
+ }
}
}
@@ -417,26 +471,32 @@ public void sortInDocumentOrder() {
if (size == UNSET_SIZE) {
return;
}
+
if (keepUnOrdered) {
removeDuplicateNodes();
return;
}
+
if (!enforceOrder || isOrdered) {
return;
}
+
inMemNodeSet = inMemNodeSet || isInMemorySet();
if (inMemNodeSet) {
FastQSort.sort(values, new InMemoryNodeComparator(), 0, size);
}
+
removeDuplicateNodes();
isOrdered = true;
}
@Override
public void removeDuplicates() {
- enforceOrder = true;
- isOrdered = false;
+ final boolean prevEnforceOrder = this.enforceOrder;
+ this.enforceOrder = true;
+ this.isOrdered = false;
sortInDocumentOrder();
+ this.enforceOrder = prevEnforceOrder;
}
private void ensureCapacity() {
@@ -452,20 +512,21 @@ private void removeDuplicateNodes() {
if (noDuplicates || size < 1) {
return;
}
+
if (inMemNodeSet) {
- int j = 0;
+ int writeIdx = 1;
for (int i = 1; i <= size; i++) {
- if (!values[i].equals(values[j])) {
- if (i != ++j) {
- values[j] = values[i];
- }
+ if (!values[i].equals(values[i - 1])) {
+ values[writeIdx++] = values[i];
}
}
- size = j;
+ size = writeIdx - 1;
+
} else {
if (itemType != Type.ANY_TYPE && Type.subTypeOf(itemType, Type.ATOMIC)) {
return;
}
+
// check if the sequence contains nodes
boolean hasNodes = false;
for (int i = 0; i <= size; i++) {
@@ -473,17 +534,22 @@ private void removeDuplicateNodes() {
hasNodes = true;
}
}
+
if (!hasNodes) {
return;
}
- final Map
- nodes = new TreeMap<>(new ItemComparator());
+
+ @Nullable Map
- nodes = null;
int j = 0;
for (int i = 0; i <= size; i++) {
if (Type.subTypeOf(values[i].getType(), Type.NODE)) {
- final Item found = nodes.get(values[i]);
+ @Nullable final Item found = nodes != null ? nodes.get(values[i]) : null;
if (found == null) {
final Item item = values[i];
values[j++] = item;
+ if (nodes == null) {
+ nodes = new Object2ObjectRBTreeMap<>(ItemComparator.WITHOUT_COLLATOR);
+ }
nodes.put(item, item);
} else {
final NodeValue nv = (NodeValue) found;
@@ -516,7 +582,11 @@ public void nodeMoved(final NodeId oldNodeId, final StoredNode newNode) {
}
private void setHasChanged() {
- state = (state == Integer.MAX_VALUE ? state = 0 : state + 1);
+ if (state == Integer.MAX_VALUE) {
+ state = 0;
+ } else {
+ state++;
+ }
}
@Override
@@ -539,6 +609,7 @@ public DocumentSet getDocumentSet() {
if (cachedSet != null) {
return cachedSet.getDocumentSet();
}
+
try {
boolean isPersistentSet = true;
for (int i = 0; i <= size; i++) {
@@ -559,19 +630,28 @@ public DocumentSet getDocumentSet() {
}
} catch (final XPathException e) {
}
+
return extractDocumentSet();
}
private DocumentSet extractDocumentSet() {
- final MutableDocumentSet docs = new DefaultDocumentSet();
+ @Nullable MutableDocumentSet docs = null;
for (int i = 0; i <= size; i++) {
if (Type.subTypeOf(values[i].getType(), Type.NODE)) {
final NodeValue node = (NodeValue) values[i];
if (node.getImplementationType() == NodeValue.PERSISTENT_NODE) {
+ if (docs == null) {
+ docs = new DefaultDocumentSet(size + 1 - i);
+ }
docs.add((org.exist.dom.persistent.DocumentImpl) node.getOwnerDocument());
}
}
}
+
+ if (docs == null) {
+ return new DefaultDocumentSet(0);
+ }
+
return docs;
}
@@ -579,193 +659,310 @@ private DocumentSet extractDocumentSet() {
@Override
public Sequence getAttributes(final NodeTest test) throws XPathException {
sortInDocumentOrder();
- final ValueSequence nodes = new ValueSequence(true);
- nodes.keepUnOrdered(keepUnOrdered);
+ @Nullable ValueSequence attributes = null;
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
- node.selectAttributes(test, nodes);
+ if (attributes == null) {
+ attributes = new ValueSequence(true);
+ attributes.keepUnOrdered(keepUnOrdered);
+ }
+ node.selectAttributes(test, attributes);
}
- return nodes;
+
+ if (attributes == null) {
+ return Sequence.EMPTY_SEQUENCE;
+ }
+
+ return attributes;
}
@Override
public Sequence getDescendantAttributes(final NodeTest test) throws XPathException {
sortInDocumentOrder();
- final ValueSequence nodes = new ValueSequence(true);
- nodes.keepUnOrdered(keepUnOrdered);
+ @Nullable ValueSequence attributes = null;
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
- node.selectDescendantAttributes(test, nodes);
+ if (attributes == null) {
+ attributes = new ValueSequence(true);
+ attributes.keepUnOrdered(keepUnOrdered);
+ }
+ node.selectDescendantAttributes(test, attributes);
}
- return nodes;
+
+ if (attributes == null) {
+ return Sequence.EMPTY_SEQUENCE;
+ }
+
+ return attributes;
}
@Override
public Sequence getChildren(final NodeTest test) throws XPathException {
sortInDocumentOrder();
- final ValueSequence nodes = new ValueSequence(true);
- nodes.keepUnOrdered(keepUnOrdered);
+ @Nullable ValueSequence children = null;
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
- node.selectChildren(test, nodes);
+ if (children == null) {
+ children = new ValueSequence(true);
+ children.keepUnOrdered(keepUnOrdered);
+ }
+ node.selectChildren(test, children);
}
- return nodes;
+
+ if (children == null) {
+ return Sequence.EMPTY_SEQUENCE;
+ }
+
+ return children;
}
@Override
public Sequence getChildrenForParent(final NodeImpl parent) {
sortInDocumentOrder();
- final ValueSequence nodes = new ValueSequence(true);
- nodes.keepUnOrdered(keepUnOrdered);
+ @Nullable ValueSequence children = null;
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
if (node.getNodeId().isChildOf(parent.getNodeId())) {
- nodes.add(node);
+ if (children == null) {
+ children = new ValueSequence(true);
+ children.keepUnOrdered(keepUnOrdered);
+ }
+ children.add(node);
}
}
- return nodes;
+
+ if (children == null) {
+ return Sequence.EMPTY_SEQUENCE;
+ }
+
+ return children;
}
@Override
public Sequence getDescendants(final boolean includeSelf, final NodeTest test) throws XPathException {
sortInDocumentOrder();
- final ValueSequence nodes = new ValueSequence(true);
- nodes.keepUnOrdered(keepUnOrdered);
+ @Nullable ValueSequence descendants = null;
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
- node.selectDescendants(includeSelf, test, nodes);
+ if (descendants == null) {
+ descendants = new ValueSequence(true);
+ descendants.keepUnOrdered(keepUnOrdered);
+ }
+ node.selectDescendants(includeSelf, test, descendants);
}
- return nodes;
+
+ if (descendants == null) {
+ return Sequence.EMPTY_SEQUENCE;
+ }
+
+ return descendants;
}
@Override
public Sequence getAncestors(final boolean includeSelf, final NodeTest test) throws XPathException {
sortInDocumentOrder();
- final ValueSequence nodes = new ValueSequence(true);
- nodes.keepUnOrdered(keepUnOrdered);
+ @Nullable ValueSequence ancestors = null;
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
- node.selectAncestors(includeSelf, test, nodes);
+ if (ancestors == null) {
+ ancestors = new ValueSequence(true);
+ ancestors.keepUnOrdered(keepUnOrdered);
+ }
+ node.selectAncestors(includeSelf, test, ancestors);
}
- return nodes;
+
+ if (ancestors == null) {
+ return Sequence.EMPTY_SEQUENCE;
+ }
+
+ return ancestors;
}
@Override
public Sequence getParents(final NodeTest test) throws XPathException {
sortInDocumentOrder();
- final ValueSequence nodes = new ValueSequence(true);
- nodes.keepUnOrdered(keepUnOrdered);
+ @Nullable ValueSequence parents = null;
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
- final NodeImpl parent = (NodeImpl) node.selectParentNode();
+ @Nullable final NodeImpl parent = (NodeImpl) node.selectParentNode();
+
if (parent != null && test.matches(parent)) {
- nodes.add(parent);
+ if (parents == null) {
+ parents = new ValueSequence(size + 1 - i);
+ parents.setIsOrdered(true);
+ parents.keepUnOrdered(keepUnOrdered);
+ }
+
+ parents.add(parent);
}
}
- return nodes;
+
+ if (parents == null) {
+ return Sequence.EMPTY_SEQUENCE;
+ }
+
+ return parents;
}
@Override
public Sequence getSelf(final NodeTest test) throws XPathException {
sortInDocumentOrder();
- final ValueSequence nodes = new ValueSequence(true);
- nodes.keepUnOrdered(keepUnOrdered);
+ @Nullable ValueSequence selves = null;
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
if ((test.getType() == Type.NODE && node.getNodeType() == Node.ATTRIBUTE_NODE) ||
test.matches(node)) {
- nodes.add(node);
+ if (selves == null) {
+ selves = new ValueSequence(size + 1 - i);
+ selves.setIsOrdered(true);
+ selves.keepUnOrdered(keepUnOrdered);
+ }
+ selves.add(node);
}
}
- return nodes;
+
+ if (selves == null) {
+ return Sequence.EMPTY_SEQUENCE;
+ }
+
+ return selves;
}
@Override
public Sequence getPrecedingSiblings(final NodeTest test) throws XPathException {
sortInDocumentOrder();
- final ValueSequence nodes = new ValueSequence(true);
- nodes.keepUnOrdered(keepUnOrdered);
+ @Nullable ValueSequence precedingSiblings = null;
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
// if the context node is an attribute or namespace node, the preceding-sibling axis is empty
if (node.getNodeType() != Node.ATTRIBUTE_NODE) {
- node.selectPrecedingSiblings(test, nodes);
+ if (precedingSiblings == null) {
+ precedingSiblings = new ValueSequence(true);
+ precedingSiblings.keepUnOrdered(keepUnOrdered);
+ }
+ node.selectPrecedingSiblings(test, precedingSiblings);
}
}
- return nodes;
+
+ if (precedingSiblings == null) {
+ return Sequence.EMPTY_SEQUENCE;
+ }
+
+ return precedingSiblings;
}
@Override
public Sequence getPreceding(final NodeTest test, final int position) throws XPathException {
sortInDocumentOrder();
- final ValueSequence nodes = new ValueSequence(true);
- nodes.keepUnOrdered(keepUnOrdered);
+ @Nullable ValueSequence preceding = null;
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
- node.selectPreceding(test, nodes, position);
+ if (preceding == null) {
+ preceding = new ValueSequence(true);
+ preceding.keepUnOrdered(keepUnOrdered);
+ }
+ node.selectPreceding(test, preceding, position);
}
- return nodes;
+
+ if (preceding == null) {
+ return Sequence.EMPTY_SEQUENCE;
+ }
+
+ return preceding;
}
@Override
public Sequence getFollowingSiblings(final NodeTest test) throws XPathException {
sortInDocumentOrder();
- final ValueSequence nodes = new ValueSequence(true);
- nodes.keepUnOrdered(keepUnOrdered);
+ @Nullable ValueSequence followingSiblings = null;
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
// if the context node is an attribute or namespace node, the following-sibling axis is empty
if (node.getNodeType() != Node.ATTRIBUTE_NODE) {
- node.selectFollowingSiblings(test, nodes);
+ if (followingSiblings == null) {
+ followingSiblings = new ValueSequence(true);
+ followingSiblings.keepUnOrdered(keepUnOrdered);
+ }
+ node.selectFollowingSiblings(test, followingSiblings);
}
}
- return nodes;
+
+ if (followingSiblings == null) {
+ return Sequence.EMPTY_SEQUENCE;
+ }
+
+ return followingSiblings;
}
@Override
public Sequence getFollowing(final NodeTest test, final int position) throws XPathException {
sortInDocumentOrder();
- final ValueSequence nodes = new ValueSequence(true);
- nodes.keepUnOrdered(keepUnOrdered);
+ @Nullable ValueSequence following = null;
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
- node.selectFollowing(test, nodes, position);
+ if (following == null) {
+ following = new ValueSequence(true);
+ following.keepUnOrdered(keepUnOrdered);
+ }
+ node.selectFollowing(test, following, position);
}
- return nodes;
+
+ if (following == null) {
+ return Sequence.EMPTY_SEQUENCE;
+ }
+
+ return following;
}
@Override
public Sequence selectDescendants(final MemoryNodeSet descendants) {
sortInDocumentOrder();
- final ValueSequence nodes = new ValueSequence(true);
- nodes.keepUnOrdered(keepUnOrdered);
+ @Nullable ValueSequence nodes = null;
+
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
for (int j = 0; j < descendants.size(); j++) {
final NodeImpl descendant = descendants.get(j);
if (descendant.getNodeId().isDescendantOrSelfOf(node.getNodeId())) {
+ if (nodes == null) {
+ nodes = new ValueSequence(true);
+ nodes.keepUnOrdered(keepUnOrdered);
+ }
nodes.add(node);
}
}
}
+
+ if (nodes == null) {
+ return Sequence.EMPTY_SEQUENCE;
+ }
+
return nodes;
}
@Override
public Sequence selectChildren(final MemoryNodeSet children) {
sortInDocumentOrder();
- final ValueSequence nodes = new ValueSequence(true);
- nodes.keepUnOrdered(keepUnOrdered);
+ @Nullable ValueSequence nodes = null;
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
for (int j = 0; j < children.size(); j++) {
final NodeImpl descendant = children.get(j);
if (descendant.getNodeId().isChildOf(node.getNodeId())) {
+ if (nodes == null) {
+ nodes = new ValueSequence(true);
+ nodes.keepUnOrdered(keepUnOrdered);
+ }
nodes.add(node);
}
}
}
+
+ if (nodes == null) {
+ return Sequence.EMPTY_SEQUENCE;
+ }
+
return nodes;
}
@@ -812,7 +1009,6 @@ public String toString() {
@Override
public boolean matchSelf(final NodeTest test) {
- //UNDERSTAND: is it required? -shabanovd
sortInDocumentOrder();
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
@@ -826,7 +1022,6 @@ public boolean matchSelf(final NodeTest test) {
@Override
public boolean matchChildren(final NodeTest test) throws XPathException {
- //UNDERSTAND: is it required? -shabanovd
sortInDocumentOrder();
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
@@ -839,7 +1034,6 @@ public boolean matchChildren(final NodeTest test) throws XPathException {
@Override
public boolean matchAttributes(final NodeTest test) {
- //UNDERSTAND: is it required? -shabanovd
sortInDocumentOrder();
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
@@ -852,7 +1046,6 @@ public boolean matchAttributes(final NodeTest test) {
@Override
public boolean matchDescendantAttributes(final NodeTest test) throws XPathException {
- //UNDERSTAND: is it required? -shabanovd
sortInDocumentOrder();
for (int i = 0; i <= size; i++) {
final NodeImpl node = (NodeImpl) values[i];
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..9945bb8da0 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
@@ -1052,11 +1100,17 @@ public void predicates2() throws XMLDBException, IOException, SAXException {
service.setProperty(OutputKeys.INDENT, "no");
- String query = "let $t := " + " A 1 "
- + " Z 2 " + " B 3 "
- + " Z 4 " + " C 5 "
- + " Z 6 " + ""
- + "return $t//a[s='Z' and preceding-sibling::*[1]/s='B']";
+ String query = "let $t := " +
+ "" +
+ " A 1 " +
+ " Z 2 " +
+ " B 3 " +
+ " Z 4 " +
+ " C 5 " +
+ " Z 6 " +
+ "" +
+ "return " +
+ "$t//a[s = 'Z' and preceding-sibling::*[1]/s = 'B']";
ResourceSet result = queryResource(service, "numbers.xml", query, 1);
assertThat(result.getResource(0).getContent().toString(), CompareMatcher.isIdenticalTo("Z 4 "));
diff --git a/exist-core/src/test/java/org/exist/xquery/functions/fn/FunLangTest.java b/exist-core/src/test/java/org/exist/xquery/functions/fn/FunLangTest.java
index df81a16463..30df2ff30c 100644
--- a/exist-core/src/test/java/org/exist/xquery/functions/fn/FunLangTest.java
+++ b/exist-core/src/test/java/org/exist/xquery/functions/fn/FunLangTest.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
*
@@ -43,57 +67,57 @@ public class FunLangTest {
@Test
public void testFnLangWithContext() throws XMLDBException {
- final ResourceSet resourceSet = existEmbeddedServer.executeQuery(
- "let $doc-frag := " +
- "" +
- "" +
- "The first line of the description." +
- "" +
- ""+
- "La première ligne de la déscription." +
- "" +
- "" +
- "return $doc-frag//desc[lang(\"en-US\")]"
- );
-
+ final String query =
+ "let $doc-frag := " +
+ "" +
+ "" +
+ "The first line of the description." +
+ "" +
+ ""+
+ "La première ligne de la déscription." +
+ "" +
+ "" +
+ "return " +
+ "$doc-frag//desc[lang(\"en-US\")]";
+ final ResourceSet resourceSet = existEmbeddedServer.executeQuery(query);
assertEquals(1, resourceSet.getSize());
assertEquals("\n The first line of the description.\n", resourceSet.getResource(0).getContent());
}
- @Test
+ @Test
public void testFnLangWithArgument() throws XMLDBException {
- final ResourceSet resourceSet = existEmbeddedServer.executeQuery(
- "let $doc-frag := " +
- "" +
- "" +
- "The first line of the description." +
- "" +
- ""+
- "La première ligne de la déscription." +
- "" +
- "" +
- "return lang(\"en-US\", $doc-frag//desc[@n eq \"2\"])"
- );
-
+ final String query =
+ "let $doc-frag := " +
+ "" +
+ "" +
+ "The first line of the description." +
+ "" +
+ ""+
+ "La première ligne de la déscription." +
+ "" +
+ "" +
+ "return " +
+ "lang(\"en-US\", $doc-frag//desc[@n eq \"2\"])";
+ final ResourceSet resourceSet = existEmbeddedServer.executeQuery(query);
assertEquals(1, resourceSet.getSize());
assertEquals("false", resourceSet.getResource(0).getContent());
}
@Test
public void testFnLangWithAttributeArgument() throws XMLDBException {
- final ResourceSet resourceSet = existEmbeddedServer.executeQuery(
- "let $doc-frag := " +
- "" +
- "" +
- "The first line of the description." +
- "" +
- ""+
- "La première ligne de la déscription." +
- "" +
- "" +
- "return lang(\"en-US\", $doc-frag//desc/@n[. eq \"1\"])"
- );
-
+ final String query =
+ "let $doc-frag := " +
+ "" +
+ "" +
+ "The first line of the description." +
+ "" +
+ ""+
+ "La première ligne de la déscription." +
+ "" +
+ "" +
+ "return " +
+ "lang(\"en-US\", $doc-frag//desc/@n[. eq \"1\"])";
+ final ResourceSet resourceSet = existEmbeddedServer.executeQuery(query);
assertEquals(1, resourceSet.getSize());
assertEquals("true", resourceSet.getResource(0).getContent());
}
diff --git a/exist-core/src/test/xquery/parenthesizedLocationStep.xml b/exist-core/src/test/xquery/parenthesizedLocationStep.xml
index 7b5b796721..31c5e2676d 100644
--- a/exist-core/src/test/xquery/parenthesizedLocationStep.xml
+++ b/exist-core/src/test/xquery/parenthesizedLocationStep.xml
@@ -1,5 +1,29 @@