Skip to content
Merged
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
59 changes: 16 additions & 43 deletions docs/next-engine-ast-boundary.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,31 @@
# Engine / AST dependency boundary

The target is an execution engine independent of parser and SPARQL AST types.
The current engine still exposes three dependencies. `NextModuleBoundaryTest`
inventories them as exact source-path / referenced-type pairs, rather than
exempting whole files or packages.
The query execution engine is independent of parser and SPARQL AST types.
`NextModuleBoundaryTest` enforces this invariant with zero exceptions across all
engine sources.

## Existing exceptions
## Decoupling history

Paths below are relative to `next/query/impl/engine`.
Previously, three source files in `next/query/impl/engine` retained AST dependencies:

| Source | AST type | Current use and callers |
| --- | --- | --- |
| `model/Filter.java` | `TermAst` | Return types of `getFilterExpression()` and `coreseNextSource()`. `NextFilterFromAst` implements both in the bridge. `Exp` delegates the first; `AstBackedExprTest` checks its result. No production caller of `coreseNextSource()` was found in `next`. |
| `pattern/Exp.java` | `TermAst` | Return type of `getFilterExpression()`, delegating to `Filter`. No production caller of this accessor was found in `next`. |
| `pattern/Query.java` | `QueryAst` | Field `ast` and accessors `getAST()`, `getGlobalAST()`, `setAST()`. `CoreseAstQueryBuilder` sets the source AST for each query form; `QueryTest` tests the accessors. No external production reader of these accessors was found in `next`. |

This is a source-reference inventory, not a statement that these APIs can be
removed without checking downstream clients. Bridge implementations may still
own AST objects internally after the engine contracts stop exposing their types.
- `model/Filter.java`: returned `TermAst` via `getFilterExpression()` and `coreseNextSource()`.
These AST accessors were removed from the engine interface; the bridge implementation
`NextFilterFromAst` retains AST-level access internally where needed.
- `pattern/Exp.java`: exposed `getFilterExpression()` delegating to `Filter`. Removed as it
had no engine callers.
- `pattern/Query.java`: retained `QueryAst ast` with `getAST()`, `getGlobalAST()`, and `setAST()`.
Removed from `Query`; query builder compilation produces pure engine operator trees.

## Guard behavior

- Zero exceptions: `EXISTING_ENGINE_AST_DEPENDENCIES` is empty.
- Parser dependencies have no exceptions.
- Both the actual `query.impl.sparql.ast` package and the historical
`query.impl.ast` package are checked.
- Imports, wildcard imports, static references and fully qualified references
are detected by the existing source-reference matching approach.
- Adding a type to an already listed file is rejected.
- Adding an existing AST type to another file is rejected.
- Removing a dependency requires removing its exception in the same change.

The source scanner also sees fully qualified names in comments and strings. It
does not perform bytecode or transitive dependency analysis. Its regression test
uses synthetic sources to check new files, new types, wildcard imports and fully
qualified references outside imports.

## Follow-up: remove the exceptions

1. Check repository-wide and downstream uses of the source-AST accessors before
removing them; assess compatibility of these implementation classes.
2. Remove the unused `Exp` forwarding accessor and the AST accessors from the
engine `Filter` contract. Keep source-AST access inside the bridge where needed;
adapt `AstBackedExprTest` to test bridge behavior rather than an engine AST API.
3. Remove AST retention from `Query` if no required consumer exists. If diagnostics
need source information, keep it in a bridge-owned structure with an explicit
lifecycle, rather than replacing typed accessors with `Object` or unsafe casts.
4. Remove the corresponding builder writes and accessor-only tests. Preserve
behavioral tests for query forms, expressions and subqueries.
5. Delete each exception as its dependency disappears, then restore the strict
no-AST-dependencies invariant in the engine package documentation.

Do not introduce new runtime behavior as part of this follow-up. Validate with
the architecture tests, the full core test suite and a comparison against the
existing W3C baseline.
are detected by the source-reference scanner.
- Any new dependency on parser or AST packages within `query/impl/engine` causes
`NextModuleBoundaryTest.queryEngineMustNotAddParserOrAstDependencies` to fail immediately.

## Validation

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
package fr.inria.corese.core.next.query.impl.engine.model;


import fr.inria.corese.core.next.query.impl.sparql.ast.TermAst;

import java.util.List;
import java.util.Optional;


