From 54dc6e42e29c1857e427a9395be18eca01721f3c Mon Sep 17 00:00:00 2001 From: Adam Retter Date: Fri, 4 Jul 2025 12:03:04 +0200 Subject: [PATCH 1/4] [bugfix] XML Attribute names in the Range Index config should be of type QName and not NCName --- schema/collection.xconf.xsd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema/collection.xconf.xsd b/schema/collection.xconf.xsd index 43574fe1ea..7a40be0898 100644 --- a/schema/collection.xconf.xsd +++ b/schema/collection.xconf.xsd @@ -333,7 +333,7 @@ - + From dcab3bd4df2a970da4ca4ae6b8e7eb0cba077853 Mon Sep 17 00:00:00 2001 From: Adam Retter Date: Sat, 5 Jul 2025 11:32:45 +0200 Subject: [PATCH 2/4] [test] Add tests to check the behaviour of prefixed attributes within conditions of the Range Index config See https://github.com/eXist-db/exist/issues/5189 --- extensions/indexes/range/pom.xml | 2 + .../src/test/xquery/range/conditions.xql | 65 +++++++++++++++++-- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/extensions/indexes/range/pom.xml b/extensions/indexes/range/pom.xml index a31c10e1da..c26feebee2 100644 --- a/extensions/indexes/range/pom.xml +++ b/extensions/indexes/range/pom.xml @@ -186,6 +186,7 @@ pom.xml src/test/resources-filtered/conf.xml src/test/resources/log4j2.xml + src/test/xquery/range/conditions.xql src/main/java/org/exist/indexing/range/RangeIndexAnalyzer.java src/main/java/org/exist/indexing/range/RangeIndexConfigAttributeCondition.java src/main/java/org/exist/indexing/range/RangeIndexConfigElement.java @@ -203,6 +204,7 @@ pom.xml src/test/resources-filtered/conf.xml src/test/resources/log4j2.xml + src/test/xquery/range/conditions.xql src/main/java/org/exist/indexing/range/RangeIndexAnalyzer.java src/main/java/org/exist/indexing/range/RangeIndexConfigAttributeCondition.java src/main/java/org/exist/indexing/range/RangeIndexConfigElement.java diff --git a/extensions/indexes/range/src/test/xquery/range/conditions.xql b/extensions/indexes/range/src/test/xquery/range/conditions.xql index bed3116e3a..7b67ccea04 100644 --- a/extensions/indexes/range/src/test/xquery/range/conditions.xql +++ b/extensions/indexes/range/src/test/xquery/range/conditions.xql @@ -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 : @@ -36,7 +60,9 @@ declare namespace stats="http://exist-db.org/xquery/profiling"; declare variable $ct:COLLECTION_CONFIG := + xmlns:tei="http://www.tei-c.org/ns/1.0" + xmlns:other1="http://other1" + xmlns:other2="http://other2"> @@ -59,6 +85,14 @@ declare variable $ct:COLLECTION_CONFIG := + + + + + + + + @@ -134,7 +168,7 @@ declare variable $ct:COLLECTION_CONFIG := declare variable $ct:DATA := - + conditional fields! @@ -143,7 +177,7 @@ declare variable $ct:DATA := - publiziert + publiziert literarisch @@ -161,10 +195,10 @@ declare variable $ct:DATA := Alexandria - startswithendswith - foo - foo - literarisch + startswithendswith + foo + foo + literarisch @@ -533,3 +567,20 @@ function ct:optimize-matches-no-case() { collection($ct:COLLECTION)//tei:p[matches(@type, "bb")][. = "something"] }; +(:~ + : See: https://github.com/eXist-db/exist/issues/5189 + :) +declare + %test:assertEquals("publiziert", "startswithendswith") +function ct:other1-index-keys() { + range:index-keys-for-field("other1", function($k, $n) { $k }, 10) +}; + +(:~ + : See: https://github.com/eXist-db/exist/issues/5189 + :) +declare + %test:assertEquals("foo", "literarisch", "startswithendswith") +function ct:other2-index-keys() { + range:index-keys-for-field("other2", function($k, $n) { $k }, 10) +}; From 03e3b3d9ba21e00f7e463eb55179f2ed95621a02 Mon Sep 17 00:00:00 2001 From: Adam Retter Date: Sat, 5 Jul 2025 11:37:20 +0200 Subject: [PATCH 3/4] [test] Show that namespaces are not respected correctly within conditions of the Range Index config --- extensions/indexes/range/src/test/xquery/range/conditions.xql | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/indexes/range/src/test/xquery/range/conditions.xql b/extensions/indexes/range/src/test/xquery/range/conditions.xql index 7b67ccea04..5f45944c06 100644 --- a/extensions/indexes/range/src/test/xquery/range/conditions.xql +++ b/extensions/indexes/range/src/test/xquery/range/conditions.xql @@ -199,6 +199,7 @@ declare variable $ct:DATA := foo foo literarisch + other1-not-same-namespace From a36a2f68525aae3cc8fbd1831abe3b1b21c60150 Mon Sep 17 00:00:00 2001 From: Adam Retter Date: Sat, 5 Jul 2025 12:32:16 +0200 Subject: [PATCH 4/4] [bugfix] Handle QNames correctly in attribute names within conditions of the Range Index config Closes https://github.com/eXist-db/exist/issues/5189 --- .../RangeIndexConfigAttributeCondition.java | 56 +++++++++++++++---- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/extensions/indexes/range/src/main/java/org/exist/indexing/range/RangeIndexConfigAttributeCondition.java b/extensions/indexes/range/src/main/java/org/exist/indexing/range/RangeIndexConfigAttributeCondition.java index 65e8b243e4..697d7c2006 100644 --- a/extensions/indexes/range/src/main/java/org/exist/indexing/range/RangeIndexConfigAttributeCondition.java +++ b/extensions/indexes/range/src/main/java/org/exist/indexing/range/RangeIndexConfigAttributeCondition.java @@ -46,6 +46,8 @@ package org.exist.indexing.range; import org.exist.dom.QName; +import org.exist.dom.persistent.ElementImpl; +import org.exist.dom.persistent.NodeImpl; import org.exist.storage.ElementValue; import org.exist.storage.NodePath; import org.exist.util.DatabaseConfigurationException; @@ -77,8 +79,7 @@ */ public class RangeIndexConfigAttributeCondition extends RangeIndexConfigCondition{ - private final String attributeName; - private final QName attribute; + private final QName attributeName; private @Nullable final String value; private final Operator operator; private final boolean caseSensitive; @@ -94,13 +95,23 @@ public RangeIndexConfigAttributeCondition(Element elem, NodePath parentPath) thr throw new DatabaseConfigurationException("Range index module: Attribute condition cannot be defined for an attribute:" + parentPath.toString()); } - this.attributeName = elem.getAttribute("attribute"); - if (this.attributeName.isEmpty()) { + final String attributeValue = elem.getAttribute("attribute"); + if (attributeValue.isEmpty()) { throw new DatabaseConfigurationException("Range index module: Empty or no attribute qname in condition"); } try { - this.attribute = new QName(QName.extractLocalName(this.attributeName), XMLConstants.NULL_NS_URI, QName.extractPrefix(this.attributeName), ElementValue.ATTRIBUTE); + final String attrLocalName = QName.extractLocalName(attributeValue); + @Nullable final String attrPrefix = QName.extractPrefix(attributeValue); + if (attrPrefix != null) { + @Nullable final String attrNamespace = findNamespaceForPrefix(attrPrefix, (ElementImpl) elem); + if (attrNamespace == null) { + throw new DatabaseConfigurationException("Range index module: Missing namespace declaration for attribute qname in condition"); + } + this.attributeName = new QName(attrLocalName, attrNamespace, attrPrefix, ElementValue.ATTRIBUTE); + } else { + this.attributeName = new QName(attrLocalName, XMLConstants.NULL_NS_URI, null, ElementValue.ATTRIBUTE); + } } catch (final QName.IllegalQNameException e) { throw new DatabaseConfigurationException("Rand index module error: " + e.getMessage(), e); } @@ -169,6 +180,24 @@ public RangeIndexConfigAttributeCondition(Element elem, NodePath parentPath) thr } + private static @Nullable String findNamespaceForPrefix(final String prefix, ElementImpl contextElem) { + while (contextElem != null) { + final String namespace = contextElem.getNamespaceForPrefix(prefix); + if (namespace != null) { + return namespace; + } + + @Nullable final Node parentNode = contextElem.getParentNode(); + if (parentNode != null && parentNode instanceof ElementImpl) { + contextElem = (ElementImpl) parentNode; + } else { + contextElem = null; + } + } + + return null; + } + // lazily evaluate lowercase value to convert once when needed private String getLowercaseValue() { if (this.lowercaseValue == null) { @@ -180,15 +209,18 @@ private String getLowercaseValue() { return this.lowercaseValue; } - @Override - public boolean matches(Node node) { - - if (node.getNodeType() == Node.ELEMENT_NODE && matchValue(((Element)node).getAttribute(attributeName))) { - return true; + public boolean matches(final Node node) { + final String attrValue; + if (attributeName.hasNamespace()) { + attrValue = ((Element) node).getAttributeNS(attributeName.getNamespaceURI(), attributeName.getLocalPart()); + } else { + attrValue = ((Element) node).getAttribute(attributeName.getLocalPart()); } - return false; + return node.getNodeType() == Node.ELEMENT_NODE + && matchValue(attrValue); + } private boolean matchValue(String testValue) { @@ -365,7 +397,7 @@ public boolean find(Predicate predicate) { if (qname.getNameType() == ElementValue.ATTRIBUTE && operator.equals(this.operator) - && qname.equals(this.attribute) + && qname.equals(attributeName) && valueTypeMatches && foundValue.equals(requiredValue)) {