Skip to content

[7.x.x] Make sure that Attribute QNames are correctly handled within conditions of the Range Index config #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: develop-7.x.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions extensions/indexes/range/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@
<include>pom.xml</include>
<include>src/test/resources-filtered/conf.xml</include>
<include>src/test/resources/log4j2.xml</include>
<include>src/test/xquery/range/conditions.xql</include>
<include>src/main/java/org/exist/indexing/range/RangeIndexAnalyzer.java</include>
<include>src/main/java/org/exist/indexing/range/RangeIndexConfigAttributeCondition.java</include>
<include>src/main/java/org/exist/indexing/range/RangeIndexConfigElement.java</include>
Expand All @@ -210,6 +211,7 @@
<exclude>pom.xml</exclude>
<exclude>src/test/resources-filtered/conf.xml</exclude>
<exclude>src/test/resources/log4j2.xml</exclude>
<exclude>src/test/xquery/range/conditions.xql</exclude>
<exclude>src/main/java/org/exist/indexing/range/RangeIndexAnalyzer.java</exclude>
<exclude>src/main/java/org/exist/indexing/range/RangeIndexConfigAttributeCondition.java</exclude>
<exclude>src/main/java/org/exist/indexing/range/RangeIndexConfigElement.java</exclude>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -78,8 +80,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;
Expand All @@ -96,13 +97,23 @@ public RangeIndexConfigAttributeCondition(final Element elem, final NodePath par
"Range index module: Attribute condition cannot be defined for an attribute:" + parentPath);
}

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);
}
Expand Down Expand Up @@ -171,6 +182,24 @@ public RangeIndexConfigAttributeCondition(final Element elem, final NodePath par

}

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) {
Expand All @@ -184,8 +213,16 @@ private String getLowercaseValue() {

@Override
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 node.getNodeType() == Node.ELEMENT_NODE
&& matchValue(((Element) node).getAttribute(attributeName));
&& matchValue(attrValue);

}

private boolean matchValue(final String testValue) {
Expand Down Expand Up @@ -312,7 +349,7 @@ public boolean find(final Predicate predicate) {
}

final QName qname = testStep.getTest().getName();
if (qname.getNameType() != ElementValue.ATTRIBUTE || !qname.equals(attribute)) {
if (qname.getNameType() != ElementValue.ATTRIBUTE || !qname.equals(attributeName)) {
return false;
}

Expand Down
66 changes: 59 additions & 7 deletions extensions/indexes/range/src/test/xquery/range/conditions.xql
Original file line number Diff line number Diff line change
@@ -1,4 +1,28 @@
(:
: Elemental
: Copyright (C) 2024, Evolved Binary Ltd
:
: [email protected]
: 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
:
Expand Down Expand Up @@ -36,7 +60,9 @@ declare namespace stats="http://exist-db.org/xquery/profiling";
declare variable $ct:COLLECTION_CONFIG :=
<collection xmlns="http://exist-db.org/collection-config/1.0">
<index xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tei="http://www.tei-c.org/ns/1.0">
xmlns:tei="http://www.tei-c.org/ns/1.0"
xmlns:other1="http://other1"
xmlns:other2="http://other2">
<range>
<create qname="tei:note">
<condition attribute="type" value="availability" />
Expand All @@ -59,6 +85,14 @@ declare variable $ct:COLLECTION_CONFIG :=
<field name="orig_place" match="tei:place/tei:placeName" type="xs:string" case="no"></field>
</create>
<create qname="tei:note" type="xs:string" case="no" />
<create qname="tei:note">
<condition attribute="other1:type" value="other1" />
<field name="other1" type="xs:string" case="no"/>
</create>
<create qname="tei:note">
<condition attribute="other2:type" value="other2" />
<field name="other2" type="xs:string" case="no"/>
</create>
<create qname="tei:placeName">
<condition attribute="type" value="someType" />
<condition attribute="cert" value="high" />
Expand Down Expand Up @@ -134,7 +168,7 @@ declare variable $ct:COLLECTION_CONFIG :=


declare variable $ct:DATA :=
<TEI xmlns="http://www.tei-c.org/ns/1.0">
<TEI xmlns="http://www.tei-c.org/ns/1.0" xmlns:other1="http://other1" xmlns:other2="http://other2">
<teiHeader>
<fileDesc>
<titleStmt><title>conditional fields!</title></titleStmt>
Expand All @@ -143,7 +177,7 @@ declare variable $ct:DATA :=
<msDesc>
<msContents>
<msItemStruct>
<note type="availability">publiziert</note>
<note type="availability" other1:type="other1">publiziert</note>
<note type="text_type">literarisch</note>
<note type="orig_place">
<place>
Expand All @@ -161,10 +195,11 @@ declare variable $ct:DATA :=
<placeName cert="high" type="someOtherType">Alexandria</placeName>
</place>
</note>
<note type="start_end">startswithendswith</note>
<note>foo</note>
<note type="bar">foo</note>
<note type="something">literarisch</note>
<note type="start_end" other1:type="other1" other2:type="other2">startswithendswith</note>
<note type="other2" other1:type="start_end">foo</note>
<note type="bar" other2:type="other2">foo</note>
<note type="something" other2:type="other2">literarisch</note>
<note xmlns:other1="http://not-other-one" other1:type="other1">other1-not-same-namespace</note>
</msItemStruct>
</msContents>
</msDesc>
Expand Down Expand Up @@ -533,3 +568,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)
};
2 changes: 1 addition & 1 deletion schema/collection.xconf.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@
<xs:attribute name="at" type="xs:anyURI" use="required" form="unqualified"/>
</xs:attributeGroup>
<xs:attributeGroup name="attributeReq">
<xs:attribute name="attribute" type="xs:NCName" use="required"/>
<xs:attribute name="attribute" type="xs:QName" use="required"/>
</xs:attributeGroup>
<xs:attributeGroup name="boostOpt">
<xs:attribute name="boost" type="xs:double" use="optional" form="unqualified"/>
Expand Down