package org.exist.xquery.functions.util; import org.exist.test.ExistXmldbEmbeddedServer; import org.exist.xmldb.*; import org.exist.xquery.ErrorCodes; import org.junit.After; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; import static org.junit.Assert.*; import org.exist.xquery.XPathException; import org.w3c.dom.Node; import org.xmldb.api.base.Collection; import org.xmldb.api.DatabaseManager; import org.xmldb.api.base.Resource; import org.xmldb.api.base.ResourceSet; import org.xmldb.api.base.XMLDBException; import org.xmldb.api.modules.BinaryResource; import org.xmldb.api.modules.CollectionManagementService; /** * * @author jim.fuller@webcomposite.com */ public class EvalTest { @ClassRule public static final ExistXmldbEmbeddedServer existEmbeddedServer = new ExistXmldbEmbeddedServer(false, true); private Resource invokableQuery; private final static String INVOKABLE_QUERY_FILENAME = "invokable.xql"; private final static String INVOKABLE_QUERY_EXTERNAL_VAR_NAME = "some-value"; public EvalTest() { } @Before public void setUp() throws Exception { invokableQuery = existEmbeddedServer.getRoot().createResource(INVOKABLE_QUERY_FILENAME, "BinaryResource"); invokableQuery.setContent( "declare variable $" + INVOKABLE_QUERY_EXTERNAL_VAR_NAME + " external;\n" + "<hello>{$" + INVOKABLE_QUERY_EXTERNAL_VAR_NAME + "}</hello>" ); ((EXistResource) invokableQuery).setMimeType("application/xquery"); existEmbeddedServer.getRoot().storeResource(invokableQuery); } @After public void tearDown() throws Exception { existEmbeddedServer.getRoot().removeResource(invokableQuery); } @Test public void eval() throws XPathException, XMLDBException { final String query = "let $query := 'let $a := 1 return $a'\n" + "return\n" + "util:eval($query)"; final ResourceSet result = existEmbeddedServer.executeQuery(query); final String r = (String) result.getResource(0).getContent(); assertEquals("1", r); } @Test public void evalWithExternalVars() throws XPathException, XMLDBException { final String query = "let $value := 'world' return\n" + "\tutil:eval(xs:anyURI('/db/" + INVOKABLE_QUERY_FILENAME + "'), false(), (xs:QName('" + INVOKABLE_QUERY_EXTERNAL_VAR_NAME + "'), $value))"; final ResourceSet result = existEmbeddedServer.executeQuery(query); final LocalXMLResource res = (LocalXMLResource)result.getResource(0); final Node n = res.getContentAsDOM(); assertEquals(n.getLocalName(), "hello"); assertEquals("world", n.getFirstChild().getNodeValue()); } @Test public void evalwithPI() throws XPathException, XMLDBException { final String query = "let $query := 'let $a := <test><?pi test?></test> return count($a//processing-instruction())'\n" + "return\n" + "util:eval($query)"; final ResourceSet result = existEmbeddedServer.executeQuery(query); final String r = (String) result.getResource(0).getContent(); assertEquals("1", r); } @Test public void evalInline() throws XPathException, XMLDBException { final String query = "let $xml := document{<test><a><b/></a></test>}\n" + "let $query := 'count(.//*)'\n" + "return\n" + "util:eval-inline($xml,$query)"; final ResourceSet result = existEmbeddedServer.executeQuery(query); final String r = (String) result.getResource(0).getContent(); assertEquals("3", r); } @Test public void testEvalWithContextVariable() throws XPathException, XMLDBException { final String query = "let $xml := <test><a/><b/></test>\n" + "let $context := <static-context>\n" + "<variable name='xml'>{$xml}</variable>\n" + "</static-context>\n" + "let $query := 'count($xml//*) mod 2 = 0'\n" + "return\n" + "util:eval-with-context($query, $context, false())"; final ResourceSet result = existEmbeddedServer.executeQuery(query); final String r = (String) result.getResource(0).getContent(); assertEquals("true", r); } @Test public void testEvalSupplyingContext() throws XPathException, XMLDBException { final String query = "let $xml := <test><a/></test>\n" + "let $context := <static-context>\n" + "<default-context>{$xml}</default-context>\n" + "</static-context>\n" + "let $query := 'count(.//*) mod 2 = 0'\n" + "return\n" + "util:eval-with-context($query, $context, false())"; final ResourceSet result = existEmbeddedServer.executeQuery(query); final String r = (String) result.getResource(0).getContent(); assertEquals("true", r); } @Test public void testEvalSupplyingContextAndVariable() throws XPathException, XMLDBException { final String query = "let $xml := <test><a/></test>\n" + "let $context := <static-context>\n" + "<variable name='xml'>{$xml}</variable>\n" + "<default-context>{$xml}</default-context>\n" + "</static-context>\n" + "let $query := 'count($xml//*) + count(.//*)'\n" + "return\n" + "util:eval-with-context($query, $context, false())"; final ResourceSet result = existEmbeddedServer.executeQuery(query); final String r = (String) result.getResource(0).getContent(); assertEquals("3", r); } @Test public void evalInContextWithPreDeclaredNamespace() throws XMLDBException { createCollection("testEvalInContextWithPreDeclaredNamespace"); final String query = "xquery version \"1.0\";\r\n" + "declare namespace db = \"http://docbook.org/ns/docbook\";\r\n" + "import module namespace util = \"http://exist-db.org/xquery/util\";\r\n" + "let $q := \"/db:article\" return\r\n" + "util:eval($q)"; existEmbeddedServer.executeQuery(query); } @Test public void evalInContextWithPreDeclaredNamespaceAcrossLocalFunctionBoundary() throws XMLDBException { createCollection("testEvalInContextWithPreDeclaredNamespace"); final String query = "xquery version \"1.0\";\r\n" + "import module namespace util = \"http://exist-db.org/xquery/util\";\r\n" + "declare namespace db = \"http://docbook.org/ns/docbook\";\r\n" + "declare function local:process($q as xs:string) {\r\n" + "\tutil:eval($q)\r\n" + "};\r\n" + "let $q := \"/db:article\" return\r\n" + "local:process($q)"; existEmbeddedServer.executeQuery(query); } //should fail with - Error while evaluating expression: /db:article. XPST0081: No namespace defined for prefix db [at line 5, column 9] @Test(expected=XMLDBException.class) public void evalInContextWithPreDeclaredNamespaceAcrossModuleBoundary() throws XMLDBException { Collection testHome = createCollection("testEvalInContextWithPreDeclaredNamespace"); final String processorModule = "xquery version \"1.0\";\r\n" + "module namespace processor = \"http://processor\";\r\n" + "import module namespace util = \"http://exist-db.org/xquery/util\";\r\n" + "declare function processor:process($q as xs:string) {\r\n" + "\tutil:eval($q)\r\n" + "};"; writeModule(testHome, "processor.xqm", processorModule); final String query = "xquery version \"1.0\";\r\n" + "import module namespace processor = \"http://processor\" at \"xmldb:exist://" + testHome.getName() + "/processor.xqm\";\r\n" + "declare namespace db = \"http://docbook.org/ns/docbook\";\r\n" + "let $q := \"/db:article\" return\r\n" + "processor:process($q)"; existEmbeddedServer.executeQuery(query); } /** * The original issue was caused by VariableReference inside util:eval * not calling XQueryContext#popNamespaceContext when a variable * reference could not be resolved, which led to the wrong * namespaces being present in the XQueryContext the next time * the same query was executed */ @Test public void evalWithMissingVariableReferenceShouldReportTheSameErrorEachTime() throws XMLDBException { final String testHomeName = "testEvalWithMissingVariableReferenceShouldReportTheSameErrorEachTime"; final Collection testHome = createCollection(testHomeName); final String configModuleName = "config-test.xqm"; final String configModule = "xquery version \"1.0\";\r\n" + "module namespace ct = \"http://config/test\";\r\n" + "declare variable $ct:var1 { request:get-parameter(\"var1\", ()) };"; writeModule(testHome, configModuleName, configModule); final String testModuleName = "test.xqy"; final String testModule = "import module namespace ct = \"http://config/test\" at \"xmldb:exist:///db/" + testHomeName + "/" + configModuleName + "\";\r\n" + "declare namespace x = \"http://x\";\r\n" + "declare function local:hello() {\r\n" + " (\r\n" + "<x:hello>hello</x:hello>,\r\n" + "util:eval(\"$ct:var1\")\r\n" + ")\r\n" + "};\r\n" + "local:hello()"; writeModule(testHome, testModuleName, testModule); //run the 1st time try { executeModule(testHome, testModuleName); } catch(final XMLDBException e) { final Throwable cause = e.getCause(); assertTrue(cause instanceof XPathException); assertEquals(ErrorCodes.XPDY0002, ((XPathException) cause).getErrorCode()); } //run a 2nd time, error code should be the same! try { executeModule(testHome, testModuleName); } catch(final XMLDBException e) { final Throwable cause = e.getCause(); assertTrue(cause instanceof XPathException); assertEquals(ErrorCodes.XPDY0002, ((XPathException)cause).getErrorCode()); } } private Collection createCollection(String collectionName) throws XMLDBException { Collection collection = existEmbeddedServer.getRoot().getChildCollection(collectionName); if (collection == null) { CollectionManagementService cmService = (CollectionManagementService) existEmbeddedServer.getRoot().getService("CollectionManagementService", "1.0"); cmService.createCollection(collectionName); } collection = DatabaseManager.getCollection(XmldbURI.LOCAL_DB + "/" + collectionName, "admin", ""); assertNotNull(collection); return collection; } private void writeModule(Collection collection, String modulename, String module) throws XMLDBException { BinaryResource res = (BinaryResource) collection.createResource(modulename, "BinaryResource"); ((EXistResource) res).setMimeType("application/xquery"); res.setContent(module.getBytes()); collection.storeResource(res); collection.close(); } private ResourceSet executeModule(final Collection collection, final String moduleName) throws XMLDBException { final XPathQueryServiceImpl service = (XPathQueryServiceImpl) collection.getService("XQueryService", "1.0"); final XmldbURI moduleUri = ((CollectionImpl)collection).getPathURI().append(moduleName); return service.executeStoredQuery(moduleUri.toString()); } }