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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
99 changes: 83 additions & 16 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,74 @@ sourceSets {
}
}

// Compile and test the native implementation without historical Corese classes
// on either classpath. No main output or JavaCC sources are included here.
val nextSources = sourceSets.create("next") {
java.setSrcDirs(listOf("src/main/java", antlrGeneratedDir))
java.include("fr/inria/corese/core/next/**/*.java")
resources.setSrcDirs(listOf("src/main/resources"))
resources.include("META-INF/services/fr.inria.corese.core.next.storage.api.plugin.StoragePlugin")
}

val nextTestSources = sourceSets.create("nextTest") {
java.setSrcDirs(listOf("src/test/java"))
java.include("fr/inria/corese/core/next/**/*.java")
resources.setSrcDirs(listOf("src/test/resources"))
compileClasspath = nextSources.output + configurations[compileClasspathConfigurationName]
runtimeClasspath = output + nextSources.output + configurations[runtimeClasspathConfigurationName]
}

// Native sources and tests define their own dependency graph. The historical
// build inherits those dependencies, while the native build never inherits its output or libraries.
configurations.implementation.get().extendsFrom(configurations[nextSources.implementationConfigurationName])
configurations[nextTestSources.implementationConfigurationName]
.extendsFrom(configurations[nextSources.implementationConfigurationName])
configurations.testImplementation.get().extendsFrom(configurations[nextTestSources.implementationConfigurationName])
configurations.testRuntimeOnly.get().extendsFrom(configurations[nextTestSources.runtimeOnlyConfigurationName])

tasks.named<JavaCompile>(nextSources.compileJavaTaskName) {
dependsOn("generateGrammarSource")
options.encoding = "UTF-8"
options.compilerArgs.addAll(listOf("-proc:none", "-Xlint:all", "-Werror"))
}

val nextTest by tasks.registering(Test::class) {
description = "Tests the native implementation with no historical Corese classes available."
group = "verification"
testClassesDirs = nextTestSources.output.classesDirs
classpath = nextTestSources.runtimeClasspath
useJUnitPlatform()
systemProperty("corese.next.isolated", "true")
systemProperty("java.util.logging.config.file", "src/test/resources/logging.properties")
}

val nextJar by tasks.registering(Jar::class) {
description = "Builds the isolated native classes and their storage plugin descriptor."
group = "build"
archiveClassifier.set("next")
from(nextSources.output)
}

val nextJavadoc by tasks.registering(Javadoc::class) {
description = "Checks the native API documentation without historical Corese classes."
group = "documentation"
source = nextSources.allJava.matching { exclude("**/generated/**") }
classpath = nextSources.output + nextSources.compileClasspath
destinationDir = layout.buildDirectory.dir("docs/next-javadoc").get().asFile
options.encoding = "UTF-8"
(options as StandardJavadocDocletOptions).addStringOption("Xdoclint:all,-missing", "-quiet")
}

tasks.register("checkNext") {
description = "Compiles, packages, and tests next independently of historical Corese classes."
group = "verification"
dependsOn(nextTest, nextJar, nextJavadoc)
}

tasks.named("check") {
dependsOn(nextSources.compileJavaTaskName)
}

