package org.exist.xquery;
import java.io.IOException;
import java.util.Optional;
import org.exist.EXistException;
import org.exist.collections.Collection;
import org.exist.collections.IndexInfo;
import org.exist.collections.triggers.TriggerException;
import org.exist.dom.persistent.DocumentImpl;
import org.exist.security.PermissionDeniedException;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.storage.serializers.Serializer;
import org.exist.storage.txn.TransactionManager;
import org.exist.storage.txn.Txn;
import org.exist.test.ExistEmbeddedServer;
import org.exist.util.DatabaseConfigurationException;
import org.exist.util.LockException;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.value.NodeValue;
import org.exist.xquery.value.Sequence;
import org.junit.*;
import org.xml.sax.SAXException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class XQueryUpdateTest {
protected static XmldbURI TEST_COLLECTION = XmldbURI.create(XmldbURI.ROOT_COLLECTION + "/test");
protected static String TEST_XML =
"<?xml version=\"1.0\"?>" +
"<products/>";
protected static String UPDATE_XML =
"<progress total=\"100\" done=\"0\" failed=\"0\" passed=\"0\"/>";
protected final static int ITEMS_TO_APPEND = 1000;
@Test
public void append() throws EXistException, PermissionDeniedException, XPathException, SAXException {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) {
XQuery xquery = pool.getXQueryService();
String query =
" declare variable $i external;\n" +
" update insert\n" +
" <product id='id{$i}' num='{$i}'>\n" +
" <description>Description {$i}</description>\n" +
" <price>{$i + 1.0}</price>\n" +
" <stock>{$i * 10}</stock>\n" +
" </product>\n" +
" into /products";
XQueryContext context = new XQueryContext(pool);
CompiledXQuery compiled = xquery.compile(broker, context, query);
for (int i = 0; i < ITEMS_TO_APPEND; i++) {
context.declareVariable("i", Integer.valueOf(i));
xquery.execute(broker, compiled, null);
}
Sequence seq = xquery.execute(broker, "/products", null);
assertEquals(seq.getItemCount(), 1);
Serializer serializer = broker.getSerializer();
serializer.serialize((NodeValue) seq.itemAt(0));
seq = xquery.execute(broker, "//product", null);
assertEquals(ITEMS_TO_APPEND, seq.getItemCount());
seq = xquery.execute(broker, "//product[price > 0.0]", null);
assertEquals(ITEMS_TO_APPEND, seq.getItemCount());
}
}
@Test
public void appendAttributes() throws EXistException, PermissionDeniedException, XPathException, SAXException, LockException, IOException {
append();
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) {
XQuery xquery = pool.getXQueryService();
String query =
" declare variable $i external;\n" +
" update insert\n" +
" attribute name { concat('n', $i) }\n" +
" into //product[@num = $i]";
XQueryContext context = new XQueryContext(pool);
CompiledXQuery compiled = xquery.compile(broker, context, query);
for (int i = 0; i < ITEMS_TO_APPEND; i++) {
context.declareVariable("i", Integer.valueOf(i));
xquery.execute(broker, compiled, null);
}
Sequence seq = xquery.execute(broker, "/products", null);
assertEquals(seq.getItemCount(), 1);
Serializer serializer = broker.getSerializer();
serializer.serialize((NodeValue) seq.itemAt(0));
seq = xquery.execute(broker, "//product", null);
assertEquals(ITEMS_TO_APPEND, seq.getItemCount());
seq = xquery.execute(broker, "//product[@name = 'n20']", null);
assertEquals(1, seq.getItemCount());
store(broker, "attribs.xml", "<test attr1='aaa' attr2='bbb'>ccc</test>");
query = "update insert attribute attr1 { 'eee' } into /test";
//testing duplicate attribute ...
xquery.execute(broker, query, null);
seq = xquery.execute(broker, "xmldb:document('" + TEST_COLLECTION + "/attribs.xml')/test[@attr1 = 'eee']", null);
assertEquals(1, seq.getItemCount());
serializer.serialize((NodeValue) seq.itemAt(0));
}
}
@Test
public void insertBefore() throws EXistException, PermissionDeniedException, XPathException, SAXException {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) {
String query =
" update insert\n" +
" <product id='original'>\n" +
" <description>Description</description>\n" +
" <price>0</price>\n" +
" <stock>10</stock>\n" +
" </product>\n" +
" into /products";
XQuery xquery = pool.getXQueryService();
xquery.execute(broker, query, null);
query =
" declare variable $i external;\n" +
" update insert\n" +
" <product id='id{$i}'>\n" +
" <description>Description {$i}</description>\n" +
" <price>{$i + 1.0}</price>\n" +
" <stock>{$i * 10}</stock>\n" +
" </product>\n" +
" preceding /products/product[1]";
XQueryContext context = new XQueryContext(pool);
CompiledXQuery compiled = xquery.compile(broker, context, query);
for (int i = 0; i < ITEMS_TO_APPEND; i++) {
context.declareVariable("i", Integer.valueOf(i));
xquery.execute(broker, compiled, null);
}
Sequence seq = xquery.execute(broker, "/products", null);
assertEquals(seq.getItemCount(), 1);
Serializer serializer = broker.getSerializer();
serializer.serialize((NodeValue) seq.itemAt(0));
seq = xquery.execute(broker, "//product", null);
assertEquals(ITEMS_TO_APPEND + 1, seq.getItemCount());
seq = xquery.execute(broker, "//product[price > 0.0]", null);
assertEquals(ITEMS_TO_APPEND, seq.getItemCount());
}
}
@Test
public void insertAfter() throws EXistException, PermissionDeniedException, XPathException, SAXException {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) {
String query =
" update insert\n" +
" <product id='original'>\n" +
" <description>Description</description>\n" +
" <price>0</price>\n" +
" <stock>10</stock>\n" +
" </product>\n" +
" into /products";
XQuery xquery = pool.getXQueryService();
xquery.execute(broker, query, null);
query =
" declare variable $i external;\n" +
" update insert\n" +
" <product id='id{$i}'>\n" +
" <description>Description {$i}</description>\n" +
" <price>{$i + 1.0}</price>\n" +
" <stock>{$i * 10}</stock>\n" +
" </product>\n" +
" following /products/product[1]";
XQueryContext context = new XQueryContext(pool);
CompiledXQuery compiled = xquery.compile(broker, context, query);
for (int i = 0; i < ITEMS_TO_APPEND; i++) {
context.declareVariable("i", Integer.valueOf(i));
xquery.execute(broker, compiled, null);
}
Sequence seq = xquery.execute(broker, "/products", null);
assertEquals(seq.getItemCount(), 1);
Serializer serializer = broker.getSerializer();
serializer.serialize((NodeValue) seq.itemAt(0));
seq = xquery.execute(broker, "//product", null);
assertEquals(ITEMS_TO_APPEND + 1, seq.getItemCount());
seq = xquery.execute(broker, "//product[price > 0.0]", null);
assertEquals(ITEMS_TO_APPEND, seq.getItemCount());
}
}
@Test
public void update() throws EXistException, PermissionDeniedException, XPathException, SAXException {
append();
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) {
XQuery xquery = pool.getXQueryService();
String query =
"declare option exist:output-size-limit '-1';\n" +
"for $prod at $i in //product return\n" +
" update value $prod/description\n" +
" with concat('Updated Description', $i)";
Sequence seq = xquery.execute(broker, query, null);
seq = xquery.execute(broker, "//product[starts-with(description, 'Updated')]", null);
assertEquals(seq.getItemCount(), ITEMS_TO_APPEND);
Serializer serializer = broker.getSerializer();
serializer.serialize((NodeValue) seq.itemAt(0));
for (int i = 1; i <= ITEMS_TO_APPEND; i++) {
seq = xquery.execute(broker, "//product[description &= 'Description" + i + "']", null);
assertEquals(1, seq.getItemCount());
serializer.serialize((NodeValue) seq.itemAt(0));
}
seq = xquery.execute(broker, "//product[description &= 'Updated']", null);
assertEquals(seq.getItemCount(), ITEMS_TO_APPEND);
serializer.serialize((NodeValue) seq.itemAt(0));
query =
"declare option exist:output-size-limit '-1';\n" +
"for $prod at $count in //product return\n" +
" update value $prod/stock/text()\n" +
" with (400 + $count)";
seq = xquery.execute(broker, query, null);
seq = xquery.execute(broker, "//product[stock > 400]", null);
assertEquals(seq.getItemCount(), ITEMS_TO_APPEND);
seq = xquery.execute(broker, "//product[stock &= '401']", null);
assertEquals(seq.getItemCount(), 1);
serializer.serialize((NodeValue) seq.itemAt(0));
query =
"declare option exist:output-size-limit '-1';\n" +
"for $prod in //product return\n" +
" update value $prod/@num\n" +
" with xs:int($prod/@num) * 3";
seq = xquery.execute(broker, query, null);
seq = xquery.execute(broker, "/products", null);
assertEquals(seq.getItemCount(), 1);
seq = xquery.execute(broker, "//product[@num = 3]", null);
assertEquals(seq.getItemCount(), 1);
serializer.serialize((NodeValue) seq.itemAt(0));
query =
"declare option exist:output-size-limit '-1';\n" +
"for $prod in //product return\n" +
" update value $prod/stock\n" +
" with (<local>10</local>,<external>1</external>)";
seq = xquery.execute(broker, query, null);
seq = xquery.execute(broker, "/products", null);
assertEquals(seq.getItemCount(), 1);
seq = xquery.execute(broker, "//product/stock/external[. = 1]", null);
assertEquals(seq.getItemCount(), ITEMS_TO_APPEND);
}
}
@Test
public void remove() throws EXistException, PermissionDeniedException, XPathException, SAXException {
append();
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) {
XQuery xquery = pool.getXQueryService();
String query =
"for $prod in //product return\n" +
" update delete $prod\n";
Sequence seq = xquery.execute(broker, query, null);
seq = xquery.execute(broker, "//product", null);
assertEquals(seq.getItemCount(), 0);
}
}
@Test
public void rename() throws EXistException, PermissionDeniedException, XPathException, SAXException {
append();
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) {
XQuery xquery = pool.getXQueryService();
String query =
"for $prod in //product return\n" +
" update rename $prod/description as 'desc'\n";
Sequence seq = xquery.execute(broker, query, null);
seq = xquery.execute(broker, "//product/desc", null);
assertEquals(seq.getItemCount(), ITEMS_TO_APPEND);
query =
"for $prod in //product return\n" +
" update rename $prod/@num as 'count'\n";
seq = xquery.execute(broker, query, null);
seq = xquery.execute(broker, "//product/@count", null);
assertEquals(seq.getItemCount(), ITEMS_TO_APPEND);
}
}
@Test
public void replace() throws EXistException, PermissionDeniedException, XPathException, SAXException {
append();
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) {
XQuery xquery = pool.getXQueryService();
String query =
"for $prod in //product return\n" +
" update replace $prod/description with <desc>An updated description.</desc>\n";
Sequence seq = xquery.execute(broker, query, null);
seq = xquery.execute(broker, "//product/desc", null);
assertEquals(seq.getItemCount(), ITEMS_TO_APPEND);
query =
"for $prod in //product return\n" +
" update replace $prod/@num with '1'\n";
seq = xquery.execute(broker, query, null);
seq = xquery.execute(broker, "//product/@num", null);
assertEquals(seq.getItemCount(), ITEMS_TO_APPEND);
query =
"for $prod in //product return\n" +
" update replace $prod/desc/text() with 'A new update'\n";
seq = xquery.execute(broker, query, null);
seq = xquery.execute(broker, "//product[starts-with(desc, 'A new')]", null);
assertEquals(seq.getItemCount(), ITEMS_TO_APPEND);
}
}
@Test
public void attrUpdate() throws EXistException, LockException, SAXException, PermissionDeniedException, IOException, XPathException {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) {
store(broker, "test.xml", UPDATE_XML);
String query =
"let $progress := /progress\n" +
"for $i in 1 to 100\n" +
"let $done := $progress/@done\n" +
"return (\n" +
" update value $done with xs:int($done + 1),\n" +
" xs:int(/progress/@done)\n" +
")";
XQuery xquery = pool.getXQueryService();
@SuppressWarnings("unused")
Sequence result = xquery.execute(broker, query, null);
}
}
@Test
public void appendCDATA() throws EXistException, PermissionDeniedException, XPathException, SAXException {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) {
XQuery xquery = pool.getXQueryService();
String query =
" declare variable $i external;\n" +
" update insert\n" +
" <product>\n" +
" <description><![CDATA[me & you <>]]></description>\n" +
" </product>\n" +
" into /products";
XQueryContext context = new XQueryContext(pool);
CompiledXQuery compiled = xquery.compile(broker, context, query);
for (int i = 0; i < ITEMS_TO_APPEND; i++) {
xquery.execute(broker, compiled, null);
}
Sequence seq = xquery.execute(broker, "/products", null);
assertEquals(seq.getItemCount(), 1);
Serializer serializer = broker.getSerializer();
serializer.serialize((NodeValue) seq.itemAt(0));
seq = xquery.execute(broker, "//product", null);
assertEquals(ITEMS_TO_APPEND, seq.getItemCount());
}
}
@Ignore
@Test
public void insertAttribDoc_1730726() throws EXistException, PermissionDeniedException, XPathException {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) {
String query =
"declare namespace xmldb = \"http://exist-db.org/xquery/xmldb\"; "+
"let $uri := xmldb:store(\"/db\", \"insertAttribDoc.xml\", <C/>) "+
"let $node := doc($uri)/element() "+
"let $attrib := <Value f=\"ATTRIB VALUE\"/>/@* "+
"return update insert $attrib into $node";
XQuery xquery = pool.getXQueryService();
@SuppressWarnings("unused")
Sequence result = xquery.execute(broker, query, null);
}
}
@ClassRule
public static final ExistEmbeddedServer existEmbeddedServer = new ExistEmbeddedServer(true, false);
@Before
public void loadTestData() throws EXistException, DatabaseConfigurationException, LockException, SAXException, PermissionDeniedException, IOException {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) {
store(broker, "test.xml", TEST_XML);
}
}
@After
public void removeTestData() throws EXistException, PermissionDeniedException, IOException, TriggerException {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
final TransactionManager transact = pool.getTransactionManager();
try(final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()));
final Txn transaction = transact.beginTransaction()) {
final Collection root = broker.getOrCreateCollection(transaction, TEST_COLLECTION);
assertNotNull(root);
broker.removeCollection(transaction, root);
transact.commit(transaction);
}
}
private void store(DBBroker broker, String docName, String data) throws PermissionDeniedException, EXistException, SAXException, LockException, IOException {
Collection root;
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
final TransactionManager mgr = pool.getTransactionManager();
try (final Txn transaction = mgr.beginTransaction()) {
root = broker.getOrCreateCollection(transaction, TEST_COLLECTION);
broker.saveCollection(transaction, root);
IndexInfo info = root.validateXMLResource(transaction, broker, XmldbURI.create(docName), data);
//TODO : unlock the collection here ?
root.store(transaction, broker, info, data);
mgr.commit(transaction);
}
DocumentImpl doc = root.getDocument(broker, XmldbURI.create(docName));
broker.getSerializer().serialize(doc);
}
}