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
44 changes: 44 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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)'
```
2 changes: 2 additions & 0 deletions src/main/java/org/rumbledb/compiler/CloneVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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()
);
Expand Down
12 changes: 10 additions & 2 deletions src/main/java/org/rumbledb/compiler/ModuleImportLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@

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 {

private ModuleImportLoader() {
}

public static LibraryModule load(
public static List<LibraryModule> load(
String namespace,
List<String> locationHints,
StaticContext importingModuleContext,
Expand All @@ -37,6 +40,7 @@ public static LibraryModule load(
String normalizedNamespace = URILiteralUtils.normalizeAsAnyURI(namespace);
List<String> candidates = locationHints.isEmpty() ? List.of(normalizedNamespace) : locationHints;
Exception lastFailure = null;
Map<String, LibraryModule> loadedModules = new LinkedHashMap<>();

for (String candidate : candidates) {
URI location;
Expand Down Expand Up @@ -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,
Expand Down
103 changes: 103 additions & 0 deletions src/main/java/org/rumbledb/compiler/ModuleImportSurface.java
Original file line number Diff line number Diff line change
@@ -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<Name, ImportedVariableBinding> variableBindings;
private final Map<FunctionIdentifier, FunctionSignature> 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<Name, ImportedVariableBinding> getVariableBindings() {
return this.variableBindings;
}

public Map<FunctionIdentifier, FunctionSignature> getFunctionSignatures() {
return this.functionSignatures;
}
}
13 changes: 7 additions & 6 deletions src/main/java/org/rumbledb/compiler/ModulePruningVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -38,7 +38,7 @@ public class ModulePruningVisitor extends AbstractNodeVisitor<Void> {

@SuppressWarnings("unused")
private final RumbleConfiguration configuration;
private final List<String> visitedModules;
private final Set<String> visitedModules;

/**
* Builds a new visitor.
Expand All @@ -47,17 +47,18 @@ public class ModulePruningVisitor extends AbstractNodeVisitor<Void> {
*/
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;
}

Expand Down
Loading