diff --git a/build.gradle.kts b/build.gradle.kts
index 8db4fa9fb..9b4839b89 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -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(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")
@@ -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
@@ -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
@@ -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")
}
diff --git a/docs/memory-storage-final-validation.md b/docs/memory-storage-final-validation.md
index fb41df884..33996829f 100644
--- a/docs/memory-storage-final-validation.md
+++ b/docs/memory-storage-final-validation.md
@@ -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
diff --git a/docs/next-engine-ast-boundary.md b/docs/next-engine-ast-boundary.md
index 0da162598..eba71ebbd 100644
--- a/docs/next-engine-ast-boundary.md
+++ b/docs/next-engine-ast-boundary.md
@@ -10,7 +10,7 @@ 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()`.
@@ -18,7 +18,7 @@ Previously, three source files in `next/query/impl/engine` retained AST dependen
## 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.
@@ -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.
diff --git a/docs/source/next_repository.md b/docs/source/next_repository.md
index 0a8e50bf7..3fda77bef 100644
--- a/docs/source/next_repository.md
+++ b/docs/source/next_repository.md
@@ -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.
@@ -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 . ?s \"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
diff --git a/src/main/java/fr/inria/corese/core/next/common/exception/ConfigurationException.java b/src/main/java/fr/inria/corese/core/next/common/exception/ConfigurationException.java
index 7cbed6659..53073277e 100644
--- a/src/main/java/fr/inria/corese/core/next/common/exception/ConfigurationException.java
+++ b/src/main/java/fr/inria/corese/core/next/common/exception/ConfigurationException.java
@@ -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);
diff --git a/src/main/java/fr/inria/corese/core/next/data/api/exception/SerializationException.java b/src/main/java/fr/inria/corese/core/next/data/api/exception/SerializationException.java
index 4a587dfe0..dfa9275e4 100644
--- a/src/main/java/fr/inria/corese/core/next/data/api/exception/SerializationException.java
+++ b/src/main/java/fr/inria/corese/core/next/data/api/exception/SerializationException.java
@@ -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;
diff --git a/src/main/java/fr/inria/corese/core/next/data/api/model/RdfValueOrder.java b/src/main/java/fr/inria/corese/core/next/data/api/model/RdfValueOrder.java
index 345f279c6..26c104af5 100644
--- a/src/main/java/fr/inria/corese/core/next/data/api/model/RdfValueOrder.java
+++ b/src/main/java/fr/inria/corese/core/next/data/api/model/RdfValueOrder.java
@@ -1,11 +1,13 @@
package fr.inria.corese.core.next.data.api.model;
+import fr.inria.corese.core.next.data.Values;
import fr.inria.corese.core.next.data.api.literal.CoreDatatype;
import fr.inria.corese.core.next.data.api.literal.RDFDatatype;
import fr.inria.corese.core.next.data.api.literal.XSDDatatype;
import fr.inria.corese.core.next.data.api.term.BNode;
import fr.inria.corese.core.next.data.api.term.IRI;
import fr.inria.corese.core.next.data.api.term.Literal;
+import fr.inria.corese.core.next.data.api.term.SimpleLiteral;
import fr.inria.corese.core.next.data.api.term.Triple;
import java.math.BigDecimal;
@@ -109,10 +111,12 @@ private static int compareLiterals(Literal left, Literal right) {
private static int compareCompatibleValues(Literal left, Literal right) {
if (isNumeric(left) && isNumeric(right)) {
+ Literal numericLeft = numericValue(left);
+ Literal numericRight = numericValue(right);
if (isFloatingPoint(left) || isFloatingPoint(right)) {
- return compareFloatingPoint(left.doubleValue(), right.doubleValue());
+ return compareFloatingPoint(numericLeft.doubleValue(), numericRight.doubleValue());
}
- return decimalValue(left).compareTo(decimalValue(right));
+ return decimalValue(numericLeft).compareTo(decimalValue(numericRight));
}
if (isBoolean(left) && isBoolean(right)) {
return Boolean.compare(left.booleanValue(), right.booleanValue());
@@ -120,6 +124,12 @@ private static int compareCompatibleValues(Literal left, Literal right) {
return left.getLabel().compareTo(right.getLabel());
}
+ /** Generic typed literals retain their RDF spelling but have no numeric accessors. */
+ private static Literal numericValue(Literal literal) {
+ return literal instanceof SimpleLiteral
+ ? Values.factory().createLiteral(literal.getLabel(), literal.getDatatype()) : literal;
+ }
+
private static int compareFloatingPoint(double left, double right) {
if (Double.isNaN(left)) {
return Double.isNaN(right) ? 0 : -1;
diff --git a/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDate.java b/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDate.java
index 5b1997eba..41aa70f06 100644
--- a/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDate.java
+++ b/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDate.java
@@ -20,6 +20,7 @@ public final class SimpleDate extends AbstractTemporalPointLiteral {
private static final long serialVersionUID = 1L;
private final String label;
+ @SuppressWarnings("serial") // Calendar implementations have VM-specific serialization; RDF is the portable representation.
private final XMLGregorianCalendar calendar;
public SimpleDate(String lexicalValue) {
diff --git a/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDateTime.java b/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDateTime.java
index 957ff29fa..c2277b83a 100644
--- a/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDateTime.java
+++ b/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDateTime.java
@@ -20,6 +20,7 @@ public final class SimpleDateTime extends AbstractTemporalPointLiteral {
private static final long serialVersionUID = 1L;
private final String label;
+ @SuppressWarnings("serial") // Calendar implementations have VM-specific serialization; RDF is the portable representation.
private final XMLGregorianCalendar calendar;
public SimpleDateTime(String lexicalValue) {
diff --git a/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleTime.java b/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleTime.java
index cfa0701df..219e29685 100644
--- a/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleTime.java
+++ b/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleTime.java
@@ -20,6 +20,7 @@ public final class SimpleTime extends AbstractTemporalPointLiteral {
private static final long serialVersionUID = 1L;
private final String label;
+ @SuppressWarnings("serial") // Calendar implementations have VM-specific serialization; RDF is the portable representation.
private final XMLGregorianCalendar calendar;
public SimpleTime(String lexicalValue) {
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/nquads/NQuadsListener.java b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/nquads/NQuadsListener.java
index 3317f1c28..6cafd8fcd 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/nquads/NQuadsListener.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/nquads/NQuadsListener.java
@@ -3,9 +3,8 @@
import fr.inria.corese.core.next.data.api.term.*;
import fr.inria.corese.core.next.data.api.model.*;
import fr.inria.corese.core.next.data.api.factory.ValueFactory;
-import fr.inria.corese.core.next.data.api.io.option.IOOptions;
import fr.inria.corese.core.next.data.api.exception.ParsingException;
-import fr.inria.corese.core.next.data.impl.io.parser.support.AbstractNTriplesNQuadsListener;
+import fr.inria.corese.core.next.data.impl.io.parser.support.NTriplesQuadsTermDecoder;
import fr.inria.corese.core.next.generated.antlr.NQuadsBaseListener;
import fr.inria.corese.core.next.generated.antlr.NQuadsParser;
@@ -16,7 +15,7 @@
*/
public class NQuadsListener extends NQuadsBaseListener {
- private final AbstractNTriplesNQuadsListener abstractNTriplesQuadsListener;
+ private final NTriplesQuadsTermDecoder termDecoder;
private final Model model;
private final ValueFactory factory;
@@ -29,20 +28,16 @@ public class NQuadsListener extends NQuadsBaseListener {
*
* @param model RDF model to populate
* @param factory ValueFactory for creating RDF resources
- * @param options IO configuration options
*/
- public NQuadsListener(Model model, ValueFactory factory, IOOptions options) {
+ public NQuadsListener(Model model, ValueFactory factory) {
this.model = model;
this.factory = factory;
- this.abstractNTriplesQuadsListener = new AbstractNTriplesNQuadsListener(model, factory, options) {
- };
+ this.termDecoder = new NTriplesQuadsTermDecoder(factory);
}
@Override
public void enterStatement(NQuadsParser.StatementContext ctx) {
-
-
currentSubject = extractSubject(ctx.subject());
currentPredicate = extractPredicate(ctx.predicate());
currentGraph = (ctx.graphLabel() != null) ? extractGraph(ctx.graphLabel()) : null;
@@ -68,7 +63,7 @@ public void exitStatement(NQuadsParser.StatementContext ctx) {
*/
protected Resource extractSubject(NQuadsParser.SubjectContext ctx) {
if (ctx.IRIREF() != null) {
- String iri = abstractNTriplesQuadsListener.unescapeUri(stripAngles(ctx.IRIREF().getText()));
+ String iri = termDecoder.unescapeUri(stripAngles(ctx.IRIREF().getText()));
return factory.createIRI(iri);
}
if (ctx.BLANK_NODE_LABEL() != null) {
@@ -84,7 +79,7 @@ protected Resource extractSubject(NQuadsParser.SubjectContext ctx) {
*/
protected IRI extractPredicate(NQuadsParser.PredicateContext ctx) {
if (ctx.IRIREF() != null) {
- String iri = abstractNTriplesQuadsListener.unescapeUri(stripAngles(ctx.IRIREF().getText()));
+ String iri = termDecoder.unescapeUri(stripAngles(ctx.IRIREF().getText()));
return factory.createIRI(iri);
}
throw new ParsingException("Unsupported N-Quads predicate: " + ctx.getText());
@@ -95,7 +90,7 @@ protected IRI extractPredicate(NQuadsParser.PredicateContext ctx) {
*/
protected Value extractObject(NQuadsParser.ObjectContext ctx) {
if (ctx.IRIREF() != null) {
- String iri = abstractNTriplesQuadsListener.unescapeUri(stripAngles(ctx.IRIREF().getText()));
+ String iri = termDecoder.unescapeUri(stripAngles(ctx.IRIREF().getText()));
return factory.createIRI(iri);
}
if (ctx.BLANK_NODE_LABEL() != null) {
@@ -114,7 +109,7 @@ protected Value extractObject(NQuadsParser.ObjectContext ctx) {
*/
protected Resource extractGraph(NQuadsParser.GraphLabelContext ctx) {
if (ctx.IRIREF() != null) {
- String iri = abstractNTriplesQuadsListener.unescapeUri(stripAngles(ctx.IRIREF().getText()));
+ String iri = termDecoder.unescapeUri(stripAngles(ctx.IRIREF().getText()));
return factory.createIRI(iri);
}
if (ctx.BLANK_NODE_LABEL() != null) {
@@ -137,26 +132,26 @@ protected Literal extractLiteral(NQuadsParser.LiteralContext ctx) {
throw new ParsingException("Missing literal token: " + ctx.getText());
}
- String label = abstractNTriplesQuadsListener.unescapeLiteral(rawText);
+ String label = termDecoder.unescapeLiteral(rawText);
IRI datatype = null;
String langTag = null;
if (ctx.IRIREF() != null) {
- String iri = abstractNTriplesQuadsListener.unescapeUri(stripAngles(ctx.IRIREF().getText()));
+ String iri = termDecoder.unescapeUri(stripAngles(ctx.IRIREF().getText()));
datatype = factory.createIRI(iri);
} else if (ctx.LANGTAG() != null) {
langTag = ctx.LANGTAG().getText().substring(1);
}
- return abstractNTriplesQuadsListener.createLiteral(label, datatype, langTag);
+ return termDecoder.createLiteral(label, datatype, langTag);
}
/**
* Strips angle brackets from an IRI reference.
*/
private String stripAngles(String iriRef) {
- return abstractNTriplesQuadsListener.stripAngles(iriRef);
+ return termDecoder.stripAngles(iriRef);
}
/**
@@ -167,34 +162,11 @@ private String stripAngles(String iriRef) {
* @throws ParsingException if the blank node label is invalid.
*/
protected void validateBlankNodeLabel(String label) {
- abstractNTriplesQuadsListener.validateBlankNodeLabel(label);
+ termDecoder.validateBlankNodeLabel(label);
if (!label.matches("^\\w[\\w\\-\\.]*$")) {
throw new ParsingException("Invalid blank node label syntax: " + label);
}
}
- /**
- * Unescapes common N-Quads literal escape sequences.
- *
- * @param literalText The raw literal string from ANTLR (including quotes)
- * @return The unescaped literal string without surrounding quotes
- * @deprecated Use helper.unescapeLiteral instead
- */
- @Deprecated(since = "4.5.0", forRemoval = false)
- protected String unescapeLiteral(String literalText) {
- return abstractNTriplesQuadsListener.unescapeLiteral(literalText);
- }
-
- /**
- * Unescapes common N-Quads URI escape sequences.
- *
- * @param uri The escaped URI string
- * @return The unescaped URI string
- * @deprecated Use helper.unescapeUri instead
- */
- @Deprecated(since = "4.5.0", forRemoval = false)
- protected String unescapeUri(String uri) {
- return abstractNTriplesQuadsListener.unescapeUri(uri);
- }
}
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/nquads/NQuadsParser.java b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/nquads/NQuadsParser.java
index 76dcee7fc..6c9d44b2c 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/nquads/NQuadsParser.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/nquads/NQuadsParser.java
@@ -71,7 +71,7 @@ public void parse(Reader reader, String baseURI) throws ParsingException {
fr.inria.corese.core.next.generated.antlr.NQuadsParser parser = new fr.inria.corese.core.next.generated.antlr.NQuadsParser(tokens);
configureErrorHandling(parser);
- NQuadsListener listener = new NQuadsListener(getModel(), getValueFactory(), getConfig());
+ NQuadsListener listener = new NQuadsListener(getModel(), getValueFactory());
LineRDFParser.parse(reader, lexer, parser, parser::nquadsDoc, listener);
} catch (ParsingException e) {
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/ntriples/NTriplesListener.java b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/ntriples/NTriplesListener.java
index 606b960e0..5cbb605b6 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/ntriples/NTriplesListener.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/ntriples/NTriplesListener.java
@@ -6,9 +6,8 @@
import fr.inria.corese.core.next.data.api.term.Resource;
import fr.inria.corese.core.next.data.api.term.Value;
import fr.inria.corese.core.next.data.api.factory.ValueFactory;
-import fr.inria.corese.core.next.data.api.io.option.IOOptions;
import fr.inria.corese.core.next.data.api.exception.ParsingException;
-import fr.inria.corese.core.next.data.impl.io.parser.support.AbstractNTriplesNQuadsListener;
+import fr.inria.corese.core.next.data.impl.io.parser.support.NTriplesQuadsTermDecoder;
import fr.inria.corese.core.next.generated.antlr.NTriplesBaseListener;
import fr.inria.corese.core.next.generated.antlr.NTriplesParser;
@@ -19,7 +18,7 @@
*/
public class NTriplesListener extends NTriplesBaseListener {
- private final AbstractNTriplesNQuadsListener abstractNTriplesQuadsListener;
+ private final NTriplesQuadsTermDecoder termDecoder;
private final Model model;
private final ValueFactory factory;
@@ -31,12 +30,11 @@ public class NTriplesListener extends NTriplesBaseListener {
*
* @param model The RDF model to populate.
* @param factory The ValueFactory for creating RDF resources.
- * @param options IOOptions for configuration (if any).
*/
- public NTriplesListener(Model model, ValueFactory factory, IOOptions options) {
+ public NTriplesListener(Model model, ValueFactory factory) {
this.model = model;
this.factory = factory;
- this.abstractNTriplesQuadsListener = new AbstractNTriplesNQuadsListener(model, factory, options) {};
+ this.termDecoder = new NTriplesQuadsTermDecoder(factory);
}
@Override
@@ -58,12 +56,12 @@ public void exitTriple(NTriplesParser.TripleContext ctx) {
*/
public Resource extractSubject(NTriplesParser.SubjectContext ctx) {
if (ctx.IRIREF() != null) {
- String iri = abstractNTriplesQuadsListener.unescapeUri(abstractNTriplesQuadsListener.stripAngles(ctx.IRIREF().getText()));
+ String iri = termDecoder.unescapeUri(termDecoder.stripAngles(ctx.IRIREF().getText()));
return factory.createIRI(iri);
}
if (ctx.BLANK_NODE_LABEL() != null) {
- String label = abstractNTriplesQuadsListener.extractBlankNodeLabel(ctx.BLANK_NODE_LABEL().getText());
- abstractNTriplesQuadsListener.validateBlankNodeLabel(label);
+ String label = termDecoder.extractBlankNodeLabel(ctx.BLANK_NODE_LABEL().getText());
+ termDecoder.validateBlankNodeLabel(label);
return factory.createBNode(label);
}
throw new ParsingException("Unsupported N-Triples subject: " + ctx.getText());
@@ -74,7 +72,7 @@ public Resource extractSubject(NTriplesParser.SubjectContext ctx) {
*/
public IRI extractPredicate(NTriplesParser.PredicateContext ctx) {
if (ctx.IRIREF() != null) {
- String iri = abstractNTriplesQuadsListener.unescapeUri(abstractNTriplesQuadsListener.stripAngles(ctx.IRIREF().getText()));
+ String iri = termDecoder.unescapeUri(termDecoder.stripAngles(ctx.IRIREF().getText()));
return factory.createIRI(iri);
}
throw new ParsingException("Unsupported N-Triples predicate: " + ctx.getText());
@@ -85,12 +83,12 @@ public IRI extractPredicate(NTriplesParser.PredicateContext ctx) {
*/
public Value extractObject(NTriplesParser.ObjectContext ctx) {
if (ctx.IRIREF() != null) {
- String iri = abstractNTriplesQuadsListener.unescapeUri(abstractNTriplesQuadsListener.stripAngles(ctx.IRIREF().getText()));
+ String iri = termDecoder.unescapeUri(termDecoder.stripAngles(ctx.IRIREF().getText()));
return factory.createIRI(iri);
}
if (ctx.BLANK_NODE_LABEL() != null) {
- String label = abstractNTriplesQuadsListener.extractBlankNodeLabel(ctx.BLANK_NODE_LABEL().getText());
- abstractNTriplesQuadsListener.validateBlankNodeLabel(label);
+ String label = termDecoder.extractBlankNodeLabel(ctx.BLANK_NODE_LABEL().getText());
+ termDecoder.validateBlankNodeLabel(label);
return factory.createBNode(label);
}
if (ctx.literal() != null) {
@@ -104,56 +102,19 @@ public Value extractObject(NTriplesParser.ObjectContext ctx) {
*/
protected Literal extractLiteral(NTriplesParser.LiteralContext ctx) {
String rawText = ctx.STRING_LITERAL_QUOTE().getText();
- String label = abstractNTriplesQuadsListener.unescapeLiteral(rawText);
+ String label = termDecoder.unescapeLiteral(rawText);
IRI datatype = null;
String langTag = null;
if (ctx.IRIREF() != null) {
- String iri = abstractNTriplesQuadsListener.unescapeUri(abstractNTriplesQuadsListener.stripAngles(ctx.IRIREF().getText()));
+ String iri = termDecoder.unescapeUri(termDecoder.stripAngles(ctx.IRIREF().getText()));
datatype = factory.createIRI(iri);
} else if (ctx.LANGTAG() != null) {
langTag = ctx.LANGTAG().getText().substring(1);
}
- return abstractNTriplesQuadsListener.createLiteral(label, datatype, langTag);
+ return termDecoder.createLiteral(label, datatype, langTag);
}
- /**
- * Validates a blank node label according to RDF N-Triples specification.
- * Blank node labels must not be empty and must not contain a colon.
- *
- * @param label The blank node label (without the "_:" prefix)
- * @throws ParsingException if the label is invalid
- * @deprecated Use helper.validateBlankNodeLabel instead
- */
- @Deprecated(since = "4.5.0", forRemoval = false)
- protected void validateBlankNodeLabel(String label) throws ParsingException {
- abstractNTriplesQuadsListener.validateBlankNodeLabel(label);
- }
-
- /**
- * Unescapes common N-Triples literal escape sequences.
- *
- * @param literalText The raw literal string from ANTLR (including quotes)
- * @return The unescaped literal string without surrounding quotes
- * @deprecated Use helper.unescapeLiteral instead
- */
- @Deprecated(since = "4.5.0", forRemoval = false)
- public String unescapeLiteral(String literalText) {
- return abstractNTriplesQuadsListener.unescapeLiteral(literalText);
- }
-
- /**
- * Unescapes common N-Triples URI escape sequences.
- *
- * @param uri The escaped URI string
- * @return The unescaped URI string
- * @deprecated Use helper.unescapeUri instead
- */
- @Deprecated(since = "4.5.0", forRemoval = false)
- protected String unescapeUri(String uri) {
- return abstractNTriplesQuadsListener.unescapeUri(uri);
- }
}
-
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/ntriples/NTriplesParser.java b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/ntriples/NTriplesParser.java
index 37f7f1fbd..70e2afee3 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/ntriples/NTriplesParser.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/ntriples/NTriplesParser.java
@@ -84,7 +84,7 @@ public void parse(Reader reader, String baseURI) throws ParsingException {
antlrParser.setErrorHandler(new BailErrorStrategy());
antlrParser.addErrorListener(new NTriplesErrorListener());
- NTriplesListener listener = new NTriplesListener(getModel(), getValueFactory(), getConfig());
+ NTriplesListener listener = new NTriplesListener(getModel(), getValueFactory());
LineRDFParser.parse(reader, lexer, antlrParser, antlrParser::ntriplesDoc, listener);
} catch (ParseCancellationException pce) {
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/rdfa/RDFaParser.java b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/rdfa/RDFaParser.java
index db5648063..f3e594eb2 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/rdfa/RDFaParser.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/rdfa/RDFaParser.java
@@ -41,7 +41,7 @@
* This parser does NOT support vocabulary expansion
*
*/
-public class RDFaParser extends AbstractRDFParser {
+public final class RDFaParser extends AbstractRDFParser {
private static final Logger logger = LoggerFactory.getLogger(RDFaParser.class);
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/support/AbstractTurtleTriGListener.java b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/support/AbstractTurtleTriGListener.java
index 4551ac417..f4d1017d3 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/support/AbstractTurtleTriGListener.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/support/AbstractTurtleTriGListener.java
@@ -51,7 +51,6 @@ protected AbstractTurtleTriGListener(Model model, ValueFactory factory, String b
this.baseURI = baseURI;
this.prefixHandler = new PrefixHandler(true);
- initializeBasePrefix();
}
/**
@@ -68,12 +67,6 @@ public BNode scopedBlankNode(String label) {
return blankNodeScope.computeIfAbsent(label, k -> factory.createBNode());
}
- /**
- * Registers the base URI as the empty prefix namespace.
- */
- public void initializeBasePrefix() {
- }
-
/**
* Extracts IRI from angle brackets and processes Unicode escape sequences.
*
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/support/AbstractNTriplesNQuadsListener.java b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/support/NTriplesQuadsTermDecoder.java
similarity index 92%
rename from src/main/java/fr/inria/corese/core/next/data/impl/io/parser/support/AbstractNTriplesNQuadsListener.java
rename to src/main/java/fr/inria/corese/core/next/data/impl/io/parser/support/NTriplesQuadsTermDecoder.java
index a226c7129..d9480fb22 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/support/AbstractNTriplesNQuadsListener.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/support/NTriplesQuadsTermDecoder.java
@@ -2,32 +2,26 @@
import fr.inria.corese.core.next.data.api.term.IRI;
import fr.inria.corese.core.next.data.api.term.Literal;
-import fr.inria.corese.core.next.data.api.model.Model;
import fr.inria.corese.core.next.data.api.factory.ValueFactory;
-import fr.inria.corese.core.next.data.api.io.option.IOOptions;
import fr.inria.corese.core.next.data.api.exception.ParsingException;
/**
- * Base class for N-Triples/N-Quads parsers providing common escape handling
- * and validation logic.
+ * Decodes lexical RDF terms shared by the N-Triples and N-Quads listeners.
+ *
+ *
This helper handles escapes and term construction; it does not receive
+ * parse-tree events or retain the destination model and parser configuration.
*/
-public abstract class AbstractNTriplesNQuadsListener {
+public final class NTriplesQuadsTermDecoder {
- protected final Model model;
- protected final ValueFactory factory;
- protected final IOOptions options;
+ private final ValueFactory factory;
/**
- * Constructs a parser helper.
+ * Creates a decoder using the listener's RDF value factory.
*
- * @param model RDF model to populate
* @param factory ValueFactory for creating RDF terms
- * @param options IO configuration options
*/
- protected AbstractNTriplesNQuadsListener(Model model, ValueFactory factory, IOOptions options) {
- this.model = model;
+ public NTriplesQuadsTermDecoder(ValueFactory factory) {
this.factory = factory;
- this.options = options;
}
/**
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/nquads/NQuadsSerializerOptions.java b/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/nquads/NQuadsSerializerOptions.java
index 2b7239827..e1e9101b6 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/nquads/NQuadsSerializerOptions.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/nquads/NQuadsSerializerOptions.java
@@ -26,7 +26,7 @@ protected NQuadsSerializerOptions(Builder builder) {
* Provides a fluent API for constructing {@code NQuadsConfig} instances with default values
* specific to the N-Quads format.
*/
- public static class Builder extends AbstractNFamilyOptions.AbstractNFamilyBuilder {
+ public static final class Builder extends AbstractNFamilyOptions.AbstractNFamilyBuilder {
/**
* Default constructor initializes all options with their default values for N-Quads.
*/
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/ntriples/NTriplesSerializerOptions.java b/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/ntriples/NTriplesSerializerOptions.java
index 59e246932..e0fc5d45c 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/ntriples/NTriplesSerializerOptions.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/ntriples/NTriplesSerializerOptions.java
@@ -26,7 +26,7 @@ protected NTriplesSerializerOptions(Builder builder) {
* Provides a fluent API for constructing {@code NTriplesConfig} instances with default values
* specific to the N-Triples format.
*/
- public static class Builder extends AbstractNFamilyOptions.AbstractNFamilyBuilder {
+ public static final class Builder extends AbstractNFamilyOptions.AbstractNFamilyBuilder {
/**
* Default constructor initializes all options with their default values for N-Triples.
*/
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/option/AbstractSerializerOptions.java b/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/option/AbstractSerializerOptions.java
index aed74d2d9..9fbe54f2c 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/option/AbstractSerializerOptions.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/option/AbstractSerializerOptions.java
@@ -198,23 +198,23 @@ public abstract static class AbstractBuilder> {
protected AbstractBuilder(IOOptions otherOptions) {
if(otherOptions instanceof AbstractSerializerOptions abstractSerializerOptions) {
- this.escapeUnicode(abstractSerializerOptions.escapeUnicode());
- this.trailingDot(abstractSerializerOptions.trailingDot());
- this.strictMode(abstractSerializerOptions.isStrictMode());
- this.validateURIs(abstractSerializerOptions.validateURIs());
- this.includeContext(abstractSerializerOptions.includeContext());
+ this.escapeUnicode = abstractSerializerOptions.escapeUnicode();
+ this.trailingDot = abstractSerializerOptions.trailingDot();
+ this.strictMode = abstractSerializerOptions.isStrictMode();
+ this.validateURIs = abstractSerializerOptions.validateURIs();
+ this.includeContext = abstractSerializerOptions.includeContext();
}
if(otherOptions instanceof BaseIRIOptions baseIRIOptions) {
- this.baseIRI(baseIRIOptions.getBaseIRI());
+ this.baseIRI = baseIRIOptions.getBaseIRI();
}
if(otherOptions instanceof LineEndingOptions lineEndingOptions) {
- this.lineEnding(lineEndingOptions.getLineEnding());
+ this.lineEnding = Objects.requireNonNull(lineEndingOptions.getLineEnding());
}
if(otherOptions instanceof BlankNodeIdGenerationOptions blankNodeIdGenerationOptions) {
- this.stableBlankNodeIds(blankNodeIdGenerationOptions.stableBlankNodeIds());
+ this.stableBlankNodeIds = blankNodeIdGenerationOptions.stableBlankNodeIds();
}
if(otherOptions instanceof DatatypePolicyOptions datatypePolicyOptions) {
- this.literalDatatypePolicy(datatypePolicyOptions.getLiteralDatatypePolicy());
+ this.literalDatatypePolicy = Objects.requireNonNull(datatypePolicyOptions.getLiteralDatatypePolicy());
}
}
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/option/AbstractTFamilyOptions.java b/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/option/AbstractTFamilyOptions.java
index 179922f7c..f5d8d86b2 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/option/AbstractTFamilyOptions.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/option/AbstractTFamilyOptions.java
@@ -130,7 +130,7 @@ protected AbstractTFamilyOptions(AbstractTFamilyBuilder> builder) {
this.sortSubjects = builder.sortSubjects;
this.sortPredicates = builder.sortPredicates;
- if (this.escapeUnicode() && this.useMultilineLiterals) {
+ if (this.escapeUnicode && this.useMultilineLiterals) {
throw new IllegalArgumentException("Cannot enable both escapeUnicode and useMultilineLiterals in Turtle TriG configs.");
}
}
@@ -315,25 +315,25 @@ public abstract static class AbstractTFamilyBuilder {
+ public static final class Builder extends AbstractSerializerOptions.AbstractBuilder {
protected boolean usePrefixes = true;
protected boolean autoDeclarePrefixes = true;
protected PrefixOrdering prefixOrdering = PrefixOrdering.ALPHABETICAL;
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/trig/TriGSerializerOptions.java b/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/trig/TriGSerializerOptions.java
index ca23d7fd7..c5b3f6c24 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/trig/TriGSerializerOptions.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/trig/TriGSerializerOptions.java
@@ -28,7 +28,7 @@ protected TriGSerializerOptions(Builder builder) {
* Provides a fluent API for constructing {@code TriGConfig} instances with default values
* specific to the TriG format.
*/
- public static class Builder extends AbstractTFamilyOptions.AbstractTFamilyBuilder {
+ public static final class Builder extends AbstractTFamilyOptions.AbstractTFamilyBuilder {
/**
* Default constructor initializes all options with their default values for TriG.
*/
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/turtle/TurtleSerializerOptions.java b/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/turtle/TurtleSerializerOptions.java
index 6930f974a..6b4141357 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/turtle/TurtleSerializerOptions.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/io/serializer/turtle/TurtleSerializerOptions.java
@@ -29,7 +29,7 @@ protected TurtleSerializerOptions(Builder builder) {
* Provides a fluent API for constructing {@code TurtleConfig} instances with default values
* specific to the Turtle format.
*/
- public static class Builder extends AbstractTFamilyOptions.AbstractTFamilyBuilder {
+ public static final class Builder extends AbstractTFamilyOptions.AbstractTFamilyBuilder {
/**
* Default constructor initializes all options with their default values for Turtle.
*/
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/model/ModelException.java b/src/main/java/fr/inria/corese/core/next/data/impl/model/ModelException.java
index 50529ea71..ac188b7e1 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/model/ModelException.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/model/ModelException.java
@@ -6,6 +6,9 @@
* Exception thrown when a Model operation fails.
*/
public class ModelException extends CoreseException {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
+
/**
* The operation that failed.
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/namespace/PrefixHandler.java b/src/main/java/fr/inria/corese/core/next/data/impl/namespace/PrefixHandler.java
index 4facd9a9f..cc3ca00c4 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/namespace/PrefixHandler.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/namespace/PrefixHandler.java
@@ -21,7 +21,7 @@
/**
* Unified prefix handler for managing namespace prefix mappings across all RDF formats.
*/
-public class PrefixHandler implements PrefixMapping {
+public final class PrefixHandler implements PrefixMapping {
/**
* Map of prefix to namespace URI.
diff --git a/src/main/java/fr/inria/corese/core/next/data/spi/model/AbstractStatement.java b/src/main/java/fr/inria/corese/core/next/data/spi/model/AbstractStatement.java
index 0e13b5491..4201e7415 100644
--- a/src/main/java/fr/inria/corese/core/next/data/spi/model/AbstractStatement.java
+++ b/src/main/java/fr/inria/corese/core/next/data/spi/model/AbstractStatement.java
@@ -17,6 +17,9 @@
*/
public abstract class AbstractStatement implements Statement {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
+
/**
* Compares this statement to another object for equality.
diff --git a/src/main/java/fr/inria/corese/core/next/data/spi/term/AbstractBNode.java b/src/main/java/fr/inria/corese/core/next/data/spi/term/AbstractBNode.java
index 0ddf72c62..78cc100bb 100644
--- a/src/main/java/fr/inria/corese/core/next/data/spi/term/AbstractBNode.java
+++ b/src/main/java/fr/inria/corese/core/next/data/spi/term/AbstractBNode.java
@@ -7,6 +7,9 @@
* A blank node (BNode) ...
*/
public abstract class AbstractBNode implements BNode {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
+
/**
* Returns the string value of this blank node, which is its unique identifier.
diff --git a/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractDuration.java b/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractDuration.java
index 10ff2fa7e..01f3d9407 100644
--- a/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractDuration.java
+++ b/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractDuration.java
@@ -17,6 +17,9 @@
* Abstract class representing a duration literal in RDF.
*/
public abstract class AbstractDuration extends AbstractLiteral implements Comparable {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
+
/**
* Constructor for AbstractDuration.
diff --git a/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractLiteral.java b/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractLiteral.java
index 178204292..8b5278b0e 100644
--- a/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractLiteral.java
+++ b/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractLiteral.java
@@ -20,6 +20,9 @@
* Base class for all literals. All value accessors throw an exception by default.
*/
public abstract class AbstractLiteral implements Literal {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
+
protected IRI datatype;
diff --git a/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractNumber.java b/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractNumber.java
index c04e66cc8..c503d9e45 100644
--- a/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractNumber.java
+++ b/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractNumber.java
@@ -6,6 +6,9 @@
* Abstract class representing a number literal in RDF.
*/
public abstract class AbstractNumber extends AbstractLiteral implements Comparable {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
+
/**
diff --git a/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractStringLiteral.java b/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractStringLiteral.java
index 73e0e6a2e..683e7fe7b 100644
--- a/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractStringLiteral.java
+++ b/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractStringLiteral.java
@@ -10,6 +10,9 @@
@SuppressWarnings("java:S1210")
public abstract class AbstractStringLiteral extends AbstractLiteral implements Comparable {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
+
/**
* Constructs an instance of AbstractStringLiteral with the specified datatype.
*
diff --git a/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractTemporalPointLiteral.java b/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractTemporalPointLiteral.java
index dbae48126..1dccba928 100644
--- a/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractTemporalPointLiteral.java
+++ b/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractTemporalPointLiteral.java
@@ -6,6 +6,9 @@
* Represents a temporal literal representing a point in time. Typically a date or a date and time. Implements the comparison between temporal literals.
*/
public abstract class AbstractTemporalPointLiteral extends AbstractLiteral implements Comparable {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
+
/**
* Constructor for AbstractTemporalPointLiteral.
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/Query.java b/src/main/java/fr/inria/corese/core/next/query/api/Query.java
index 77982e326..04204a3cd 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/Query.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/Query.java
@@ -2,6 +2,7 @@
import fr.inria.corese.core.next.data.api.term.Value;
import fr.inria.corese.core.next.query.api.dataset.Dataset;
+import fr.inria.corese.core.next.query.api.diagnostics.QueryExecution;
import fr.inria.corese.core.next.query.api.exception.QueryEvaluationException;
import fr.inria.corese.core.next.query.api.result.BindingSet;
import fr.inria.corese.core.next.query.api.result.GraphQueryResult;
@@ -50,6 +51,23 @@ public interface Query {
*/
T evaluate() throws QueryEvaluationException;
+ /**
+ * Executes this query once with planning and producer diagnostics enabled.
+ * Bindings, dataset, timeout and result ownership follow {@link #evaluate()}.
+ * This is an actual execution, not a dry-run explanation. No extra storage
+ * counts or scans are performed solely to populate the profile. Ordinary
+ * {@code evaluate()} calls do not allocate diagnostic collectors or wrappers.
+ *
+ *
For progressive SELECT results, {@code profile()} snapshots work performed
+ * so far. Consume or close the result before requesting final measurements.
+ * Lazy evaluation failures surface while advancing the result and release its
+ * resources. Prepared queries and diagnostic access are not thread-safe.
+ *
+ * @return result and access to immutable progress measurements
+ * @throws QueryEvaluationException if evaluation fails
+ */
+ QueryExecution evaluateWithDiagnostics() throws QueryEvaluationException;
+
/**
* Sets or replaces an initial variable binding.
*
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/diagnostics/QueryExecution.java b/src/main/java/fr/inria/corese/core/next/query/api/diagnostics/QueryExecution.java
new file mode 100644
index 000000000..098a62e39
--- /dev/null
+++ b/src/main/java/fr/inria/corese/core/next/query/api/diagnostics/QueryExecution.java
@@ -0,0 +1,40 @@
+package fr.inria.corese.core.next.query.api.diagnostics;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
+/**
+ * Query result and access to immutable measurements of its progress. For a lazy
+ * SELECT, profile() snapshots the work performed so far; consume or close the
+ * result before requesting final measurements. Client idle time is excluded.
+ * This wrapper owns no resources. The caller must close tuple and graph results.
+ * Both result consumption and profile access belong to the evaluation thread.
+ *
+ * @param query result type
+ */
+public final class QueryExecution {
+ private final T result;
+ private final Supplier profiles;
+
+ /**
+ * Creates an execution with a non-null result and profile source.
+ *
+ * @param result evaluated result owned by the caller
+ * @param profiles source of immutable progress measurements
+ * @throws NullPointerException if either argument is null
+ */
+ public QueryExecution(T result, Supplier profiles) {
+ this.result = Objects.requireNonNull(result, "result");
+ this.profiles = Objects.requireNonNull(profiles, "profiles");
+ }
+
+ /** @return the evaluated result, which must be closed when resource-bearing */
+ public T result() {
+ return result;
+ }
+
+ /** @return an immutable snapshot of the work completed so far */
+ public QueryExecutionProfile profile() {
+ return profiles.get();
+ }
+}
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/diagnostics/QueryExecutionProfile.java b/src/main/java/fr/inria/corese/core/next/query/api/diagnostics/QueryExecutionProfile.java
new file mode 100644
index 000000000..c2d8017d1
--- /dev/null
+++ b/src/main/java/fr/inria/corese/core/next/query/api/diagnostics/QueryExecutionProfile.java
@@ -0,0 +1,191 @@
+package fr.inria.corese.core.next.query.api.diagnostics;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Immutable measurements from an explicitly instrumented evaluation. Timings
+ * include instrumentation overhead and are wall-clock durations, not CPU times.
+ * Patterns follow the compiled traversal order within each operator scope; this
+ * is not a chronological trace of joins, branches or repeated invocations.
+ *
+ * @param compilationNanos time translating the parsed AST and capturing its compilation image;
+ * excludes parsing and initial query preparation
+ * @param planningNanos time completing the runtime query, including ordering,
+ * statistics and node indexing
+ * @param executionSetupNanos time creating independent execution containers from a
+ * compilation image; zero for direct non-prepared calls
+ * @param planReused whether a cached compilation image was reused without translation or planning
+ * @param evaluationNanos time executing the completed runtime query, including
+ * result mappings and cursor cleanup; excludes
+ * client idle time and graph template materialization
+ * @param patterns ordered logical patterns, followed by auxiliary runtime lookups
+ * such as path steps and dynamically compiled EXISTS patterns
+ * @param delivery actual result-delivery mode, fallback reason and first-row observation
+ */
+public record QueryExecutionProfile(long compilationNanos, long planningNanos, long executionSetupNanos,
+ long evaluationNanos, boolean planReused, List patterns, ResultDelivery delivery) {
+
+ /**
+ * Copies the pattern list so the profile retains no mutable engine state.
+ *
+ * @throws IllegalArgumentException if a duration is negative
+ * @throws NullPointerException if the list, an element or the delivery observation is null
+ */
+ public QueryExecutionProfile {
+ if (compilationNanos < 0 || planningNanos < 0 || executionSetupNanos < 0 || evaluationNanos < 0) {
+ throw new IllegalArgumentException("Durations must be non-negative");
+ }
+ patterns = List.copyOf(patterns);
+ Objects.requireNonNull(delivery, "delivery");
+ }
+
+ /** How this invocation makes its result available to the caller. */
+ public enum ExecutionMode {
+ /** Eligible SELECT rows are produced as the caller advances the cursor. */
+ STREAMING,
+ /** Evaluation completes before the result is returned. */
+ MATERIALIZED
+ }
+
+ /** First guard that selected full evaluation; this is not an exhaustive query analysis. */
+ public enum FallbackReason {
+ /** No fallback: a progressive cursor was actually opened. */
+ NONE,
+ /** The invocation is ASK, CONSTRUCT or DESCRIBE rather than SELECT. */
+ QUERY_FORM,
+ /** ORDER BY requires completing the input before delivering the selected rows. */
+ ORDER_BY,
+ /** GROUP BY, aggregate expressions or HAVING require full evaluation. */
+ AGGREGATION,
+ /** A property path uses the established complete evaluator. */
+ PROPERTY_PATH,
+ /** A nested SELECT uses the established subquery evaluator. */
+ SUBQUERY,
+ /** MINUS uses the established buffered evaluator. */
+ MINUS,
+ /** SERVICE uses the established service evaluator. */
+ SERVICE,
+ /** An OPTIONAL condition or right-hand scope requires full evaluation. */
+ COMPLEX_OPTIONAL,
+ /** A scoped join retains its buffered strategy instead of repeated independent scans. */
+ SCOPED_JOIN,
+ /** Repeated evaluation of an independent scope would risk quadratic work. */
+ REPEATED_SCOPE,
+ /** Another operator or extension is not supported by the progressive cursor. */
+ UNSUPPORTED_OPERATOR,
+ /** The backend cannot capture an independent stable read snapshot. */
+ SNAPSHOT_UNAVAILABLE
+ }
+
+ /** Meaning of the first-row timing for this invocation. */
+ public enum FirstRowStatus {
+ /** First-row timing applies only to SELECT results. */
+ NOT_APPLICABLE,
+ /** Neither availability nor successful exhaustion has been observed, including early close or failure. */
+ UNOBSERVED,
+ /** At least one projected solution is ready, whether or not the caller consumed it. */
+ AVAILABLE,
+ /** Evaluation completed successfully with no projected solution after OFFSET and LIMIT. */
+ EMPTY
+ }
+
+ /**
+ * Result strategy and first-row observation, independent of the query's row count.
+ * The timing accumulates work from entry into the profiled evaluation through
+ * the first successful availability observation. It includes compilation and
+ * planning, excludes initial preparation and client idle time, and is frozen
+ * once a row is ready. Materialized SELECT rows are ready before evaluate returns;
+ * streaming rows become ready in the first successful hasNext or next call.
+ * Closing without a successful observation does not establish that a query is empty.
+ *
+ * @param mode actual result-delivery mode for this invocation
+ * @param fallbackReason first materialization guard, or NONE for a progressive cursor
+ * @param firstRowStatus whether a SELECT row is ready, absent or not observed; not applicable to other query forms
+ * @param firstRowNanos accumulated active wall-clock nanoseconds to availability, or -1 unless AVAILABLE
+ */
+ public record ResultDelivery(ExecutionMode mode, FallbackReason fallbackReason,
+ FirstRowStatus firstRowStatus, long firstRowNanos) {
+ /**
+ * Validates the strategy and observation without inventing a timing for an absent row.
+ *
+ * @throws NullPointerException if a classification is null
+ * @throws IllegalArgumentException if the strategy or first-row timing is inconsistent
+ */
+ public ResultDelivery {
+ Objects.requireNonNull(mode, "mode");
+ Objects.requireNonNull(fallbackReason, "fallbackReason");
+ Objects.requireNonNull(firstRowStatus, "firstRowStatus");
+ if ((mode == ExecutionMode.STREAMING) != (fallbackReason == FallbackReason.NONE)) {
+ throw new IllegalArgumentException("Only progressive delivery has no fallback reason");
+ }
+ if ((firstRowStatus == FirstRowStatus.NOT_APPLICABLE) != (fallbackReason == FallbackReason.QUERY_FORM)) {
+ throw new IllegalArgumentException("First-row observations apply only to SELECT");
+ }
+ if (firstRowNanos < -1 || (firstRowStatus == FirstRowStatus.AVAILABLE) != (firstRowNanos >= 0)) {
+ throw new IllegalArgumentException("Only an available first row has a non-negative timing");
+ }
+ }
+ }
+
+ /**
+ * A logical triple/path pattern or an auxiliary lookup compiled during execution (paths or EXISTS).
+ * Estimates describe unbound patterns at planning time, before runtime bindings
+ * and inter-graph deduplication. They are neither join-output predictions nor
+ * guarantees about the state later read by execution. No extra counts are
+ * requested for diagnostics: {@code -1} means the planner did not obtain one.
+ *
+ * @param location operator scope and child positions in the completed plan
+ * @param pattern textual pattern, including its GRAPH scope when applicable
+ * @param auxiliary whether this is an unplanned runtime lookup
+ * @param estimatedCardinality planner's count, or {@code -1} if unavailable
+ * @param iteratorOpenings successful openings of this producer iterable;
+ * these are not necessarily storage cursors
+ * @param candidateEdges edges returned by {@code next()} to the engine,
+ * after producer deduplication and before RDF matching;
+ * summed across invocations, not distinct solutions or
+ * internal index visits
+ */
+ public record Pattern(String location, String pattern, boolean auxiliary,
+ long estimatedCardinality, long iteratorOpenings, long candidateEdges) {
+ /**
+ * Checks the pattern description and non-negative counters.
+ *
+ * @throws NullPointerException if a description is null
+ * @throws IllegalArgumentException if a count is outside its documented range
+ */
+ public Pattern {
+ Objects.requireNonNull(location, "location");
+ Objects.requireNonNull(pattern, "pattern");
+ if (estimatedCardinality < -1 || iteratorOpenings < 0 || candidateEdges < 0) {
+ throw new IllegalArgumentException("Invalid pattern counters");
+ }
+ }
+ }
+
+ /**
+ * Formats the measurements without inspecting the repository or executing a query.
+ *
+ * @return a human-readable plan and its producer counters
+ */
+ public String format() {
+ StringBuilder text = new StringBuilder("Compilation: ").append(compilationNanos)
+ .append(" ns; planning: ").append(planningNanos)
+ .append(" ns; execution setup: ").append(executionSetupNanos)
+ .append(" ns; reused plan: ").append(planReused).append("; evaluation: ").append(evaluationNanos).append(" ns\n");
+ text.append("Delivery: ").append(delivery.mode()).append("; fallback: ").append(delivery.fallbackReason())
+ .append("; first row: ").append(delivery.firstRowStatus());
+ if (delivery.firstRowStatus() == FirstRowStatus.AVAILABLE) {
+ text.append(" (").append(delivery.firstRowNanos()).append(" ns)");
+ }
+ text.append('\n');
+ for (Pattern entry : patterns) {
+ text.append(entry.location()).append(" ").append(entry.pattern())
+ .append(" | estimate=").append(entry.estimatedCardinality() < 0
+ ? "unknown" : Long.toString(entry.estimatedCardinality()))
+ .append("; iterations=").append(entry.iteratorOpenings())
+ .append("; candidates=").append(entry.candidateEdges()).append('\n');
+ }
+ return text.toString();
+ }
+}
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/diagnostics/package-info.java b/src/main/java/fr/inria/corese/core/next/query/api/diagnostics/package-info.java
new file mode 100644
index 000000000..2e832dc9a
--- /dev/null
+++ b/src/main/java/fr/inria/corese/core/next/query/api/diagnostics/package-info.java
@@ -0,0 +1,2 @@
+/** Opt-in execution diagnostics, independent of the mutable engine and storage state. */
+package fr.inria.corese.core.next.query.api.diagnostics;
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryEvaluationException.java b/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryEvaluationException.java
index b52f1ebfc..78ded8360 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryEvaluationException.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryEvaluationException.java
@@ -5,6 +5,9 @@
*/
@SuppressWarnings("java:S110")
public class QueryEvaluationException extends QueryException {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
+
/**
* Constructs a QueryEvaluationException with a detail message.
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryException.java b/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryException.java
index e93e30c68..a982bc00c 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryException.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryException.java
@@ -7,6 +7,9 @@
* Base exception for all query-related errors in the Corese query API.
*/
public class QueryException extends CoreseException {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
+
/**
* Constructs a new QueryException with the specified detail message.
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/exception/QuerySyntaxException.java b/src/main/java/fr/inria/corese/core/next/query/api/exception/QuerySyntaxException.java
index 0cc4162c6..50916d9b9 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/exception/QuerySyntaxException.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/exception/QuerySyntaxException.java
@@ -5,6 +5,9 @@
*/
@SuppressWarnings("java:S110")
public class QuerySyntaxException extends QueryException {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
+
private final int line;
private final int column;
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryTimeoutException.java b/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryTimeoutException.java
index d5d75c31a..4896937c5 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryTimeoutException.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryTimeoutException.java
@@ -5,6 +5,9 @@
*/
@SuppressWarnings("java:S110")
public class QueryTimeoutException extends QueryEvaluationException {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
+
/**
* Constructs a QueryTimeoutException with the timeout value that was exceeded.
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryTypeErrorException.java b/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryTypeErrorException.java
index aa8daad11..9ed657382 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryTypeErrorException.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryTypeErrorException.java
@@ -7,6 +7,9 @@
*/
@SuppressWarnings("java:S110") // Retains the public query exception hierarchy and distinguishes SPARQL type errors.
public final class QueryTypeErrorException extends QueryEvaluationException {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
+
public QueryTypeErrorException(String message) {
super(message);
}
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryValidationException.java b/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryValidationException.java
index de20ae0d9..c44bf79ca 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryValidationException.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryValidationException.java
@@ -5,6 +5,9 @@
*/
@SuppressWarnings("java:S110")
public class QueryValidationException extends QueryException {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
+
/**
* Constructs a QueryValidationException with a detail message.
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/exception/RepositoryException.java b/src/main/java/fr/inria/corese/core/next/query/api/exception/RepositoryException.java
index 30296af26..3b7ebd7f4 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/exception/RepositoryException.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/exception/RepositoryException.java
@@ -9,6 +9,9 @@
*/
@SuppressWarnings("java:S110")
public class RepositoryException extends QueryException {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
+
/**
* Constructs a RepositoryException with a detail message.
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/exception/UnsupportedQueryFeatureException.java b/src/main/java/fr/inria/corese/core/next/query/api/exception/UnsupportedQueryFeatureException.java
index 939dd14b9..399fa7968 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/exception/UnsupportedQueryFeatureException.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/exception/UnsupportedQueryFeatureException.java
@@ -5,6 +5,9 @@
*/
@SuppressWarnings("java:S110")
public class UnsupportedQueryFeatureException extends QueryException {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
+
/**
* Constructs an UnsupportedQueryFeatureException with a detail message.
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/result/TupleQueryResult.java b/src/main/java/fr/inria/corese/core/next/query/api/result/TupleQueryResult.java
index e01f3012e..8d29faf40 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/result/TupleQueryResult.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/result/TupleQueryResult.java
@@ -16,7 +16,17 @@
* corresponding to the projection variables of the query.
*
*
- *
Implementations are not required to be thread-safe.
+ *
Results may evaluate lazily during {@link #hasNext()} and {@link #next()}.
+ * Native progressive results must be consumed on the thread that evaluated the
+ * query. They retain a stable read snapshot, including the transaction's visible
+ * uncommitted data, until exhaustion, failure or closure. Always use
+ * try-with-resources, especially for partial consumption: closing a stream early
+ * releases the result, but a stream terminal operation alone does not close it.
+ * Closing a result never commits or rolls back its source transaction.
+ *
+ *
Implementations are not required to be thread-safe. Native cursors permit
+ * cross-thread close for cancellation. Their query timeout spans preparation,
+ * traversal and client idle time; idle expiration never interrupts client code.
*
* @see TupleQuery
* @see TupleQuery#evaluate()
@@ -37,6 +47,8 @@ public interface TupleQueryResult extends Closeable, Iterable {
*
* @return {@code true} if another binding set is available, {@code false} otherwise
* @throws IllegalStateException if this result has been closed
+ * @throws fr.inria.corese.core.next.query.api.exception.QueryEvaluationException
+ * if lazy evaluation fails, is interrupted or reaches its timeout
*/
boolean hasNext();
@@ -47,6 +59,8 @@ public interface TupleQueryResult extends Closeable, Iterable {
* @throws NoSuchElementException if no more solutions are available
* (i.e., {@link #hasNext()} would return {@code false})
* @throws IllegalStateException if this result has been closed
+ * @throws fr.inria.corese.core.next.query.api.exception.QueryEvaluationException
+ * if lazy evaluation fails, is interrupted or reaches its timeout
*/
BindingSet next();
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/diagnostics/DiagnosticProducer.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/diagnostics/DiagnosticProducer.java
new file mode 100644
index 000000000..e830a5264
--- /dev/null
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/diagnostics/DiagnosticProducer.java
@@ -0,0 +1,74 @@
+package fr.inria.corese.core.next.query.impl.engine.diagnostics;
+
+import fr.inria.corese.core.next.query.impl.engine.model.Edge;
+import fr.inria.corese.core.next.query.impl.engine.model.Node;
+import fr.inria.corese.core.next.query.impl.engine.pattern.Query;
+import fr.inria.corese.core.next.query.impl.engine.spi.Environment;
+import fr.inria.corese.core.next.query.impl.engine.storage.StorageManagerProducer;
+import fr.inria.corese.core.next.storage.api.StorageManager;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Opt-in producer decorator. The ordinary producer and its cursors have no
+ * diagnostic fields, counter updates or per-statement branches. The underlying
+ * producer retains cursor ownership, cancellation and snapshot release.
+ */
+public final class DiagnosticProducer extends StorageManagerProducer {
+ private final ExecutionDiagnostics diagnostics;
+
+ /**
+ * Creates a request-scoped instrumented producer.
+ *
+ * @param storage session-visible storage
+ * @param diagnostics collector confined to the evaluation thread
+ * @throws NullPointerException if either argument is null
+ */
+ public DiagnosticProducer(StorageManager storage, ExecutionDiagnostics diagnostics) {
+ super(storage);
+ this.diagnostics = Objects.requireNonNull(diagnostics, "diagnostics");
+ }
+
+ @Override
+ public long estimateCardinality(Query query, Node graphNode, Edge edge) {
+ long count = super.estimateCardinality(query, graphNode, edge);
+ diagnostics.estimated(edge, count);
+ return count;
+ }
+
+ @Override
+ public Iterable getEdges(Node graphNode, List from, Edge queryEdge, Environment environment) {
+ Iterable delegate = super.getEdges(graphNode, from, queryEdge, environment);
+ return new DiagnosticEdges(delegate, diagnostics.executing(queryEdge, graphNode));
+ }
+
+ @Override
+ public void closeEdges(Iterable edges) {
+ super.closeEdges(edges instanceof DiagnosticEdges measured ? measured.delegate : edges);
+ }
+
+ /** Wraps only explicitly profiled traversals and preserves repeatable iteration. */
+ private record DiagnosticEdges(Iterable delegate,
+ ExecutionDiagnostics.PatternCounters counters) implements Iterable {
+ @Override
+ public Iterator iterator() {
+ Iterator source = delegate.iterator();
+ counters.opened();
+ return new Iterator<>() {
+ @Override
+ public boolean hasNext() {
+ return source.hasNext();
+ }
+
+ @Override
+ public Edge next() {
+ Edge edge = source.next();
+ counters.returned();
+ return edge;
+ }
+ };
+ }
+ }
+}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/diagnostics/ExecutionDiagnostics.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/diagnostics/ExecutionDiagnostics.java
new file mode 100644
index 000000000..9dc53a5d0
--- /dev/null
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/diagnostics/ExecutionDiagnostics.java
@@ -0,0 +1,268 @@
+package fr.inria.corese.core.next.query.impl.engine.diagnostics;
+
+import fr.inria.corese.core.next.query.api.diagnostics.QueryExecutionProfile;
+import fr.inria.corese.core.next.query.api.diagnostics.QueryExecutionProfile.ExecutionMode;
+import fr.inria.corese.core.next.query.api.diagnostics.QueryExecutionProfile.FallbackReason;
+import fr.inria.corese.core.next.query.api.diagnostics.QueryExecutionProfile.FirstRowStatus;
+import fr.inria.corese.core.next.query.api.diagnostics.QueryExecutionProfile.ResultDelivery;
+import fr.inria.corese.core.next.data.api.term.Value;
+import fr.inria.corese.core.next.data.impl.io.serializer.rdfc10.StatementUtils;
+import fr.inria.corese.core.next.query.impl.engine.model.Edge;
+import fr.inria.corese.core.next.query.impl.engine.model.Node;
+import fr.inria.corese.core.next.query.impl.engine.path.PropertyPathEdge;
+import fr.inria.corese.core.next.query.impl.engine.pattern.Exp;
+import fr.inria.corese.core.next.query.impl.engine.pattern.Query;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+import java.util.Set;
+import java.util.Objects;
+import java.util.function.LongSupplier;
+
+/**
+ * Collector confined to the evaluation thread. Timeout cancellation closes the
+ * producer but never reads or updates these counters. Only immutable strings,
+ * counts and copied lists escape through {@link #snapshot()}.
+ */
+public final class ExecutionDiagnostics {
+ private final LongSupplier nanoTime;
+ private final IdentityHashMap counters = new IdentityHashMap<>();
+ private final Map auxiliary = new HashMap<>();
+ private final List ordered = new ArrayList<>();
+ private final Set expressions = Collections.newSetFromMap(new IdentityHashMap<>());
+ private long compilationNanos;
+ private long planningNanos;
+ private long evaluationNanos;
+ private long executionSetupNanos;
+ private boolean planReused;
+ private ExecutionMode mode = ExecutionMode.MATERIALIZED;
+ private FallbackReason fallbackReason = FallbackReason.QUERY_FORM;
+ private FirstRowStatus firstRowStatus = FirstRowStatus.NOT_APPLICABLE;
+ private long firstRowNanos = -1;
+ private long activeNanos;
+ private long workStarted;
+ private int workDepth;
+
+ /** Creates an evaluation-thread collector using the monotonic system clock. */
+ public ExecutionDiagnostics() {
+ this(System::nanoTime);
+ }
+
+ /** Creates a collector with a monotonic clock, allowing deterministic timing verification. */
+ ExecutionDiagnostics(LongSupplier nanoTime) {
+ this.nanoTime = Objects.requireNonNull(nanoTime, "nanoTime");
+ }
+
+ /** Starts a segment of query work; nested segments do not double-count time. */
+ public void beginWork() {
+ if (workDepth++ == 0) workStarted = nanoTime.getAsLong();
+ }
+
+ /**
+ * Ends a query-work segment, excluding subsequent client idle time.
+ *
+ * @return elapsed nanoseconds for an outer segment, or zero for a nested segment
+ * @throws IllegalStateException if no query-work segment is active
+ */
+ public long endWork() {
+ if (workDepth == 0) throw new IllegalStateException("No diagnostic work segment is active");
+ if (--workDepth != 0) return 0;
+ long elapsed = nanoTime.getAsLong() - workStarted;
+ activeNanos += elapsed;
+ return elapsed;
+ }
+
+ /**
+ * Records the actual SELECT strategy after checking algebra and snapshot availability.
+ *
+ * @param reason NONE for streaming, otherwise the guard selecting full evaluation
+ * @throws IllegalArgumentException if the reason describes a non-SELECT query form
+ */
+ public void selectDelivery(FallbackReason reason) {
+ Objects.requireNonNull(reason, "reason");
+ if (reason == FallbackReason.QUERY_FORM) throw new IllegalArgumentException("Expected a SELECT strategy");
+ fallbackReason = reason;
+ mode = reason == FallbackReason.NONE ? ExecutionMode.STREAMING : ExecutionMode.MATERIALIZED;
+ firstRowStatus = FirstRowStatus.UNOBSERVED;
+ firstRowNanos = -1;
+ }
+
+ /**
+ * Records the first successful SELECT availability or exhaustion observation.
+ * Later observations cannot overwrite the first timing or turn an early close into emptiness.
+ *
+ * @param available whether a projected solution is ready after OFFSET and LIMIT
+ */
+ public void firstRow(boolean available) {
+ if (firstRowStatus != FirstRowStatus.UNOBSERVED) return;
+ firstRowStatus = available ? FirstRowStatus.AVAILABLE : FirstRowStatus.EMPTY;
+ if (available) {
+ firstRowNanos = activeNanos + (workDepth == 0 ? 0 : nanoTime.getAsLong() - workStarted);
+ }
+ }
+
+ /**
+ * Records runtime-query translation duration.
+ *
+ * @param nanos elapsed wall-clock nanoseconds
+ */
+ public void compilationTime(long nanos) {
+ compilationNanos += nanos;
+ }
+
+ /**
+ * Records completion duration and captures the resulting operator order.
+ *
+ * @param query completed runtime query
+ * @param nanos elapsed completion nanoseconds
+ */
+ public void planned(Query query, long nanos) {
+ planningNanos += nanos;
+ visit(query, "query", null);
+ }
+
+ /**
+ * Records evaluation duration, excluding completion and plan capture.
+ *
+ * @param nanos elapsed evaluation nanoseconds
+ */
+ public void evaluationTime(long nanos) {
+ evaluationNanos += nanos;
+ }
+
+ /** Records structural execution-frame creation, independently of compilation and planning.
+ * @param nanos elapsed frame creation nanoseconds
+ */
+ public void executionSetupTime(long nanos) {
+ executionSetupNanos += nanos;
+ }
+
+ /** Records whether this evaluation used an already cached compilation image.
+ * @param reused whether compilation and planning were reused
+ */
+ public void reusedPlan(boolean reused) {
+ planReused = reused;
+ }
+
+ /**
+ * Remembers an estimate already requested by the planner.
+ *
+ * @param edge identity of the compiled pattern
+ * @param count estimate or {@code -1}
+ */
+ public void estimated(Edge edge, long count) {
+ counters.computeIfAbsent(edge, ignored -> new PatternCounters()).estimate = count;
+ }
+
+ /**
+ * Finds the counters for an executed edge, adding auxiliary runtime lookups when needed.
+ *
+ * @param edge compiled edge or dynamically compiled pattern
+ * @param graph effective graph supplied to the producer
+ * @return mutable counters confined to the evaluation thread
+ */
+ PatternCounters executing(Edge edge, Node graph) {
+ PatternCounters entry = counters.get(edge);
+ if (entry != null && entry.location != null) {
+ return entry;
+ }
+ // Runtime EXISTS/path operators may be recreated for every binding.
+ // Aggregate their identical descriptions without retaining each transient edge.
+ String text = pattern(edge, graph);
+ PatternCounters runtime = auxiliary.get(text);
+ long estimate = entry == null ? -1 : entry.estimate;
+ if (runtime == null) {
+ runtime = new PatternCounters();
+ runtime.estimate = estimate;
+ describe(runtime, text, "runtime/auxiliary", true);
+ auxiliary.put(text, runtime);
+ } else if (runtime.estimate != estimate) {
+ // Identical auxiliary descriptions may originate in different scopes.
+ runtime.estimate = -1;
+ }
+ return runtime;
+ }
+
+ private void visit(Exp expression, String location, Node graph) {
+ if (!expressions.add(expression)) {
+ return;
+ }
+ Node scope = expression.isGraph() ? expression.getGraphName() : graph;
+ String here = location + "/" + expression.type();
+ if (expression.getEdge() != null) {
+ Edge edge = expression.getEdge();
+ PatternCounters entry = counters.computeIfAbsent(edge, ignored -> new PatternCounters());
+ if (entry.location == null) {
+ describe(entry, pattern(edge, scope), here, false);
+ }
+ }
+ for (int i = 0; i < expression.size(); i++) {
+ visit(expression.get(i), here + "[" + i + "]", scope);
+ }
+ // EXISTS expressions compile their actual operators during evaluation.
+ // Do not call Expr.getPattern(): it would create a different, unexecuted tree.
+ }
+
+ private static String pattern(Edge edge, Node graph) {
+ String predicate = edge instanceof PropertyPathEdge path
+ ? path.path().toString() : node(edge.getProperty());
+ String triple = node(edge.getNode(0)) + " " + predicate + " " + node(edge.getNode(1));
+ return graph == null ? triple : "GRAPH " + node(graph) + " { " + triple + " }";
+ }
+
+ private static String node(Node node) {
+ if (node == null) {
+ return "*";
+ }
+ if (!node.isVariable() && node.getDatatypeValue() instanceof Value value) {
+ return StatementUtils.serializeForComparison(value);
+ }
+ return node.toString();
+ }
+
+ private void describe(PatternCounters entry, String pattern, String location, boolean auxiliaryLookup) {
+ entry.pattern = pattern;
+ entry.location = location;
+ entry.auxiliary = auxiliaryLookup;
+ ordered.add(entry);
+ }
+
+ /**
+ * Copies measurements without retaining compiled nodes, bindings or storage snapshots.
+ *
+ * @return immutable execution profile
+ */
+ public QueryExecutionProfile snapshot() {
+ return new QueryExecutionProfile(compilationNanos, planningNanos, executionSetupNanos, evaluationNanos, planReused,
+ ordered.stream().map(PatternCounters::snapshot).toList(),
+ new ResultDelivery(mode, fallbackReason, firstRowStatus, firstRowNanos));
+ }
+
+ /** Mutable producer counters, never shared with the caller or watchdog thread. */
+ static final class PatternCounters {
+ private String location;
+ private String pattern;
+ private boolean auxiliary;
+ private long estimate = -1;
+ private long openings;
+ private long candidates;
+
+ /** Records a successful iterable opening. */
+ void opened() {
+ openings++;
+ }
+
+ /** Records one candidate returned by the producer iterator. */
+ void returned() {
+ candidates++;
+ }
+
+ private QueryExecutionProfile.Pattern snapshot() {
+ return new QueryExecutionProfile.Pattern(location, pattern, auxiliary, estimate, openings, candidates);
+ }
+ }
+}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/diagnostics/package-info.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/diagnostics/package-info.java
new file mode 100644
index 000000000..b8c3105e8
--- /dev/null
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/diagnostics/package-info.java
@@ -0,0 +1,2 @@
+/** Request-scoped instrumentation used only by explicitly profiled evaluations. */
+package fr.inria.corese.core.next.query.impl.engine.diagnostics;
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/ApproximateSearchEnv.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/ApproximateSearchEnv.java
deleted file mode 100644
index bf5a34ddf..000000000
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/ApproximateSearchEnv.java
+++ /dev/null
@@ -1,104 +0,0 @@
-package fr.inria.corese.core.next.query.impl.engine.eval;
-
-import fr.inria.corese.core.next.query.impl.engine.model.Expr;
-import fr.inria.corese.core.next.query.impl.engine.model.Node;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-
-/**
- * Data structure: Key -> (node -> Value)
- * Key: (?variable, ) Value: (node, SIMILARITY, algs)
- *
- * @author Fuqi Song, Wimmics Inria I3S
- */
-public class ApproximateSearchEnv {
-
- private final Map> all;
-
- public ApproximateSearchEnv() {
- this.all = new HashMap<>();
- }
-
- public Double getSimilarity(Expr variable, Node node) {
- Key key = new Key(variable);
- Value r = this.get(key, node);
- return (r == null) ? null : r.getSimilarity();
- }
-
- private Value get(Key key, Node node) {
- Map candidates = all.get(key);
- return candidates == null ? null : candidates.get(node);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder("Approximate search\n");
- for (Map.Entry> entrySet : all.entrySet()) {
- Key key = entrySet.getKey();
- Map value = entrySet.getValue();
- sb.append(key).append("\n");
- for (Value v : value.values()) {
- sb.append("\t").append(v.toString()).append("\n");
- }
- }
- return sb.toString();
- }
-
- static class Key {
-
- private final Expr variable;
- private Node uri;
-
- public Key(Expr variable) {
- this.variable = variable;
- }
-
- public Expr getVar() {
- return variable;
- }
-
- @Override
- public int hashCode() {
- return 5;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
- Key other = (Key) obj;
- return Objects.equals(this.variable, other.variable);
- }
-
- @Override
- public String toString() {
- return "Key{" + "variable=" + variable + ", uri=" + uri + '}';
- }
- }
-
- static class Value {
-
- private final Node node;
- private static final double SIMILARITY = -1;
- private final String algorithms;
-
- public Value(Node node, String algorithms) {
- this.node = node;
- this.algorithms = algorithms;
- }
-
- public double getSimilarity() {
- return SIMILARITY;
- }
-
- @Override
- public String toString() {
- return "[" + node + ", " + SIMILARITY + ", " + algorithms + "]";
- }
- }
-}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/CompleteSPARQL.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/CompleteSPARQL.java
deleted file mode 100644
index 9630a9c18..000000000
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/CompleteSPARQL.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
-
-package fr.inria.corese.core.next.query.impl.engine.eval;
-
-import fr.inria.corese.core.next.query.api.exception.QueryTypeErrorException;
-import fr.inria.corese.core.next.query.impl.engine.pattern.Exp;
-import fr.inria.corese.core.next.query.impl.engine.pattern.Query;
-import fr.inria.corese.core.next.query.impl.engine.solution.Mapping;
-import fr.inria.corese.core.next.query.impl.engine.solution.Mappings;
-
-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.engine.spi.Producer;
-import fr.inria.corese.core.next.data.api.model.DatatypeValue;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Compute select expression,
- * Compute value of group by, order by
- *
- * @author Olivier Corby, Wimmics INRIA I3S, 2016
- */
-public class CompleteSPARQL {
- private static final Logger logger = LoggerFactory.getLogger(CompleteSPARQL.class);
-
- Eval eval;
- Query query;
-
- CompleteSPARQL(Query q, Eval e) {
- this.eval = e;
- this.query = q;
- }
-
- /**
- * Complete:
- * select (exp as var)
- * group by, order by
- *
- */
- void complete(Producer p, Mappings map) {
- selectExpression(query, p, map);
- distinct(query, map);
- orderGroup(query, p, map);
- }
-
- void distinct(Query q, Mappings map) {
- if (q.isAggregate() || !map.isDistinct()) {
- return;
- }
-
- new ArrayList<>(map.getList()).forEach(m -> {
- map.getList().clear();
- map.submit(m);
- });
- }
- void selectExpression(Query q, Producer p, Mappings map) {
- if (query.isSelectExpression()) {
- HashMap bnode = new HashMap<>();
- for (Mapping m : map) {
- bnode.clear();
- m.setMap(bnode);
- m.setQuery(q);
- Mapping res = selectExpression(q, p, m);
- if (res == null) {
- logger.warn("Select: exp != var value: {}", m);
- }
- }
- }
- }
-
- Mapping selectExpression(Query q, Producer p, Mapping m) {
- ArrayList ql = new ArrayList<>();
- ArrayList tl = new ArrayList<>();
-
- for (Exp e : q.getSelectFun()) {
- if (!completeProjection(e, p, m, ql, tl)) {
- return null;
- }
- }
-
- if (!ql.isEmpty()) {
- m.complete(ql, tl);
- }
-
- return m;
- }
-
- private boolean completeProjection(Exp expression, Producer producer, Mapping mapping,
- List queryNodes, List targetNodes) {
- Filter filter = expression.getFilter();
- if (filter == null) {
- return true;
- }
- Node queryNode = expression.getNode();
- Node current = mapping.getNodeValue(queryNode);
- if (expression.isAggregate()) {
- // Aggregates are evaluated later; reserve their output slot now.
- if (current == null) {
- queryNodes.add(queryNode);
- targetNodes.add(null);
- }
- return true;
- }
- Node result;
- try {
- result = eval.eval(null, filter, mapping, producer);
- } catch (QueryTypeErrorException error) {
- return true;
- }
- if (result == null) {
- return true;
- }
- if (current != null) {
- return current.equals(result);
- }
- queryNodes.add(queryNode);
- targetNodes.add(result);
- mapping.setNodeValue(queryNode, result);
- return true;
- }
-
- void orderGroup(Query q, Producer p, Mappings map) {
- for (Mapping m : map) {
- Node[] snode = new Node[q.getOrderBy().size()];
- Node[] gnode = new Node[q.getGroupBy().size()];
- orderGroup(q.getOrderBy(), snode, p, m);
- orderGroup(q.getGroupBy(), gnode, p, m);
- m.setOrderBy(snode);
- m.setGroupBy(gnode);
- }
- }
-
- void orderGroup(List lExp, Node[] nodes, Producer p, Mapping m) {
- int n = 0;
- for (Exp e : lExp) {
- nodes[n] = resolveOrderGroupNode(e, p, m);
- n++;
- }
- }
-
- private Node resolveOrderGroupNode(Exp e, Producer p, Mapping m) {
- Node qNode = e.getNode();
- Node node = qNode != null ? m.getNodeValue(qNode) : null;
- if (node == null) {
- node = evaluateOrderGroupFilter(e, p, m);
- }
- if (qNode != null && node != null) {
- m.setNode(qNode, node);
- }
- return node;
- }
-
- private Node evaluateOrderGroupFilter(Exp e, Producer p, Mapping m) {
- Filter f = e.getFilter();
- if (f != null && !e.isAggregate()) {
- try {
- return eval.eval(null, f, m, p);
- } catch (QueryTypeErrorException error) {
- return null;
- }
- }
- return null;
- }
-
-}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EnvironmentImpl.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EnvironmentImpl.java
index 23081f33d..efffa461c 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EnvironmentImpl.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EnvironmentImpl.java
@@ -19,97 +19,97 @@
public class EnvironmentImpl implements Environment {
protected static final String NOT_SUPPORTED_YET = "Not supported yet.";
- protected Query query;
+ protected Query query;
- public EnvironmentImpl(){
- // The base environment starts without a query or bindings.
- }
+ public EnvironmentImpl(){
+ // The base environment starts without a query or bindings.
+ }
@Override
- public int count() {
- return 0;
- }
+ public int count() {
+ return 0;
+ }
@Override
- public Node getNode(Expr variable){
- return null;
- }
+ public Node getNode(Expr variable){
+ return null;
+ }
@Override
public int size() {
return 0;
}
- @Override
- public Node getNode(String label) {
- return null;
- }
+ @Override
+ public Node getNode(String label) {
+ return null;
+ }
- @Override
- public Node getNode(Node node) {
- return null;
- }
+ @Override
+ public Node getNode(Node node) {
+ return null;
+ }
- @Override
- public Node getQueryNode(int n) {
- return null;
- }
+ @Override
+ public Node getQueryNode(int n) {
+ return null;
+ }
- @Override
- public Node getQueryNode(String label) {
- return null;
- }
+ @Override
+ public Node getQueryNode(String label) {
+ return null;
+ }
- @Override
- public boolean isBound(Node node) {
- return false;
- }
+ @Override
+ public boolean isBound(Node node) {
+ return false;
+ }
- @Override
- public int pathLength(Node node) {
- return 0;
- }
+ @Override
+ public int pathLength(Node node) {
+ return 0;
+ }
- @Override
- public Query getQuery() {
- return query;
- }
+ @Override
+ public Query getQuery() {
+ return query;
+ }
- @Override
- public KgramEventDispatcher getEventManager() {
- return null;
- }
+ @Override
+ public KgramEventDispatcher getEventManager() {
+ return null;
+ }
- @Override
- public Node getGraphNode() {
- return null;
- }
+ @Override
+ public Node getGraphNode() {
+ return null;
+ }
- @Override
- public void setObject(Object o) {
- // The base environment does not retain an attached object.
- }
+ @Override
+ public void setObject(Object o) {
+ // The base environment does not retain an attached object.
+ }
- @Override
- public Object getObject() {
- return null;
- }
+ @Override
+ public Object getObject() {
+ return null;
+ }
- @Override
- public void setExp(Exp exp) {
- // Expression storage is supplied by concrete execution environments.
- }
+ @Override
+ public void setExp(Exp exp) {
+ // Expression storage is supplied by concrete execution environments.
+ }
- @Override
- public Exp getExp() {
- return null;
- }
+ @Override
+ public Exp getExp() {
+ return null;
+ }
- public Map getMap() {
- return Map.of();
- }
+ public Map getMap() {
+ return Map.of();
+ }
@Override
public Edge[] getEdges() {
@@ -152,11 +152,6 @@ public boolean hasBind() {
throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
}
- @Override
- public ApproximateSearchEnv getAppxSearchEnv() {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
@Override
public void setBind(BindingContext b) {
throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Eval.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Eval.java
index ac3742fae..92ac8e051 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Eval.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Eval.java
@@ -18,10 +18,8 @@
import fr.inria.corese.core.next.query.impl.engine.spi.Environment;
import fr.inria.corese.core.next.query.impl.engine.spi.Evaluator;
import fr.inria.corese.core.next.query.impl.engine.spi.Matcher;
-import fr.inria.corese.core.next.query.impl.engine.spi.Plugin;
import fr.inria.corese.core.next.query.impl.engine.spi.Producer;
import fr.inria.corese.core.next.query.impl.engine.spi.Provider;
-import fr.inria.corese.core.next.query.impl.engine.spi.SPARQLEngine;
import fr.inria.corese.core.next.query.api.exception.QueryTypeErrorException;
import fr.inria.corese.core.next.data.Values;
@@ -29,7 +27,6 @@
import fr.inria.corese.core.next.query.impl.engine.event.EventImpl;
import fr.inria.corese.core.next.query.impl.engine.event.KgramEventDispatcher;
import fr.inria.corese.core.next.query.impl.engine.event.ResultListener;
-import fr.inria.corese.core.next.query.impl.engine.path.PathFinder;
import fr.inria.corese.core.next.query.impl.engine.model.NodeImpl;
import fr.inria.corese.core.next.data.api.model.DatatypeValue;
import org.slf4j.Logger;
@@ -42,29 +39,29 @@
import static fr.inria.corese.core.next.query.impl.engine.model.ExprType.UNNEST;
/**
- * KGRAM Knowledge Graph Abstract Machine Compute graph homomorphism and
- * (extended) SPARQL Use: a Stack of expression Exp a Memory for Node/Edge
- * bindings an abstract Producer of candidate Node/Edge an abstract Evaluator of
- * Filter an abstract Matcher of Node/Edge
- *
- * - path statement is an EDGE with a boolean isPath this edge needs an Edge
- * Node (a property variable)
- *
- *
- * Note: future optimization: query ordering, search by dichotomy (in a cache)
+ * Backtracking evaluator for compiled SPARQL operator graphs.
+ *
+ *
{@link Stack} holds the pending operators, {@link Memory} owns their node
+ * and edge bindings, and {@link Producer} enumerates candidates. Expression
+ * evaluation and RDF-term compatibility use the evaluator and matcher contracts.
+ * Producer traversals are closed when exhausted or abandoned.
+ *
+ *
Instances and their mutable binding state are confined to the evaluation thread.
+ * A timeout watchdog may invoke {@link #finish()} concurrently; cancellation is
+ * published through volatile flags in this evaluator and its operator helpers.
*
* @author Olivier Corby, Edelweiss, INRIA 2010
*/
-public final class Eval implements ExpType, Plugin {
+public final class Eval implements ExpType {
static final int STOP = -2;
- private static final int DISPLAY_RESULT_LIMIT = 10;
static final Logger logger = LoggerFactory.getLogger(Eval.class);
- // draft test: when edge() has Mappings map parameter, push clause values(map)
+
+ // Propagate candidate mappings when evaluating edges.
private boolean pushEdgeMappings = true;
- // draft test: graph() has Mappings map parameter and eval body with map parameter
+ // Restrict graph evaluation using incoming solution mappings.
private boolean parameterGraphMappings = true;
- // draft test: union() has Mappings map parameter and eval branch with map parameter
+ // Supply incoming mappings independently to both UNION branches.
private boolean parameterUnionMappings = true;
KgramEventDispatcher manager;
@@ -75,9 +72,6 @@ public final class Eval implements ExpType, Plugin {
// Filter evaluator
Evaluator evaluator;
Matcher match;
- List lPathFinder;
- // Processing EXTERN expressions
- Plugin plugin;
// Stacks for binding edges and nodes
Memory memory;
Query query;
@@ -85,10 +79,8 @@ public final class Eval implements ExpType, Plugin {
Mappings results;
// initial results to be completed
Mappings initialResults;
- EvalSPARQL evalSparql;
- CompleteSPARQL completeSparql;
List empty = new ArrayList<>(0);
- EvalGraph evalGraphNew;
+ EvalGraph graphEvaluator;
EvalJoin join;
EvalOptional optional;
int // count number of eval() calls
@@ -106,7 +98,6 @@ public final class Eval implements ExpType, Plugin {
boolean hasFilter = false;
private ResultListener listener;
private ProcessVisitor visitor;
- private SPARQLEngine sparqlEngine;
// Edge and Node producer
private Producer producer;
private Stack current;
@@ -115,12 +106,9 @@ public final class Eval implements ExpType, Plugin {
private boolean hasCandidate = false;
private boolean hasStatement = false;
private boolean hasProduce = false;
- private boolean stopped = false;
+ private volatile boolean stopped = false;
private boolean joinMappings = true;
- public Eval() {
- }
-
/**
* @param p edge and node producer
* @param e filter evaluator, given an environment (access to variable
@@ -131,11 +119,9 @@ public Eval(Producer p, Evaluator e, Matcher m) {
saveProducer = producer;
evaluator = e;
match = m;
- plugin = this;
- lPathFinder = new ArrayList<>();
setVisitor(new ProcessVisitorDefault());
e.setKGRAM(this);
- evalGraphNew = new EvalGraph(this);
+ graphEvaluator = new EvalGraph(this);
join = new EvalJoin(this);
optional = new EvalOptional(this);
@@ -194,15 +180,15 @@ public void set(Evaluator e) {
/**
* Main eval function
*/
- public Mappings query(Query q) throws SparqlException {
+ public Mappings query(Query q) {
return query(null, q, null);
}
- public Mappings query(Node graphNode, Query q, Mapping m) throws SparqlException {
+ public Mappings query(Node graphNode, Query q, Mapping m) {
return queryBasic(graphNode, q, m);
}
- Mappings queryBasic(Node graphNode, Query q, Mapping m) throws SparqlException {
+ Mappings queryBasic(Node graphNode, Query q, Mapping m) {
if (hasEvent) {
send(Event.BEGIN, q);
}
@@ -221,11 +207,7 @@ Mappings queryBasic(Node graphNode, Query q, Mapping m) throws SparqlException {
}
return map;
} finally {
- try {
- getProducer().finish(q);
- } finally {
- clean();
- }
+ getProducer().finish(q);
}
}
@@ -243,9 +225,7 @@ void share(Mapping m) {
}
}
- // store ProcessVisitor into Bind for future sharing by
- // Transformer and Interpreter exist
- // use case: metadata @share
+ // Propagate a shareable visitor to nested evaluations through their bindings.
void share(ProcessVisitor vis) {
if (vis.isShareable() && getBind().getVisitor() == null) {
getBind().setVisitor(vis);
@@ -255,38 +235,31 @@ void share(ProcessVisitor vis) {
/**
* Mapping m is binding parameter, possibly null
- * a) from template call with parameter: st:call-template(st:name, ?x, ?y)
- * b) from query exec call with parameter: exec.query(q, m)
+ * Supplied bindings are applied before the main query or subquery is evaluated.
* Mappings map is results or previous statement, possibly null
* use case: optional(A, B) map = relevant subset of results of A
* for main query and subquery
*/
- Mappings eval(Node gNode, Query q, Mapping m, Mappings map) throws SparqlException {
+ Mappings eval(Node gNode, Query q, Mapping m, Mappings map) {
init(q);
if (q.isValidate()) {
// just compile and complete query
return getResults();
}
if (q.isCheck()) {
- // Draft
- Checker check = Checker.create(this);
+ QueryDefinitionChecker check = QueryDefinitionChecker.create(this);
check.check(q);
}
if (!q.isFail()) {
- queryWE(gNode, q, m, map);
+ query(gNode, q, m, map);
if (q.getQueryProfile() == Query.COUNT_PROFILE) {
countProfile();
} else {
- if (q.isAlgebra()) {
- getMemory().setResults(getResults());
- completeSparql.complete(getProducer(), getResults());
- }
aggregate();
// order by
complete();
- template();
}
}
@@ -294,28 +267,14 @@ Mappings eval(Node gNode, Query q, Mapping m, Mappings map) throws SparqlExcepti
return getResults();
}
- void queryWE(Node gNode, Query q, Mapping m, Mappings map) throws SparqlException {
- try {
- query(gNode, q, m, map);
- } catch (SparqlException ex) {
- if (ex.isStop()) {
- // LDScriptException stopped means stopped query processing
- return;
- }
- // exception means this is an error
- throw ex;
- }
- }
-
/**
* Mapping m is binding parameter, possibly null
- * a) from template call with parameter: st:call-template(st:name, ?x, ?y)
- * b) from query exec call with parameter: exec.query(q, m)
+ * Supplied bindings are applied before the main query or subquery is evaluated.
* Mappings map is results or previous statement, possibly null
* use case: optional(A, B) map = relevant subset of results of A
*/
@SuppressWarnings("java:S1845") // Query execution method named query by convention
- void query(Node gNode, Query q, Mapping m, Mappings map) throws SparqlException {
+ void query(Node gNode, Query q, Mapping m, Mappings map) {
if (m != null) {
// bind mapping variables into memory
bind(m);
@@ -334,11 +293,10 @@ void query(Node gNode, Query q, Mapping m, Mappings map) throws SparqlException
/**
* Bind external values one by one in memory and eval one by one
*/
- void queryWithValues(Node gNode, Query q, Mappings map)
- throws SparqlException {
+ void queryWithValues(Node gNode, Query q, Mappings map) {
Exp values = q.getValues();
- if (!values.isPostpone() && !q.isAlgebra()) {
+ if (!values.isPostpone()) {
for (Mapping m : values.getMappings()) {
if (stopped) {
return;
@@ -353,11 +311,10 @@ void queryWithValues(Node gNode, Query q, Mappings map)
eval(gNode, q, map);
}
- void eval(Node gNode, Query q, Mappings map) throws SparqlException {
+ void eval(Node gNode, Query q, Mappings map) {
evalExp(gNode, q.getBody(), map);
}
- void evalExp(Node gNode, Exp exp, Mappings map)
- throws SparqlException {
+ void evalExp(Node gNode, Exp exp, Mappings map) {
Stack stack = Stack.create(exp);
set(stack);
eval(getProducer(), gNode, stack, map, 0);
@@ -372,11 +329,6 @@ void countProfile() {
getResults().add(m);
}
- // draft for processing EXTERN expression
- public void add(Plugin p) {
- plugin = p;
- }
-
public void setMappings(Mappings lMap) {
initialResults = lMap;
}
@@ -387,7 +339,7 @@ public void setMappings(Mappings lMap) {
* for optional, minus, union, join, graph
* not for subquery which has its own processing
*/
- Mappings subEval(Query q, Node gNode, Stack stack, Mappings map, int n) throws SparqlException {
+ Mappings subEval(Query q, Node gNode, Stack stack, Mappings map, int n) {
setSubEval(true);
starter(q);
eval(getProducer(), gNode, stack, map, n);
@@ -404,41 +356,24 @@ Mappings subEval(Query q, Node gNode, Stack stack, Mappings map, int n) throws S
*/
// use case: optional, join, minus
- public Mappings subEval(Producer p, Node gNode, Node queryNode, Exp exp, Exp main, Mappings map) throws SparqlException {
+ public Mappings subEval(Producer p, Node gNode, Node queryNode, Exp exp, Exp main, Mappings map) {
return subEval(p, gNode, queryNode, exp, main, map, null, false, false);
}
- /**
- * Evaluates an EXISTS graph pattern against the bindings of the current solution.
- * The nested evaluation receives a fresh memory while copying every currently
- * bound query node, so correlated variables retain SPARQL EXISTS semantics.
- public boolean exists(Producer producer, Node graphNode, Exp pattern) throws SparqlException {
- return exists(producer, graphNode, getMemory(), pattern);
- }
-
/**
* Evaluates an EXISTS graph pattern against the bindings of the current solution.
* The nested evaluation receives a fresh memory while copying every currently
* bound query node from the active environment, so correlated variables retain
* SPARQL EXISTS semantics even when evaluated inside an OPTIONAL or aggregate context.
*/
- public boolean exists(Producer producer, Node graphNode, Environment env, Exp pattern) throws SparqlException {
+ public boolean exists(Producer producer, Node graphNode, Environment env, Exp pattern) {
Memory nestedMemory = new Memory(match, evaluator);
evaluator.init(nestedMemory);
nestedMemory.init(getQuery());
switch (env) {
- case Mapping map -> {
- nestedMemory.setAppxSearchEnv(getMemory().getAppxSearchEnv());
- nestedMemory.copy(map, pattern);
- }
- case Memory mem -> {
- nestedMemory.setAppxSearchEnv(mem.getAppxSearchEnv());
- mem.copyInto(nestedMemory, pattern);
- }
- case null, default -> {
- nestedMemory.setAppxSearchEnv(getMemory().getAppxSearchEnv());
- getMemory().copyInto(nestedMemory, pattern);
- }
+ case Mapping map -> nestedMemory.copy(map, pattern);
+ case Memory mem -> mem.copyInto(nestedMemory, pattern);
+ case null, default -> getMemory().copyInto(nestedMemory, pattern);
}
Eval nested = copy(nestedMemory, producer);
@@ -460,13 +395,12 @@ public boolean exists(Producer producer, Node graphNode, Environment env, Exp pa
*/
@SuppressWarnings("java:S107") // Internal KGRAM sub-evaluation method requires 9 execution parameters
Mappings subEval(Producer p, Node graphNode, Node queryGraphNode, Exp exp, Exp main,
- Mappings map, Mapping m, boolean bind, boolean external) throws SparqlException {
+ Mappings map, Mapping m, boolean bind, boolean external) {
Memory mem = new Memory(match, getEvaluator());
getEvaluator().init(mem);
// share Binding (global variable, context, etc.)
mem.share(getMemory());
mem.init(getQuery());
- mem.setAppxSearchEnv(getMemory().getAppxSearchEnv());
Eval eval = copy(mem, p);
if (external) {
if (queryGraphNode != null) {
@@ -494,7 +428,7 @@ void bind(Memory freshMemory, Exp exp, Exp main, Mapping m, boolean bind) {
freshMemory.push(m, -1);
}
- // QuerySorter may have computed exp node list candidate for binding
+ // QueryPlanner may have computed exp node list candidate for binding
if (main.isGraph() && main.getNodeList() != null) {
bindExpNodeList(freshMemory, main, main.getGraphName());
} else if ((bind || main.isBinary()) && exp.getNodeList() != null) {
@@ -510,7 +444,7 @@ void bind(Memory freshMemory, Exp exp, Exp main, Mapping m, boolean bind) {
* Bind relevant nodes of exp from memory to fresh memory
* except may be a graphNode: do not bind it here
* because it is bound somewhere else by subEval
- * getNodeList() is computer by QuerySorter
+ * getNodeList() is computer by QueryPlanner
*/
void bindExpNodeList(Memory freshMemory, Exp exp, Node except) {
for (Node qnode : exp.getNodeList()) {
@@ -567,26 +501,21 @@ void bindService(Exp exp, Memory freshMemory) {
* exp stack
*/
public Eval copy(Memory m, Producer p) {
- return copy(m, p, getEvaluator(), query, false);
+ return copy(m, p, getEvaluator(), false);
}
// extern = true if the statement to evaluate is LDScript query:
// let (select where)
public Eval copy(Memory m, Producer p, boolean extern) {
- return copy(m, p, getEvaluator(), query, extern);
+ return copy(m, p, getEvaluator(), extern);
}
- // q may be the subQuery
- Eval copy(Memory m, Producer p, Evaluator e, Query q, boolean extern) {
+ Eval copy(Memory m, Producer p, Evaluator e, boolean extern) {
Eval ev = create(p, e, getMatcher());
ev.pushEdgeMappings = pushEdgeMappings;
ev.parameterGraphMappings = parameterGraphMappings;
ev.parameterUnionMappings = parameterUnionMappings;
ev.joinMappings = joinMappings;
- if (q != null) {
- ev.complete(q);
- }
- ev.setSPARQLEngine(getSPARQLEngine());
ev.setMemory(m);
ev.set(getProvider());
if (!extern || getVisitor().isShareable()) {
@@ -697,17 +626,9 @@ void initMemory(Query q) {
}
getProducer().init(q);
evaluator.start(memory);
- if (q.isAlgebra()) {
- complete(q);
- }
}
}
- void complete(Query q) {
- evalSparql = new EvalSPARQL(q, this);
- completeSparql = new CompleteSPARQL(q, this);
- }
-
void profile(Query q) {
// select count(*) as ?c where pattern
// do not built Mapping, just count them
@@ -764,10 +685,6 @@ private void aggregate() {
results.aggregate(evaluator, memory, getProducer());
}
- private void template() {
- results.template(evaluator, memory, getProducer());
- }
-
/**
* We can bind nodes before processing query
*/
@@ -790,44 +707,6 @@ void bind(Mapping map) {
}
}
- private PathFinder getPathFinder(Exp exp, Producer p) {
- List lp = lPathFinder;
- for (PathFinder pf : lp) {
- if (pf.getEdge() == exp.getEdge()) {
- return pf;
- }
- }
- PathFinder pathFinder = PathFinder.create(this, p, query);
-
- pathFinder.set(getListener());
- pathFinder.setList(query.getGlobalQuery().isListPath());
- // rdf:type/rdfs:subClassOf* generated system path does not store the list of edges
- // to be optimized
- pathFinder.setStorePath(query.getGlobalQuery().isStorePath() && !exp.isSystem());
- pathFinder.setCache(query.getGlobalQuery().isCachePath());
- // Note: subQuery
- pathFinder.setCheckLoop(query.isCheckLoop());
- pathFinder.setCountPath(query.isCountPath());
- pathFinder.init(exp.getRegex(), exp.getMin(), exp.getMax());
- // Note: check this with clean()
- if (p.getMode() != Producer.EXTENSION && p.getQuery() == memory.getQuery()) {
- // do nothing
- } else {
- lPathFinder.add(pathFinder);
- }
- return pathFinder;
- }
-
- /**
- * What should be done before throw LimitException - close path threads if
- * any
- */
- public void clean() {
- for (PathFinder pf : lPathFinder) {
- pf.stop();
- }
- }
-
private int solution(Producer p, Mapping m, int n) {
int backtrack = n - 1;
int status = store(p, m);
@@ -835,13 +714,11 @@ private int solution(Producer p, Mapping m, int n) {
return STOP;
}
if (results.size() >= limit) {
- clean();
// backjump to send finish events to listener
// and perform 'close instructions' if any
return STOP;
}
if (!getVisitor().limit(results)) {
- clean();
return STOP;
}
if (backjump != -1) {
@@ -863,7 +740,7 @@ private int solution(Producer p, Mapping m, int n) {
/**
* gNode is a graph name URI if any, may be null
*/
- int eval(Producer p, Node gNode, Stack stack, int n) throws SparqlException {
+ int eval(Producer p, Node gNode, Stack stack, int n) {
return eval(p, gNode, stack, null, n);
}
@@ -881,7 +758,7 @@ int eval(Producer p, Node gNode, Stack stack, int n) throws SparqlException {
* Eventually, and() edge() path() transform Mappings map into values clause
*/
@SuppressWarnings("java:S3776") // Core KGRAM evaluation dispatch loop coordinating stack traversal and backtracking
- int eval(Producer p, Node graphNode, Stack stack, Mappings map, int n) throws SparqlException {
+ int eval(Producer p, Node graphNode, Stack stack, Mappings map, int n) {
int backtrack = n - 1;
boolean isEvent = hasEvent;
Memory env = getMemory();
@@ -910,127 +787,99 @@ int eval(Producer p, Node graphNode, Stack stack, Mappings map, int n) throws Sp
// A false filter was detected at compile time, or this expression was
// identified as always failing: no use to evaluate it.
if (!exp.isFail()) {
- if (exp.isBGPAble()) {
- // @deprecated
- // evaluate and record result for next time
- // template optimization
- exp.setBGPAble(false);
- backtrack = bgpAble(p, graphNode, exp, stack, n);
- exp.setBGPAble(true);
- } else {
- // draft test not used
- if (query.getGlobalQuery().isAlgebra()) {
- Type type = exp.type();
- if (type == Type.BGP || type == Type.JOIN || type == Type.MINUS || type == Type.OPTIONAL || type == Type.GRAPH || type == Type.UNION) {
- process(graphNode, p, exp);
- return backtrack;
- }
- }
-
- if (hasStatement) {
- getVisitor().statement(this, getGraphNode(graphNode), exp);
- }
-
- if (!isJoinMappings()) {
- // for testing and debug
- map = null;
- }
-
- switch (exp.type()) {
-
- case EMPTY:
-
- eval(p, graphNode, stack, n + 1);
- break;
-
- case AND:
- backtrack = and(p, graphNode, exp, stack, map, n);
- break;
-
- case BGP:
- backtrack = p.evaluatesBGPWithEdges()
- ? and(p, graphNode, exp, stack, map, n)
- : bgp(p, graphNode, exp, stack, n);
- break;
-
- case SERVICE:
- // @note: map processing is not optimal for service with union
- // we pass mappings only for variables that are in-scope in
- // both branches of the union
- // it can be bypassed with values var (undef)
- backtrack = service(p, graphNode, exp, map, stack, n);
- break;
-
- case GRAPH:
- backtrack =
- evalGraphNew.eval(p, graphNode, exp, map, stack, n);
- break;
-
- case UNION:
- backtrack = union(p, graphNode, exp, map, stack, n);
- break;
-
- case OPTIONAL:
- backtrack = optional.eval(p, graphNode, exp, map, stack, n);
- break;
- case MINUS:
- backtrack = minus(p, graphNode, exp, map, stack, n);
- break;
- case JOIN:
- backtrack = join.eval(p, graphNode, exp, map, stack, n);
- break;
- case QUERY:
- backtrack = query(p, graphNode, exp, map, stack, n);
- break;
- case FILTER:
- backtrack = filter(p, graphNode, exp, stack, n);
- break;
- case BIND:
- backtrack = bind(p, graphNode, exp, map, stack, n);
- break;
-
- case PATH:
- backtrack = path(p, graphNode, exp, map, stack, n);
- break;
-
- case EDGE:
- backtrack = edge(p, graphNode, exp, map, stack, n);
- break;
-
- case VALUES:
- backtrack = values(p, graphNode, exp, stack, n);
- break;
-
- /*
- * ********************************
- *
- * Draft extensions
- *
- */
- case OPT_BIND:
- // use case: ?x p ?y FILTER ?t = ?y BIND(?t, ?y) ?z q ?t
- // not effective
- backtrack = optBind(p, graphNode, exp, stack, n);
- break;
-
- case ACCEPT:
- // use case: select distinct ?x where
- // check that ?x is distinct
- if (optim) {
- if (getResults().accept(env.getNode(exp.getNode()))) {
- // backjump here when a mapping will be found with this node
- // see store()
- backjump = n - 1;
- backtrack = eval(p, graphNode, stack, n + 1);
- }
- } else {
+ if (hasStatement) {
+ getVisitor().statement(this, getGraphNode(graphNode), exp);
+ }
+
+ if (!isJoinMappings()) {
+ // for testing and debug
+ map = null;
+ }
+
+ switch (exp.type()) {
+
+ case EMPTY:
+
+ eval(p, graphNode, stack, n + 1);
+ break;
+
+ case AND:
+ backtrack = and(p, graphNode, exp, stack, map, n);
+ break;
+
+ case BGP:
+ backtrack = p.evaluatesBGPWithEdges()
+ ? and(p, graphNode, exp, stack, map, n)
+ : bgp(p, graphNode, exp, stack, n);
+ break;
+
+ case SERVICE:
+ // @note: map processing is not optimal for service with union
+ // we pass mappings only for variables that are in-scope in
+ // both branches of the union
+ // it can be bypassed with values var (undef)
+ backtrack = service(p, graphNode, exp, map, stack, n);
+ break;
+
+ case GRAPH:
+ backtrack =
+ graphEvaluator.eval(p, graphNode, exp, map, stack, n);
+ break;
+
+ case UNION:
+ backtrack = union(p, graphNode, exp, map, stack, n);
+ break;
+
+ case OPTIONAL:
+ backtrack = optional.eval(p, graphNode, exp, map, stack, n);
+ break;
+ case MINUS:
+ backtrack = minus(p, graphNode, exp, map, stack, n);
+ break;
+ case JOIN:
+ backtrack = join.eval(p, graphNode, exp, map, stack, n);
+ break;
+ case QUERY:
+ backtrack = query(p, graphNode, exp, map, stack, n);
+ break;
+ case FILTER:
+ backtrack = filter(p, graphNode, exp, stack, n);
+ break;
+ case BIND:
+ backtrack = bind(p, graphNode, exp, map, stack, n);
+ break;
+
+
+ case EDGE:
+ backtrack = edge(p, graphNode, exp, map, stack, n);
+ break;
+
+ case VALUES:
+ backtrack = values(p, graphNode, exp, stack, n);
+ break;
+
+ case OPT_BIND:
+ // Binding constraints introduced by the filter optimizer.
+ backtrack = optBind(p, graphNode, exp, stack, n);
+ break;
+
+ case ACCEPT:
+ // use case: select distinct ?x where
+ // check that ?x is distinct
+ if (optim) {
+ if (getResults().accept(env.getNode(exp.getNode()))) {
+ // backjump here when a mapping will be found with this node
+ // see store()
+ backjump = n - 1;
backtrack = eval(p, graphNode, stack, n + 1);
}
- break;
+ } else {
+ backtrack = eval(p, graphNode, stack, n + 1);
+ }
+ break;
- default:
- break;
- }
+ default:
+ break;
}
}
@@ -1041,7 +890,7 @@ int eval(Producer p, Node graphNode, Stack stack, Mappings map, int n) throws Sp
return backtrack;
}
- private int minus(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) throws SparqlException {
+ private int minus(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) {
int backtrack = n - 1;
Memory env = getMemory();
@@ -1053,7 +902,7 @@ private int minus(Producer p, Node graphNode, Exp exp, Mappings data, Stack stac
return backtrack;
}
- MappingSet set1 = new MappingSet(env.getQuery(), map1);
+ MappingSet set1 = new MappingSet(map1);
Exp rest = exp.rest();
Mappings minusMappings = set1.prepareMappingsRest(rest);
@@ -1061,7 +910,7 @@ private int minus(Producer p, Node graphNode, Exp exp, Mappings data, Stack stac
getVisitor().minus(this, getGraphNode(graphNode), exp, map1, map2);
- MappingSet set = new MappingSet(env.getQuery(), exp, set1, new MappingSet(memory.getQuery(), map2));
+ MappingSet set = new MappingSet(exp, set1, new MappingSet(map2));
set.start();
for (Mapping map : map1) {
@@ -1087,7 +936,7 @@ boolean isFederate(Exp exp) {
return exp.isRecFederate();
}
- private int union(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) throws SparqlException {
+ private int union(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) {
int backtrack = n - 1;
// join(A, union(B, C)) ; map = eval(A).distinct(inscopenodes())
@@ -1106,7 +955,7 @@ private int union(Producer p, Node graphNode, Exp exp, Mappings data, Stack stac
/** Continues the enclosing query once for every solution of a UNION branch. */
private int evalUnionMappings(
- Producer p, Node graphNode, Stack stack, int n, Mappings mappings, int backtrack) throws SparqlException {
+ Producer p, Node graphNode, Stack stack, int n, Mappings mappings, int backtrack) {
Memory environment = getMemory();
for (Mapping mapping : mappings) {
if (stopped) {
@@ -1131,7 +980,7 @@ private int evalUnionMappings(
* federated case, map is included in copy of exp as a values(var, map)
* clause
*/
- Mappings unionBranch(Producer p, Node graphNode, Exp exp, Exp main, Mappings data) throws SparqlException {
+ Mappings unionBranch(Producer p, Node graphNode, Exp exp, Exp main, Mappings data) {
if (exp.isFirstWith()) {
// union in union: eval inner union with parameter data as is
return subEval(p, graphNode, null, exp, main, data);
@@ -1152,7 +1001,7 @@ Mappings unionBranch(Producer p, Node graphNode, Exp exp, Exp main, Mappings dat
Mappings unionData(Exp exp, Mappings data) {
if (data != null) {
if (data.getNodeList() == null) {
- MappingSet ms = new MappingSet(getQuery(), data);
+ MappingSet ms = new MappingSet(data);
return ms.prepareMappingsRest(exp);
} else if (data.getJoinMappings() != null) {
// select relevant variables from original join Mappings
@@ -1163,7 +1012,7 @@ Mappings unionData(Exp exp, Mappings data) {
}
- private int and(Producer p, Node graphNode, Exp exp, Stack stack, Mappings data, int n) throws SparqlException {
+ private int and(Producer p, Node graphNode, Exp exp, Stack stack, Mappings data, int n) {
getVisitor().bgp(this, getGraphNode(graphNode), exp, null);
if (data != null && exp.size() > 0 && exp.get(0).isEdgePath()) {
@@ -1179,7 +1028,7 @@ private int and(Producer p, Node graphNode, Exp exp, Stack stack, Mappings data,
}
}
- private int bgp(Producer p, Node graphNode, Exp exp, Stack stack, int n) throws SparqlException {
+ private int bgp(Producer p, Node graphNode, Exp exp, Stack stack, int n) {
int backtrack = n - 1;
List from = getQuery().getFrom(graphNode);
Mappings map = p.getMappings(graphNode, from, exp, getMemory());
@@ -1201,7 +1050,7 @@ private int bgp(Producer p, Node graphNode, Exp exp, Stack stack, int n) throws
return backtrack;
}
- private int service(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) throws SparqlException {
+ private int service(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) {
int backtrack = n - 1;
Memory env = getMemory();
Node serv = exp.first().getNode();
@@ -1244,7 +1093,7 @@ void complete(Query q, Mapping map, boolean addNode) {
int i = 0;
for (Node node : map.getQueryNodes()) {
Node out = q.getOuterNode(node);
- // draft: use case ?_server_0
+ // Preserve auxiliary service bindings absent from the outer projection.
if (out == null) {
out = node;
if (addNode && !q.getSelect().contains(node)) {
@@ -1270,7 +1119,7 @@ Node getGraphNode(Node node) {
/**
* bind(exp as var)
*/
- private int bind(Producer p, Node graphNode, Exp exp, Mappings map, Stack stack, int n) throws SparqlException {
+ private int bind(Producer p, Node graphNode, Exp exp, Mappings map, Stack stack, int n) {
if (exp.isFunctional()) {
return extBind(p, graphNode, exp, stack, n);
}
@@ -1306,7 +1155,7 @@ private int bind(Producer p, Node graphNode, Exp exp, Mappings map, Stack stack,
* values (?x ?y) { unnest(exp) }
* compiled as extended bind
*/
- private int extBind(Producer p, Node graphNode, Exp exp, Stack stack, int n) throws SparqlException {
+ private int extBind(Producer p, Node graphNode, Exp exp, Stack stack, int n) {
int backtrack = n - 1;
Memory env = getMemory();
env.setGraphNode(graphNode);
@@ -1344,7 +1193,7 @@ HashMap toMap(List list) {
/**
*
*/
- private int filter(Producer p, Node graphNode, Exp exp, Stack stack, int n) throws SparqlException {
+ private int filter(Producer p, Node graphNode, Exp exp, Stack stack, int n) {
int backtrack = n - 1;
Memory env = getMemory();
boolean success = true;
@@ -1439,99 +1288,7 @@ public Mappings eval(Filter f, Environment env, Producer p, List nodes) {
return getProducer().map(nodes, res, n);
}
- /**
- * Enumerate candidate edges
- * exp = query edge
- * graphNode = null for default graph triple pattern
- * graphNode = URI of named graph for named graph triple pattern
- * graph ?g { exp } evaluates exp for each value of ?g = g_i
- * in this case graphNode = g_i and from is useless here
- * from named has been used in named graph pattern evaluator
- * data is possible relevant bindings coming from preceding statement evaluation
- */
- @SuppressWarnings("java:S3776") // Core KGRAM property path evaluation loop managing automaton candidate matching and backtracking
- private int path(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) throws SparqlException {
- int backtrack = n - 1;
- int evENUM = Event.ENUM;
- PathFinder path = getPathFinder(exp, p);
- Memory env = getMemory();
- Query qq = getQuery();
- boolean isEvent = hasEvent;
-
- if (data != null && data.getNodeList() != null && isPushEdgeMappings()) {
- // push values(data) before edge in stack
- if (logger.isInfoEnabled()) {
- logger.info("Push path mappings:\nvalue {}\n{}",
- data.getNodeList(), data.toString(false, false, DISPLAY_RESULT_LIMIT));
- }
- return eval(p, graphNode, stack.addCopy(n, exp.getValues(data)), n);
- }
-
- Filter f = getFilterForPath(stack, n);
- path.start(exp.getEdge(), qq.getPathNode(), env, f);
- boolean isSuccess = false;
-
- List list = getGraphNodeList(p, graphNode, env, qq);
- Node backtrackNode = getBacktrackNode(p, graphNode, env);
-
- for (Mapping map : path.candidate(graphNode, list, env)) {
- if (stopped) {
- path.stop();
- return STOP;
- }
- boolean success = match(map) && env.push(map, n);
-
- if (isEvent) {
- send(evENUM, exp, map, success);
- }
-
- if (success) {
- isSuccess = true;
- backtrack = eval(p, graphNode, stack, n + 1);
- env.pop(map);
-
- if (backtrack < n) {
- path.stop();
- // remove it to get fresh automaton next time
- lPathFinder.remove(path);
- return backtrack;
- }
- }
- }
-
- if (!isSuccess && optim) {
- // backjump to max index where nodes are bound for first time:
- backtrack = env.getIndex(backtrackNode, exp.getEdge());
- }
- path.stop();
- return backtrack;
- }
-
- private Filter getFilterForPath(Stack stack, int n) {
- if (stack.size() > n + 1 && stack.get(n + 1).isFilter()) {
- return stack.get(n + 1).getFilter();
- }
- return null;
- }
-
- private Node getBacktrackNode(Producer p, Node graphNode, Memory env) {
- if (p.getMode() == Producer.EXTENSION) {
- if (p.getQuery() == env.getQuery()) {
- return p.getGraphNode();
- }
- return null;
- }
- return graphNode;
- }
-
- private List getGraphNodeList(Producer p, Node graphNode, Memory env, Query qq) {
- if (p.getMode() == Producer.EXTENSION && p.getQuery() == env.getQuery()) {
- return empty;
- }
- return qq.getFrom(graphNode);
- }
-
- private int values(Producer p, Node graphNode, Exp exp, Stack stack, int n) throws SparqlException {
+ private int values(Producer p, Node graphNode, Exp exp, Stack stack, int n) {
int backtrack = n - 1;
getVisitor().values(this, getGraphNode(graphNode), exp, exp.getMappings());
@@ -1600,7 +1357,7 @@ void free(List varList, Mapping map) {
* data may be null
*/
@SuppressWarnings("java:S3776") // Core KGRAM edge evaluation algorithm managing graph matching and backjump optimization
- private int edge(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) throws SparqlException {
+ private int edge(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) {
int backtrack = n - 1;
int evENUM = Event.ENUM;
boolean isSuccess = false;
@@ -1615,7 +1372,7 @@ private int edge(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack
// for default graph triple pattern, consider select from if any
// for named graph triple pattern, from named is useless here because graphNode = URI of one named graph
List graphNodeList = qq.getFrom(graphNode);
- // the backtrack graphNode (deprecated)
+ // Graph binding used to determine the backtracking level.
Node backtrackGraphNode = graphNode;
boolean matchNBNode = qEdge.isMatchArity();
@@ -1639,7 +1396,7 @@ private int edge(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack
Iterable entities;
if (hasProduce) {
- // draft not used
+ // A query visitor may provide candidates before the producer is consulted.
entities = produce(graphNode, qEdge);
if (entities == null) {
entities = p.getEdges(graphNode, graphNodeList, qEdge, env);
@@ -1717,7 +1474,7 @@ private int edge(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack
* select * where {{select * where {?x p ?y}} . ?y q ?z}
* new eval, new memory, share only sub query select variables
*/
- private int query(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) throws SparqlException {
+ private int query(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) {
int backtrack = n - 1;
int evENUM = Event.ENUM;
boolean isEvent = hasEvent;
@@ -1727,7 +1484,7 @@ private int query(Producer p, Node graphNode, Exp exp, Mappings data, Stack stac
// copy current Eval, new stack
// bind sub query select nodes in new memory
- Eval ev = copy(copyMemory(env, getQuery(), subQuery, null), p, getEvaluator(), subQuery, false);
+ Eval ev = copy(copyMemory(env, getQuery(), subQuery, null), p, getEvaluator(), false);
Mappings lMap = ev.eval(graphNode, subQuery, null, selectQueryMappings(data));
getVisitor().query(this, getGraphNode(graphNode), exp, lMap);
@@ -1869,7 +1626,7 @@ void store(Mapping ans) {
}
boolean acceptable(Mapping m) {
- return getQuery().getGlobalQuery().isAlgebra() || getResults().acceptable(m);
+ return getResults().acceptable(m);
}
@@ -1893,22 +1650,6 @@ private boolean push(Producer p, Edge qEdge, Edge ent, int n) {
}
-
- // for path
- private boolean match(Mapping map) {
- int i = 0;
- Memory env = getMemory();
- Matcher mm = getMatcher();
- for (Node qNode : map.getQueryNodes()) {
- Node node = map.getNode(i++);
- if (!mm.match(qNode, node, env)) {
- return false;
- }
- }
- return true;
- }
-
-
public void createManager() {
if (manager == null) {
setEventManager(new KgramEventDispatcher());
@@ -1953,14 +1694,6 @@ public Stack getStack() {
return current;
}
- public SPARQLEngine getSPARQLEngine() {
- return sparqlEngine;
- }
-
- public void setSPARQLEngine(SPARQLEngine sparqlEngine) {
- this.sparqlEngine = sparqlEngine;
- }
-
public ProcessVisitor getVisitor() {
return visitor;
}
@@ -1980,37 +1713,18 @@ public void setStop(boolean stopped) {
- /*
- * *************************************************************************
- *
- * Alternative interpreter
- *
+ /**
+ * Requests cooperative termination, including active join, graph, and optional
+ * evaluation. May be called by the timeout watchdog; remaining evaluation state
+ * must only be accessed by the owning execution thread.
*/
-
public void finish() {
setStop(true);
join.setStop();
- evalGraphNew.setStop();
+ graphEvaluator.setStop();
optional.setStop(true);
}
- /**
- * SPARQL algebra requires kgram to compute BGP exp and return Mappings
- * List from = query.getFrom(gNode); Mappings map =
- * p.getMappings(gNode, from, exp, memory);
- */
- Mappings exec(Node gNode, Producer p, Exp exp) throws SparqlException {
- List from = query.getFrom(gNode);
- return p.getMappings(gNode, from, exp, memory);
- }
-
- /**
- * Evaluate exp with SPARQL Algebra on Mappings, not with Memory stack
- */
- void process(Node gNode, Producer p, Exp exp) {
- results = evalSparql.eval(gNode, p, exp);
- }
-
/**
* use case:
*
The owner must close this cursor, including after partial consumption. The
+ * surrounding execution owns cancellation and the producer's stable read snapshot.
+ */
+public final class SelectCursor implements Iterator, AutoCloseable {
+ private final Eval eval;
+ private final Eval cancellation;
+ private final Mapping parameters;
+ private final Node scopeGraph;
+ private final boolean nested;
+ private final boolean applySlice;
+ private final Query query;
+ private final Memory memory;
+ private final Producer producer;
+ private final List instructions = new ArrayList<>();
+ private final Frame[] frames;
+ private int depth;
+ private long accepted;
+ private long emitted;
+ private Mapping next;
+ private boolean closed;
+
+ /**
+ * Returns the first guard selecting full evaluation, without reading any data.
+ * NONE means that the algebra is eligible; the executor must still capture an
+ * independent read snapshot. The decision also drives diagnostics so reporting
+ * cannot drift from the strategy actually selected.
+ *
+ * @param query completed execution query
+ * @return first materialization guard, or NONE for eligible algebra
+ */
+ public static FallbackReason fallbackReason(Query query) {
+ if (!query.isSelect()) return FallbackReason.QUERY_FORM;
+ if (query.isOrderBy()) return FallbackReason.ORDER_BY;
+ return operatorFallbackReason(query);
+ }
+
+ /**
+ * Whether this ordered SELECT can supply unsliced solutions to a bounded selector.
+ * Aggregate and scope guards remain identical to those of the progressive cursor.
+ *
+ * @param query completed execution query
+ * @return whether ORDER BY has a finite, representable LIMIT plus OFFSET and eligible input
+ */
+ public static boolean supportsOrderedInput(Query query) {
+ return query.isSelect() && query.isOrderBy() && query.getLimit() < Integer.MAX_VALUE
+ && (long) query.getLimit() + query.getOffset() <= Integer.MAX_VALUE
+ && operatorFallbackReason(query) == FallbackReason.NONE;
+ }
+
+ private static FallbackReason operatorFallbackReason(Query query) {
+ if (query.hasGroupBy() || query.isAggregate() || query.getHaving() != null) return FallbackReason.AGGREGATION;
+ return fallbackReason(query.getBody(), query.getValues() != null && !singleton(query.getValues()));
+ }
+
+ private static FallbackReason fallbackReason(Exp exp, boolean repeated) {
+ return switch (exp.type()) {
+ case AND, BGP -> sequenceReason(exp, repeated);
+ case UNION -> unionReason(exp, repeated);
+ case JOIN -> joinReason(exp, repeated);
+ case GRAPH -> graphReason(exp, repeated);
+ case OPTIONAL -> optionalReason(exp, repeated);
+ case EDGE -> exp.getEdge() instanceof PropertyPathEdge ? FallbackReason.PROPERTY_PATH : FallbackReason.NONE;
+ case QUERY -> FallbackReason.SUBQUERY;
+ case SERVICE -> FallbackReason.SERVICE;
+ case MINUS -> FallbackReason.MINUS;
+ case FILTER -> exp.isPostpone() ? FallbackReason.COMPLEX_OPTIONAL : FallbackReason.NONE;
+ case BIND -> exp.isFunctional() ? FallbackReason.UNSUPPORTED_OPERATOR : FallbackReason.NONE;
+ case EMPTY, VALUES -> FallbackReason.NONE;
+ default -> FallbackReason.UNSUPPORTED_OPERATOR;
+ };
+ }
+
+ private static FallbackReason sequenceReason(Exp exp, boolean repeated) {
+ boolean inputRepeats = repeated;
+ for (Exp child : exp) {
+ FallbackReason reason = fallbackReason(child, inputRepeats);
+ if (reason != FallbackReason.NONE) return reason;
+ inputRepeats |= !singleton(child);
+ }
+ return FallbackReason.NONE;
+ }
+
+ private static FallbackReason unionReason(Exp exp, boolean repeated) {
+ for (Exp branch : exp) {
+ if (repeated && !tripleJoin(branch) && !singleton(branch)) return FallbackReason.REPEATED_SCOPE;
+ FallbackReason reason = fallbackReason(branch, false);
+ if (reason != FallbackReason.NONE) return reason;
+ }
+ return FallbackReason.NONE;
+ }
+
+ private static FallbackReason joinReason(Exp exp, boolean repeated) {
+ if (tripleJoin(exp)) return FallbackReason.NONE;
+ if (!singleton(exp.first())) return FallbackReason.SCOPED_JOIN;
+ FallbackReason left = fallbackReason(exp.first(), repeated);
+ return left == FallbackReason.NONE ? fallbackReason(exp.rest(), repeated) : left;
+ }
+
+ private static FallbackReason graphReason(Exp exp, boolean repeated) {
+ if (repeated && !tripleJoin(exp.get(1)) && !singleton(exp.get(1))) return FallbackReason.REPEATED_SCOPE;
+ return fallbackReason(exp.get(1), false);
+ }
+
+ private static FallbackReason optionalReason(Exp exp, boolean repeated) {
+ if (exp.isPostpone()) return FallbackReason.COMPLEX_OPTIONAL;
+ FallbackReason left = fallbackReason(exp.first(), repeated);
+ if (left != FallbackReason.NONE) return left;
+ return tripleJoin(exp.rest()) ? FallbackReason.NONE : FallbackReason.COMPLEX_OPTIONAL;
+ }
+
+ /** Avoid repeated independent scans where the existing evaluator uses a buffered join. */
+ private static boolean singleton(Exp exp) {
+ return switch (exp.type()) {
+ case AND, BGP, JOIN -> exp.getExpList().stream().allMatch(SelectCursor::singleton);
+ case EMPTY, FILTER, BIND -> true;
+ case VALUES -> exp.getMappings().size() <= 1;
+ default -> false;
+ };
+ }
+
+ private static boolean tripleJoin(Exp exp) {
+ return switch (exp.type()) {
+ case AND, BGP, JOIN -> exp.getExpList().stream().allMatch(SelectCursor::tripleJoin);
+ case EDGE -> !(exp.getEdge() instanceof PropertyPathEdge);
+ case EMPTY -> true;
+ default -> false;
+ };
+ }
+
+ /**
+ * Initializes one independent execution without requesting any candidate edge.
+ *
+ * @param eval fresh evaluator owned by this execution
+ * @param query completed eligible SELECT
+ * @param bindings initial parameter bindings, or null
+ */
+ public SelectCursor(Eval eval, Query query, Mapping bindings) {
+ this(null, eval, query, bindings, query.getBody(), null, true);
+ }
+
+ /**
+ * Opens eligible ORDER BY input without applying OFFSET or LIMIT before ranking.
+ * Projection and DISTINCT still run for every candidate, using the ordinary evaluator.
+ * The caller owns both this cursor and its producer's stable read view.
+ *
+ * @param eval fresh evaluator owned by this execution
+ * @param query completed query accepted by {@link #supportsOrderedInput(Query)}
+ * @param bindings initial parameter bindings, or null
+ * @return unsliced input cursor
+ */
+ public static SelectCursor orderedInput(Eval eval, Query query, Mapping bindings) {
+ return new SelectCursor(null, eval, query, bindings, query.getBody(), null, false);
+ }
+
+ private SelectCursor(SelectCursor parent, Eval eval, Query query, Mapping bindings, Exp body, Node graph,
+ boolean applySlice) {
+ this.eval = eval;
+ this.query = query;
+ this.scopeGraph = graph;
+ this.applySlice = applySlice;
+ parameters = bindings;
+ nested = parent != null;
+ cancellation = nested ? parent.cancellation : eval;
+ producer = eval.getProducer();
+ eval.setSubEval(nested);
+ eval.init(query);
+ memory = eval.getMemory();
+ eval.share(bindings);
+ if (!nested) {
+ producer.start(query);
+ eval.getVisitor().init(query);
+ eval.getVisitor().before(query);
+ }
+ if (bindings != null) eval.bind(bindings);
+ if (!nested && query.getValues() != null) append(query.getValues());
+ append(body);
+ frames = new Frame[instructions.size()];
+ }
+
+ private void append(Exp exp) {
+ append(instructions, exp);
+ }
+
+ private static void append(List target, Exp exp) {
+ if (exp.isFail()) {
+ target.add(exp);
+ } else if (exp.isAnd() || exp.isBGP() || exp.type() == Type.JOIN && tripleJoin(exp)) {
+ for (Exp child : exp) append(target, child);
+ } else {
+ target.add(exp);
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (next != null) return true;
+ if (closed) return false;
+ if (query.isFail() || emitted >= limit()) {
+ close();
+ return false;
+ }
+ while (depth >= 0 && !cancellation.isStop()) {
+ checkInterrupted();
+ if (depth == frames.length) {
+ depth--;
+ if (selectRow()) return true;
+ } else if (advance()) {
+ depth++;
+ } else {
+ frames[depth].close();
+ frames[depth--] = null;
+ }
+ }
+ close();
+ return false;
+ }
+
+ private static void checkInterrupted() {
+ if (Thread.currentThread().isInterrupted()) throw new QueryEvaluationException("Query interrupted");
+ }
+
+ private boolean selectRow() {
+ Mapping row = memory.store(query, producer, nested);
+ if (row == null || !nested && !eval.acceptable(row)
+ || accepted++ < (nested || !applySlice ? 0 : query.getOffset())) return false;
+ if (++emitted >= limit()) close();
+ next = row;
+ return true;
+ }
+
+ /** Requests cooperative cancellation without accessing the owner's binding frames. */
+ public void cancel() {
+ cancellation.finish();
+ }
+
+ private long limit() {
+ return nested || !applySlice ? Long.MAX_VALUE : query.getLimit();
+ }
+
+ private boolean advance() {
+ if (frames[depth] == null) frames[depth] = frame(instructions.get(depth), scopeGraph, depth);
+ return frames[depth].advance();
+ }
+
+ private Frame frame(Exp exp, Node graph, int index) {
+ if (exp.isFail()) return new OnceFrame(exp, graph, index, false);
+ return switch (exp.type()) {
+ case EDGE -> new EdgeFrame(exp, graph, index);
+ case VALUES -> new ValuesFrame(exp, index);
+ case JOIN -> new JoinFrame(exp, graph, index);
+ case UNION -> new UnionFrame(exp, graph, index);
+ case GRAPH -> new GraphFrame(exp, index);
+ case OPTIONAL -> new OptionalFrame(exp, graph, index);
+ default -> new OnceFrame(exp, graph, index, true);
+ };
+ }
+
+ @Override
+ public Mapping next() {
+ if (!hasNext()) throw new NoSuchElementException();
+ Mapping row = next;
+ next = null;
+ return row;
+ }
+
+ /** @return whether the limit was reached and the final buffered row consumed */
+ public boolean isFinished() {
+ return closed && next == null;
+ }
+
+ /** Releases active frames from the innermost binding scope outward. Idempotent. */
+ @Override
+ public void close() {
+ next = null;
+ if (closed) return;
+ closed = true;
+ try {
+ for (int i = frames.length - 1; i >= 0; i--) {
+ if (frames[i] != null) {
+ frames[i].close();
+ frames[i] = null;
+ }
+ }
+ } finally {
+ try {
+ eval.getEvaluator().finish(memory);
+ } finally {
+ if (!nested) producer.finish(query);
+ }
+ }
+ }
+
+ private interface Frame extends AutoCloseable {
+ boolean advance();
+ @Override void close();
+ }
+
+ /** Pure triples can reuse bindings; lexical expression scopes need fresh memory. */
+ private Frame scope(Exp body, Node graph, int index) {
+ return tripleJoin(body) ? new Pipeline(body, graph, index) : new ScopedFrame(body, graph, index);
+ }
+
+ /** Evaluates a lexical group independently, then joins each local row into its caller. */
+ private final class ScopedFrame implements Frame {
+ private final SelectCursor child;
+ private final int index;
+ private Mapping bound;
+
+ private ScopedFrame(Exp body, Node graph, int index) {
+ this.index = index;
+ Eval evaluator = Eval.create(producer, eval.getEvaluator(), eval.getMatcher());
+ child = new SelectCursor(SelectCursor.this, evaluator, query, parameters, body, graph, true);
+ }
+
+ @Override
+ public boolean advance() {
+ release();
+ while (child.hasNext()) {
+ Mapping row = child.next();
+ if (memory.push(row, index)) {
+ bound = row;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void release() {
+ if (bound != null) {
+ memory.pop(bound);
+ bound = null;
+ }
+ }
+
+ @Override
+ public void close() {
+ release();
+ child.close();
+ }
+ }
+
+ private final class JoinFrame implements Frame {
+ private final Exp exp;
+ private final Node graph;
+ private final int index;
+ private final Frame left;
+ private Frame right;
+
+ private JoinFrame(Exp exp, Node graph, int index) {
+ this.exp = exp;
+ this.graph = graph;
+ this.index = index;
+ left = scope(exp.first(), graph, index);
+ }
+
+ @Override
+ public boolean advance() {
+ if (right != null && right.advance()) return true;
+ releaseRight();
+ while (left.advance() && !cancellation.isStop()) {
+ right = scope(exp.rest(), graph, index + 1);
+ if (right.advance()) return true;
+ releaseRight();
+ }
+ return false;
+ }
+
+ private void releaseRight() {
+ if (right != null) {
+ right.close();
+ right = null;
+ }
+ }
+
+ @Override
+ public void close() {
+ releaseRight();
+ left.close();
+ }
+ }
+
+ /** A resumable binding scope; closing it restores its input environment. */
+ private final class Pipeline implements Frame {
+ private final List steps = new ArrayList<>();
+ private final Frame[] active;
+ private final Node graph;
+ private final int base;
+ private int position;
+ private boolean yielded;
+
+ private Pipeline(Exp exp, Node graph, int base) {
+ this.graph = graph;
+ this.base = base;
+ append(steps, exp);
+ active = new Frame[steps.size()];
+ }
+
+ @Override
+ public boolean advance() {
+ if (yielded) {
+ position--;
+ yielded = false;
+ }
+ while (position >= 0 && !cancellation.isStop()) {
+ checkInterrupted();
+ if (position == active.length) {
+ yielded = true;
+ return true;
+ }
+ if (active[position] == null) active[position] = frame(steps.get(position), graph, base + position);
+ if (active[position].advance()) {
+ position++;
+ } else {
+ active[position].close();
+ active[position--] = null;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void close() {
+ for (int i = active.length - 1; i >= 0; i--) {
+ if (active[i] != null) {
+ active[i].close();
+ active[i] = null;
+ }
+ }
+ position = -1;
+ }
+ }
+
+ private final class UnionFrame implements Frame {
+ private final Exp exp;
+ private final Node graph;
+ private final int index;
+ private int branch;
+ private Frame current;
+
+ private UnionFrame(Exp exp, Node graph, int index) {
+ this.exp = exp;
+ this.graph = graph;
+ this.index = index;
+ }
+
+ @Override
+ public boolean advance() {
+ while (!cancellation.isStop()) {
+ if (current != null && current.advance()) return true;
+ close();
+ if (branch == exp.size()) return false;
+ current = scope(exp.get(branch++), graph, index);
+ }
+ return false;
+ }
+
+ @Override
+ public void close() {
+ if (current != null) {
+ current.close();
+ current = null;
+ }
+ }
+ }
+
+ private final class OptionalFrame implements Frame {
+ private final Exp exp;
+ private final Node graph;
+ private final int index;
+ private final Frame left;
+ private Frame right;
+ private boolean matched;
+
+ private OptionalFrame(Exp exp, Node graph, int index) {
+ this.exp = exp;
+ this.graph = graph;
+ this.index = index;
+ left = scope(exp.first(), graph, index);
+ }
+
+ @Override
+ public boolean advance() {
+ while (!cancellation.isStop()) {
+ if (right != null) {
+ if (right.advance()) {
+ matched = true;
+ return true;
+ }
+ right.close();
+ right = null;
+ if (!matched) return unmatched();
+ }
+ if (!left.advance()) return false;
+ matched = false;
+ right = scope(exp.rest(), graph, index);
+ }
+ return false;
+ }
+
+ private boolean unmatched() {
+ matched = true;
+ return true;
+ }
+
+ @Override
+ public void close() {
+ if (right != null) right.close();
+ right = null;
+ left.close();
+ }
+ }
+
+ private final class GraphFrame implements Frame {
+ private final Exp body;
+ private final Node name;
+ private final int index;
+ private final Iterator graphs;
+ private Frame current;
+ private Node selected;
+ private boolean bound;
+
+ private GraphFrame(Exp exp, int index) {
+ body = exp.get(1);
+ name = exp.getGraphName();
+ this.index = index;
+ graphs = producer.getGraphNodes(name, query.getFrom(name), memory).iterator();
+ }
+
+ @Override
+ public boolean advance() {
+ releaseName();
+ while (!cancellation.isStop()) {
+ if (advanceBody()) return true;
+ close();
+ if (!graphs.hasNext()) return false;
+ selected = graphs.next();
+ if (eval.getMatcher().match(name, selected, memory)) {
+ current = scope(body, selected, index + 1);
+ }
+ }
+ return false;
+ }
+
+ private boolean advanceBody() {
+ while (current != null && current.advance()) {
+ if (memory.push(name, selected, index)) {
+ bound = true;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void releaseName() {
+ if (bound) {
+ memory.pop(name);
+ bound = false;
+ }
+ }
+
+ @Override
+ public void close() {
+ releaseName();
+ if (current != null) {
+ current.close();
+ current = null;
+ }
+ }
+ }
+
+ private final class EdgeFrame implements Frame {
+ private final Edge pattern;
+ private final int index;
+ private final Iterable source;
+ private final Iterator candidates;
+ private boolean bound;
+
+ private EdgeFrame(Exp exp, Node graph, int index) {
+ this.index = index;
+ pattern = exp.getEdge();
+ memory.setExp(exp);
+ source = producer.getEdges(graph, query.getFrom(graph), pattern, memory);
+ candidates = source.iterator();
+ }
+
+ @Override
+ public boolean advance() {
+ release();
+ while (candidates.hasNext()) {
+ Edge candidate = candidates.next();
+ if (candidate != null && eval.getMatcher().match(pattern, candidate, memory)
+ && (!pattern.isMatchArity() || pattern.nbNode() == candidate.nbNode())
+ && memory.push(producer, pattern, candidate, index)) {
+ bound = true;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void release() {
+ if (bound) {
+ memory.pop(pattern);
+ bound = false;
+ }
+ }
+
+ @Override
+ public void close() {
+ release();
+ producer.closeEdges(source);
+ }
+ }
+
+ private final class ValuesFrame implements Frame {
+ private final Exp exp;
+ private final Iterator rows;
+ private final int index;
+ private Mapping bound;
+
+ private ValuesFrame(Exp exp, int index) {
+ this.exp = exp;
+ this.index = index;
+ rows = exp.getMappings().iterator();
+ }
+
+ @Override
+ public boolean advance() {
+ close();
+ while (rows.hasNext()) {
+ checkInterrupted();
+ Mapping row = rows.next();
+ if (eval.valuesBinding(exp.getNodeList(), row, index)) {
+ bound = row;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void close() {
+ if (bound != null) {
+ eval.free(exp.getNodeList(), bound);
+ bound = null;
+ }
+ }
+ }
+
+ private final class OnceFrame implements Frame {
+ private final Exp exp;
+ private final boolean enabled;
+ private final Node graph;
+ private final int index;
+ private boolean used;
+ private boolean bound;
+
+ private OnceFrame(Exp exp, Node graph, int index, boolean enabled) {
+ this.exp = exp;
+ this.graph = graph;
+ this.index = index;
+ this.enabled = enabled;
+ }
+
+ @Override
+ public boolean advance() {
+ close();
+ if (used || !enabled) return false;
+ used = true;
+ return switch (exp.type()) {
+ case FILTER -> eval.test(graph, exp.getFilter(), memory, producer);
+ case BIND -> bind();
+ default -> true;
+ };
+ }
+
+ private boolean bind() {
+ Node value;
+ try {
+ value = eval.eval(graph, exp.getFilter(), memory, producer);
+ } catch (QueryTypeErrorException error) {
+ return true;
+ }
+ if (value == null) return true;
+ bound = memory.push(exp.getNode(), value, index);
+ return bound;
+ }
+
+ @Override
+ public void close() {
+ if (bound) {
+ memory.pop(exp.getNode());
+ bound = false;
+ }
+ }
+ }
+}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/SparqlException.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/SparqlException.java
deleted file mode 100644
index d54f140a1..000000000
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/SparqlException.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package fr.inria.corese.core.next.query.impl.engine.eval;
-
-/**
- *
- * @author corby
- */
-public class SparqlException extends Exception {
- private static final long serialVersionUID = 1L;
-
- private final boolean stop;
-
- public SparqlException() {
- this(false);
- }
-
- public SparqlException(boolean stop) {
- this.stop = stop;
- }
-
- // isStop true means stop query processing, perform aggregate etc. and return partial result
- // isStop false means this is an exception
- // see LDScriptException in sparql
-
- public boolean isStop() {
- return stop;
- }
-
-}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Stack.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Stack.java
index 9acf9130e..6e8b59113 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Stack.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Stack.java
@@ -13,36 +13,39 @@
* @author Olivier Corby, Edelweiss, INRIA 2009
*/
public class Stack extends ArrayList implements ExpType {
+ @java.io.Serial
+ private static final long serialVersionUID = 1L;
- public static Stack create(Exp e){
- Stack st = new Stack();
- st.add(e);
- return st;
- }
+ public static Stack create(Exp e){
+ Stack st = new Stack();
+ st.add(e);
+ return st;
+ }
- Stack copy(){
- Stack st = new Stack();
- st.addAll(this);
- return st;
- }
+ Stack copy(){
+ Stack st = new Stack();
+ st.addAll(this);
+ return st;
+ }
- /**
- * Push all elements of AND in the stack
- */
- Stack and(Exp exp, int n){
- remove(n);
- int i = 0;
- for (Exp e : exp){
+
+ /**
+ * Push all elements of AND in the stack
+ */
+ Stack and(Exp exp, int n){
+ remove(n);
+ int i = 0;
+ for (Exp e : exp){
if (e.getBind() != null){
add(n + i++, e.getBind());
}
add(n + i++, e);
- }
- return this;
- }
+ }
+ return this;
+ }
Stack addCopy(int n, Exp exp) {
Stack copy = copy();
@@ -52,13 +55,13 @@ Stack addCopy(int n, Exp exp) {
@Override
- public String toString(){
- StringBuilder str = new StringBuilder();
- int i = 0;
- for (Exp e : this){
- str.append(i++).append(" ").append(e).append(", ");
- }
- return str.toString();
- }
+ public String toString(){
+ StringBuilder str = new StringBuilder();
+ int i = 0;
+ for (Exp e : this){
+ str.append(i++).append(" ").append(e).append(", ");
+ }
+ return str.toString();
+ }
}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/Event.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/Event.java
index e167adfc4..c3767de39 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/Event.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/Event.java
@@ -10,70 +10,70 @@
*/
public interface Event {
- int BEGIN = 0;
- int START = 1;
- int ENUM = 2;
- int MATCH = 3;
- int FILTER = 4;
- int BIND = 5;
- int GRAPH = 6;
- int PATH = 7;
- int PATHSTEP = 8;
+ int BEGIN = 0;
+ int START = 1;
+ int ENUM = 2;
+ int MATCH = 3;
+ int FILTER = 4;
+ int BIND = 5;
+ int GRAPH = 6;
+ int PATH = 7;
+ int PATHSTEP = 8;
- int AGG = 13;
+ int AGG = 13;
- int FINISH = 14;
- int DISTINCT = 15;
- int LIMIT = 16;
+ int FINISH = 14;
+ int DISTINCT = 15;
+ int LIMIT = 16;
- int RESULT = 19;
- int END = 20;
+ int RESULT = 19;
+ int END = 20;
- int ALL = 50;
+ int ALL = 50;
- // events from user to listener
+ // events from user to listener
- // next step
+ // next step
int STEP = 101;
- // next edge/node/path
+ // next edge/node/path
int NEXT = 102;
- // skip trace until next expression in stack
+ // skip trace until next expression in stack
int FORWARD = 103;
- // eval current expression silently
+ // eval current expression silently
int COMPLETE = 104;
- // eval until current expression succeed
+ // eval until current expression succeed
int SUCCESS = 105;
- // resume execution silently
+ // resume execution silently
int QUIT = 109;
- // pprint current Mapping
+ // pprint current Mapping
int MAP = 110;
- int VERBOSE = 111;
- int HELP = 112;
+ int VERBOSE = 111;
+ int HELP = 112;
- int getSort();
+ int getSort();
- Object getObject();
+ Object getObject();
- Object getArg(int n);
+ Object getArg(int n);
- Exp getExp();
+ Exp getExp();
- boolean isSuccess();
+ boolean isSuccess();
}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/EventImpl.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/EventImpl.java
index f61afe6ee..0fc339508 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/EventImpl.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/EventImpl.java
@@ -12,107 +12,107 @@
public class EventImpl implements Event {
- int type;
- boolean isSuccess = true;
- Object object;
- Object arg;
- Object arg2;
-
- EventImpl(int n, Object o){
- type = n;
- object = o;
- }
-
- EventImpl(int n, Object o, Object o2){
- type = n;
- object = o;
- arg = o2;
- if (o2 instanceof Boolean success){
- isSuccess = success;
- }
- }
-
- EventImpl(int n, Object o, Object o2, Object o3){
- type = n;
- object = o;
- arg = o2;
- arg2 = o3;
- if (o3 instanceof Boolean success){
- isSuccess = success;
- }
- }
-
- public String toString(){
- String str = getTitle();
- if (object != null){
- str += " ";
- if (object instanceof Exp exp && exp.isEdge()){
- str += "("+ exp.getEdge().getEdgeIndex() + ") ";
- }
- str += object;
- }
- if (arg != null){
- str += "\n";
- str += arg;
- }
- if (arg2 != null && ! (arg2 instanceof Stack)) str += "\n" + arg2;
-
- return str;
- }
-
-
- String getTitle(){
- return switch (type) {
- case BEGIN -> "begin";
- case START -> "start";
- case ENUM -> "enum";
- case FILTER -> "filter";
- case BIND -> "bind";
- case MATCH -> "match";
- case GRAPH -> "graph";
- case PATH -> "path";
- case PATHSTEP, STEP -> "step";
- case FINISH -> "finish";
- case AGG -> "aggregate";
- case DISTINCT -> "distinct";
- case LIMIT -> "limit";
- case RESULT -> "result";
- case END -> "end";
- case COMPLETE -> "complete";
- case FORWARD -> "forward";
- case MAP -> "map";
- case NEXT -> "next";
- case QUIT -> "quit";
- case SUCCESS -> "success";
- case VERBOSE -> "verbose";
- case HELP -> "help";
- default -> "event(" + type + ')';
- };
- }
-
-
-
- public static EventImpl create(int type, Object obj){
+ int type;
+ boolean isSuccess = true;
+ Object object;
+ Object arg;
+ Object arg2;
+
+ EventImpl(int n, Object o){
+ type = n;
+ object = o;
+ }
+
+ EventImpl(int n, Object o, Object o2){
+ type = n;
+ object = o;
+ arg = o2;
+ if (o2 instanceof Boolean success){
+ isSuccess = success;
+ }
+ }
+
+ EventImpl(int n, Object o, Object o2, Object o3){
+ type = n;
+ object = o;
+ arg = o2;
+ arg2 = o3;
+ if (o3 instanceof Boolean success){
+ isSuccess = success;
+ }
+ }
+
+ public String toString(){
+ String str = getTitle();
+ if (object != null){
+ str += " ";
+ if (object instanceof Exp exp && exp.isEdge()){
+ str += "("+ exp.getEdge().getEdgeIndex() + ") ";
+ }
+ str += object;
+ }
+ if (arg != null){
+ str += "\n";
+ str += arg;
+ }
+ if (arg2 != null && ! (arg2 instanceof Stack)) str += "\n" + arg2;
+
+ return str;
+ }
+
+
+ String getTitle(){
+ return switch (type) {
+ case BEGIN -> "begin";
+ case START -> "start";
+ case ENUM -> "enum";
+ case FILTER -> "filter";
+ case BIND -> "bind";
+ case MATCH -> "match";
+ case GRAPH -> "graph";
+ case PATH -> "path";
+ case PATHSTEP, STEP -> "step";
+ case FINISH -> "finish";
+ case AGG -> "aggregate";
+ case DISTINCT -> "distinct";
+ case LIMIT -> "limit";
+ case RESULT -> "result";
+ case END -> "end";
+ case COMPLETE -> "complete";
+ case FORWARD -> "forward";
+ case MAP -> "map";
+ case NEXT -> "next";
+ case QUIT -> "quit";
+ case SUCCESS -> "success";
+ case VERBOSE -> "verbose";
+ case HELP -> "help";
+ default -> "event(" + type + ')';
+ };
+ }
+
+
+
+ public static EventImpl create(int type, Object obj){
return new EventImpl(type, obj);
- }
+ }
- public static EventImpl create(int type, Object obj, Object arg){
+ public static EventImpl create(int type, Object obj, Object arg){
return new EventImpl(type, obj, arg);
- }
+ }
- public static EventImpl create(int type, Object obj, Object arg, Object arg2){
+ public static EventImpl create(int type, Object obj, Object arg, Object arg2){
return new EventImpl(type, obj, arg, arg2);
- }
+ }
- public int getSort(){
- return type;
- }
+ public int getSort(){
+ return type;
+ }
- public Object getObject(){
- return object;
- }
+ public Object getObject(){
+ return object;
+ }
- public Object getArg(int n){
+ public Object getArg(int n){
return switch (n) {
case 0 -> object;
case 1 -> arg;
@@ -121,15 +121,15 @@ public Object getArg(int n){
};
}
- public Exp getExp(){
- if (object instanceof Exp expression){
- return expression;
- }
- return null;
- }
+ public Exp getExp(){
+ if (object instanceof Exp expression){
+ return expression;
+ }
+ return null;
+ }
- public boolean isSuccess(){
- return isSuccess;
- }
+ public boolean isSuccess(){
+ return isSuccess;
+ }
}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/EventListener.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/EventListener.java
index 47bb6d597..97bcd1e7d 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/EventListener.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/EventListener.java
@@ -8,13 +8,13 @@
*/
public interface EventListener {
- boolean send(Event e);
+ boolean send(Event e);
- boolean handle(int sort);
+ boolean handle(int sort);
- boolean handle(int sort, boolean b);
+ boolean handle(int sort, boolean b);
- void setObject(Object obj);
+ void setObject(Object obj);
}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/KgramEventDispatcher.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/KgramEventDispatcher.java
index c3ab90590..c66473378 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/KgramEventDispatcher.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/KgramEventDispatcher.java
@@ -12,7 +12,6 @@
*/
public class KgramEventDispatcher implements Iterable {
- boolean isEval = false;
List observers = new ArrayList<>();
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/ResultListener.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/ResultListener.java
index 7e467757c..95b7956bf 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/ResultListener.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/ResultListener.java
@@ -2,7 +2,6 @@
import fr.inria.corese.core.next.query.impl.engine.model.Edge;
import fr.inria.corese.core.next.query.impl.engine.model.Expr;
-import fr.inria.corese.core.next.query.impl.engine.model.Regex;
import fr.inria.corese.core.next.query.impl.engine.spi.Environment;
import fr.inria.corese.core.next.query.impl.engine.pattern.Exp;
import fr.inria.corese.core.next.query.impl.engine.path.Path;
@@ -15,24 +14,21 @@
*/
public interface ResultListener {
- /**
- * For each solution, kgram call process(env)
- * If return true: Mapping created as usual
- * If return false: Mapping not created
- */
- boolean process(Environment env);
+ /**
+ * For each solution, kgram call process(env)
+ * If return true: Mapping created as usual
+ * If return false: Mapping not created
+ */
+ boolean process(Environment env);
- /**
- * For each path, kgram call process(path)
- * If return true: Mapping created as usual
- * If return false: Mapping not created
- */
- boolean process(Path path);
+ /**
+ * For each path, kgram call process(path)
+ * If return true: Mapping created as usual
+ * If return false: Mapping not created
+ */
+ boolean process(Path path);
- boolean enter(Edge ent, Regex exp, int size);
-
- boolean leave(Edge ent, Regex exp, int size);
boolean listen(Exp exp, Edge query, Edge target);
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/User.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/User.java
deleted file mode 100644
index f51b492b8..000000000
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/User.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package fr.inria.corese.core.next.query.impl.engine.event;
-
-/**
- * Model user interaction with debugger
- *
- * @author Olivier Corby, Edelweiss, INRIA 2010
- *
- */
-public interface User {
-
- int get();
-
- String help();
-
-
-}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/Checker.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/Checker.java
deleted file mode 100644
index 9b94d7d0f..000000000
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/Checker.java
+++ /dev/null
@@ -1,216 +0,0 @@
-package fr.inria.corese.core.next.query.impl.engine.filter;
-
-import fr.inria.corese.core.next.query.impl.engine.model.Expr;
-import fr.inria.corese.core.next.query.impl.engine.model.ExprType;
-
-import java.util.ArrayList;
-import java.util.List;
-
-
-/**
- * Filter Exp Checker
- * Check presence of patterns that are always true or always false
- * Such as:
- * ?x != ?x
- * ?x > ?y && ?x < ?y
- *
- * @author Olivier Corby, Edelweiss, INRIA 2010
- *
- */
-public final class Checker implements ExprType {
- static final int BOOL = BOOLEAN;
-
- private final List alwaysFalse;
- private final Matcher matcher;
-
- public Checker(){
- matcher = new Matcher();
- alwaysFalse = createAlwaysFalsePatterns();
- }
-
- /**
- * Check one filter
- */
- public boolean check(Expr ee){
- // match = true means that a false pattern matches
- // hence return false (check correctness is false)
- return !match(ee);
- }
-
-
- /**
- * Return true if a false pattern matches
- * return false otherwise
- */
- boolean match(Expr ee){
-
-
- return match(ee, alwaysFalse);
- }
-
-
- boolean match(Expr ee, List pat) {
- for (FilterPattern p : pat){
- if (matcher.match(p, ee)){
- return true;
- }
- }
- return false;
- }
-
-
-
- /*************************************************
- * Always false patterns
- *
- */
-
- List createAlwaysFalsePatterns(){
- List patterns = new ArrayList<>();
- patterns.add(neqSelf());
- patterns.add(ltSelf());
- patterns.add(notEqSelf());
- patterns.add(notGeSelf());
- patterns.add(patNotPat());
- patterns.add(notOr());
- patterns.add(eqNeq());
- patterns.add(eqGt());
- patterns.add(ltGt());
- patterns.add(gtNotGe());
- patterns.add(eqNotGe());
- return List.copyOf(patterns);
- }
-
-
- /**
- * ?from=cst || ?to=cst
- */
- FilterPattern path(String v1, String v2){
- FilterPattern e1 = term(EQ, FilterPattern.variable(v1), constant());
- FilterPattern e2 = term(EQ, FilterPattern.variable(v2), constant());
- return or(e1, e2);
- }
-
- // always false
- FilterPattern neqSelf(){
- // EXP != EXP
- FilterPattern exp = pat();
- return term(NE, exp, exp);
-
- }
-
- FilterPattern ltSelf(){
- // EXP < EXP
- FilterPattern exp = pat();
- return term(LT, exp, exp);
-
- }
-
- FilterPattern notEqSelf(){
- // !(EXP = EXP)
- FilterPattern exp = pat();
- return not(term(EQ, exp, exp));
-
- }
-
- FilterPattern notGeSelf(){
- // !(EXP >= EXP)
- FilterPattern exp = pat();
- return not(term(GE, exp, exp));
-
- }
-
-
-
- FilterPattern patNotPat(){
- // EXP && ! EXP
- FilterPattern exp = pat();
- return and(exp, not(exp));
- }
-
-
- FilterPattern notOr(){
- // ! (EXP || ! EXP)
- FilterPattern exp = pat();
- return not(or(exp, not(exp)));
- }
-
-
- // Note:
- // we can have both with list of values
- // ?x = xpath() && ?x != xpath()
- FilterPattern eqNeq(){
- // EXP1 = EXP2 && EXP1 != EXP2
- FilterPattern e1 = pat();
- FilterPattern e2 = pat();
- return and(term(EQ, e1, e2), term(NE, e1, e2));
- }
-
-
- FilterPattern eqGt(){
- // EXP1 = EXP2 && EXP1 > EXP2
- FilterPattern e1 = pat();
- FilterPattern e2 = pat();
- return and(term(EQ, e1, e2), term(GT, e1, e2));
- }
-
- FilterPattern ltGt(){
- // EXP1 > EXP2 && EXP1 < EXP2
- FilterPattern e1 = pat();
- FilterPattern e2 = pat();
- return and(term(GT, e1, e2), term(LT, e1, e2));
- }
-
-
- FilterPattern gtNotGe(){
- // EXP1 > EXP2 && ! (EXP1 >= EXP2)
- FilterPattern e1 = pat();
- FilterPattern e2 = pat();
- return and(term(GT, e1, e2), not(term(GE, e1, e2)));
- }
-
- FilterPattern eqNotGe(){
- // EXP1 = EXP2 && ! (EXP1 >= EXP2)
- FilterPattern e1 = pat();
- FilterPattern e2 = pat();
- return and(term(EQ, e1, e2), not(term(GE, e1, e2)));
- }
-
-
- FilterPattern constant(){
- return FilterPattern.constant();
- }
-
-
- FilterPattern pat() {
- return new FilterPattern(ExprType.JOKER);
- }
-
- FilterPattern pat(FilterPattern e1) {
- return new FilterPattern(ExprType.BOOLEAN, ExprType.NOT, e1);
- }
-
- FilterPattern pat(int type, int ope, FilterPattern e1, FilterPattern e2){
- return new FilterPattern(type, ope, e1, e2);
- }
-
- FilterPattern not(FilterPattern e){
- return pat(e);
- }
-
- FilterPattern and(FilterPattern e1, FilterPattern e2){
- return pat(BOOLEAN, AND, e1, e2);
- }
-
- FilterPattern or(FilterPattern e1, FilterPattern e2){
- return pat(BOOLEAN, OR, e1, e2);
- }
-
- FilterPattern term(int ope, FilterPattern e1, FilterPattern e2){
- return pat(TERM, ope, e1, e2);
- }
-
-
-
-
-}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/Compile.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/Compile.java
deleted file mode 100644
index 8dc8e7d4d..000000000
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/Compile.java
+++ /dev/null
@@ -1,254 +0,0 @@
-package fr.inria.corese.core.next.query.impl.engine.filter;
-
-import fr.inria.corese.core.next.query.impl.engine.model.ExpType;
-import fr.inria.corese.core.next.query.impl.engine.model.Expr;
-import fr.inria.corese.core.next.query.impl.engine.model.ExprType;
-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.engine.pattern.Exp;
-import fr.inria.corese.core.next.query.impl.engine.pattern.Query;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Filter Exp Compiler for Optimizations
- * If Filter leads to optimization, we tag the Filter Exp
- * with e.g. a BIND(?x = ?y)
- * Query.bind() will handle later this tag BIND
- * (1)
- * x p t . y p z . filter(y = x)
- * ->
- * x p t . bind(y, x) . y p z
- * (2)
- * x p t filter(?x = )
- * ->
- * bind(x, ) . x p t
- * (3)
- * x p t filter(?x = || ?x = )
- * ->
- * bind(x, (, )) . x p t
- * (4)
- * optional{} !bound(?x)
- * !bound() backjump before optional{}
- * if there is no optional, !bound() backjump to edge before where ?x is bound
- *
- * @author Olivier Corby, Edelweiss, INRIA 2010
- *
- */
-public class Compile implements ExprType {
-
- Query kgramQuery;
- Matcher matcher;
- Checker checker;
-
- public Compile(Query q){
- kgramQuery = q;
- matcher = new Matcher();
- checker = new Checker();
- }
-
- /**
- * exp is a FILTER Exp
- * when !bound(?x) : get Node ?x for optimizing backjump with optional
- * when ?x = ?y : get list Node, for optimizing edge
- * Note:
- * assign filter to edge and put edge first in its component:
- * x p t . y p z filter(y = x)
- * we can bind y = x before enumerate y p z
- */
- public void process(Query q, Exp exp) {
- this.kgramQuery = q;
- Filter ff = exp.getFilter();
- Expr ee = ff.getExp();
-
- switch (ee.oper()){
-
- case OR:
- or(exp);
- break;
-
- case EQ:
- eq(exp);
- break;
- // Note: IN, VALUES
- case IN:
- in(exp);
- break;
- default:
- if (matcher.isGL(ee.oper())){
- gl(exp);
- }
-
- }
-
- }
-
-
-
- /**
- * ?x = ?y
- * ?x = cst
- */
- void eq(Exp exp){
- Filter ff = exp.getFilter();
- Expr ee = ff.getExp();
-
- // ?x = ?y
- FilterPattern pat = new FilterPattern(TERM, EQ, VARIABLE, VARIABLE);
- if (matcher.match(pat, ee)){
- // compute Node list corresponding to variables
- List lNode = kgramQuery.getNodes(exp);
- if (lNode.size()==2){
- Exp bind = Exp.create(ExpType.Type.OPT_BIND);
- for (Node qNode : lNode){
- Exp variable = Exp.create(ExpType.Type.NODE, qNode);
- bind.add(variable);
- }
- exp.add(bind);
- }
- }
- // ?x = 'constant'
- else if (matcher.match(new FilterPattern(TERM, EQ, VARIABLE, CONSTANT), ee)){
- Exp bind = buildCst(exp, ExpType.Type.OPT_BIND);
- if (bind != null){
- exp.add(bind);
- }
- }
- }
-
-
-
- Exp buildCst(Exp exp, ExpType.Type type){
- Filter ff = exp.getFilter();
- Expr ee = ff.getExp();
- Node node = kgramQuery.getProperAndSubSelectNode(ff.getVariables().getFirst());
- if (node != null){
- // variable ?x
- Exp bind = Exp.create(type, Exp.create(ExpType.Type.NODE, node));
- List list = new ArrayList<>();
- list.add(getConstants(ee).getFirst());
- bind.setObject(list);
- return bind;
- }
- return null;
- }
-
- Exp buildVar(Exp exp){
- List lNode = kgramQuery.getNodes(exp);
- if (lNode.size()==2){
- Exp bind = Exp.create(ExpType.Type.TEST);
- for (Node node : lNode){
- bind.add(Exp.create(ExpType.Type.NODE, node));
- }
- return bind;
- }
- return null;
- }
-
-
-
- /**
- * VAR < CST
- */
- void gl(Exp exp){
- Filter ff = exp.getFilter();
- Expr ee = ff.getExp();
- FilterPattern pat = new FilterPattern(TERM, GL, VARIABLE, CONSTANT);
- if (matcher.match(pat, ee)){
- Exp test = buildCst(exp, ExpType.Type.TEST);
- if (test!=null)
- exp.add(test);
- }
- else {
- pat = new FilterPattern(TERM, GL, VARIABLE, VARIABLE);
- if (matcher.match(pat, ee)){
- Exp test = buildVar(exp);
- if (test!=null)
- exp.add(test);
- }
- }
-
- }
-
- /**
- * ?x = cst1 || ?x = cst2
- */
- void or(Exp exp){
- Filter ff = exp.getFilter();
- Expr ee = ff.getExp();
- FilterPattern pat = new FilterPattern(BOOLEAN, OR, new FilterPattern(TERM, EQ, VARIABLE, CONSTANT));
- pat.setRec();
- pat.setMatchConstant();
- if (matcher.match(pat, ee)) {
- Node node = kgramQuery.getProperAndSubSelectNode(ff.getVariables().getFirst());
- if (node != null){
- List list = getConstants(ee);
- Exp bind = Exp.create(ExpType.Type.OPT_BIND, Exp.create(ExpType.Type.NODE, node));
- bind.setObject(list);
- exp.add(bind);
- }
- }
-
- }
-
- /*
- * ?x IN (2 4)
- * only consider the case with all constants (for the moment)
- */
- void in(Exp exp) {
- Filter ff = exp.getFilter();
- Expr expr = ff.getExp();
- List lvar = ff.getVariables();
- Expr variable = expr.getExp(0);
- if (! variable.isVariable()){
- return;
- }
- List values = expr.getExp(1).getExpList();
- List list = new ArrayList<>();
- //all has to be constants
- for (Expr e : values) {
- if (e.type() != CONSTANT) {
- return;
- }
- list.add(e);
- }
-
- Node node = kgramQuery.getProperAndSubSelectNode(lvar.getFirst());
- if (node != null) {
- Exp bind = Exp.create(ExpType.Type.OPT_BIND, Exp.create(ExpType.Type.NODE, node));
- bind.setObject(list);
- exp.add(bind);
- }
- }
-
- List getConstants(Expr exp){
- List list = new ArrayList<>();
- return getConstants(exp, list);
- }
-
-
- List getConstants(Expr exp, List list){
- switch (exp.type()){
-
- case CONSTANT:
- list.add(exp); break;
-
- case VARIABLE: break;
-
- default:
- for (Expr ee : exp.getExpList()){
- getConstants(ee, list);
- }
- }
- return list;
- }
-
-
-
- public boolean isLocal(Filter f){
- return f.isBound() ||
- f.getExp().oper() == DEBUG;
- }
-}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/ExpressionPatternMatcher.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/ExpressionPatternMatcher.java
new file mode 100644
index 000000000..0e1775d9f
--- /dev/null
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/ExpressionPatternMatcher.java
@@ -0,0 +1,230 @@
+package fr.inria.corese.core.next.query.impl.engine.filter;
+
+import fr.inria.corese.core.next.query.impl.engine.model.Expr;
+import fr.inria.corese.core.next.query.impl.engine.model.ExprType;
+
+
+/**
+ * Filter Exp ExpressionPatternMatcher
+ *
+ * @author Olivier Corby, Edelweiss, INRIA 2010
+ *
+ */
+final class ExpressionPatternMatcher implements ExprType {
+ boolean rec = false;
+ boolean matchConstant = true;
+
+
+ public boolean match(FilterPattern qe, Expr te){
+ MatchBind bind = MatchBind.create();
+ return match(qe, te, bind);
+ }
+
+ boolean match(FilterPattern qe, Expr te, MatchBind bind){
+ rec = qe.isRec();
+ matchConstant = qe.isMatchConstant();
+ return process(qe, te, bind);
+ }
+
+
+
+
+ /**
+ * Expr matcher
+ * Special case:
+ * OR(EXP) with rec=true
+ * means match OR(EXP) recursively with OR(EXP1, OR(EXP2, EXP3))
+ * EXP is matched with all EXPi
+ * Pattern variable store target variable
+ * Afterwards, Pattern variable must match same target variables in all EXPi
+ *
+ */
+ boolean process(Expr qe, Expr te, MatchBind bind) {
+ if (qe.type() == ALTER) {
+ return matchAlternative(qe, te, bind);
+ }
+ if (!matchType(qe, te)) {
+ return false;
+ }
+ if ((qe.type() != CONSTANT || matchConstant) && bind.hasValue(qe)) {
+ return process(bind.getValue(qe), te, bind);
+ }
+ return switch (qe.type()) {
+ case VARIABLE, CONSTANT -> matchVarConst(qe, te, bind);
+ case FUNCTION, JOKER -> matchBoolFunAny(qe, te, bind);
+ case BOOLEAN -> matchBoolean(qe, te, bind);
+ case TERM -> matchTerm(qe, te, bind);
+ default -> false;
+ };
+ }
+
+ private boolean matchAlternative(Expr qe, Expr te, MatchBind bind) {
+ for (Expr alternative : qe.getExpList()) {
+ int size = bind.size();
+ if (process(alternative, te, bind)) {
+ return true;
+ }
+ bind.clean(size);
+ }
+ return false;
+ }
+
+ private boolean matchBoolean(Expr qe, Expr te, MatchBind bind) {
+ if (qe.arity() == 2 && te.arity() == 2) {
+ return matchTerm(qe, te, bind);
+ }
+ return qe.arity() <= te.arity() && matchBoolFunAny(qe, te, bind);
+ }
+
+ boolean matchVarConst(Expr qe, Expr te, MatchBind bind) {
+ if (qe.type() == VARIABLE) {
+ if (qe.getLabel() != null) {
+ return qe.getLabel().equals(te.getLabel());
+ }
+ bind.setValue(qe, te);
+ } else if (qe.type() == CONSTANT && matchConstant) {
+ if (!isBindable(qe)) {
+ return qe.equals(te);
+ }
+ bind.setValue(qe, te);
+ }
+ return true;
+ }
+
+
+ /**
+ * BOOLEAN NOT
+ * BOOLEAN with rec = true
+ * OR(EXP) matches recursively OR(OR(EXP1, EXP2), EXP3)
+ * FUNCTION ANY
+ */
+ boolean matchBoolFunAny(Expr qe, Expr te, MatchBind bind) {
+ if (qe.arity() == 0) {
+ // qe e.g. Pattern(ANY, ANY) with no args
+ // first time: bind the pattern
+ bind.setValue(qe, te);
+ return true;
+ }
+ if (te.arity() == 0) {
+ // qe has arity, te has not: fail
+ return false;
+ }
+
+ int size = bind.size();
+ int i = 0;
+ Expr qarg;
+
+ for (Expr targ : te.getExpList()) {
+ if (i < qe.arity()) {
+ // use pattern ith arg
+ qarg = qe.getExp(i++);
+ } else if (rec) {
+ // reuse pattern 0th arg
+ qarg = qe.getExp(0);
+ } else {
+ // query has less arguments than target
+ // and query args match target args
+ // should reset bindings here
+ bind.clean(size);
+ return false;
+ }
+
+ if (!process(qarg, targ, bind) && (!rec || !process(qe, targ, bind))) {
+ bind.clean(size);
+ return false;
+ }
+ }
+
+ if (!bind.hasValue(qe)) {
+ // because when rec = true, qe may have already been bound to an arg of te
+ bind.setValue(qe, te);
+ }
+ return true;
+ }
+
+
+ boolean matchTerm(Expr qe, Expr te, MatchBind bind) {
+ Expr fst = qe.getExp(0);
+ Expr snd = qe.getExp(1);
+
+ if (isGL(qe.oper()) && qe.oper() != te.oper()) {
+
+ if (inverse(qe.oper(), te.oper())) {
+ // qe(x < y) VS te(y > x)
+ // try switch qe args
+ fst = qe.getExp(1);
+ snd = qe.getExp(0);
+ }
+ else return false;
+ }
+
+ int size = bind.size();
+ boolean b =
+ process(fst, te.getExp(0), bind) &&
+ process(snd, te.getExp(1), bind);
+
+ if (! b){
+ bind.clean(size);
+
+ if (symmetric(qe)){
+ // try switch arguments of qe
+ // switch ?x = ?y to ?y = ?x
+ b =
+ process(snd, te.getExp(0), bind) &&
+ process(fst, te.getExp(1), bind);
+ }
+ }
+
+ if (b) bind.setValue(qe, te);
+ return b;
+ }
+
+ /**
+ * Is it a Pattern constant
+ */
+ boolean isBindable(Expr exp){
+ return exp instanceof FilterPattern;
+ }
+
+ boolean symmetric(Expr qe){
+ if (qe.type() == BOOLEAN && qe.oper() == JOKER) return true;
+ int t = qe.oper();
+ return t == EQ || t == NE || t == PLUS || t == MULT || t == AND || t == OR;
+ }
+
+ boolean inverse(int t1, int t2){
+ return
+ (t1 == LT && t2 == GT) ||
+ (t1 == GT && t2 == LT) ||
+ (t1 == GE && t2 == LE) ||
+ (t1 == LE && t2 == GE) ;
+ }
+
+ boolean isGL(int tt){
+ return tt == GL || tt == LT || tt == LE || tt == GT || tt == GE;
+ }
+
+
+ /**
+ * accept comparison operator with any comparison because term matcher
+ * will permute arguments as needed
+ */
+ boolean match(int qt, int tt){
+ if (qt == JOKER) return true;
+ if (isGL(qt)) return isGL(tt);
+ if (qt == EQNE) return tt == EQ || tt == NE;
+ return qt == tt;
+ }
+
+ boolean matchType(Expr qe, Expr te){
+ if (qe.oper() == EXIST || te.oper() == EXIST) return false;
+ if (qe.type() == JOKER) {
+ return true;
+ }
+ return switch (qe.type()) {
+ case VARIABLE, CONSTANT -> match(qe.type(), te.type());
+ default -> match(qe.type(), te.type()) && match(qe.oper(), te.oper());
+ };
+ }
+
+}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/FilterOptimizer.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/FilterOptimizer.java
new file mode 100644
index 000000000..f8bf10441
--- /dev/null
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/FilterOptimizer.java
@@ -0,0 +1,252 @@
+package fr.inria.corese.core.next.query.impl.engine.filter;
+
+import fr.inria.corese.core.next.query.impl.engine.model.ExpType;
+import fr.inria.corese.core.next.query.impl.engine.model.Expr;
+import fr.inria.corese.core.next.query.impl.engine.model.ExprType;
+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.engine.pattern.Exp;
+import fr.inria.corese.core.next.query.impl.engine.pattern.Query;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Filter Exp Compiler for Optimizations
+ * If Filter leads to optimization, we tag the Filter Exp
+ * with e.g. a BIND(?x = ?y)
+ * Query.bind() will handle later this tag BIND
+ * (1)
+ * x p t . y p z . filter(y = x)
+ * ->
+ * x p t . bind(y, x) . y p z
+ * (2)
+ * x p t filter(?x = )
+ * ->
+ * bind(x, ) . x p t
+ * (3)
+ * x p t filter(?x = || ?x = )
+ * ->
+ * bind(x, (, )) . x p t
+ * (4)
+ * optional{} !bound(?x)
+ * !bound() backjump before optional{}
+ * if there is no optional, !bound() backjump to edge before where ?x is bound
+ *
+ * @author Olivier Corby, Edelweiss, INRIA 2010
+ *
+ */
+public final class FilterOptimizer implements ExprType {
+
+ private Query kgramQuery;
+ private final ExpressionPatternMatcher matcher;
+
+ public FilterOptimizer(Query q){
+ kgramQuery = q;
+ matcher = new ExpressionPatternMatcher();
+ }
+
+ /**
+ * exp is a FILTER Exp
+ * when !bound(?x) : get Node ?x for optimizing backjump with optional
+ * when ?x = ?y : get list Node, for optimizing edge
+ * Note:
+ * assign filter to edge and put edge first in its component:
+ * x p t . y p z filter(y = x)
+ * we can bind y = x before enumerate y p z
+ */
+ public void process(Query q, Exp exp) {
+ this.kgramQuery = q;
+ Filter ff = exp.getFilter();
+ Expr ee = ff.getExp();
+
+ switch (ee.oper()){
+
+ case OR:
+ or(exp);
+ break;
+
+ case EQ:
+ eq(exp);
+ break;
+ // Note: IN, VALUES
+ case IN:
+ in(exp);
+ break;
+ default:
+ if (matcher.isGL(ee.oper())){
+ gl(exp);
+ }
+
+ }
+
+ }
+
+
+
+ /**
+ * ?x = ?y
+ * ?x = cst
+ */
+ void eq(Exp exp){
+ Filter ff = exp.getFilter();
+ Expr ee = ff.getExp();
+
+ // ?x = ?y
+ FilterPattern pat = new FilterPattern(TERM, EQ, VARIABLE, VARIABLE);
+ if (matcher.match(pat, ee)){
+ // compute Node list corresponding to variables
+ List lNode = kgramQuery.getNodes(exp);
+ if (lNode.size()==2){
+ Exp bind = Exp.create(ExpType.Type.OPT_BIND);
+ for (Node qNode : lNode){
+ Exp variable = Exp.create(ExpType.Type.NODE, qNode);
+ bind.add(variable);
+ }
+ exp.add(bind);
+ }
+ }
+ // ?x = 'constant'
+ else if (matcher.match(new FilterPattern(TERM, EQ, VARIABLE, CONSTANT), ee)){
+ Exp bind = buildCst(exp, ExpType.Type.OPT_BIND);
+ if (bind != null){
+ exp.add(bind);
+ }
+ }
+ }
+
+
+
+ Exp buildCst(Exp exp, ExpType.Type type){
+ Filter ff = exp.getFilter();
+ Expr ee = ff.getExp();
+ Node node = kgramQuery.getProperAndSubSelectNode(ff.getVariables().getFirst());
+ if (node != null){
+ // variable ?x
+ Exp bind = Exp.create(type, Exp.create(ExpType.Type.NODE, node));
+ List list = new ArrayList<>();
+ list.add(getConstants(ee).getFirst());
+ bind.setObject(list);
+ return bind;
+ }
+ return null;
+ }
+
+ Exp buildVar(Exp exp){
+ List lNode = kgramQuery.getNodes(exp);
+ if (lNode.size()==2){
+ Exp bind = Exp.create(ExpType.Type.TEST);
+ for (Node node : lNode){
+ bind.add(Exp.create(ExpType.Type.NODE, node));
+ }
+ return bind;
+ }
+ return null;
+ }
+
+
+
+ /**
+ * VAR < CST
+ */
+ void gl(Exp exp){
+ Filter ff = exp.getFilter();
+ Expr ee = ff.getExp();
+ FilterPattern pat = new FilterPattern(TERM, GL, VARIABLE, CONSTANT);
+ if (matcher.match(pat, ee)){
+ Exp test = buildCst(exp, ExpType.Type.TEST);
+ if (test!=null)
+ exp.add(test);
+ }
+ else {
+ pat = new FilterPattern(TERM, GL, VARIABLE, VARIABLE);
+ if (matcher.match(pat, ee)){
+ Exp test = buildVar(exp);
+ if (test!=null)
+ exp.add(test);
+ }
+ }
+
+ }
+
+ /**
+ * ?x = cst1 || ?x = cst2
+ */
+ void or(Exp exp){
+ Filter ff = exp.getFilter();
+ Expr ee = ff.getExp();
+ FilterPattern pat = new FilterPattern(BOOLEAN, OR, new FilterPattern(TERM, EQ, VARIABLE, CONSTANT));
+ pat.setRec();
+ pat.setMatchConstant();
+ if (matcher.match(pat, ee)) {
+ Node node = kgramQuery.getProperAndSubSelectNode(ff.getVariables().getFirst());
+ if (node != null){
+ List list = getConstants(ee);
+ Exp bind = Exp.create(ExpType.Type.OPT_BIND, Exp.create(ExpType.Type.NODE, node));
+ bind.setObject(list);
+ exp.add(bind);
+ }
+ }
+
+ }
+
+ /*
+ * ?x IN (2 4)
+ * only consider the case with all constants (for the moment)
+ */
+ void in(Exp exp) {
+ Filter ff = exp.getFilter();
+ Expr expr = ff.getExp();
+ List lvar = ff.getVariables();
+ Expr variable = expr.getExp(0);
+ if (! variable.isVariable()){
+ return;
+ }
+ List values = expr.getExp(1).getExpList();
+ List list = new ArrayList<>();
+ //all has to be constants
+ for (Expr e : values) {
+ if (e.type() != CONSTANT) {
+ return;
+ }
+ list.add(e);
+ }
+
+ Node node = kgramQuery.getProperAndSubSelectNode(lvar.getFirst());
+ if (node != null) {
+ Exp bind = Exp.create(ExpType.Type.OPT_BIND, Exp.create(ExpType.Type.NODE, node));
+ bind.setObject(list);
+ exp.add(bind);
+ }
+ }
+
+ List getConstants(Expr exp){
+ List list = new ArrayList<>();
+ return getConstants(exp, list);
+ }
+
+
+ List getConstants(Expr exp, List list){
+ switch (exp.type()){
+
+ case CONSTANT:
+ list.add(exp); break;
+
+ case VARIABLE: break;
+
+ default:
+ for (Expr ee : exp.getExpList()){
+ getConstants(ee, list);
+ }
+ }
+ return list;
+ }
+
+
+
+ public boolean isLocal(Filter f){
+ return f.isBound() ||
+ f.getExp().oper() == DEBUG;
+ }
+}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/FilterPattern.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/FilterPattern.java
index d3f23ce8e..5ed1be1a0 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/FilterPattern.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/FilterPattern.java
@@ -1,5 +1,8 @@
package fr.inria.corese.core.next.query.impl.engine.filter;
+import fr.inria.corese.core.next.query.impl.engine.pattern.PlanCopy;
+import fr.inria.corese.core.next.query.impl.engine.pattern.PlanCopyable;
+
import fr.inria.corese.core.next.query.impl.engine.model.BindingContext;
import fr.inria.corese.core.next.query.impl.engine.model.Expr;
import fr.inria.corese.core.next.query.impl.engine.model.ExprType;
@@ -15,290 +18,182 @@
import java.util.List;
/**
- * Filter Expression Pattern Matcher.
+ * Filter Expression Pattern ExpressionPatternMatcher.
*
* This class provides pattern matching capabilities for filter expressions.
*
* @author Olivier Corby, Edelweiss, INRIA 2010
*/
-public final class FilterPattern implements ExprType, Expr {
+public final class FilterPattern implements ExprType, Expr, PlanCopyable {
private static final String NOT_SUPPORTED_YET = "Not supported yet.";
- int type;
- int oper;
- String label;
- // recursive pattern, ako *
- boolean rec = false;
- boolean matchConstant = true;
- List args;
- Expr exp;
-
- FilterPattern(int t) {
- type = t;
- oper = JOKER;
- args = new ArrayList<>();
- }
-
- FilterPattern(int t, int o) {
- this(t);
- oper = o;
- }
-
- FilterPattern(int t, int o, Expr e1) {
- this(t, o);
- add(e1);
- }
-
- FilterPattern(int t, int o, Expr e1, Expr e2) {
- this(t, o, e1);
- add(e2);
- }
-
-
- public FilterPattern(int t, int o, int e1, int e2){
- this(t, o);
- add(new FilterPattern(e1));
- add(new FilterPattern(e2));
- }
-
- static FilterPattern variable(String label) {
- FilterPattern p = new FilterPattern(VARIABLE);
- p.setLabel(label);
- return p;
- }
-
- static FilterPattern constant() {
- return new FilterPattern(CONSTANT);
- }
-
- void add(Expr exp) {
- args.add(exp);
- }
-
- public int arity() {
- return args.size();
- }
-
- void setRec() {
- rec = true;
- }
-
- boolean isRec() {
- return rec;
- }
-
- void setMatchConstant() {
- matchConstant = false;
- }
-
- boolean isMatchConstant() {
- return matchConstant;
- }
-
- public Expr getExp(int i) {
- return args.get(i);
- }
-
- public List getExpList() {
- return args;
- }
-
- public Filter getFilter() {
- return null;
- }
-
- public int getIndex() {
- return 0;
- }
-
- public String getLabel() {
- return label;
- }
-
- void setLabel(String l) {
- label = l;
- }
-
- @Override
- public DatatypeValue getValue() {
- return null;
- }
-
- public boolean isAggregate() {
- return false;
- }
-
- public boolean isBound() {
- return false;
- }
-
- public int oper() {
- return oper;
- }
-
- public void setIndex(int index) {
- // FilterPattern indices are immutable
- }
-
- public int type() {
- return type;
- }
-
- public String toString() {
- return "pat(" + type + ", " + oper + ")";
- }
-
- @Override
- public Exp getPattern() {
- return null;
- }
-
- @Override
- public boolean isDistinct() {
- return false;
- }
-
- @Override
- public String getModality() {
- return null;
- }
-
- @Override
- public boolean isVariable() {
- return false;
- }
-
- @Override
- public Expr getArg() {
- return null;
- }
-
- @Override
- public void setArg(Expr exp) {
- // FilterPattern arguments are immutable
- }
-
- @Override
- public boolean isRecAggregate() {
- return false;
- }
-
- @Override
- public boolean isExist() {
- return false;
- }
-
- @Override
- public boolean isRecExist() {
- return false;
- }
-
- @Override
- public boolean isFuncall() {
- return false;
- }
-
- @Override
- public void setOper(int n) {
- // FilterPattern operator is immutable
- }
-
- @Override
- public void setExp(int i, Expr e) {
- // FilterPattern sub-expressions are immutable
- }
-
- @Override
- public Expr getDefine() {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public void setDefine(Expr exp) {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public int subtype() {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public Expr getFunction() {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public Expr getBody() {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public Expr getVariable() {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public Expr getDefinition() {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public boolean isSystem() {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public boolean isTrace() {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public boolean isDebug() {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public boolean isPublic() {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public DatatypeValue getDatatypeValue() {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public void setPublic(boolean b) {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public void setSubtype(int n) {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public boolean match(int oper) {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public boolean isConstant() {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public boolean hasMetadata(String type) {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public boolean isDynamic() {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
-
- @Override
- public DatatypeValue evalWE(Evaluator eval, BindingContext b, Environment env, Producer p) {
- throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
- }
+ int type;
+ int oper;
+ String label;
+ // recursive pattern, ako *
+ boolean rec = false;
+ boolean matchConstant = true;
+ List args;
+
+ /** Copies optimizer metadata into the execution graph. */
+ @Override
+ public FilterPattern copyPlan(PlanCopy copies) {
+ FilterPattern result = new FilterPattern(type);
+ copies.remember(this, result);
+ result.oper = oper;
+ result.label = label;
+ result.rec = rec;
+ result.matchConstant = matchConstant;
+ result.args = copies.copy(args);
+ return result;
+ }
+
+ FilterPattern(int t) {
+ type = t;
+ oper = JOKER;
+ args = new ArrayList<>();
+ }
+
+ FilterPattern(int t, int o) {
+ this(t);
+ oper = o;
+ }
+
+ FilterPattern(int t, int o, Expr e1) {
+ this(t, o);
+ add(e1);
+ }
+
+ FilterPattern(int t, int o, Expr e1, Expr e2) {
+ this(t, o, e1);
+ add(e2);
+ }
+
+ public FilterPattern(int t, int o, int e1, int e2){
+ this(t, o);
+ add(new FilterPattern(e1));
+ add(new FilterPattern(e2));
+ }
+
+ static FilterPattern variable(String label) {
+ FilterPattern p = new FilterPattern(VARIABLE);
+ p.setLabel(label);
+ return p;
+ }
+
+ static FilterPattern constant() {
+ return new FilterPattern(CONSTANT);
+ }
+
+ void add(Expr exp) {
+ args.add(exp);
+ }
+
+ public int arity() {
+ return args.size();
+ }
+
+ void setRec() {
+ rec = true;
+ }
+
+ boolean isRec() {
+ return rec;
+ }
+
+ void setMatchConstant() {
+ matchConstant = false;
+ }
+
+ boolean isMatchConstant() {
+ return matchConstant;
+ }
+
+ public Expr getExp(int i) {
+ return args.get(i);
+ }
+
+ public List getExpList() {
+ return args;
+ }
+
+ public Filter getFilter() {
+ return null;
+ }
+
+ public int getIndex() {
+ return 0;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ void setLabel(String l) {
+ label = l;
+ }
+
+ @Override
+ public DatatypeValue getValue() {
+ return null;
+ }
+
+ public boolean isAggregate() {
+ return false;
+ }
+
+ public int oper() {
+ return oper;
+ }
+
+ public void setIndex(int index) {
+ // FilterPattern indices are immutable
+ }
+
+ public int type() {
+ return type;
+ }
+
+ public String toString() {
+ return "pat(" + type + ", " + oper + ")";
+ }
+
+ @Override
+ public Exp getPattern() {
+ return null;
+ }
+
+ @Override
+ public boolean isVariable() {
+ return false;
+ }
+
+ @Override
+ public boolean isRecAggregate() {
+ return false;
+ }
+
+ @Override
+ public boolean isRecExist() {
+ return false;
+ }
+
+ @Override
+ public int subtype() {
+ throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
+ }
+
+ @Override
+ public boolean match(int oper) {
+ throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
+ }
+
+ @Override
+ public boolean isConstant() {
+ throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
+ }
+
+ @Override
+ public DatatypeValue evalWE(Evaluator eval, BindingContext b, Environment env, Producer p) {
+ throw new UnsupportedOperationException(NOT_SUPPORTED_YET);
+ }
}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/MatchBind.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/MatchBind.java
index 49dcd401e..fd1ba114c 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/MatchBind.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/MatchBind.java
@@ -11,61 +11,61 @@
/**
- * Manage bindings of Matcher
+ * Manage bindings of ExpressionPatternMatcher
* Can bind and unbind
*
* @author Olivier Corby, Edelweiss, INRIA 2010
*
*/
public class MatchBind {
- private static final Logger logger = LoggerFactory.getLogger(MatchBind.class);
+ private static final Logger logger = LoggerFactory.getLogger(MatchBind.class);
- Hashtable table;
- List stack;
+ Hashtable table;
+ List stack;
- MatchBind (){
- table = new Hashtable<>();
- stack = new ArrayList<>();
- }
+ MatchBind (){
+ table = new Hashtable<>();
+ stack = new ArrayList<>();
+ }
- static MatchBind create(){
- return new MatchBind();
- }
+ static MatchBind create(){
+ return new MatchBind();
+ }
- boolean hasValue(Expr exp){
- return table.containsKey(exp);
- }
+ boolean hasValue(Expr exp){
+ return table.containsKey(exp);
+ }
- Expr getValue(Expr exp){
- return table.get(exp);
- }
+ Expr getValue(Expr exp){
+ return table.get(exp);
+ }
- void setValue(Expr qe, Expr te){
- if (qe instanceof FilterPattern){
- table.put(qe, te);
- stack.add(qe);
- }
- }
+ void setValue(Expr qe, Expr te){
+ if (qe instanceof FilterPattern){
+ table.put(qe, te);
+ stack.add(qe);
+ }
+ }
- void reset(Expr exp){
- table.remove(exp);
- stack.remove(exp);
- }
+ void reset(Expr exp){
+ table.remove(exp);
+ stack.remove(exp);
+ }
- int size(){
- return stack.size();
- }
+ int size(){
+ return stack.size();
+ }
- /**
- * Remove last bindings until stack.size() = size
- */
- void clean(int size){
- while (stack.size()>size){
- reset(stack.getLast());
- }
- if (table.size() != stack.size()){
- logger.error("Match Bind: Stack and Table have different sizes");
- }
- }
+ /**
+ * Remove last bindings until stack.size() = size
+ */
+ void clean(int size){
+ while (stack.size()>size){
+ reset(stack.getLast());
+ }
+ if (table.size() != stack.size()){
+ logger.error("Match Bind: Stack and Table have different sizes");
+ }
+ }
}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/Matcher.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/Matcher.java
deleted file mode 100644
index ccf2f8094..000000000
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/Matcher.java
+++ /dev/null
@@ -1,230 +0,0 @@
-package fr.inria.corese.core.next.query.impl.engine.filter;
-
-import fr.inria.corese.core.next.query.impl.engine.model.Expr;
-import fr.inria.corese.core.next.query.impl.engine.model.ExprType;
-
-
-/**
- * Filter Exp Matcher
- *
- * @author Olivier Corby, Edelweiss, INRIA 2010
- *
- */
-public class Matcher implements ExprType {
- boolean rec = false;
- boolean matchConstant = true;
-
-
- public boolean match(FilterPattern qe, Expr te){
- MatchBind bind = MatchBind.create();
- return match(qe, te, bind);
- }
-
- boolean match(FilterPattern qe, Expr te, MatchBind bind){
- rec = qe.isRec();
- matchConstant = qe.isMatchConstant();
- return process(qe, te, bind);
- }
-
-
-
-
- /**
- * Expr matcher
- * Special case:
- * OR(EXP) with rec=true
- * means match OR(EXP) recursively with OR(EXP1, OR(EXP2, EXP3))
- * EXP is matched with all EXPi
- * Pattern variable store target variable
- * Afterwards, Pattern variable must match same target variables in all EXPi
- *
- */
- boolean process(Expr qe, Expr te, MatchBind bind) {
- if (qe.type() == ALTER) {
- return matchAlternative(qe, te, bind);
- }
- if (!matchType(qe, te)) {
- return false;
- }
- if ((qe.type() != CONSTANT || matchConstant) && bind.hasValue(qe)) {
- return process(bind.getValue(qe), te, bind);
- }
- return switch (qe.type()) {
- case VARIABLE, CONSTANT -> matchVarConst(qe, te, bind);
- case FUNCTION, JOKER -> matchBoolFunAny(qe, te, bind);
- case BOOLEAN -> matchBoolean(qe, te, bind);
- case TERM -> matchTerm(qe, te, bind);
- default -> false;
- };
- }
-
- private boolean matchAlternative(Expr qe, Expr te, MatchBind bind) {
- for (Expr alternative : qe.getExpList()) {
- int size = bind.size();
- if (process(alternative, te, bind)) {
- return true;
- }
- bind.clean(size);
- }
- return false;
- }
-
- private boolean matchBoolean(Expr qe, Expr te, MatchBind bind) {
- if (qe.arity() == 2 && te.arity() == 2) {
- return matchTerm(qe, te, bind);
- }
- return qe.arity() <= te.arity() && matchBoolFunAny(qe, te, bind);
- }
-
- boolean matchVarConst(Expr qe, Expr te, MatchBind bind) {
- if (qe.type() == VARIABLE) {
- if (qe.getLabel() != null) {
- return qe.getLabel().equals(te.getLabel());
- }
- bind.setValue(qe, te);
- } else if (qe.type() == CONSTANT && matchConstant) {
- if (!isBindable(qe)) {
- return qe.equals(te);
- }
- bind.setValue(qe, te);
- }
- return true;
- }
-
-
- /**
- * BOOLEAN NOT
- * BOOLEAN with rec = true
- * OR(EXP) matches recursively OR(OR(EXP1, EXP2), EXP3)
- * FUNCTION ANY
- */
- boolean matchBoolFunAny(Expr qe, Expr te, MatchBind bind) {
- if (qe.arity() == 0) {
- // qe e.g. Pattern(ANY, ANY) with no args
- // first time: bind the pattern
- bind.setValue(qe, te);
- return true;
- }
- if (te.arity() == 0) {
- // qe has arity, te has not: fail
- return false;
- }
-
- int size = bind.size();
- int i = 0;
- Expr qarg;
-
- for (Expr targ : te.getExpList()) {
- if (i < qe.arity()) {
- // use pattern ith arg
- qarg = qe.getExp(i++);
- } else if (rec) {
- // reuse pattern 0th arg
- qarg = qe.getExp(0);
- } else {
- // query has less arguments than target
- // and query args match target args
- // should reset bindings here
- bind.clean(size);
- return false;
- }
-
- if (!process(qarg, targ, bind) && (!rec || !process(qe, targ, bind))) {
- bind.clean(size);
- return false;
- }
- }
-
- if (!bind.hasValue(qe)) {
- // because when rec = true, qe may have already been bound to an arg of te
- bind.setValue(qe, te);
- }
- return true;
- }
-
-
- boolean matchTerm(Expr qe, Expr te, MatchBind bind) {
- Expr fst = qe.getExp(0);
- Expr snd = qe.getExp(1);
-
- if (isGL(qe.oper()) && qe.oper() != te.oper()) {
-
- if (inverse(qe.oper(), te.oper())) {
- // qe(x < y) VS te(y > x)
- // try switch qe args
- fst = qe.getExp(1);
- snd = qe.getExp(0);
- }
- else return false;
- }
-
- int size = bind.size();
- boolean b =
- process(fst, te.getExp(0), bind) &&
- process(snd, te.getExp(1), bind);
-
- if (! b){
- bind.clean(size);
-
- if (symmetric(qe)){
- // try switch arguments of qe
- // switch ?x = ?y to ?y = ?x
- b =
- process(snd, te.getExp(0), bind) &&
- process(fst, te.getExp(1), bind);
- }
- }
-
- if (b) bind.setValue(qe, te);
- return b;
- }
-
- /**
- * Is it a Pattern constant
- */
- boolean isBindable(Expr exp){
- return exp instanceof FilterPattern;
- }
-
- boolean symmetric(Expr qe){
- if (qe.type() == BOOLEAN && qe.oper() == JOKER) return true;
- int t = qe.oper();
- return t == EQ || t == NE || t == PLUS || t == MULT || t == AND || t == OR;
- }
-
- boolean inverse(int t1, int t2){
- return
- (t1 == LT && t2 == GT) ||
- (t1 == GT && t2 == LT) ||
- (t1 == GE && t2 == LE) ||
- (t1 == LE && t2 == GE) ;
- }
-
- boolean isGL(int tt){
- return tt == GL || tt == LT || tt == LE || tt == GT || tt == GE;
- }
-
-
- /**
- * accept comparison operator with any comparison because term matcher
- * will permute arguments as needed
- */
- boolean match(int qt, int tt){
- if (qt == JOKER) return true;
- if (isGL(qt)) return isGL(tt);
- if (qt == EQNE) return tt == EQ || tt == NE;
- return qt == tt;
- }
-
- boolean matchType(Expr qe, Expr te){
- if (qe.oper() == EXIST || te.oper() == EXIST) return false;
- if (qe.type() == JOKER) {
- return true;
- }
- return switch (qe.type()) {
- case VARIABLE, CONSTANT -> match(qe.type(), te.type());
- default -> match(qe.type(), te.type()) && match(qe.oper(), te.oper());
- };
- }
-
-}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/EdgeInv.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/EdgeInv.java
index 1a2b0c8cc..9847f610d 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/EdgeInv.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/EdgeInv.java
@@ -105,4 +105,4 @@ public Iterable