Skip to content

Commit 5dc9ca8

Browse files
authored
Merge pull request #6164 from joewiz/feature/fix-skipped-tests
[bugfix] Fix 3 skipped test failures: ResourceSet, util:eval, XUpdate
2 parents 2c2712d + 121e782 commit 5dc9ca8

6 files changed

Lines changed: 113 additions & 27 deletions

File tree

exist-core/src/main/java/org/exist/xmldb/ResourceSetHelper.java

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,40 +21,79 @@
2121
*/
2222
package org.exist.xmldb;
2323

24-
import java.util.HashMap;
24+
import java.util.LinkedHashMap;
2525
import java.util.Map;
26-
import java.util.Set;
2726

27+
import org.exist.dom.persistent.NodeProxy;
2828
import org.xmldb.api.base.Resource;
2929
import org.xmldb.api.base.ResourceSet;
3030
import org.xmldb.api.base.XMLDBException;
3131

3232
/**
3333
* @author jmv
34-
*
3534
*/
3635
public class ResourceSetHelper {
3736

37+
/**
38+
* Computes the intersection of two resource sets.
39+
*
40+
* <p>For {@link LocalXMLResource} instances backed by persistent nodes,
41+
* intersection is determined by node identity (document ID + node ID).
42+
* For other resources, the resource ID ({@link Resource#getId()}) is used
43+
* as the identity key.</p>
44+
*
45+
* @param s1 the first resource set
46+
* @param s2 the second resource set
47+
* @return a new resource set containing only resources present in both sets
48+
* @throws XMLDBException if an error occurs accessing the resources
49+
*/
3850
public static ResourceSet intersection(final ResourceSet s1, final ResourceSet s2) throws XMLDBException {
39-
final Map<String, Resource> m1 = new MapResourceSet(s1).getResourcesMap();
40-
final Map<String, Resource> m2 = new MapResourceSet(s2).getResourcesMap();
41-
final Set<String> set1 = m1.keySet();
42-
final Set<String> set2 = m2.keySet();
43-
set1.retainAll(set2);
44-
final Map<String, Resource> m = new HashMap<>();
45-
for (String key : set1) {
46-
final Resource resource = m1.get(key);
47-
m.put(key, resource);
51+
// Build a map from identity key to resource for the first set
52+
final Map<String, Resource> m1 = buildIdentityMap(s1);
53+
54+
// Build the intersection: for each resource in s2, check if it's also in s1.
55+
// Use the node identity key (not res.getId()) as the MapResourceSet key
56+
// to avoid collapsing multiple nodes from the same document into one entry.
57+
final Map<String, Resource> result = new LinkedHashMap<>();
58+
for (long i = 0; i < s2.getSize(); i++) {
59+
final Resource res = s2.getResource(i);
60+
final String key = getIdentityKey(res);
61+
if (m1.containsKey(key) && !result.containsKey(key)) {
62+
result.put(key, m1.get(key));
63+
}
64+
}
65+
66+
return new MapResourceSet(result);
67+
}
68+
69+
/**
70+
* Builds a map from identity key to resource.
71+
*/
72+
private static Map<String, Resource> buildIdentityMap(final ResourceSet rs) throws XMLDBException {
73+
final Map<String, Resource> map = new LinkedHashMap<>();
74+
for (long i = 0; i < rs.getSize(); i++) {
75+
final Resource res = rs.getResource(i);
76+
final String key = getIdentityKey(res);
77+
map.putIfAbsent(key, res);
78+
}
79+
return map;
80+
}
81+
82+
/**
83+
* Returns a key that uniquely identifies a resource by node identity.
84+
*
85+
* <p>For {@link LocalXMLResource} instances backed by a {@link NodeProxy},
86+
* the key combines the document ID and the node ID to uniquely identify
87+
* the node within the database. For other resources, the resource ID string
88+
* is used as a fallback.</p>
89+
*/
90+
private static String getIdentityKey(final Resource res) throws XMLDBException {
91+
if (res instanceof LocalXMLResource localRes) {
92+
final NodeProxy proxy = localRes.getNode();
93+
if (proxy != null) {
94+
return proxy.getOwnerDocument().getDocId() + "#" + proxy.getNodeId();
95+
}
4896
}
49-
final MapResourceSet res = new MapResourceSet(m);
50-
/*
51-
VectorResourceSet res = new VectorResourceSet();
52-
Collection c1 = new VectorResourceSet(s1).getResources();
53-
res.getResources().addAll( c1 );
54-
Collection c2 = new VectorResourceSet(s2).getResources();
55-
res.getResources().retainAll(c2);
56-
return res;
57-
*/
58-
return res;
97+
return res.getId();
5998
}
6099
}

exist-core/src/main/java/org/exist/xquery/functions/util/Eval.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,14 @@ private Sequence doEval(final XQueryContext evalContext, final Sequence contextS
361361
exprContext = initContextSequence;
362362
}
363363

364+
// If no explicit context was provided and we have a context sequence
365+
// from the calling expression (e.g., path expression step), use it.
366+
// This allows relative expressions like <a><b/></a>/util:eval('*')
367+
// to see the context item.
368+
if (exprContext == null && contextSequence != null && !contextSequence.isEmpty()) {
369+
exprContext = contextSequence;
370+
}
371+
364372
Sequence result = null;
365373
try {
366374
if (!isCalledAs(FS_EVAL_AND_SERIALIZE_NAME)) {

exist-core/src/main/java/org/exist/xupdate/Append.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@
2727
import org.exist.collections.triggers.TriggerException;
2828
import org.exist.dom.persistent.DocumentImpl;
2929
import org.exist.dom.persistent.DocumentSet;
30+
import org.exist.dom.persistent.ElementImpl;
31+
import org.exist.dom.persistent.IStoredNode;
3032
import org.exist.dom.persistent.StoredNode;
33+
import org.exist.dom.persistent.TextImpl;
3134
import org.exist.security.Permission;
3235
import org.exist.security.PermissionDeniedException;
3336
import org.exist.storage.DBBroker;
@@ -36,6 +39,7 @@
3639
import org.exist.storage.txn.Txn;
3740
import org.exist.util.LockException;
3841
import org.exist.xquery.XPathException;
42+
import org.w3c.dom.Node;
3943
import org.w3c.dom.NodeList;
4044

4145
/**
@@ -85,6 +89,10 @@ public long process(Txn transaction) throws PermissionDeniedException, LockExcep
8589
throw new PermissionDeniedException("User '" + broker.getCurrentSubject().getName() + "' does not have permission to write to the document '" + doc.getDocumentURI() + "'!");
8690
}
8791
node.appendChildren(transaction, children, child);
92+
// Merge adjacent text nodes to maintain XDM compliance (issue #6160)
93+
if (node instanceof ElementImpl) {
94+
mergeAdjacentTextNodes(transaction, (ElementImpl) node);
95+
}
8896
doc.setLastModified(System.currentTimeMillis());
8997
modifiedDocuments.add(doc);
9098
broker.storeXMLResource(transaction, doc);
@@ -98,6 +106,37 @@ public long process(Txn transaction) throws PermissionDeniedException, LockExcep
98106
}
99107
}
100108

109+
/**
110+
* Merges adjacent text node children of the given element.
111+
* After an append, two text nodes may end up adjacent (e.g., the existing
112+
* last text child and a newly appended text node). The XDM spec requires
113+
* that adjacent text nodes be merged into a single text node.
114+
*/
115+
private void mergeAdjacentTextNodes(final Txn transaction, final ElementImpl element) {
116+
final NodeList childNodes = element.getChildNodes();
117+
final int length = childNodes.getLength();
118+
IStoredNode<?> prevTextNode = null;
119+
for (int i = 0; i < length; i++) {
120+
final Node child = childNodes.item(i);
121+
if (child.getNodeType() == Node.TEXT_NODE && child instanceof IStoredNode) {
122+
if (prevTextNode != null) {
123+
// Found adjacent text nodes: merge by updating prev and removing current
124+
final String mergedValue = prevTextNode.getNodeValue() + child.getNodeValue();
125+
final TextImpl mergedText = new TextImpl(prevTextNode.getExpression(), mergedValue);
126+
mergedText.setOwnerDocument(element.getOwnerDocument());
127+
element.updateChild(transaction, prevTextNode, mergedText);
128+
element.removeChild(transaction, child);
129+
// prevTextNode stays as the merged node for potential further merges
130+
// but we need to re-read it since updateChild replaced it
131+
return; // only one merge needed per append
132+
}
133+
prevTextNode = (IStoredNode<?>) child;
134+
} else {
135+
prevTextNode = null;
136+
}
137+
}
138+
}
139+
101140
@Override
102141
public String getName() {
103142
return "append";

exist-core/src/test/java/org/exist/xmldb/ResourceSetTest.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323

2424
import org.exist.test.ExistXmldbEmbeddedServer;
2525
import org.exist.util.io.InputStreamUtil;
26-
import org.junit.*;
26+
import org.junit.After;
27+
import org.junit.Before;
28+
import org.junit.ClassRule;
29+
import org.junit.Test;
2730
import org.xmldb.api.base.*;
2831
import org.xmldb.api.modules.*;
2932

@@ -68,13 +71,12 @@ public void tearDown() throws XMLDBException {
6871
service.removeCollection(TEST_COLLECTION);
6972
}
7073

71-
@Ignore("ResourceSet intersection returns wrong count, see #6157")
7274
@Test
7375
public void intersection1() throws XMLDBException {
7476
final String xpathPrefix = "doc('/db/" + TEST_COLLECTION + "/shakes.xsl')/*/*";
7577
final String query1 = xpathPrefix + "[position() >= 5 ]";
7678
final String query2 = xpathPrefix + "[position() <= 10]";
77-
final int expected = 87;
79+
final int expected = 6;
7880

7981
final XPathQueryService service = testCollection.getService(XPathQueryService.class);
8082

exist-core/src/test/java/org/exist/xquery/XQueryFunctionsTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,6 @@ public void exclusiveLock() throws XMLDBException {
393393
assertEquals("<root/>", r);
394394
}
395395

396-
@Ignore("Context item not propagated into util:eval(), see #6159")
397396
@Test
398397
public void utilEval1() throws XMLDBException {
399398
String query = "<a><b/></a>/util:eval('*')";

exist-core/src/test/java/org/exist/xquery/XQueryTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1659,7 +1659,6 @@ public void largeAttributeText() throws XMLDBException {
16591659
assertEquals(1, result.getSize());
16601660
}
16611661

1662-
@Ignore("Adjacent text nodes not merged after XUpdate append, see #6160")
16631662
@Test
16641663
public void xupdateWithAdjacentTextNodes() throws XMLDBException {
16651664
String query = "let $name := xmldb:store('/db' , 'xupdateTest.xml', <test>aaa</test>)" +

0 commit comments

Comments
 (0)