diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..f10ad63915 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,44 @@ +# Session Notes + +## Namespace and parent-pointer findings + +- Parsed XML elements need `inheritNamespacesFromParent = true` in `src/main/java/org/rumbledb/items/xml/ElementItem.java` so that inherited namespaces from loaded documents remain visible to functions that depend on in-scope namespaces. +- Parent-pointer optimization must be disabled conservatively for queries that need ancestor namespace context. The current centralized mechanism is `src/main/java/org/rumbledb/compiler/ParentPointerAnalysisVisitor.java`, called from `src/main/java/org/rumbledb/compiler/VisitorHelpers.java`. +- The analysis must recognize both `Name.FN_NS` and `Name.JSONIQ_DEFAULT_FUNCTION_NS`, otherwise unprefixed builtin calls in JSONiq can be missed. +- The validated guarded functions are: + - `fn:lang#1`, `fn:lang#2` + - `fn:in-scope-prefixes#1` + - `fn:namespace-uri-for-prefix#2` + - `fn:serialize#1`, `fn:serialize#2` + - `fn:innermost#1` + - `fn:outermost#1` + +## Important A/B result + +- On July 23, 2026, `fn:serialize` was tested with an explicit A/B rebuild. +- With `serialize#1/#2` removed from `ParentPointerAnalysisVisitor`, the three QT3 queries below all lost the expected XML 1.1 namespace undeclaration `xmlns:p=""`: + - `fn/serialize.xml:serialize-xml-035` + - `fn/serialize.xml:serialize-xml-035b` + - `fn/serialize.xml:serialize-xml-135` +- After restoring the `serialize` guard, direct `spark-submit` execution again produced the correct serialized output containing `section xmlns:p=""`. +- Conclusion: `fn:serialize` really does depend on ancestor namespace context in these cases, so keeping it in the parent-pointer guard set is required. + +## Report interpretation note + +- A stale `xquery-tests.html` can disagree with the current jar. When a regression looks suspicious, verify it with direct `spark-submit` execution against the actual rebuilt jar in `target/rumbledb-2.1.0-jar-with-dependencies.jar`. +- For this repo, do not rely on `mvn compile` alone when you need to refresh the runnable jar. +- The rebuild command that reliably refreshes the runnable jar is: + +```sh +mvn clean compile assembly:single +``` + +## Useful direct checks used in this session + +```sh +spark-submit target/rumbledb-2.1.0-jar-with-dependencies.jar run --default-language xquery31 -q 'string-join(in-scope-prefixes((doc("file:///Users/ghislain/Code/rumble-test-suite/qt3tests/docs/auction.xml")//*)[19]), "|")' +``` + +```sh +spark-submit target/rumbledb-2.1.0-jar-with-dependencies.jar run --default-language xquery31 -q 'let $d := doc("file:///Users/ghislain/Code/rumble-test-suite/qt3tests/fn/serialize/serialize-035-src.xml") let $params := map {"method" : "xml", "version" : "1.1", "undeclare-prefixes" : true()} return serialize($d, $params)' +``` diff --git a/src/main/java/org/rumbledb/compiler/CloneVisitor.java b/src/main/java/org/rumbledb/compiler/CloneVisitor.java index c9abace33b..6c60fe07e2 100644 --- a/src/main/java/org/rumbledb/compiler/CloneVisitor.java +++ b/src/main/java/org/rumbledb/compiler/CloneVisitor.java @@ -134,6 +134,8 @@ public Node visitLibraryModule(LibraryModule module, Node argument) { module.getMetadata() ); result.setStaticContext(module.getStaticContext()); + result.setLocation(module.getLocation()); + result.setModuleIdentity(module.getModuleIdentity()); return result; } diff --git a/src/main/java/org/rumbledb/compiler/DynamicContextVisitor.java b/src/main/java/org/rumbledb/compiler/DynamicContextVisitor.java index fdfe29a820..cca954dddc 100644 --- a/src/main/java/org/rumbledb/compiler/DynamicContextVisitor.java +++ b/src/main/java/org/rumbledb/compiler/DynamicContextVisitor.java @@ -367,19 +367,20 @@ public DynamicContext visitTypeDeclaration(TypeDeclaration declaration, DynamicC @Override public DynamicContext visitLibraryModule(LibraryModule module, DynamicContext argument) { - if (!this.importedModuleContexts.containsKey(module.getNamespace())) { + String moduleLocation = module.getModuleIdentityOrLocation(); + if (!this.importedModuleContexts.containsKey(moduleLocation)) { DynamicContext newContext = new DynamicContext(this.configuration, this.externalBindings); newContext.setNamedFunctions(argument.getNamedFunctions()); DynamicContext importedContext = visitDescendants(module, newContext); - this.importedModuleContexts.put(module.getNamespace(), importedContext); + this.importedModuleContexts.put(moduleLocation, importedContext); } argument.getVariableValues() .importModuleValues( - this.importedModuleContexts.get(module.getNamespace()).getVariableValues() + this.importedModuleContexts.get(moduleLocation).getVariableValues() ); argument.getInScopeSchemaTypes() .importModuleTypes( - this.importedModuleContexts.get(module.getNamespace()).getInScopeSchemaTypes() + this.importedModuleContexts.get(moduleLocation).getInScopeSchemaTypes() ); return argument; } diff --git a/src/main/java/org/rumbledb/compiler/FunctionInliningVisitor.java b/src/main/java/org/rumbledb/compiler/FunctionInliningVisitor.java index 7f8f7564d9..afc15d92d2 100644 --- a/src/main/java/org/rumbledb/compiler/FunctionInliningVisitor.java +++ b/src/main/java/org/rumbledb/compiler/FunctionInliningVisitor.java @@ -30,14 +30,23 @@ import java.util.List; import java.util.UUID; -import static org.rumbledb.expressions.module.Prolog.getFunctionDeclarationFromProlog; - - public class FunctionInliningVisitor extends CloneVisitor { private String queryLanguage; + private static FunctionDeclaration getDirectFunctionDeclaration( + Prolog prolog, + org.rumbledb.context.FunctionIdentifier functionIdentifier + ) { + for (FunctionDeclaration declaration : prolog.getFunctionDeclarations()) { + if (declaration.getFunctionIdentifier().equals(functionIdentifier)) { + return declaration; + } + } + return null; + } + private boolean isVariableReferenced(Node expression, Name name) { if (expression instanceof VariableReferenceExpression variableReference) { return variableReference.getVariableName().equals(name); @@ -465,7 +474,7 @@ public Node visitMainModule(MainModule mainModule, Node argument) { // 2. Contain an exit statement. @Override public Node visitFunctionCall(FunctionCallExpression expression, Node argument) { - FunctionDeclaration targetFunction = getFunctionDeclarationFromProlog( + FunctionDeclaration targetFunction = getDirectFunctionDeclaration( (Prolog) argument, expression.getFunctionIdentifier() ); diff --git a/src/main/java/org/rumbledb/compiler/ModuleImportLoader.java b/src/main/java/org/rumbledb/compiler/ModuleImportLoader.java index d8c3b73ef7..5a571c4f3b 100644 --- a/src/main/java/org/rumbledb/compiler/ModuleImportLoader.java +++ b/src/main/java/org/rumbledb/compiler/ModuleImportLoader.java @@ -18,7 +18,10 @@ import java.io.IOException; import java.net.URI; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** Shared module import semantics for the JSONiq and XQuery frontends. */ final class ModuleImportLoader { @@ -26,7 +29,7 @@ final class ModuleImportLoader { private ModuleImportLoader() { } - public static LibraryModule load( + public static List load( String namespace, List locationHints, StaticContext importingModuleContext, @@ -37,6 +40,7 @@ public static LibraryModule load( String normalizedNamespace = URILiteralUtils.normalizeAsAnyURI(namespace); List candidates = locationHints.isEmpty() ? List.of(normalizedNamespace) : locationHints; Exception lastFailure = null; + Map loadedModules = new LinkedHashMap<>(); for (String candidate : candidates) { URI location; @@ -64,12 +68,16 @@ public static LibraryModule load( ); } - return module; + loadedModules.putIfAbsent(location.toString(), module); } catch (IOException | CannotRetrieveResourceException e) { lastFailure = e; } } + if (!loadedModules.isEmpty()) { + return new ArrayList<>(loadedModules.values()); + } + RumbleException exception = new ModuleNotFoundException( "Module not found: %s, cause: %s".formatted( normalizedNamespace, diff --git a/src/main/java/org/rumbledb/compiler/ModuleImportSurface.java b/src/main/java/org/rumbledb/compiler/ModuleImportSurface.java new file mode 100644 index 0000000000..7b4b88c64f --- /dev/null +++ b/src/main/java/org/rumbledb/compiler/ModuleImportSurface.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Authors: OpenAI + * + */ + +package org.rumbledb.compiler; + +import org.rumbledb.context.FunctionIdentifier; +import org.rumbledb.context.Name; +import org.rumbledb.exceptions.ExceptionMetadata; +import org.rumbledb.expressions.ExecutionMode; +import org.rumbledb.types.FunctionSignature; +import org.rumbledb.types.SequenceType; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * The spec-visible surface of a library module import: only declarations made directly in the imported module. + */ +final class ModuleImportSurface { + + static final class ImportedVariableBinding { + private final Name name; + private final SequenceType sequenceType; + private final ExceptionMetadata metadata; + private final ExecutionMode storageMode; + private final boolean assignable; + + ImportedVariableBinding( + Name name, + SequenceType sequenceType, + ExceptionMetadata metadata, + ExecutionMode storageMode, + boolean assignable + ) { + this.name = name; + this.sequenceType = sequenceType; + this.metadata = metadata; + this.storageMode = storageMode; + this.assignable = assignable; + } + + public Name getName() { + return this.name; + } + + public SequenceType getSequenceType() { + return this.sequenceType; + } + + public ExceptionMetadata getMetadata() { + return this.metadata; + } + + public ExecutionMode getStorageMode() { + return this.storageMode; + } + + public boolean isAssignable() { + return this.assignable; + } + } + + private final Map variableBindings; + private final Map functionSignatures; + + ModuleImportSurface() { + this.variableBindings = new LinkedHashMap<>(); + this.functionSignatures = new LinkedHashMap<>(); + } + + public void addVariableBinding(ImportedVariableBinding binding) { + this.variableBindings.put(binding.getName(), binding); + } + + public void addFunctionSignature(FunctionIdentifier identifier, FunctionSignature signature) { + this.functionSignatures.put(identifier, signature); + } + + public Map getVariableBindings() { + return this.variableBindings; + } + + public Map getFunctionSignatures() { + return this.functionSignatures; + } +} diff --git a/src/main/java/org/rumbledb/compiler/ModulePruningVisitor.java b/src/main/java/org/rumbledb/compiler/ModulePruningVisitor.java index e2d83b2fd7..77a94182ba 100644 --- a/src/main/java/org/rumbledb/compiler/ModulePruningVisitor.java +++ b/src/main/java/org/rumbledb/compiler/ModulePruningVisitor.java @@ -20,8 +20,8 @@ package org.rumbledb.compiler; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; import org.rumbledb.config.RumbleConfiguration; import org.rumbledb.expressions.AbstractNodeVisitor; @@ -38,7 +38,7 @@ public class ModulePruningVisitor extends AbstractNodeVisitor { @SuppressWarnings("unused") private final RumbleConfiguration configuration; - private final List visitedModules; + private final Set visitedModules; /** * Builds a new visitor. @@ -47,17 +47,18 @@ public class ModulePruningVisitor extends AbstractNodeVisitor { */ ModulePruningVisitor(RumbleConfiguration configuration) { this.configuration = configuration; - this.visitedModules = new ArrayList<>(); + this.visitedModules = new HashSet<>(); } @Override public Void visitLibraryModule(LibraryModule libraryModule, Void argument) { - if (this.visitedModules.contains(libraryModule.getNamespace())) { + String moduleOrigin = libraryModule.getModuleIdentityOrLocation(); + if (this.visitedModules.contains(moduleOrigin)) { Prolog prolog = libraryModule.getProlog(); prolog.clearDeclarations(); } visitDescendants(libraryModule, argument); - this.visitedModules.add(libraryModule.getNamespace()); + this.visitedModules.add(moduleOrigin); return argument; } diff --git a/src/main/java/org/rumbledb/compiler/StaticContextVisitor.java b/src/main/java/org/rumbledb/compiler/StaticContextVisitor.java index cb0b20db77..140eb58fbd 100644 --- a/src/main/java/org/rumbledb/compiler/StaticContextVisitor.java +++ b/src/main/java/org/rumbledb/compiler/StaticContextVisitor.java @@ -22,12 +22,18 @@ import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; +import org.rumbledb.context.BuiltinFunctionCatalogue; +import org.rumbledb.context.FunctionIdentifier; import org.rumbledb.context.Name; import org.rumbledb.context.StaticContext; import org.rumbledb.errorcodes.ErrorVariables; +import org.rumbledb.exceptions.DuplicateFunctionIdentifierException; import org.rumbledb.exceptions.OurBadException; import org.rumbledb.exceptions.ParsingException; import org.rumbledb.exceptions.UndeclaredVariableException; @@ -97,9 +103,13 @@ public class StaticContextVisitor extends AbstractNodeVisitor { private static final String SERIALIZATION_NAMESPACE = "http://www.w3.org/2010/xslt-xquery-serialization"; private final Map importedModuleContexts; + private final Map importedModuleSurfaces; + private final IdentityHashMap> importedModuleLocationsByContext; StaticContextVisitor() { this.importedModuleContexts = new HashMap<>(); + this.importedModuleSurfaces = new HashMap<>(); + this.importedModuleLocationsByContext = new IdentityHashMap<>(); } @Override @@ -129,27 +139,101 @@ public StaticContext visit(Node node, StaticContext argument) { @Override public StaticContext visitMainModule(MainModule mainModule, StaticContext argument) { this.importedModuleContexts.clear(); + this.importedModuleSurfaces.clear(); + this.importedModuleLocationsByContext.clear(); StaticContext generatedContext = visitDescendants(mainModule, argument); return generatedContext; } @Override public StaticContext visitLibraryModule(LibraryModule libraryModule, StaticContext argument) { - if (!this.importedModuleContexts.containsKey(libraryModule.getNamespace())) { + String moduleLocation = libraryModule.getModuleIdentityOrLocation(); + if (!this.importedModuleContexts.containsKey(moduleLocation)) { StaticContext moduleContext = libraryModule.getStaticContext(); this.visit(libraryModule.getProlog(), moduleContext); - this.importedModuleContexts.put(libraryModule.getNamespace(), moduleContext); + this.importedModuleContexts.put(moduleLocation, moduleContext); + this.importedModuleSurfaces.put( + moduleLocation, + buildImportSurface(libraryModule.getProlog(), moduleContext) + ); } - argument.importModuleContext( - this.importedModuleContexts.get(libraryModule.getNamespace()) + Set importedModuleLocations = this.importedModuleLocationsByContext.computeIfAbsent( + argument, + ignored -> new HashSet<>() ); + if (importedModuleLocations.contains(moduleLocation)) { + return argument; + } + StaticContext importedContext = this.importedModuleContexts.get(moduleLocation); + ModuleImportSurface importedSurface = this.importedModuleSurfaces.get(moduleLocation); + for (ModuleImportSurface.ImportedVariableBinding binding : importedSurface.getVariableBindings().values()) { + Name variableName = binding.getName(); + if (argument.hasVariableInScopeOnly(variableName)) { + throw new VariableAlreadyExistsException(variableName, libraryModule.getMetadata()); + } + } + for (FunctionIdentifier functionIdentifier : importedSurface.getFunctionSignatures().keySet()) { + if ( + BuiltinFunctionCatalogue.exists(functionIdentifier, argument.getQueryLanguage()) + || argument.hasFunctionSignatureInScopeOnly(functionIdentifier) + ) { + throw new DuplicateFunctionIdentifierException(functionIdentifier, libraryModule.getMetadata()); + } + } + for (ModuleImportSurface.ImportedVariableBinding binding : importedSurface.getVariableBindings().values()) { + Name variableName = binding.getName(); + argument.addVariable( + variableName, + binding.getSequenceType(), + binding.getMetadata(), + binding.isAssignable() + ); + argument.setVariableStorageMode( + variableName, + binding.getStorageMode() + ); + } + for ( + Map.Entry entry : importedSurface.getFunctionSignatures() + .entrySet() + ) { + argument.addFunctionSignature( + entry.getKey(), + entry.getValue() + ); + } + importedModuleLocations.add(moduleLocation); argument.getInScopeSchemaTypes() .importModuleTypes( - this.importedModuleContexts.get(libraryModule.getNamespace()).getInScopeSchemaTypes() + importedContext.getInScopeSchemaTypes() ); return argument; } + private static ModuleImportSurface buildImportSurface(Prolog prolog, StaticContext moduleContext) { + ModuleImportSurface surface = new ModuleImportSurface(); + for (VariableDeclaration declaration : prolog.getVariableDeclarations()) { + Name variableName = declaration.getVariableName(); + surface.addVariableBinding( + new ModuleImportSurface.ImportedVariableBinding( + variableName, + moduleContext.getVariableSequenceType(variableName), + moduleContext.getVariableMetadata(variableName), + moduleContext.getVariableStorageMode(variableName), + moduleContext.getIsAssignable(variableName) + ) + ); + } + for (FunctionDeclaration declaration : prolog.getFunctionDeclarations()) { + FunctionIdentifier functionIdentifier = declaration.getFunctionIdentifier(); + surface.addFunctionSignature( + functionIdentifier, + moduleContext.getFunctionSignature(functionIdentifier) + ); + } + return surface; + } + // region primary @Override public StaticContext visitVariableReference(VariableReferenceExpression expression, StaticContext argument) { @@ -194,6 +278,15 @@ public StaticContext visitFunctionDeclaration(FunctionDeclaration declaration, S populateFunctionDeclarationStaticContext(functionDeclarationContext, expression); // visit the body first to make its execution mode available while adding the function to the catalog this.visit(expression.getBody(), functionDeclarationContext); + if ( + BuiltinFunctionCatalogue.exists(expression.getFunctionIdentifier(), argument.getQueryLanguage()) + || argument.hasFunctionSignatureInScopeOnly(expression.getFunctionIdentifier()) + ) { + throw new DuplicateFunctionIdentifierException( + expression.getFunctionIdentifier(), + declaration.getMetadata() + ); + } argument.addFunctionSignature( expression.getFunctionIdentifier(), new FunctionSignature( @@ -432,7 +525,12 @@ public StaticContext visitVariableDeclaration(VariableDeclaration variableDeclar if (variableDeclaration.getExpression() != null) { this.visit(variableDeclaration.getExpression(), argument); } - // first pass. + if (argument.hasVariableInScopeOnly(variableDeclaration.getVariableName())) { + throw new VariableAlreadyExistsException( + variableDeclaration.getVariableName(), + variableDeclaration.getMetadata() + ); + } argument.addVariable( variableDeclaration.getVariableName(), variableDeclaration.getActualSequenceType(), diff --git a/src/main/java/org/rumbledb/compiler/TranslationVisitor.java b/src/main/java/org/rumbledb/compiler/TranslationVisitor.java index 28548fe333..ffdb0669ae 100644 --- a/src/main/java/org/rumbledb/compiler/TranslationVisitor.java +++ b/src/main/java/org/rumbledb/compiler/TranslationVisitor.java @@ -485,15 +485,15 @@ public Node visitProlog(JsoniqParser.PrologContext ctx) { List libraryModules = new ArrayList<>(); Set namespaces = new HashSet<>(); for (JsoniqParser.ModuleImportContext namespace : ctx.moduleImport()) { - LibraryModule libraryModule = this.processModuleImport(namespace); - libraryModules.add(libraryModule); - if (namespaces.contains(libraryModule.getNamespace())) { + String importedNamespace = URILiteralUtils.normalizeAsAnyURI(processURILiteral(namespace.targetNamespace)); + if (namespaces.contains(importedNamespace)) { throw new DuplicateModuleTargetNamespaceException( - "Duplicate module target namespace: " + libraryModule.getNamespace(), + "Duplicate module target namespace: " + importedNamespace, createMetadataFromContext(namespace) ); } - namespaces.add(libraryModule.getNamespace()); + namespaces.add(importedNamespace); + libraryModules.addAll(this.processModuleImport(namespace)); } // parse variables and function @@ -4070,7 +4070,7 @@ private String resolveCollationUri(UriLiteralContext ctx) { return uri.toString(); } - public LibraryModule processModuleImport(JsoniqParser.ModuleImportContext ctx) { + public List processModuleImport(JsoniqParser.ModuleImportContext ctx) { ExceptionMetadata metadata = createMetadataFromContext(ctx); String namespace = processURILiteral(ctx.targetNamespace); if (namespace.isEmpty()) { @@ -4090,7 +4090,7 @@ public LibraryModule processModuleImport(JsoniqParser.ModuleImportContext ctx) { .map(this::processURILiteral) .map(URILiteralUtils::normalizeAsAnyURI) .collect(Collectors.toList()); - LibraryModule libraryModule = ModuleImportLoader.load( + List libraryModules = ModuleImportLoader.load( namespace, locationHints, this.moduleContext, @@ -4100,11 +4100,11 @@ public LibraryModule processModuleImport(JsoniqParser.ModuleImportContext ctx) { if (ctx.ncName() != null) { bindNamespace( ctx.ncName().getText(), - libraryModule.getNamespace(), + namespace, metadata ); } - return libraryModule; + return libraryModules; } public ExceptionMetadata generateMetadata(Token start, Token end) { diff --git a/src/main/java/org/rumbledb/compiler/VisitorHelpers.java b/src/main/java/org/rumbledb/compiler/VisitorHelpers.java index 4a259f1bca..edcefcc112 100644 --- a/src/main/java/org/rumbledb/compiler/VisitorHelpers.java +++ b/src/main/java/org/rumbledb/compiler/VisitorHelpers.java @@ -178,11 +178,7 @@ private static ModuleSource readModuleSource( .resolve(location, configuration, metadata) ) { String query = IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8.name()); - URI systemId = resource.getSystemId(); - if (configuration.semantics().staticBaseUri() != null) { - systemId = resolveStaticBaseUri(configuration.semantics().staticBaseUri()); - } - return new ModuleSource(query, systemId); + return new ModuleSource(query, resource.getSystemId()); } } @@ -259,12 +255,18 @@ static LibraryModule parseLibraryModuleFromLocation( ) throws IOException { ModuleSource source = readModuleSource(location, compilationConfiguration, metadata); - return parseLibraryModule( + LibraryModule libraryModule = parseLibraryModule( source.query(), source.systemId(), importingModuleContext, compilationConfiguration ); + // Keep the requested absolute import location for diagnostics and spec-visible import behavior, + // but deduplicate module loading by the resolved resource identity when the implementation can + // determine that distinct import URIs refer to the same underlying resource. + libraryModule.setLocation(location.toString()); + libraryModule.setModuleIdentity(source.systemId().toString()); + return libraryModule; } public static MainModule parseMainModuleFromQuery( @@ -507,6 +509,8 @@ private static LibraryModule parseJSONiqLibraryModule( // TODO Handle module extras JsoniqParser.ModuleContext main = parser.moduleAndThisIsIt().module(); LibraryModule libraryModule = (LibraryModule) visitor.visit(main); + libraryModule.setLocation(uri.toString()); + libraryModule.setModuleIdentity(uri.toString()); resolveDependencies(libraryModule, configuration); // no static context population, as this is done in a single shot via the importing main module. return libraryModule; @@ -553,6 +557,8 @@ private static LibraryModule parseXQueryLibraryModule( // TODO Handle module extras XQueryParser.ModuleContext main = parser.module(); LibraryModule libraryModule = (LibraryModule) visitor.visit(main); + libraryModule.setLocation(uri.toString()); + libraryModule.setModuleIdentity(uri.toString()); resolveDependencies(libraryModule, configuration); // no static context population, as this is done in a single shot via the importing main module. return libraryModule; diff --git a/src/main/java/org/rumbledb/compiler/XQueryTranslationVisitor.java b/src/main/java/org/rumbledb/compiler/XQueryTranslationVisitor.java index 09e8c12196..8498520e37 100644 --- a/src/main/java/org/rumbledb/compiler/XQueryTranslationVisitor.java +++ b/src/main/java/org/rumbledb/compiler/XQueryTranslationVisitor.java @@ -393,15 +393,17 @@ public Node visitProlog(XQueryParser.PrologContext ctx) { } else if (child instanceof XQueryParser.SchemaImportContext) { // Not supported yet; previously skipped as well. } else if (child instanceof XQueryParser.ModuleImportContext namespace) { - LibraryModule libraryModule = this.processModuleImport(namespace); - libraryModules.add(libraryModule); - if (namespaces.contains(libraryModule.getNamespace())) { + String importedNamespace = URILiteralUtils.normalizeAsAnyURI( + processURILiteral(namespace.targetNamespace) + ); + if (namespaces.contains(importedNamespace)) { throw new DuplicateModuleTargetNamespaceException( - "Duplicate module target namespace: " + libraryModule.getNamespace(), + "Duplicate module target namespace: " + importedNamespace, createMetadataFromContext(namespace) ); } - namespaces.add(libraryModule.getNamespace()); + namespaces.add(importedNamespace); + libraryModules.addAll(this.processModuleImport(namespace)); } } @@ -3762,7 +3764,7 @@ private String resolveCollationUri(UriLiteralContext ctx) { return uri.toString(); } - public LibraryModule processModuleImport(XQueryParser.ModuleImportContext ctx) { + public List processModuleImport(XQueryParser.ModuleImportContext ctx) { ExceptionMetadata metadata = createMetadataFromContext(ctx); String namespace = processURILiteral(ctx.targetNamespace); if (namespace.isEmpty()) { @@ -3782,7 +3784,7 @@ public LibraryModule processModuleImport(XQueryParser.ModuleImportContext ctx) { .map(this::processURILiteral) .map(URILiteralUtils::normalizeAsAnyURI) .collect(Collectors.toList()); - LibraryModule libraryModule = ModuleImportLoader.load( + List libraryModules = ModuleImportLoader.load( namespace, locationHints, this.moduleContext, @@ -3792,11 +3794,11 @@ public LibraryModule processModuleImport(XQueryParser.ModuleImportContext ctx) { if (ctx.ncName() != null) { bindNamespace( ctx.ncName().getText(), - libraryModule.getNamespace(), + namespace, metadata ); } - return libraryModule; + return libraryModules; } public ExceptionMetadata generateMetadata(Token start, Token end) { diff --git a/src/main/java/org/rumbledb/context/StaticContext.java b/src/main/java/org/rumbledb/context/StaticContext.java index 8090d78ce9..fc86d2c7c1 100644 --- a/src/main/java/org/rumbledb/context/StaticContext.java +++ b/src/main/java/org/rumbledb/context/StaticContext.java @@ -282,6 +282,10 @@ public SequenceType getVariableSequenceType(Name varName) { return getInScopeVariable(varName).getSequenceType(); } + public ExceptionMetadata getVariableMetadata(Name varName) { + return getInScopeVariable(varName).getMetadata(); + } + public ExecutionMode getVariableStorageMode(Name varName) { return getInScopeVariable(varName).getStorageMode(); } @@ -317,6 +321,22 @@ public void addFunctionSignature(FunctionIdentifier identifier, FunctionSignatur this.staticallyKnownFunctionSignatures.put(identifier, signature); } + public boolean hasFunctionSignatureInScopeOnly(FunctionIdentifier identifier) { + return this.staticallyKnownFunctionSignatures.containsKey(identifier); + } + + public Map getInScopeVariables() { + return this.inScopeVariables; + } + + public Map getStaticallyKnownFunctionSignatures() { + return this.staticallyKnownFunctionSignatures; + } + + public void show() { + System.err.println(this); + } + @Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); @@ -510,17 +530,6 @@ private String expandSerializationQName(String lexicalQName) { return "Q{" + namespace + "}" + localName; } - public void importModuleContext(StaticContext moduleContext) { - for (Name name : moduleContext.inScopeVariables.keySet()) { - InScopeVariable variable = moduleContext.inScopeVariables.get(name); - this.inScopeVariables.put(name, variable); - } - for (FunctionIdentifier fi : moduleContext.staticallyKnownFunctionSignatures.keySet()) { - FunctionSignature signature = moduleContext.staticallyKnownFunctionSignatures.get(fi); - this.staticallyKnownFunctionSignatures.put(fi, signature); - } - } - public void setUserDefinedFunctionsExecutionModes( UserDefinedFunctionExecutionModes staticallyKnownFunctionSignatures ) { diff --git a/src/main/java/org/rumbledb/expressions/module/LibraryModule.java b/src/main/java/org/rumbledb/expressions/module/LibraryModule.java index f1ddb418a1..af33428f46 100644 --- a/src/main/java/org/rumbledb/expressions/module/LibraryModule.java +++ b/src/main/java/org/rumbledb/expressions/module/LibraryModule.java @@ -39,6 +39,12 @@ public class LibraryModule extends Module { private final String namespace; @Getter private final Prolog prolog; + @Getter + @Setter + private String location; + @Getter + @Setter + private String moduleIdentity; public LibraryModule(Prolog prolog, String namespace, ExceptionMetadata metadata) { super(metadata); @@ -84,5 +90,8 @@ public void serializeToJSONiq(StringBuilder sb, int indent) { sb.append("module namespace " + this.namespace + ";\n"); this.prolog.serializeToJSONiq(sb, indent); } -} + public String getModuleIdentityOrLocation() { + return this.moduleIdentity != null ? this.moduleIdentity : this.location; + } +}