/**
* Native filter contract backed by the Corese-next SPARQL AST.
* Native filter contract for the Corese-next execution engine.
*
* @author Olivier Corby, Edelweiss, INRIA 2010
*/
public interface Filter {

/**
* When non-empty, the Corese-next AST node this filter was produced from
* (filter expression: {@link TermAst} / constraint subtypes).
*/
default Optional<TermAst> coreseNextSource() {
return Optional.empty();
}

/**
* List of variable names contained in the filter
*
Expand All @@ -33,8 +20,6 @@ default Optional<TermAst> coreseNextSource() {
/** Evaluable expression processed by the native KGRAM evaluator. */
Expr getExp();

TermAst getFilterExpression();

/**
* Does filter contain a bound() function
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@
*
* <h2>Architectural Invariants &amp; Development Guardrails</h2>
* <ul>
* <li><b>AST Boundary</b>: New dependencies on parser or AST classes are forbidden.
* Three existing source/type dependencies in {@code Filter}, {@code Exp}, and
* {@code Query} remain explicitly inventoried by {@code NextModuleBoundaryTest}.
* Removing these exceptions is tracked in {@code docs/next-engine-ast-boundary.md};
* the target architecture executes compiled logical operators only.</li>
* <li><b>AST Boundary</b>: Dependencies on parser or AST classes are strictly forbidden.
* The execution engine operates exclusively on compiled logical operators and patterns;
* zero AST dependencies exist or are permitted, as enforced by {@code NextModuleBoundaryTest}
* and documented in {@code docs/next-engine-ast-boundary.md}.</li>
* <li><b>Storage Isolation</b>: Query evaluation and triple access must strictly
* transit through the {@link fr.inria.corese.core.next.query.impl.engine.spi.Producer}
* and Storage SPI abstractions.</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import fr.inria.corese.core.next.query.impl.engine.model.PointerType;
import fr.inria.corese.core.next.query.impl.engine.model.Regex;
import fr.inria.corese.core.next.query.impl.engine.spi.Producer;
import fr.inria.corese.core.next.query.impl.sparql.ast.TermAst;

import java.util.ArrayList;
import java.util.HashMap;
Expand Down Expand Up @@ -548,10 +547,6 @@ public void setFilter(Filter f) {
}
}

public TermAst getFilterExpression() {
return getFilter().getFilterExpression();
}

public void addFilter(Filter f) {
lFilter.add(f);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import fr.inria.corese.core.next.query.impl.engine.spi.Producer;
import fr.inria.corese.core.next.query.impl.engine.filter.Compile;
import fr.inria.corese.core.next.query.impl.engine.eval.Message;
import fr.inria.corese.core.next.query.impl.sparql.ast.QueryAst;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -88,7 +87,6 @@ public final class Query extends Exp {
// SPIN graph
Query query;
Query outerQuery;
QueryAst ast;
Object object;

// current transformer if any
Expand Down Expand Up @@ -287,18 +285,6 @@ public void setObject(Object o) {
object = o;
}

public QueryAst getGlobalAST() {
return getGlobalQuery().getAST();
}

public QueryAst getAST() {
return ast;
}

public void setAST(QueryAst o) {
ast = o;
}

public int getPlanProfile() {
return planner;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ public Query toNextQuery(AskQueryAst askQueryAst) {
compiler);
SolutionModifierCompiler.applyOrderBy(query, askQueryAst.solutionModifier(), compiler);
query.setAsk(true);
query.setAST(askQueryAst);
return query;
}

Expand Down Expand Up @@ -104,7 +103,6 @@ public Query toNextQuery(SelectQueryAst selectQueryAst) {
if (query.getHaving() != null && !query.hasGroupBy()) {
query.setAggregate(true);
}
query.setAST(selectQueryAst);
return query;
}

Expand All @@ -131,7 +129,6 @@ public Query toNextQuery(DescribeQueryAst describeQueryAst) {
compiler);
SolutionModifierCompiler.applyOrderBy(query, describeQueryAst.solutionModifier(), compiler);
DescribeQueryCompiler.compile(query, describeQueryAst, compiler);
query.setAST(describeQueryAst);
return query;
}

Expand All @@ -157,7 +154,6 @@ public Query toNextQuery(ConstructQueryAst constructQueryAst) {
compiler);
SolutionModifierCompiler.applyOrderBy(query, constructQueryAst.solutionModifier(), compiler);
ConstructQueryCompiler.compile(query, constructQueryAst, compiler);
query.setAST(constructQueryAst);
return query;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ public Expr getExp() {
return owner;
}

@Override
public TermAst getFilterExpression() {
return owner.sourceAst().orElseThrow();
}
Expand Down Expand Up @@ -83,7 +82,6 @@ public boolean isRecExist() {
return owner.isRecExist();
}

@Override
public Optional<TermAst> coreseNextSource() {
return owner.sourceAst();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,9 @@ class NextModuleBoundaryTest {
"\\bfr\\.inria\\.corese\\.core(?:\\.[A-Za-z_$][A-Za-z0-9_$]*)+");
private static final Pattern STATIC_IMPORT_PREFIX = Pattern.compile("^static\\s+");

// Engine must not depend on parser or AST types: zero exceptions.
// Exact source/type pairs only: see docs/next-engine-ast-boundary.md.
// Equality below also requires removing exceptions when their dependencies disappear.
private static final Set<String> EXISTING_ENGINE_AST_DEPENDENCIES = Set.of(
"model/Filter.java -> fr.inria.corese.core.next.query.impl.sparql.ast.TermAst",
"pattern/Exp.java -> fr.inria.corese.core.next.query.impl.sparql.ast.TermAst",
"pattern/Query.java -> fr.inria.corese.core.next.query.impl.sparql.ast.QueryAst");
private static final Set<String> EXISTING_ENGINE_AST_DEPENDENCIES = Set.of();

@Test
void sharedCodeMustNotDependOnDomainModules() throws IOException {
Expand Down Expand Up @@ -105,7 +102,7 @@ void queryEngineMustNotAddParserOrAstDependencies() throws IOException {
}

@Test
void engineBoundaryFindsSyntaxReferencesBeyondExistingExceptions(@TempDir Path sources)
void engineBoundaryDetectsForbiddenSyntaxReferences(@TempDir Path sources)
throws IOException {
Path filter = sources.resolve("model/Filter.java");
Files.createDirectories(filter.getParent());
Expand All @@ -121,8 +118,8 @@ class Filter {
Files.writeString(sources.resolve("NewOperator.java"),
"import fr.inria.corese.core.next.query.impl.sparql.ast.TermAst;");
Set<String> unexpected = engineSyntaxDependencies(sources);
unexpected.removeAll(EXISTING_ENGINE_AST_DEPENDENCIES);
assertEquals(Set.of(
"model/Filter.java -> fr.inria.corese.core.next.query.impl.sparql.ast.TermAst",
"model/Filter.java -> fr.inria.corese.core.next.query.impl.sparql.ast.QueryAst",
"model/Filter.java -> fr.inria.corese.core.next.query.impl.sparql.parser.SparqlParser",
"model/Filter.java -> fr.inria.corese.core.next.query.impl.sparql.ast.VarAst",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import fr.inria.corese.core.next.query.impl.engine.model.ExpType;
import fr.inria.corese.core.next.query.impl.engine.model.Filter;
import fr.inria.corese.core.next.query.impl.engine.model.Node;
import fr.inria.corese.core.next.query.impl.sparql.ast.QueryAst;
import fr.inria.corese.core.next.query.impl.sparql.parser.SparqlParser;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
Expand Down Expand Up @@ -649,26 +647,6 @@ void testIsTransformationTemplate() {
}
}

@Nested
@DisplayName("AST Tests")
class ASTTests {

@Test
@DisplayName("Should set and get AST")
void testSetAndGetAST() {
QueryAst ast = new SparqlParser().parse("SELECT * WHERE { ?s ?p ?o }");
query.setAST(ast);
assertSame(ast, query.getAST(), "AST should match");
}

@Test
@DisplayName("Should get global AST")
void testGetGlobalAST() {
assertDoesNotThrow(() -> query.getGlobalAST(),
"Getting global AST should not throw");
}
}

@Nested
@DisplayName("Global Query Tests")
class GlobalQueryTests {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ void boundExpressionAndFilterView() {
assertNotNull(boundExpr.getFilter());
assertTrue(boundExpr.getFilter().isBound());
assertEquals(List.of("y"), boundExpr.getFilter().getVariables());
assertEquals(boundAst, boundExpr.getFilter().getFilterExpression());
assertEquals(boundAst, ((NextFilterFromAst) boundExpr.getFilter()).getFilterExpression());
}

@Test
Expand Down
Loading