From 89b6a32640c8fa6151703d8e4a73c9c5f16c872c Mon Sep 17 00:00:00 2001 From: Juri Leino Date: Thu, 31 Oct 2024 11:10:50 +0100 Subject: [PATCH] [bugfix] allow module imports in one-off xqueries fixes #5525 - add functx to autodeploy for xquery tests - add tests for one-off queries with module imports - of a registered module without location hint - of a module with location hint - change XQueryContext to allow imports again - change SourceFactory to work with contextPath set to "." --- .../java/org/exist/source/SourceFactory.java | 2 +- .../org/exist/test/XQueryCompilationTest.java | 9 +- .../java/org/exist/xquery/XQueryContext.java | 45 +- .../org/exist/xquery/AbsolutePathTests.java | 2 + .../org/exist/xquery/ModuleImportTest.java | 162 +++ .../org/exist/xquery/conf.xml | 976 ++++++++++++++++++ 6 files changed, 1171 insertions(+), 25 deletions(-) create mode 100644 exist-core/src/test/java/org/exist/xquery/ModuleImportTest.java create mode 100644 exist-core/src/test/resources-filtered/org/exist/xquery/conf.xml diff --git a/exist-core/src/main/java/org/exist/source/SourceFactory.java b/exist-core/src/main/java/org/exist/source/SourceFactory.java index 378710be7cf..1efc60ad01a 100644 --- a/exist-core/src/main/java/org/exist/source/SourceFactory.java +++ b/exist-core/src/main/java/org/exist/source/SourceFactory.java @@ -111,7 +111,7 @@ public class SourceFactory { && ((location.startsWith("/db") && !Files.exists(Paths.get(firstPathSegment(location)))) || (contextPath != null && contextPath.startsWith("/db") && !Files.exists(Paths.get(firstPathSegment(contextPath)))))) { final XmldbURI pathUri; - if (contextPath == null) { + if (contextPath == null || ".".equals(contextPath)) { pathUri = XmldbURI.create(location); } else { pathUri = XmldbURI.create(contextPath).append(location); diff --git a/exist-core/src/main/java/org/exist/test/XQueryCompilationTest.java b/exist-core/src/main/java/org/exist/test/XQueryCompilationTest.java index 637670a2123..b4505830646 100644 --- a/exist-core/src/main/java/org/exist/test/XQueryCompilationTest.java +++ b/exist-core/src/main/java/org/exist/test/XQueryCompilationTest.java @@ -33,17 +33,22 @@ import org.exist.xquery.value.Sequence; import org.junit.ClassRule; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; + import static com.evolvedbinary.j8fu.Either.Left; import static com.evolvedbinary.j8fu.Either.Right; +import static com.ibm.icu.impl.Assert.fail; /** * Base class for test suites testing XQuery compilation * @author Juri Leino */ public abstract class XQueryCompilationTest { - @ClassRule - public static final ExistEmbeddedServer server = new ExistEmbeddedServer(true, true); + @ClassRule + public static ExistEmbeddedServer server = new ExistEmbeddedServer(true, true); protected static Either compileQuery(final String string) throws EXistException, PermissionDeniedException { final BrokerPool pool = server.getBrokerPool(); final XQuery xqueryService = pool.getXQueryService(); diff --git a/exist-core/src/main/java/org/exist/xquery/XQueryContext.java b/exist-core/src/main/java/org/exist/xquery/XQueryContext.java index 8bac2341af5..b3721c34179 100644 --- a/exist-core/src/main/java/org/exist/xquery/XQueryContext.java +++ b/exist-core/src/main/java/org/exist/xquery/XQueryContext.java @@ -587,33 +587,34 @@ public Optional getRepository() { return null; } - // use the resolved file or return null - if (resolved != null) { - - String location = ""; - - try { - - // see if the src exists in the database and if so, use that instead - Source src = repo.get().resolveStoredXQueryModuleFromDb(getBroker(), resolved); - if (src != null) { - // NOTE(AR) set the location of the module to import relative to this module's load path - so that transient imports of the imported module will resolve correctly! - location = Paths.get(XmldbURI.create(moduleLoadPath).getCollectionPath()).relativize(Paths.get(((DBSource)src).getDocumentPath().getCollectionPath())).toString(); + // use the resolved file + String location = ""; + try { + // see if the src exists in the database and if so, use that instead + Source src = repo.get().resolveStoredXQueryModuleFromDb(getBroker(), resolved); + if (src == null) { + // fallback to load the source from the filesystem + src = new FileSource(resolved, false); + } else { + final String sourceCollection = ((DBSource)src).getDocumentPath().getCollectionPath(); + if (".".equals(moduleLoadPath)) { + // module is a string passed to the xquery context, has therefore no location of its own + location = sourceCollection; } else { - // else, fallback to the one from the filesystem - src = new FileSource(resolved, false); + // NOTE(AR) set the location of the module to import relative to this module's load path + // - so that transient imports of the imported module will resolve correctly! + final Path collectionPath = Paths.get(XmldbURI.create(moduleLoadPath).getCollectionPath()); + final Path sourcePath = Paths.get(sourceCollection); + location = collectionPath.relativize(sourcePath).toString(); } + } - // build a module object from the source - final ExternalModule module = compileOrBorrowModule(namespace, prefix, location, src); - return module; + // build a module object from the source + return compileOrBorrowModule(namespace, prefix, location, src); - } catch (final PermissionDeniedException e) { - throw new XPathException(e.getMessage(), e); - } + } catch (final PermissionDeniedException | IllegalArgumentException e) { + throw new XPathException(e.getMessage(), e); } - - return null; } /** diff --git a/exist-core/src/test/java/org/exist/xquery/AbsolutePathTests.java b/exist-core/src/test/java/org/exist/xquery/AbsolutePathTests.java index d551d2582c6..6e6d85849a0 100644 --- a/exist-core/src/test/java/org/exist/xquery/AbsolutePathTests.java +++ b/exist-core/src/test/java/org/exist/xquery/AbsolutePathTests.java @@ -27,6 +27,7 @@ import org.exist.test.XQueryCompilationTest; import org.exist.xquery.value.IntegerValue; import org.exist.xquery.value.Sequence; +import org.exist.xquery.value.StringValue; import org.junit.Test; import javax.xml.transform.Source; @@ -133,4 +134,5 @@ public void topLevelAbsolutePath() throws EXistException, PermissionDeniedExcept assertThatXQResult(actual, equalTo(expected)); } + } diff --git a/exist-core/src/test/java/org/exist/xquery/ModuleImportTest.java b/exist-core/src/test/java/org/exist/xquery/ModuleImportTest.java new file mode 100644 index 00000000000..27bb782444c --- /dev/null +++ b/exist-core/src/test/java/org/exist/xquery/ModuleImportTest.java @@ -0,0 +1,162 @@ +/* + * eXist-db Open Source Native XML Database + * Copyright (C) 2001 The eXist-db Authors + * + * info@exist-db.org + * http://www.exist-db.org + * + * 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; either + * version 2.1 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 + * 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 + */ +package org.exist.xquery; + +import com.evolvedbinary.j8fu.Either; + +import org.exist.EXistException; +import org.exist.security.PermissionDeniedException; +import org.exist.storage.BrokerPool; +import org.exist.storage.DBBroker; +import org.exist.test.ExistEmbeddedServer; +import org.exist.xquery.value.Sequence; +import org.exist.xquery.value.StringValue; + +import org.junit.ClassRule; +import org.junit.Test; + +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static com.evolvedbinary.j8fu.Either.Left; +import static com.evolvedbinary.j8fu.Either.Right; +import static com.ibm.icu.impl.Assert.fail; +import static org.exist.test.XQueryAssertions.assertThatXQResult; +import static org.exist.test.XQueryAssertions.assertXQStaticError; +import static org.hamcrest.Matchers.equalTo; + +/** + * Ensure library module imports work in one-off queries + * needs functx to be installed => conf.xml => triggers => autodeploy + * + * @author Juri Leino + */ +public class ModuleImportTest { + @ClassRule + static ExistEmbeddedServer server = new ExistEmbeddedServer(null, getConfigFile(), null, false, true); + + static final BrokerPool pool = server.getBrokerPool(); + + protected static Either compileQuery(final String string) throws EXistException, PermissionDeniedException { + final XQuery xqueryService = pool.getXQueryService(); + try (final DBBroker broker = pool.getBroker()) { + try { + return Right(xqueryService.compile(new XQueryContext(broker.getDatabase()), string)); + } catch (final XPathException e) { + return Left(e); + } + } + } + + protected static Either executeQuery(final String string) throws EXistException, PermissionDeniedException { + final XQuery xqueryService = pool.getXQueryService(); + try (final DBBroker broker = pool.getBroker()) { + try { + return Right(xqueryService.execute(broker, string, null)); + } catch (final XPathException e) { + return Left(e); + } + } + } + + private static Path getConfigFile() { + final ClassLoader loader = ModuleImportTest.class.getClassLoader(); + final char separator = System.getProperty("file.separator").charAt(0); + final String packagePath = ModuleImportTest.class.getPackage().getName().replace('.', separator); + + try { + return Paths.get(loader.getResource(packagePath + separator + "conf.xml").toURI()); + } catch (final URISyntaxException e) { + fail(e); + return null; + } + } + + @Test + public void importLibraryWithoutLocation() throws EXistException, PermissionDeniedException { + final Sequence expected = new StringValue("xs:integer"); + + final String query = "import module namespace functx='http://www.functx.com';" + + "functx:atomic-type(4)"; + final Either actual = executeQuery(query); + + assertThatXQResult(actual, equalTo(expected)); + } + @Test + public void importLibraryFromDbLocation() throws EXistException, PermissionDeniedException { + final Sequence expected = new StringValue("xs:integer"); + + final String query = "import module namespace functx='http://www.functx.com'" + + " at '/db/system/repo/functx-1.0.1/functx/functx.xq';" + + "functx:atomic-type(4)"; + final Either actual = executeQuery(query); + + assertThatXQResult(actual, equalTo(expected)); + } + + @Test + public void importLibraryFromXMLDBLocation() throws EXistException, PermissionDeniedException { + final Sequence expected = new StringValue("xs:integer"); + + final String query = "import module namespace functx='http://www.functx.com'" + + " at 'xmldb:///db/system/repo/functx-1.0.1/functx/functx.xq';" + + "functx:atomic-type(4)"; + final Either actual = executeQuery(query); + + assertThatXQResult(actual, equalTo(expected)); + } + + @Test + public void importLibraryFromExistXMLDBLocation() throws EXistException, PermissionDeniedException { + final Sequence expected = new StringValue("xs:integer"); + + final String query = "import module namespace functx='http://www.functx.com'" + + " at 'xmldb:exist:///db/system/repo/functx-1.0.1/functx/functx.xq';" + + "functx:atomic-type(4)"; + final Either actual = executeQuery(query); + + assertThatXQResult(actual, equalTo(expected)); + } + + @Test + public void importLibraryFromUnknownLocation() throws EXistException, PermissionDeniedException { + + final String query = "import module namespace functx='http://www.functx.com'" + + " at 'unknown:///db/system/repo/functx-1.0.1/functx/functx.xq';" + + "functx:atomic-type(4)"; + final String expectedMessage = "error found while loading module functx: Source for module 'http://www.functx.com' not found module location hint URI 'unknown:///db/system/repo/functx-1.0.1/functx/functx.xq'."; + + assertXQStaticError(ErrorCodes.XQST0059, -1,-1, expectedMessage, compileQuery(query)); + } + + @Test + public void importLibraryFromRelativeLocation() throws EXistException, PermissionDeniedException { + final String query = "import module namespace functx='http://www.functx.com'" + + " at './functx.xq';" + + "functx:atomic-type(4)"; + final String expectedMessage = "error found while loading module functx: Source for module 'http://www.functx.com' not found module location hint URI './functx.xq'."; + + assertXQStaticError(ErrorCodes.XQST0059, -1,-1, expectedMessage, compileQuery(query)); + } + +} diff --git a/exist-core/src/test/resources-filtered/org/exist/xquery/conf.xml b/exist-core/src/test/resources-filtered/org/exist/xquery/conf.xml new file mode 100644 index 00000000000..b9bc14f5b53 --- /dev/null +++ b/exist-core/src/test/resources-filtered/org/exist/xquery/conf.xml @@ -0,0 +1,976 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +