/* * eXist Open Source Native XML Database * Copyright (C) 2011 The eXist-db Project * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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 Library General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Id$ */ package org.exist.dom; import org.junit.AfterClass; import org.junit.BeforeClass; import org.exist.EXistException; import org.exist.collections.Collection; import org.exist.collections.IndexInfo; import org.exist.security.SecurityManager; import org.exist.security.xacml.AccessContext; import org.exist.storage.BrokerPool; import org.exist.storage.DBBroker; import org.exist.storage.ElementValue; import org.exist.storage.serializers.Serializer; import org.exist.storage.txn.TransactionManager; import org.exist.storage.txn.Txn; import org.exist.util.Configuration; import org.exist.util.DatabaseConfigurationException; import org.exist.util.XMLFilenameFilter; import org.exist.xmldb.XmldbURI; import org.exist.xquery.AncestorSelector; import org.exist.xquery.ChildSelector; import org.exist.xquery.Constants; import org.exist.xquery.DescendantOrSelfSelector; import org.exist.xquery.DescendantSelector; import org.exist.xquery.NameTest; import org.exist.xquery.NodeSelector; import org.exist.xquery.XPathException; import org.exist.xquery.XQuery; import org.exist.xquery.value.Item; import org.exist.xquery.value.NodeValue; import org.exist.xquery.value.Sequence; import org.exist.xquery.value.Type; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import java.io.File; import org.junit.Test; import static org.junit.Assert.assertEquals; /** * Test basic {@link org.exist.dom.NodeSet} operations to ensure that * the used algorithms are correct. * * @author wolf * @author Adam Retter <adam@exist-db.org> */ public class BasicNodeSetTest { private final static String NESTED_XML = "<section n='1'>" + "<section n='1.1'>" + "<section n='1.1.1'>" + "<para n='1.1.1.1'/>" + "<para n='1.1.1.2'/>" + "<para n='1.1.1.3'/>" + "</section>" + "<section n='1.1.2'>" + "<para n='1.1.2.1'/>" + "</section>" + "</section>" + "<section n='1.2'>" + "<para n='1.2.1'/>" + "</section>" + "</section>"; private static BrokerPool pool = null; private static Collection root = null; private static DBBroker broker = null; private static Sequence seqSpeech = null; private static DocumentSet docs = null; @Test public void childSelector() throws XPathException { NodeSelector selector = new ChildSelector(seqSpeech.toNodeSet(), -1); NameTest test = new NameTest(Type.ELEMENT, new QName("LINE", "")); NodeSet set = broker.getElementIndex().findElementsByTagName(ElementValue.ELEMENT, seqSpeech.getDocumentSet(), test.getName(), selector); assertEquals(9492, set.getLength()); } @Test public void descendantOrSelfSelector() throws XPathException { NodeSelector selector = new DescendantOrSelfSelector(seqSpeech.toNodeSet(), -1); NameTest test = new NameTest(Type.ELEMENT, new QName("SPEECH", "")); NodeSet set = broker.getElementIndex().findElementsByTagName(ElementValue.ELEMENT, seqSpeech.getDocumentSet(), test.getName(), selector); assertEquals(2628, set.getLength()); } @Test public void ancestorSelector() throws XPathException { NodeSelector selector = new AncestorSelector(seqSpeech.toNodeSet(), -1, false, true); NameTest test = new NameTest(Type.ELEMENT, new QName("ACT", "")); NodeSet set = broker.getElementIndex().findElementsByTagName(ElementValue.ELEMENT, seqSpeech.getDocumentSet(), test.getName(), selector); assertEquals(15, set.getLength()); } @Test public void ancestorSelector_self() throws XPathException { NodeSet ns = seqSpeech.toNodeSet(); NodeSelector selector = new AncestorSelector(ns, -1, true, true); NameTest test = new NameTest(Type.ELEMENT, new QName("SPEECH", "")); NodeSet set = broker.getElementIndex().findElementsByTagName(ElementValue.ELEMENT, seqSpeech.getDocumentSet(), test.getName(), selector); assertEquals(2628, set.getLength()); } @Test public void descendantSelector() throws XPathException, SAXException { Sequence seq = executeQuery(broker, "//SCENE", 72, null); NameTest test = new NameTest(Type.ELEMENT, new QName("SPEAKER", "")); NodeSelector selector = new DescendantSelector(seq.toNodeSet(), -1); NodeSet set = broker.getElementIndex().findElementsByTagName(ElementValue.ELEMENT, seq.getDocumentSet(), test.getName(), selector); assertEquals(2639, set.getLength()); } @Test public void selectParentChild() throws XPathException, SAXException { NameTest test = new NameTest(Type.ELEMENT, new QName("SPEAKER", "")); NodeSet speakers = broker.getElementIndex().findElementsByTagName(ElementValue.ELEMENT, docs, test.getName(), null); Sequence smallSet = executeQuery(broker, "//SPEECH/LINE[fn:contains(., 'perturbed spirit')]/ancestor::SPEECH", 1, null); NodeSet result = NodeSetHelper.selectParentChild(speakers, smallSet.toNodeSet(), NodeSet.DESCENDANT, -1); assertEquals(1, result.getLength()); String value = serialize(broker, result.itemAt(0)); assertEquals(value, "<SPEAKER>HAMLET</SPEAKER>"); } @Test public void selectParentChild_2() throws XPathException, SAXException { NameTest test = new NameTest(Type.ELEMENT, new QName("SPEAKER", "")); NodeSet speakers = broker.getElementIndex().findElementsByTagName(ElementValue.ELEMENT, docs, test.getName(), null); Sequence largeSet = executeQuery(broker, "//SPEECH/LINE[fn:contains(., 'love')]/ancestor::SPEECH", 187, null); NodeSet result = NodeSetHelper.selectParentChild(speakers, largeSet.toNodeSet(), NodeSet.DESCENDANT, -1); assertEquals(187, result.getLength()); } @Test public void selectAncestorDescendant() throws XPathException, SAXException{ NameTest test = new NameTest(Type.ELEMENT, new QName("SPEAKER", "")); NodeSet speakers = broker.getElementIndex().findElementsByTagName(ElementValue.ELEMENT, docs, test.getName(), null); Sequence outerSet = executeQuery(broker, "//SCENE/TITLE[fn:contains(., 'closet')]/ancestor::SCENE", 1, null); NodeSet result = speakers.selectAncestorDescendant(outerSet.toNodeSet(), NodeSet.DESCENDANT, false, -1, true); assertEquals(56, result.getLength()); } @Test public void selectAncestorDescendant_2() throws XPathException, SAXException{ Sequence outerSet = executeQuery(broker, "//SCENE/TITLE[fn:contains(., 'closet')]/ancestor::SCENE", 1, null); NodeSet result = ((AbstractNodeSet)outerSet).selectAncestorDescendant(outerSet.toNodeSet(), NodeSet.DESCENDANT, true, -1, true); assertEquals(1, result.getLength()); } @Test public void getParents() throws XPathException, SAXException{ Sequence largeSet = executeQuery(broker, "//SPEECH/LINE[fn:contains(., 'love')]/ancestor::SPEECH", 187, null); NodeSet result = ((AbstractNodeSet)largeSet).getParents(-1); assertEquals(51, result.getLength()); } @Test public void selectAncestors() throws XPathException, SAXException { NameTest test = new NameTest(Type.ELEMENT, new QName("SCENE", "")); NodeSet scenes = broker.getElementIndex().findElementsByTagName(ElementValue.ELEMENT, docs, test.getName(), null); Sequence largeSet = executeQuery(broker, "//SPEECH/LINE[fn:contains(., 'love')]/ancestor::SPEECH", 187, null); NodeSet result = ((AbstractNodeSet)scenes).selectAncestors(largeSet.toNodeSet(), false, -1); assertEquals(49, result.getLength()); } @Test public void nodeProxy_getParents() throws XPathException, SAXException { Sequence smallSet = executeQuery(broker, "//SPEECH/LINE[fn:contains(., 'perturbed spirit')]/ancestor::SPEECH", 1, null); NodeProxy proxy = (NodeProxy) smallSet.itemAt(0); NodeSet result = proxy.getParents(-1); assertEquals(1, result.getLength()); NameTest test = new NameTest(Type.ELEMENT, new QName("SPEAKER", "")); NodeSet speakers = broker.getElementIndex().findElementsByTagName(ElementValue.ELEMENT, docs, test.getName(), null); result = speakers.selectParentChild(proxy, NodeSet.DESCENDANT, -1); assertEquals(1, result.getLength()); } @Test public void selectFollowingSiblings() throws XPathException, SAXException { Sequence largeSet = executeQuery(broker, "//SPEECH/LINE[fn:contains(., 'love')]/ancestor::SPEECH/SPEAKER", 187, null); NameTest test = new NameTest(Type.ELEMENT, new QName("LINE", "")); NodeSet lines = broker.getElementIndex().findElementsByTagName(ElementValue.ELEMENT, docs, test.getName(), null); NodeSet result = ((AbstractNodeSet) lines).selectFollowingSiblings(largeSet.toNodeSet(), -1); assertEquals(1689, result.getLength()); } @Test public void selectPrecedingSiblings() throws XPathException, SAXException { NameTest test = new NameTest(Type.ELEMENT, new QName("SPEAKER", "")); NodeSet speakers = broker.getElementIndex().findElementsByTagName(ElementValue.ELEMENT, docs, test.getName(), null); Sequence largeSet = executeQuery(broker, "//SPEECH/LINE[fn:contains(., 'love')]/ancestor::SPEECH/LINE[1]", 187, null); NodeSet result = ((AbstractNodeSet) speakers).selectPrecedingSiblings(largeSet.toNodeSet(), -1); assertEquals(187, result.getLength()); } @Test public void extArrayNodeSet_selectParentChild_1() throws XPathException, SAXException { Sequence nestedSet = executeQuery(broker, "//section[@n = ('1.1', '1.1.1')]", 2, null); NameTest test = new NameTest(Type.ELEMENT, new QName("para", "")); NodeSet children = broker.getElementIndex().findElementsByTagName(ElementValue.ELEMENT, docs, test.getName(), null); NodeSet result = children.selectParentChild(nestedSet.toNodeSet(), NodeSet.DESCENDANT); assertEquals(3, result.getLength()); } @Test public void extArrayNodeSet_selectParentChild_2() throws XPathException, SAXException { Sequence nestedSet = executeQuery(broker, "//section[@n = ('1.1', '1.1.2', '1.2')]", 3, null); NameTest test = new NameTest(Type.ELEMENT, new QName("para", "")); NodeSet children = broker.getElementIndex().findElementsByTagName(ElementValue.ELEMENT, docs, test.getName(), null); NodeSet result = children.selectParentChild(nestedSet.toNodeSet(), NodeSet.DESCENDANT); assertEquals(2, result.getLength()); } @Test public void extArrayNodeSet_selectParentChild_3() throws XPathException, SAXException { Sequence nestedSet = executeQuery(broker, "//section[@n = ('1.1', '1.1.1', '1.2')]", 3, null); NameTest test = new NameTest(Type.ELEMENT, new QName("para", "")); NodeSet children = broker.getElementIndex().findElementsByTagName(ElementValue.ELEMENT, docs, test.getName(), null); NodeSet result = children.selectParentChild(nestedSet.toNodeSet(), NodeSet.DESCENDANT); assertEquals(4, result.getLength()); } @Test public void extArrayNodeSet_selectParentChild_4() throws XPathException, SAXException { Sequence nestedSet = executeQuery(broker, "//para[@n = ('1.1.2.1')]", 1, null); NameTest test = new NameTest(Type.ELEMENT, new QName("section", "")); NodeSet sections = broker.getElementIndex().findElementsByTagName(ElementValue.ELEMENT, docs, test.getName(), null); NodeSet result = ((NodeSet) nestedSet).selectParentChild(sections.toNodeSet(), NodeSet.DESCENDANT); assertEquals(1, result.getLength()); } @Test public void testOptimizations() throws XPathException, SAXException { Serializer serializer = broker.getSerializer(); serializer.reset(); DocumentSet docs = root.allDocs(broker, new DefaultDocumentSet(), true, false); System.out.println("------------ Testing NativeElementIndex.findChildNodesByTagName ---------"); // parent set: 1.1.1; child set: 1.1.1.1, 1.1.1.2, 1.1.1.3, 1.1.2.1, 1.2.1 ExtNodeSet nestedSet = (ExtNodeSet) executeQuery(broker, "//section[@n = '1.1.1']", 1, null); NodeSet children = broker.getElementIndex().findDescendantsByTagName(ElementValue.ELEMENT, new QName("para", ""), Constants.CHILD_AXIS, docs, nestedSet, -1); assertEquals(3, children.getLength()); // parent set: 1.1; child set: 1.1.1, 1.1.2 nestedSet = (ExtNodeSet) executeQuery(broker, "//section[@n = '1.1']", 1, null); children = broker.getElementIndex().findDescendantsByTagName(ElementValue.ELEMENT, new QName("section", ""), Constants.CHILD_AXIS, docs, nestedSet, -1); assertEquals(2, children.getLength()); // parent set: 1, 1.1, 1.1.1, 1.1.2 ; child set: 1.1.1.1, 1.1.1.2, 1.1.1.3, 1.1.2.1, 1.2.1 // problem: ancestor set contains nested nodes nestedSet = (ExtNodeSet) executeQuery(broker, "//section[@n = ('1.1', '1.1.1', '1.1.2')]", 3, null); children = broker.getElementIndex().findDescendantsByTagName(ElementValue.ELEMENT, new QName("para", ""), Constants.CHILD_AXIS, docs, nestedSet, -1); assertEquals(4, children.getLength()); // parent set: 1.1, 1.1.2, 1.2 ; child set: 1.1.1.1, 1.1.1.2, 1.1.1.3, 1.1.2.1, 1.2.1 // problem: ancestor set contains nested nodes nestedSet = (ExtNodeSet) executeQuery(broker, "//section[@n = ('1.1', '1.1.2', '1.2')]", 3, null); children = broker.getElementIndex().findDescendantsByTagName(ElementValue.ELEMENT, new QName("para", ""), Constants.CHILD_AXIS, docs, nestedSet, -1); assertEquals(2, children.getLength()); nestedSet = (ExtNodeSet) executeQuery(broker, "//section[@n = '1.1']", 1, null); children = broker.getElementIndex().findDescendantsByTagName(ElementValue.ELEMENT, new QName("para", ""), Constants.DESCENDANT_AXIS, docs, nestedSet, -1); assertEquals(4, children.getLength()); nestedSet = (ExtNodeSet) executeQuery(broker, "//section[@n = '1']", 1, null); children = broker.getElementIndex().findDescendantsByTagName(ElementValue.ELEMENT, new QName("para", ""), Constants.DESCENDANT_AXIS, docs, nestedSet, -1); assertEquals(5, children.getLength()); nestedSet = (ExtNodeSet) executeQuery(broker, "//section[@n = '1.1.2']", 1, null); children = broker.getElementIndex().findDescendantsByTagName(ElementValue.ELEMENT, new QName("section", ""), Constants.DESCENDANT_SELF_AXIS, docs, nestedSet, -1); assertEquals(1, children.getLength()); nestedSet = (ExtNodeSet) executeQuery(broker, "//section[@n = '1.1.2']", 1, null); children = broker.getElementIndex().findDescendantsByTagName(ElementValue.ATTRIBUTE, new QName("n", ""), Constants.ATTRIBUTE_AXIS, docs, nestedSet, -1); assertEquals(1, children.getLength()); nestedSet = (ExtNodeSet) executeQuery(broker, "//section[@n = '1.1']", 1, null); children = broker.getElementIndex().findDescendantsByTagName(ElementValue.ATTRIBUTE, new QName("n", ""), Constants.DESCENDANT_ATTRIBUTE_AXIS, docs, nestedSet, -1); assertEquals(7, children.getLength()); System.out.println("------------ PASSED: NativeElementIndex.findChildNodesByTagName ---------"); } @Test public void virtualNodeSet_1() throws XPathException, SAXException { executeQuery(broker, "//*/LINE", 9492, null); } @Test public void virtualNodeSet_2() throws XPathException, SAXException { executeQuery(broker, "//*/LINE/*", 61, null); } @Test public void virtualNodeSet_3() throws XPathException, SAXException { executeQuery(broker, "//*/LINE/text()", 9485, null); } @Test public void virtualNodeSet_4() throws XPathException, SAXException { executeQuery(broker, "//SCENE/*/LINE", 9464, null); } @Test public void virtualNodeSet_5() throws XPathException, SAXException { executeQuery(broker, "//SCENE/*[fn:contains(LINE, 'spirit')]", 30, null); } @Test public void virtualNodeSet_6() throws XPathException, SAXException { executeQuery(broker, "//SCENE/*[fn:contains(LINE, 'the')]", 1313, null); } @Test public void virtualNodeSet_7() throws XPathException, SAXException { executeQuery(broker, "//SCENE/*/LINE[fn:contains(., 'the')]", 3198, null); } @Test public void virtualNodeSet_8() throws XPathException, SAXException { executeQuery(broker, "//SCENE[fn:contains(., 'spirit')]/ancestor::*", 16, null); } @Test public void virtualNodeSet_9() throws XPathException, SAXException { executeQuery(broker, "for $s in //SCENE/*[fn:contains(LINE, 'the')] return fn:node-name($s)", 1313, null); } @Test public void virtualNodeSet_10() throws XPathException, SAXException { executeQuery(broker, "//SPEECH[fn:contains(LINE, 'perturbed spirit')]/preceding-sibling::*", 65, null); } @Test public void virtualNodeSet_11() throws XPathException, SAXException { executeQuery(broker, "//SPEECH[fn:contains(LINE, 'perturbed spirit')]/following-sibling::*", 1, null); } private static Sequence executeQuery(DBBroker broker, String query, int expected, String expectedResult) throws XPathException, SAXException { XQuery xquery = broker.getXQueryService(); Sequence seq = xquery.execute(query, null, AccessContext.TEST); assertEquals(expected, seq.getItemCount()); if (expectedResult != null) { Item item = seq.itemAt(0); String value = serialize(broker, item); assertEquals(expectedResult, value); } return seq; } private static String serialize(DBBroker broker, Item item) throws SAXException, XPathException { Serializer serializer = broker.getSerializer(); serializer.reset(); String value; if(Type.subTypeOf(item.getType(), Type.NODE)) { value = serializer.serialize((NodeValue) item); } else { value = item.getStringValue(); } return value; } @BeforeClass public static void setUp() throws Exception { TransactionManager transact = null; Txn transaction = null; try { pool = startDB(); broker = pool.get(SecurityManager.SYSTEM_USER); transact = pool.getTransactionManager(); transaction = transact.beginTransaction(); root = broker.getOrCreateCollection(transaction, XmldbURI.create(DBBroker.ROOT_COLLECTION + "/test")); broker.saveCollection(transaction, root); String existHome = System.getProperty("exist.home"); File existDir = existHome==null ? new File(".") : new File(existHome); String directory = "samples/shakespeare"; File dir = new File(existDir, directory); // store some documents. for(File f : dir.listFiles(new XMLFilenameFilter())) { IndexInfo info = root.validateXMLResource(transaction, broker, XmldbURI.create(f.getName()), new InputSource(f.toURI().toASCIIString())); root.store(transaction, broker, info, new InputSource(f.toURI().toASCIIString()), false); } IndexInfo info = root.validateXMLResource(transaction, broker, XmldbURI.create("nested.xml"), NESTED_XML); root.store(transaction, broker, info, NESTED_XML, false); transact.commit(transaction); //for the tests docs = root.allDocs(broker, new DefaultDocumentSet(), true, false); seqSpeech = executeQuery(broker, "//SPEECH", 2628, null); } catch(Exception e) { if (pool != null) { pool.release(broker); BrokerPool.stopAll(false); pool = null; root = null; } throw e; } } private static BrokerPool startDB() throws DatabaseConfigurationException, EXistException { String home, file = "conf.xml"; home = System.getProperty("exist.home"); if (home == null) { home = System.getProperty("user.dir"); } Configuration config = new Configuration(file, home); BrokerPool.configure(1, 5, config); return BrokerPool.getInstance(); } @AfterClass public static void tearDown() { TransactionManager transact = null; Txn transaction = null; try { transact = pool.getTransactionManager(); transaction = transact.beginTransaction(); root = broker.getOrCreateCollection(transaction, XmldbURI.create(DBBroker.ROOT_COLLECTION + "/test")); // broker.removeCollection(transaction, root); transact.commit(transaction); } catch(Exception e) { if(transaction != null) { transact.abort(transaction); } } finally { if (pool != null) pool.release(broker); } BrokerPool.stopAll(false); pool = null; root = null; } }