Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/run-qt3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,15 @@ jobs:

qt3-jsoniq:
needs: [resolve-baseline, build]
uses: RumbleDB/rumble-test-suite/.github/workflows/qt3-suite.yml@master
uses: RumbleDB/rumble-test-suite/.github/workflows/qt3-suite.yml@AtHint
with:
tested_parser: jsoniq
rumble_artifact_name: rumble-build
baseline_run_id: ${{ needs.resolve-baseline.outputs.baseline_run_id }}

qt3-xquery:
needs: [resolve-baseline, build]
uses: RumbleDB/rumble-test-suite/.github/workflows/qt3-suite.yml@master
uses: RumbleDB/rumble-test-suite/.github/workflows/qt3-suite.yml@AtHint
with:
tested_parser: xquery
rumble_artifact_name: rumble-build
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ public DynamicContext visitLibraryModule(LibraryModule module, DynamicContext ar
if (!this.importedModuleContexts.containsKey(module.getNamespace())) {
DynamicContext newContext = new DynamicContext(this.configuration);
newContext.setNamedFunctions(argument.getNamedFunctions());
this.importedModuleContexts.put(module.getNamespace(), newContext);
DynamicContext importedContext = visitDescendants(module, newContext);
this.importedModuleContexts.put(module.getNamespace(), importedContext);
}
Expand Down
19 changes: 13 additions & 6 deletions src/main/java/org/rumbledb/compiler/ModuleImportLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,31 @@
ExceptionMetadata metadata
) {
URI baseURI = importingModuleContext.getStaticBaseURI();
URI namespaceURI = URILiteralUtils.resolve(baseURI, namespace, metadata);
List<String> candidates = locationHints.isEmpty() ? List.of(namespace) : locationHints;
String normalizedNamespace = URILiteralUtils.normalizeAsAnyURI(namespace);
List<String> candidates = locationHints.isEmpty() ? List.of(normalizedNamespace) : locationHints;
Exception lastFailure = null;

for (String candidate : candidates) {
URI location = URILiteralUtils.resolve(baseURI, candidate, metadata);
URI location;
try {
location = URILiteralUtils.resolve(baseURI, candidate, metadata);
} catch (RumbleException e) {
lastFailure = e;
continue;
}
try {
LibraryModule module = VisitorHelpers.parseLibraryModuleFromLocation(
location,
namespaceURI.toString(),

Check failure on line 52 in src/main/java/org/rumbledb/compiler/ModuleImportLoader.java

View workflow job for this annotation

GitHub Actions / build

namespaceURI cannot be resolved

Check failure on line 52 in src/main/java/org/rumbledb/compiler/ModuleImportLoader.java

View workflow job for this annotation

GitHub Actions / build

namespaceURI cannot be resolved
importingModuleContext,
compilationConfiguration,
metadata
);

if (!namespaceURI.toString().equals(module.getNamespace())) {
if (!normalizedNamespace.equals(module.getNamespace())) {
throw new ModuleNotFoundException(
"A module with namespace "
+ namespaceURI
+ normalizedNamespace
+ " was not found. The namespace of the module at this location was: "
+ module.getNamespace(),
metadata
Expand All @@ -66,7 +73,7 @@

RumbleException exception = new ModuleNotFoundException(
"Module not found: %s, cause: %s".formatted(
namespaceURI,
normalizedNamespace,
lastFailure != null ? lastFailure.getMessage() : "unknown"
),
metadata
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ public StaticContext visitMainModule(MainModule mainModule, StaticContext argume
public StaticContext visitLibraryModule(LibraryModule libraryModule, StaticContext argument) {
if (!this.importedModuleContexts.containsKey(libraryModule.getNamespace())) {
StaticContext moduleContext = libraryModule.getStaticContext();
this.visit(libraryModule.getProlog(), moduleContext);
this.importedModuleContexts.put(libraryModule.getNamespace(), moduleContext);
this.visit(libraryModule.getProlog(), moduleContext);
}
argument.importModuleContext(
this.importedModuleContexts.get(libraryModule.getNamespace())
Expand Down
19 changes: 8 additions & 11 deletions src/main/java/org/rumbledb/compiler/TranslationVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -371,24 +371,19 @@ public Node visitProgram(JsoniqParser.ProgramContext ctx) {
@Override
public Node visitLibraryModule(JsoniqParser.LibraryModuleContext ctx) {
String prefix = ctx.ncName().getText();
String namespace = processURILiteral(ctx.uriLiteral());
String namespace = URILiteralUtils.normalizeAsAnyURI(processURILiteral(ctx.uriLiteral()));
if (namespace.equals("")) {
throw new EmptyModuleURIException("Module URI is empty.", createMetadataFromContext(ctx));
}
URI resolvedURI = URILiteralUtils.resolve(
this.moduleContext.getStaticBaseURI(),
namespace,
createMetadataFromContext(ctx)
);
this.libraryModuleNamespace = resolvedURI.toString();
this.libraryModuleNamespace = namespace;
bindNamespace(
prefix,
resolvedURI.toString(),
namespace,
createMetadataFromContext(ctx)
);

Prolog prolog = (Prolog) this.visitProlog(ctx.prolog());
LibraryModule module = new LibraryModule(prolog, resolvedURI.toString(), createMetadataFromContext(ctx));
LibraryModule module = new LibraryModule(prolog, namespace, createMetadataFromContext(ctx));
module.setStaticContext(this.moduleContext);
return module;
}
Expand Down Expand Up @@ -480,7 +475,7 @@ public Node visitProlog(JsoniqParser.PrologContext ctx) {
uriString,
createMetadataFromContext(setterContext.baseURIDecl())
);
this.moduleContext.setStaticBaseUri(uri);
this.moduleContext.setStaticBaseUri(uri, URILiteralUtils.normalizeAsAnyURI(uriString));
baseURISet = true;
continue;
}
Expand Down Expand Up @@ -4062,8 +4057,8 @@ private String resolveCollationUri(UriLiteralContext ctx) {
}

public LibraryModule processModuleImport(JsoniqParser.ModuleImportContext ctx) {
String namespace = processURILiteral(ctx.targetNamespace);
ExceptionMetadata metadata = createMetadataFromContext(ctx);
String namespace = processURILiteral(ctx.targetNamespace);
if (namespace.isEmpty()) {
throw new EmptyModuleURIException("Module URI is empty.", metadata);
}
Expand All @@ -4076,8 +4071,10 @@ public LibraryModule processModuleImport(JsoniqParser.ModuleImportContext ctx) {
);
}
}
namespace = URILiteralUtils.normalizeAsAnyURI(namespace);
List<String> locationHints = ctx.locations.stream()
.map(this::processURILiteral)
.map(URILiteralUtils::normalizeAsAnyURI)
.collect(Collectors.toList());
LibraryModule libraryModule = ModuleImportLoader.load(
namespace,
Expand Down
174 changes: 174 additions & 0 deletions src/main/java/org/rumbledb/compiler/VariableDependenciesVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.rumbledb.config.RumbleRuntimeConfiguration;
import org.rumbledb.context.Name;
import org.rumbledb.exceptions.CycleInVariableDeclarationsException;
import org.rumbledb.exceptions.DuplicateFunctionIdentifierException;
import org.rumbledb.exceptions.ModuleDependencyCycleException;
import org.rumbledb.exceptions.OurBadException;
import org.rumbledb.exceptions.VariableAlreadyExistsException;
import org.rumbledb.expressions.AbstractNodeVisitor;
Expand All @@ -44,6 +46,7 @@
import org.rumbledb.expressions.flowr.WhereClause;
import org.rumbledb.expressions.flowr.WindowClause;
import org.rumbledb.expressions.module.FunctionDeclaration;
import org.rumbledb.expressions.module.LibraryModule;
import org.rumbledb.expressions.module.Prolog;
import org.rumbledb.expressions.module.TypeDeclaration;
import org.rumbledb.expressions.module.VariableDeclaration;
Expand All @@ -68,6 +71,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -555,6 +559,28 @@ public Void visitProlog(Prolog prolog, Void argument) {
return null;
}

@Override
public Void visitMainModule(org.rumbledb.expressions.module.MainModule mainModule, Void argument) {
visit(mainModule.getProlog(), null);
for (LibraryModule importedModule : mainModule.getProlog().getImportedModules()) {
visit(importedModule, null);
}
visit(mainModule.getProgram(), null);
return null;
}

@Override
public Void visitLibraryModule(LibraryModule libraryModule, Void argument) {
visitImportedModules(libraryModule.getProlog().getImportedModules());
visit(libraryModule.getProlog(), null);
if (isXQuery10Like()) {
validateModuleDependencyCycles(libraryModule);
} else {
validateDynamicContextCycles(libraryModule);
}
return null;
}

@Override
public Void visitVariableDeclaration(VariableDeclaration expression, Void argument) {
if (expression.getExpression() != null) {
Expand All @@ -565,6 +591,154 @@ public Void visitVariableDeclaration(VariableDeclaration expression, Void argume
return null;
}

private void visitImportedModules(List<LibraryModule> importedModules) {
for (LibraryModule importedModule : importedModules) {
visit(importedModule, null);
}
}

private boolean isXQuery10Like() {
String queryLanguage = this.rumbleRuntimeConfiguration.getQueryLanguage();
return "xquery10".equals(queryLanguage) || "jsoniq10".equals(queryLanguage);
}

private void validateModuleDependencyCycles(LibraryModule rootModule) {
DirectedAcyclicGraph<LibraryModule, DefaultEdge> dependencyGraph = new DirectedAcyclicGraph<>(
DefaultEdge.class
);
addModuleDependencyEdges(
rootModule,
dependencyGraph,
Collections.newSetFromMap(new IdentityHashMap<>())
);
}

private void addModuleDependencyEdges(
LibraryModule module,
DirectedAcyclicGraph<LibraryModule, DefaultEdge> dependencyGraph,
Set<LibraryModule> visitedModules
) {
if (!visitedModules.add(module)) {
return;
}
dependencyGraph.addVertex(module);
Prolog prolog = module.getProlog();
for (LibraryModule importedModule : prolog.getImportedModules()) {
addModuleDependencyEdges(importedModule, dependencyGraph, visitedModules);
if (dependsDirectlyOnModule(module, importedModule)) {
dependencyGraph.addVertex(importedModule);
try {
dependencyGraph.addEdge(importedModule, module);
} catch (IllegalArgumentException e) {
throw new ModuleDependencyCycleException(
"There is a cycle in the direct dependencies between imported modules.",
module.getMetadata()
);
}
}
}
}

private boolean dependsDirectlyOnModule(LibraryModule module, LibraryModule importedModule) {
Set<Name> importedNames = collectDirectDeclarationNames(importedModule.getProlog());
for (VariableDeclaration variableDeclaration : module.getProlog().getVariableDeclarations()) {
if (!Collections.disjoint(getInputVariableDependencies(variableDeclaration), importedNames)) {
return true;
}
}
for (FunctionDeclaration functionDeclaration : module.getProlog().getFunctionDeclarations()) {
if (!Collections.disjoint(getInputVariableDependencies(functionDeclaration), importedNames)) {
return true;
}
}
return false;
}

private Set<Name> collectDirectDeclarationNames(Prolog prolog) {
Set<Name> names = new TreeSet<>();
for (VariableDeclaration variableDeclaration : prolog.getVariableDeclarations()) {
names.add(variableDeclaration.getVariableName());
}
for (FunctionDeclaration functionDeclaration : prolog.getFunctionDeclarations()) {
names.add(functionDeclaration.getFunctionIdentifier().getNameWithArity());
}
for (TypeDeclaration typeDeclaration : prolog.getTypeDeclarations()) {
names.add(typeDeclaration.getDefinition().getName());
}
return names;
}

private void validateDynamicContextCycles(LibraryModule rootModule) {
Map<Name, Node> reachableDeclarations = new TreeMap<>();
collectReachableDeclarations(
rootModule,
reachableDeclarations,
Collections.newSetFromMap(new IdentityHashMap<>())
);
DirectedAcyclicGraph<Node, DefaultEdge> dependencyGraph = new DirectedAcyclicGraph<>(DefaultEdge.class);
for (Node declaration : reachableDeclarations.values()) {
dependencyGraph.addVertex(declaration);
}
for (Node declaration : reachableDeclarations.values()) {
if (!(declaration instanceof VariableDeclaration) && !(declaration instanceof FunctionDeclaration)) {
continue;
}
for (Name name : getInputVariableDependencies(declaration)) {
Node dependency = reachableDeclarations.get(name);
if (dependency == null) {
continue;
}
if (dependency instanceof FunctionDeclaration && declaration instanceof FunctionDeclaration) {
continue;
}
try {
dependencyGraph.addEdge(dependency, declaration);
} catch (IllegalArgumentException e) {
throw new CycleInVariableDeclarationsException(
"There is a cycle in the dependencies in the variable and function declarations. It is thus impossible to build the dynamic context.",
declaration.getMetadata()
);
}
}
}
}

private void collectReachableDeclarations(
LibraryModule module,
Map<Name, Node> nameToNodeMap,
Set<LibraryModule> visitedModules
) {
if (!visitedModules.add(module)) {
return;
}
Prolog prolog = module.getProlog();
for (LibraryModule importedModule : prolog.getImportedModules()) {
collectReachableDeclarations(importedModule, nameToNodeMap, visitedModules);
}
for (VariableDeclaration variableDeclaration : prolog.getVariableDeclarations()) {
Node previous = nameToNodeMap.putIfAbsent(variableDeclaration.getVariableName(), variableDeclaration);
if (previous != null && previous != variableDeclaration) {
throw new VariableAlreadyExistsException(
variableDeclaration.getVariableName(),
variableDeclaration.getMetadata()
);
}
}
for (FunctionDeclaration functionDeclaration : prolog.getFunctionDeclarations()) {
Name key = functionDeclaration.getFunctionIdentifier().getNameWithArity();
Node previous = nameToNodeMap.putIfAbsent(key, functionDeclaration);
if (previous != null && previous != functionDeclaration) {
throw new DuplicateFunctionIdentifierException(
functionDeclaration.getFunctionIdentifier(),
functionDeclaration.getMetadata()
);
}
}
for (TypeDeclaration typeDeclaration : prolog.getTypeDeclarations()) {
nameToNodeMap.putIfAbsent(typeDeclaration.getDefinition().getName(), typeDeclaration);
}
}

@Override
public Void visitFunctionDeclaration(FunctionDeclaration expression, Void argument) {
visit(expression.getExpression(), null);
Expand Down
Loading
Loading