// Ensure JavaCC generation happens before compilation
tasks.named("compileJava") {
dependsOn("javaccSparqlCorese")
Expand Down Expand Up @@ -111,6 +179,14 @@ repositories {
}

dependencies {
// === Standalone native runtime ===
add(nextSources.implementationConfigurationName, "org.slf4j:slf4j-api:2.0.17")
add(nextSources.implementationConfigurationName, "org.antlr:antlr4-runtime:4.13.2")
add(nextSources.implementationConfigurationName, "com.apicatalog:titanium-json-ld:1.6.0")
add(nextSources.implementationConfigurationName, "com.apicatalog:titanium-rdf-api:1.0.0")
add(nextSources.implementationConfigurationName, "org.eclipse.parsson:parsson:1.1.7")
add(nextSources.implementationConfigurationName, "jakarta.json:jakarta.json-api:2.1.3")
add(nextSources.implementationConfigurationName, "com.typesafe:config:1.4.3")
// === Logging ===
// As a library, corese-core should only depend on the SLF4J API.
// The actual logging implementation (Logback, Log4j2, etc.) should be chosen
Expand All @@ -124,20 +200,12 @@ dependencies {

// === Antlr dependencies ===
antlr("org.antlr:antlr4:4.13.2") // Antlr for parsing (ANTLR 4)
implementation("org.antlr:antlr4-runtime:4.13.2") // Antlr runtime for parsing

// === HTTP and XML ===
implementation("org.glassfish.jersey.core:jersey-client:3.1.10") // HTTP client (Jersey)
implementation("org.glassfish.jersey.inject:jersey-hk2:3.1.10") // Dependency injection for Jersey
implementation("com.sun.activation:jakarta.activation:2.0.1") // MIME type handling (Jakarta Activation)

// === JSONLD Parsing ===
implementation("com.apicatalog:titanium-json-ld:1.6.0") // JSON-LD processing library
implementation("com.apicatalog:titanium-rdf-api:1.0.0") // Titanium RDF API for JSON-LD processing
implementation("org.eclipse.parsson:parsson:1.1.7") // JSON parser for JSON-LD
implementation("jakarta.json:jakarta.json-api:2.1.3") // Jakarta JSON API for JSON processing


// === XML parsing ===
implementation("com.typesafe.akka:akka-stream_2.13:2.6.20") // Akka Streams for reactive streams processing
implementation("com.lightbend.akka:akka-stream-alpakka-xml_2.13:3.0.4") // Alpakka XML for XML processing with Akka Streams
Expand All @@ -148,16 +216,15 @@ dependencies {
// === Utilities ===
implementation("org.apache.commons:commons-text:1.13.1") // Text manipulation utilities (Commons Text)
implementation("org.json:json:20250517") // JSON processing
implementation("com.typesafe:config:1.4.3") // Configuration library (Typesafe Config)

// === Test dependencies ===
testImplementation(platform("org.junit:junit-bom:5.13.2")) // JUnit BOM for consistent test versions
testImplementation("org.junit.jupiter:junit-jupiter:5.13.2") // JUnit Jupiter API and engine
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.2") // JUnit platform launcher (runtime)
testImplementation("org.mockito:mockito-core:5.18.0") // Mockito core for mocking in tests
testImplementation("org.mockito:mockito-junit-jupiter:5.18.0") // Mockito integration with JUnit Jupiter
testRuntimeOnly("org.apache.logging.log4j:log4j-core:2.25.0") // Log4j2 core for internal logging
testRuntimeOnly("org.apache.logging.log4j:log4j-slf4j2-impl:2.25.0") // SLF4J binding for Log4j2 (runtime)
add(nextTestSources.implementationConfigurationName, platform("org.junit:junit-bom:5.13.2"))
add(nextTestSources.implementationConfigurationName, "org.junit.jupiter:junit-jupiter:5.13.2")
add(nextTestSources.runtimeOnlyConfigurationName, "org.junit.platform:junit-platform-launcher:1.13.2")
add(nextTestSources.implementationConfigurationName, "org.mockito:mockito-core:5.18.0")
add(nextTestSources.implementationConfigurationName, "org.mockito:mockito-junit-jupiter:5.18.0")
add(nextTestSources.runtimeOnlyConfigurationName, "org.apache.logging.log4j:log4j-core:2.25.0")
add(nextTestSources.runtimeOnlyConfigurationName, "org.apache.logging.log4j:log4j-slf4j2-impl:2.25.0")

}

Expand Down
2 changes: 1 addition & 1 deletion docs/memory-storage-final-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ conservées.

## Timeout et transactions

`NextSparqlPipelineExecutor` exécute toujours l'évaluation sur le thread qui possède
`SparqlQueryExecutor` exécute toujours l'évaluation sur le thread qui possède
la transaction. Le watchdog utilise désormais un seul `AtomicInteger` : le premier
CAS départage fin d'évaluation et expiration, sans intervalle entre la revendication
d'un timeout et sa publication. Un résultat ne peut pas être accepté après que le
Expand Down
18 changes: 16 additions & 2 deletions docs/next-engine-ast-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ Previously, three source files in `next/query/impl/engine` retained AST dependen

- `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.
`AstBackedFilter` 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.
- The engine dependency set must be empty; no allowlist or exception mechanism exists.
- Parser dependencies have no exceptions.
- Both the actual `query.impl.sparql.ast` package and the historical
`query.impl.ast` package are checked.
Expand All @@ -33,3 +33,17 @@ Previously, three source files in `next/query/impl/engine` retained AST dependen
./gradlew test --tests '*NextModuleBoundaryTest'
./gradlew check -x test
```

## Syntax-tree ownership

AST traversal contracts (`AstVisitor`, `AbstractAstVisitor`, and `VisitableAst`)
reside in `sparql.ast.visitor`. Semantic validation depends on the syntax tree;
the syntax tree cannot depend on its parser or the execution engine. The same
boundary test protects this direction, including fully qualified references.

## Independent verification

`./gradlew checkNext --offline` compiles, documents, packages, and tests native
sources with their own dependency graph. Historical Corese classes, the JavaCC
parser, and historical runtime libraries are absent from the native classpaths.
`compileNextJava` treats every Java 21 compiler warning as an error.
108 changes: 107 additions & 1 deletion docs/source/next_repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,34 @@ try (Repository repository = Repositories.create();
}
```

Native SELECT results stream basic patterns, filters, BIND, VALUES, pure grouped
joins, supported UNION and GRAPH scopes, and simple OPTIONAL patterns. They keep
only the current bindings and row, rather than accumulating all result mappings.
Aggregates, paths, services, subqueries, MINUS, and joins requiring a
buffered strategy retain full evaluation. A scoped branch also keeps full evaluation
when repeatedly rescanning it would introduce quadratic work. DISTINCT retains its
required set of previously seen projections.

For eligible ordered SELECTs with a finite LIMIT, online Top-K selection retains
at most LIMIT + OFFSET mappings instead of buffering every solution. It uses the
Corese comparator and preserves arrival order for ties. All candidates are still
evaluated before results are delivered; DISTINCT additionally retains its seen
set. Snapshot ownership, interruption and deadlines cover both scanning and final
ranking. Queries without LIMIT, unsupported input algebra, and bounds exceeding
the supported integer capacity keep the classic full evaluator.

Consume a progressive result on the thread that called `evaluate()`, and always
close it with try-with-resources, including after `stream().limit(...)`. Its independent
read snapshot includes transaction-local writes visible at evaluation, and stays stable
across later writes, commits and rollbacks. Exhaustion, SPARQL LIMIT, failure and close
release the snapshot without finishing the source transaction. Cross-thread close can
cancel a cursor. A configured timeout includes client idle time; expiration releases
an idle snapshot without interrupting unrelated client code. Lazy failures are reported
by `hasNext()` or `next()`.

Backends without independent read snapshots use full evaluation. The repository-level
`select` shortcut remains materialized as described above.

Closing a connection never closes its repository. Closing the repository makes
all its connections unusable.

Expand Down Expand Up @@ -134,12 +162,90 @@ Prepared updates intentionally expose only `execute()` today. Bindings,
datasets, inference switches, and timeouts are not advertised for updates until
the execution pipeline can honor their complete semantics.

Prepared queries retain an immutable compilation image after their first successful
planning pass. The first evaluation uses the compiler's fresh objects after a detached image has
been captured; subsequent evaluations create independent runtime operators, variable
indexes, VALUES rows, expression adapters and EXISTS caches. Execution never uses
the cached image itself. Changing bindings or the timeout reuses this plan. Changing the
ordered dataset or the session-visible storage version rebuilds it using current
cardinalities. Effective mutations, including writes inside a transaction, change
that version; no-op mutations do not. A snapshot transaction retains its version
across other sessions' commits. Its own commit preserves an unchanged visible plan,
while rollback restores the version of the data then visible outside the transaction.
Backends returning an unavailable data version are replanned on every evaluation.
A version change during planning prevents publication of that cache entry; concurrent
writes can still age estimates during execution, without changing query semantics.

## Execution diagnostics

Call `evaluateWithDiagnostics()` on a prepared SELECT, ASK, CONSTRUCT or DESCRIBE
query to execute it once and inspect immutable snapshots of its progress:

```java
try (RepositoryConnection connection = repository.getConnection()) {
var execution = connection.prepareTupleQuery(
"SELECT ?s { ?s <urn:type> <urn:Person> . ?s <urn:name> \"Alice\" }")
.evaluateWithDiagnostics();
try (TupleQueryResult result = execution.result()) {
result.forEach(System.out::println);
}
System.out.print(execution.profile().format());
}
```

The profile reports AST-to-runtime compilation, plan completion (ordering,
statistics and node indexing), execution-frame creation, and evaluation durations.
`planReused()` identifies a cache hit: compilation and planning times are then zero,
while execution-frame creation remains measured. The initial compilation duration
includes capture of the private compilation image. Parsing, client idle time
and graph template materialization are excluded. Timings include
instrumentation overhead. The ordered patterns retain their operator locations;
this is a structural plan, not a chronological trace across UNION or OPTIONAL.
Runtime path lookups and dynamically compiled EXISTS patterns are marked as
auxiliary, with identical auxiliary descriptions aggregated.

`profile.delivery()` describes the strategy actually used: `STREAMING` or
`MATERIALIZED`. Its `fallbackReason()` reports the first guard selecting full
evaluation, such as `ORDER_BY`, `AGGREGATION`, `PROPERTY_PATH` or
`SNAPSHOT_UNAVAILABLE`; `NONE` means a progressive cursor was opened. This is
the selected guard, not a complete list of blocking operators.

For SELECT, `delivery().firstRowStatus()` starts as `UNOBSERVED` until a row is
available (`AVAILABLE`) or successful evaluation establishes an empty result
after OFFSET and LIMIT (`EMPTY`). Materialized SELECT results have already reached
one of these terminal observations when evaluation returns. Closing a cursor
without advancing it, or failing before the first row, leaves it `UNOBSERVED`.
For ASK, CONSTRUCT and DESCRIBE, the status is `NOT_APPLICABLE` and the fallback
reason is `QUERY_FORM`.

`firstRowNanos()` is non-negative only for `AVAILABLE`, and otherwise returns
`-1`. It accumulates active wall-clock time from the profiled evaluation until a
row is ready: before evaluation returns for materialized results, or on the first
successful `hasNext()` or `next()` for progressive results. Compilation, planning
and cursor setup are included; client pauses and initial query preparation are excluded. Once
a row is ready, this duration remains fixed even after further reading or closure.

For each pattern, the profile records the estimate already requested by the
planner (`-1` when unavailable), producer iterator openings, and candidate edges
returned to the engine after graph deduplication and before RDF matching.
Candidates are summed across invocations: they are neither result-row counts nor
internal index visits. Estimates precede runtime bindings and may overestimate
merged-graph cardinalities. Diagnostics do not request additional counts or scans.

Ordinary `evaluate()` calls use the uninstrumented producer and cursors.
Diagnostics preserve dataset, bindings, timeout and resource ownership. The caller
must close tuple and graph results. For progressive SELECT, `execution.profile()`
reports work performed so far; consume or close the result before reading the final
profile. Every returned profile remains immutable. Lazy failures retain their usual
exceptions and release the cursor's resources.

## Transactions

Call `supportsTransactions()` before `begin()`. `isActive()` reports the state
of the transaction owned by the connection. `commit()` and `rollback()` require
an active transaction, and closing a connection rolls one back automatically.
The standard in-memory backend currently reports transactions as unsupported.
The native in-memory backend supports snapshot transactions with optimistic
conflict detection at commit.

## SPARQL feature coverage

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
* Class for any exception related to the configuration of Corese.
*/
public class ConfigurationException extends CoreseException {
@java.io.Serial
private static final long serialVersionUID = 1L;


public ConfigurationException(String msg) {
super(msg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
* stream.
*/
public class SerializationException extends CoreseException {
@java.io.Serial
private static final long serialVersionUID = 1L;

private final String formatName;
private final int lineNumber;
private final int columnNumber;
Expand Down
Loading
Loading