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 @@