Skip to content

Commit

Permalink
[bugfix] allow module imports in one-off xqueries
Browse files Browse the repository at this point in the history
fixes eXist-db#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 "."
  • Loading branch information
line-o committed Nov 19, 2024
1 parent 9f881d7 commit 89b6a32
Show file tree
Hide file tree
Showing 6 changed files with 1,171 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="mailto:[email protected]">Juri Leino</a>
*/
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<XPathException, CompiledXQuery> compileQuery(final String string) throws EXistException, PermissionDeniedException {
final BrokerPool pool = server.getBrokerPool();
final XQuery xqueryService = pool.getXQueryService();
Expand Down
45 changes: 23 additions & 22 deletions exist-core/src/main/java/org/exist/xquery/XQueryContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -587,33 +587,34 @@ public Optional<ExistRepository> 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;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -133,4 +134,5 @@ public void topLevelAbsolutePath() throws EXistException, PermissionDeniedExcept

assertThatXQResult(actual, equalTo(expected));
}

}
162 changes: 162 additions & 0 deletions exist-core/src/test/java/org/exist/xquery/ModuleImportTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* eXist-db Open Source Native XML Database
* Copyright (C) 2001 The eXist-db Authors
*
* [email protected]
* 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 <a href="mailto:[email protected]">Juri Leino</a>
*/
public class ModuleImportTest {
@ClassRule
static ExistEmbeddedServer server = new ExistEmbeddedServer(null, getConfigFile(), null, false, true);

static final BrokerPool pool = server.getBrokerPool();

protected static Either<XPathException, CompiledXQuery> 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<XPathException, Sequence> 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<XPathException, Sequence> 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<XPathException, Sequence> 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<XPathException, Sequence> 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<XPathException, Sequence> 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));
}

}
Loading

0 comments on commit 89b6a32

Please sign in to comment.