From 96440e5ee3a7c86462c6b576e6f26c766962cbdf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9mi=20C=C3=A9r=C3=A8s?=
Date: Mon, 19 Oct 2026 15:14:39 +0200
Subject: [PATCH 1/3] refactor(data): clean up term decoding, parser listeners
and serializer options
- Rename AbstractNTriplesNQuadsListener to dedicated NTriplesQuadsTermDecoder
- Remove obsolete reflection and deprecated adapters in serializer test fixtures
- Clean up serialization options and literal temporal value representations
---
.../api/exception/SerializationException.java | 3 +
.../next/data/api/model/RdfValueOrder.java | 14 +-
.../core/next/data/api/term/SimpleDate.java | 1 +
.../next/data/api/term/SimpleDateTime.java | 1 +
.../core/next/data/api/term/SimpleTime.java | 1 +
.../impl/io/parser/nquads/NQuadsListener.java | 54 +-
.../impl/io/parser/nquads/NQuadsParser.java | 2 +-
.../io/parser/ntriples/NTriplesListener.java | 67 +-
.../io/parser/ntriples/NTriplesParser.java | 2 +-
.../data/impl/io/parser/rdfa/RDFaParser.java | 2 +-
.../support/AbstractTurtleTriGListener.java | 7 -
...ner.java => NTriplesQuadsTermDecoder.java} | 22 +-
.../nquads/NQuadsSerializerOptions.java | 2 +-
.../ntriples/NTriplesSerializerOptions.java | 2 +-
.../option/AbstractSerializerOptions.java | 18 +-
.../option/AbstractTFamilyOptions.java | 28 +-
.../rdfxml/RDFXMLSerializerOptions.java | 2 +-
.../trig/TriGSerializerOptions.java | 2 +-
.../turtle/TurtleSerializerOptions.java | 2 +-
.../next/data/impl/model/ModelException.java | 3 +
.../data/impl/namespace/PrefixHandler.java | 2 +-
.../data/spi/model/AbstractStatement.java | 3 +
.../next/data/spi/term/AbstractBNode.java | 3 +
.../spi/term/literal/AbstractDuration.java | 3 +
.../spi/term/literal/AbstractLiteral.java | 3 +
.../data/spi/term/literal/AbstractNumber.java | 3 +
.../term/literal/AbstractStringLiteral.java | 3 +
.../literal/AbstractTemporalPointLiteral.java | 3 +
.../next/query/impl/engine/model/Regex.java | 84 --
.../query/impl/engine/path/GraphPath.java | 37 -
.../query/impl/engine/path/PathFinder.java | 1140 -----------------
.../impl/engine/path/PathMappingBuffer.java | 65 -
.../next/query/impl/engine/path/Record.java | 93 --
.../next/query/impl/engine/path/Visit.java | 425 ------
.../data/api/model/RdfValueOrderTest.java | 18 +
.../io/parser/nquads/NQuadsListenerTest.java | 51 +-
.../parser/ntriples/NTriplesListenerTest.java | 51 +-
.../nquads/NQuadsSerializerTest.java | 26 +-
.../ntriples/NTriplesSerializerTest.java | 28 +-
.../query/impl/engine/path/GraphPathTest.java | 26 -
.../engine/path/PathMappingBufferTest.java | 44 -
41 files changed, 187 insertions(+), 2159 deletions(-)
rename src/main/java/fr/inria/corese/core/next/data/impl/io/parser/support/{AbstractNTriplesNQuadsListener.java => NTriplesQuadsTermDecoder.java} (92%)
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Regex.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/path/GraphPath.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/path/PathFinder.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/path/PathMappingBuffer.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/path/Record.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/path/Visit.java
delete mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/path/GraphPathTest.java
delete mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/path/PathMappingBufferTest.java
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/impl/engine/model/Regex.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Regex.java
deleted file mode 100644
index cb505c6d0..000000000
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Regex.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package fr.inria.corese.core.next.query.impl.engine.model;
-
-import fr.inria.corese.core.next.data.api.model.DatatypeValue;
-
-/**
- * Interface of Property Path Regex
- *
- * @author Olivier Corby, Edelweiss, INRIA 2010
- */
-public interface Regex {
-
- int UNDEF = -1;
- int LABEL = 0;
- int NOT = 1;
- int SEQ = 2;
- int STAR = 3;
- int PLUS = 4;
- int OPTION = 5;
- int COUNT = 6;
- int ALT = 7;
- int PARA = 8;
- int TEST = 9;
- int CHECK = 10;
-
- String getName();
-
- String getLongName();
-
- DatatypeValue getDatatypeValue();
-
- int retype();
-
- int getArity();
-
- boolean isConstant();
-
- boolean isAlt();
-
- boolean isSeq();
-
- boolean isOpt();
-
- boolean isNot();
-
- boolean isDistinct();
-
- boolean isShort();
-
- // @deprecated
- boolean isInverse();
-
- // @deprecated
- void setInverse(boolean b);
-
- // SPARQL 1.1 reverse ^
- boolean isReverse();
-
- void setReverse(boolean b);
-
- boolean isStar();
-
- boolean isPlus();
-
- boolean isCounter();
-
- int getMin();
-
- int getMax();
-
- int getWeight();
-
- Regex getArg(int n);
-
- Regex reverse();
-
- Regex transform();
-
- Regex translate();
-
- int regLength();
-
- Expr getExpr();
-
-}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/GraphPath.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/GraphPath.java
deleted file mode 100644
index 97ca99f8b..000000000
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/GraphPath.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package fr.inria.corese.core.next.query.impl.engine.path;
-
-
-import fr.inria.corese.core.next.query.impl.engine.spi.Environment;
-
-/**
- * Draft to compute path in the graph
- *
- * @author Olivier Corby, Edelweiss, INRIA 2010
- */
-public class GraphPath extends Thread {
-
-
- private final Environment mem;
- private final PathFinder finder;
-
-
-
- /**
- * ?x %path ?y
- * case:
- * ?x or ?y is bound/unbound
- * filter on ?x ?y
- * Relation type on %path : ?x c:related::%path ?y
- *
- */
-
- public GraphPath(PathFinder pc, Environment mem){
- this.finder = pc;
- this.mem = mem;
- }
-
- @Override
- public void run() {
- finder.process(finder.get(mem, finder.getIndex()), mem);
- }
-}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/PathFinder.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/PathFinder.java
deleted file mode 100644
index d3c70b482..000000000
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/PathFinder.java
+++ /dev/null
@@ -1,1140 +0,0 @@
-package fr.inria.corese.core.next.query.impl.engine.path;
-
-import fr.inria.corese.core.next.query.impl.engine.eval.Eval;
-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.solution.Memory;
-
-import fr.inria.corese.core.next.query.impl.engine.model.Edge;
-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.model.Regex;
-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.Producer;
-import fr.inria.corese.core.next.query.api.exception.QueryException;
-import fr.inria.corese.core.next.query.impl.engine.event.ResultListener;
-import fr.inria.corese.core.next.query.impl.engine.model.EdgeInv;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * ********************************************************
- * ?x rdf:resf * /rdf:first
- * write paths in a synchronized buffer
- * ?x ^(p/q) ?y ::= ?y p/q ?x -> ?x inv(q)/inv(p) ?y
- * ?x p/q uri path is computed backward from uri to ?x with index=1 other=0
- * isReverse=true sequence is eval as q/p, all other exp are the same
- * ?x ^(p/q) uri ::= uri p/q ?x -> ?x inv(q)/inv(p) uri
- * PP Extensions
- * Path Variable: ?x exp :: $path ?y
- * Path Length: pathLength($path)
- * Path Enumeration: ?x exp :: $path ?y graph $path {?a ?p ?b}
- * Shortest path: ?x distinct short exp ?y ; ?x short exp ?y -- Note: complete
- * short to get only shortest
- * Path weight: ?x (rdf:first@2 / rdf:rest@1* / ^rdf:first@2) * ?y
- * Constaint: ?x exp
- * @ {?this a foaf:Person} ?y ?x exp
- * @ [a foaf:Person] ?y
- * Parallel Path: ?x (foaf:knows || ^rdfs:seeAlso) + ?y
- * Check Loop: pf.setCheckLoop(true) => exp+ exp[n,m] without loop
- * exec.setPathLoop(false) pragma {kg:path kg:loop false}
- *
- * @author Olivier Corby, Edelweiss, INRIA 2010
- * @ thanx Corentin Follenfant for the idea of property weight into the regex
- *
- ********************************************************
- */
-public class PathFinder {
-
- private static final Logger logger = LoggerFactory.getLogger(PathFinder.class);
-
- // synchronized buffer between this and projection
- private PathMappingBuffer mbuffer;
- private Environment memory;
- private ResultListener listener;
- private Eval kgram;
- private final Producer producer;
- private final Matcher matcher;
- private final Evaluator evaluator;
- private final Query query;
- private Mappings lMap;
- private final HashMap store;
- private Filter filter;
- private Memory mem;
- private Edge edge;
- private Node gNode;
- private Node targetNode;
- private Node regexNode;
- private Node varNode;
- private List from;
- private Node[] qNodes;
-
- // index of node in edge that is the start of the path
- private int index = 0;
- // the inverse of the index (i.e. the other arg)
- private int other;
- private boolean isStop = false;
- private boolean hasListener = false;
- private boolean isDistinct = false;
-
- private boolean isReverse;
- private boolean isShort;
- private boolean isOne;
-
- // if true: return list of path instead of thread buffer: 50% faster but enumerate all path
- private boolean isList = false;
- private boolean checkLoop = false;
- private boolean isCountPath = false;
- private boolean isCache = false;
- private boolean isStorePath = true;
-
- private static final int MAX_LENGTH = Integer.MAX_VALUE;
- private int min = 0;
- private int max = MAX_LENGTH;
-
- private Regex regexp;
-
- /**
- * @param isStorePath the isStorePath to set
- */
- public void setStorePath(boolean isStorePath) {
- this.isStorePath = isStorePath;
- }
-
- /**
- * @return the isCache
- */
- public boolean isCache() {
- return isCache;
- }
-
- /**
- * @param isCache the isCache to set
- */
- public void setCache(boolean isCache) {
- this.isCache = isCache;
- }
-
- public PathFinder(Producer p, Matcher match, Evaluator eval, Query q) {
- query = q;
- producer = p;
- matcher = match;
- evaluator = eval;
- lMap = new Mappings();
- store = new HashMap<>();
- }
-
- public static PathFinder create(Eval eval, Producer p, Query q) {
- PathFinder pf = new PathFinder(p, eval.getMatcher(), eval.getEvaluator(), q);
- pf.setEval(eval);
- return pf;
- }
-
- void setEval(Eval ev) {
- kgram = ev;
- }
-
- public void set(ResultListener rl) {
- listener = rl;
- hasListener = rl != null;
- }
-
- public void setCheckLoop(boolean b) {
- checkLoop = b;
- }
-
- public void setCountPath(boolean b) {
- isCountPath = b;
- if (b) {
- isDistinct = false;
- }
- }
-
- public void setList(boolean b) {
- isList = b;
- }
-
- /**
- * Start/init computation of a new list of path
- */
- public void start(Edge edge, Node node, Memory env, Filter f) {
- regexNode = node;
- List lVar = null;
- if (f != null) {
- lVar = f.getVariables();
- }
- lMap = new Mappings();
- this.edge = edge;
- int n = index(edge, env, lVar);
- start(n);
- index = n;
- targetNode = env.getNode(edge.getNode(other));
- varNode = edge.getEdgeVariable();
- if (f != null && match(edge, lVar, index)) {
- filter = f;
- init(env);
- }
- if (mem == null && node != null) {
- init(env);
- }
- }
-
- void init(Memory env) {
- mem = new Memory(matcher, evaluator);
- mem.init(env.getQuery());
- mem.init(env);
- evaluator.init(mem);
- mem.share(mem.getBind(), env.getBind());
- mem.setEval(kgram);
- }
-
- boolean match(Edge edge, List lVar, int index) {
- return lVar.size() == 1
- && edge.getNode(index).isVariable()
- && edge.getNode(index).getLabel().equals(lVar.getFirst());
- }
-
- /**
- * Compute the index of node of edge which will be start of the path If one
- * of the nodes is bound, this is it If one of the nodes is a constant, this
- * is it If there is a filter on a node, this is it Otherwise it is node at
- * index 0
- */
- int index(Edge edge, Environment mem, List lVar) {
- // which arg is bound if any ?
- for (int i = 0; i < 2; i++) {
- if (mem.isBound(edge.getNode(i))) {
- return i;
- }
- }
- for (int i = 0; i < 2; i++) {
- if (edge.getNode(i).isConstant()) {
- return i;
- }
- }
- if (lVar != null) {
- for (int i = 0; i < 2; i++) {
- if (match(edge, lVar, i)) {
- return i;
- }
- }
- }
- return 0;
- }
-
- /**
- * Enumerate all path, return a list of path 50% faster that thread but
- * enumerate *all* path
- */
- public Iterable candidate2(Node gNode, List from, Environment mem) {
- this.gNode = gNode;
- this.from = from;
- this.memory = mem;
- Node cstart = get(memory, index);
-
- Mappings map = getMappings(cstart);
- if (map != null) {
- return map;
- }
-
- lMap = new Mappings();
- process(cstart, memory);
- putMappings(cstart, lMap);
- return lMap;
- }
-
- /**
- * Retrieve solution in cache Note: manage two tables for two possible index
- */
- Mappings getMappings(Node cstart) {
- if (isCache() && cstart != null) {
- return store.get(cstart.getIndex());
- }
- return null;
- }
-
- void putMappings(Node start, Mappings map) {
- if (isCache() && start != null) {
- store.put(start.getIndex(), map);
- }
- }
-
- /**
- * Enumerate path in a parallel thread, return a synchronised buffer Useful
- * if backjump or have a limit in sparql query
- */
- public Iterable candidate(Node gNode, List from, Environment env) {
- isStop = false;
- if (mem != null) {
- mem.setGraphNode(gNode);
- }
- if (isList) {
- return candidate2(gNode, from, env);
- }
- this.gNode = gNode;
- this.from = from;
- this.memory = env;
- mstart(env);
- // return path enumeration (read the synchronized buffer)
- return mbuffer;
- }
-
- int getIndex() {
- return index;
- }
-
- void mstart(Environment mem) {
- // buffer store path enumeration
- mbuffer = new PathMappingBuffer();
- // path enumeration in a thread
- GraphPath graphPath = new GraphPath(this, mem);
- // launch path computing (one by one) eg launch process() below
- graphPath.start();
- }
-
- public void stop() {
- isStop = true;
- }
-
- /**
- * init at creation time, no need to change. pmax comes from pathLength() <=
- * pmax
- */
- public void init(Regex exp, int pmin, int pmax) {
- regexp = exp;
-
- isReverse = false;
- isShort = false;
- isOne = false;
- other = 1;
-
- if (exp.isShort()) {
- isShort = true;
- if (exp.isDistinct()) {
- isOne = true;
- }
- }
-
- // set min and max path length
- // filter pathLength($path) > val
- // the length of the minimal path that matches regex:
- int length = regexp.regLength();
- min = Math.max(min, length);
- max = Math.max(max, length);
-
- // user default values
- if (pmin != -1) {
- min = Math.max(min, pmin);
- }
- // either IntegerMax or pathLength($p) <= max
- if (pmax != -1) {
- max = pmax;
- }
- }
-
- public Edge getEdge() {
- return edge;
- }
-
- /**
- * start at run time depends on index : which arg is bound or where we start
- */
- void start(int n) {
- index = n;
- if (index == 1) {
- other = 0;
- isReverse = true;
- } else {
- other = 1;
- isReverse = false;
- }
- }
-
- Node get(Environment memory, int i) {
- if (edge == null) {
- return null;
- }
- Node qc = edge.getNode(i);
- return memory.getNode(qc);
- }
-
- void process(Node cstart, Environment memory) {
- // Is the source of edge bound ?
- // In which case all path relations come from same source
- Node csource = null;
- if (gNode != null) {
- csource = gNode.isConstant() ? gNode : memory.getNode(gNode);
- }
-
- // the start concept for path
- Path path = new Path(isReverse);
- path.setMax(max);
- path.setIsShort(isShort);
-
- if (isShort && cstart != null) {
- // if null, will be done later
- producer.initPath(edge, 0);
- }
-
- Regex regexp1 = regexp.transform();
- eval(regexp1, path, cstart, csource);
-
- // in order to stop enumeration, return null
- if (!isList) {
- mbuffer.put(null, false);
- }
- }
-
- /**
- * Path result as a Mapping start and last nodes the path variable whose
- * index is used (e.g. pathLength) to retrieve the list of edges in the
- * memory the list of edges
- */
- private Mapping result(Path path, Node gNode, Node src, Node start, boolean isReverse) {
- Edge ee = edge;
- int length = 3;
- int ip = 2;
- int is = 3;
-
- if (!isStorePath) {
- length = 2;
- is = 2;
- }
- if (src != null) {
- length += 1;
- }
-
- Node n1;
- Node n2;
- if (path.size() == 0) {
- n1 = start;
- n2 = start;
- } else {
- n1 = path.firstNode();
- n2 = path.lastNode();
- }
-
- if (!check(n1, n2)) {
- return null;
- }
-
- if (qNodes == null) {
- // computed once and then shared by all Mapping
- qNodes = new Node[length];
- qNodes[0] = ee.getNode(0);
- qNodes[1] = ee.getNode(1);
- if (src != null) {
- qNodes[is] = gNode;
- }
- if (isStorePath) {
- qNodes[ip] = ee.getEdgeVariable();
- }
- }
-
- Node[] tNodes = new Node[length];
- tNodes[0] = n1;
- tNodes[1] = n2;
-
- if (src != null) {
- tNodes[is] = src;
- }
-
- Mapping map = Mapping.create(qNodes, tNodes);
- if (isStorePath) {
- Path edges = path.copy(producer);
- if (isReverse) {
- edges.reverse();
- }
- tNodes[ip] = getPathNode(edges);
- }
-
- return map;
- }
-
- /**
- * Generate a unique Blank Node wrt query that represents the path Use a
- * predefined filter pathNode()
- */
- Node getPathNode(Path p) {
- Filter f = query.getGlobalFilter(Query.PATHNODE);
- Node node = producer.getNode(
- f.getExp().evalWE(evaluator, memory.getBind(), memory, producer));
- if (node == null) {
- throw new IllegalStateException("The path-node expression returned no RDF value");
- }
- node.setObject(p);
- return node;
- }
-
- /**
- * Check if target node match its query node and its binding
- */
- private boolean check(Node n0, Node n1) {
- if (index == 0) {
- if (!matcher.match(edge.getNode(1), n1, memory)) {
- return false;
- }
- return targetNode == null || targetNode.match(n1);
- } else {
- if (!matcher.match(edge.getNode(0), n0, memory)) {
- return false;
- }
- return targetNode == null || targetNode.match(n0);
- }
- }
-
- /**
- * cstart is bound when edge node is a bound variable
- * cstart is not bound when edge node is a constant, this case
- * is processed by Producer
- */
- private Iterable getNodeIterator(Node cstart) {
- if (cstart == null) {
- return getNodeIterator(edge, from, null);
- } else {
- // start is bound
- ArrayList list = new ArrayList<>();
- list.add(cstart);
- return list;
- }
- }
-
- public Iterable getNodeIterator(final Edge edge, List from, List regex) {
- Iterable iter = producer.getNodes(gNode, from, edge, memory, regex, index);
-
- if (filter == null) {
- return iter;
- }
-
- final Iterator it = iter.iterator();
-
- return () -> new Iterator<>() {
- @Override
- public boolean hasNext() {
- return it.hasNext();
- }
-
- @Override
- public Node next() {
- while (hasNext()) {
- Node entity = it.next();
- if (entity == null) {
- return null;
- }
- Node node = entity.getNode();
- if (test(node)) {
- return entity;
- }
- }
- return null;
- }
- };
- }
-
- /**
- * exp
- * @ {?this rdf:type c:Person}
- */
- boolean test(Filter filter, Path path, Node qNode, Node node) {
- mem.push(qNode, node);
- if (varNode != null) {
- Node pathNode = getPathNode(path);
- mem.push(varNode, pathNode);
- }
- boolean test;
- try {
- test = filter.getExp().test(evaluator, mem.getBind(), mem, producer);
- } catch (QueryException ex) {
- test = false;
- }
- mem.pop(qNode);
- if (varNode != null) {
- mem.pop(varNode);
- }
- return test;
- }
-
- boolean test(Node node) {
- Node qNode = edge.getNode(index);
- mem.push(qNode, node);
- boolean test;
- try {
- test = filter.getExp().test(evaluator, mem.getBind(), mem, producer);
- } catch (QueryException ex) {
- test = false;
- }
- mem.pop(qNode);
- return test;
- }
-
- /*
- * *******************************************************************************
- *
- * New version interprets regex directly with a stack
- *
- *******************************************************************************
- */
- /**
- * rewrite ! (^ p) as ^ (! p) rewrite ^(p/q) as ^q/^p
- */
- void eval(Regex exp, Path path, Node start, Node src) {
- Record stack = new Record(Visit.create(isReverse, isCountPath));
- stack.push(exp);
- try {
- eval(stack, path, start, src);
- } catch (StackOverflowError e) {
- logger.error("** Property Path Size: {}", path.size());
- logger.error("** Property Path Error: \n{}", String.valueOf(e));
- }
- }
-
- /**
- * top of stack is current exp to eval rest of stack is in sequence path may
- * be walked left to right if start is bound or right to left if end is
- * bound in the later case, index = 1
- */
- void eval(Record stack, Path path, Node start, Node src) {
- if (isStop) {
- return;
- }
-
- if (stack.isEmpty()) {
- if (stack.getTarget() != null) {
- // this is a parallel path check, path is finished: stop it
- if (start.match(stack.getTarget())) {
- // it is successful
- stack.setSuccess();
- }
- return;
- }
-
- result(stack, path, start, src);
- return;
- }
-
- Regex exp = stack.pop();
-
- switch (exp.retype()) {
- case Regex.TEST:
- evalTest(exp, stack, path, start, src);
- break;
-
- case Regex.LABEL, Regex.NOT:
- evalLabel(exp, stack, path, start, src);
- break;
-
- case Regex.SEQ:
- evalSeq(exp, stack, path, start, src);
- break;
-
- case Regex.PARA:
- evalPara(exp, stack, path, start, src);
- break;
-
- case Regex.CHECK:
- evalCheck(exp, stack, path, start, src);
- break;
-
- case Regex.PLUS:
- // exp+
- if (start == null && stack.getVisit().knows(exp)) {
- stack.push(exp);
- return;
- }
- plus(exp, stack, path, start, src);
- break;
-
- case Regex.COUNT:
- // regex count: exp[1, n]
- count(exp, stack, path, start, src);
- break;
-
- case Regex.STAR:
- // exp*
- if (start == null && stack.getVisit().knows(exp)) {
- stack.push(exp);
- return;
- }
- star(exp, stack, path, start, src);
- break;
-
- case Regex.ALT:
- evalAlt(exp, stack, path, start, src);
- break;
-
- case Regex.OPTION:
- option(exp, stack, path, start, src);
- break;
-
- default:
- break;
- }
- }
-
- private void evalTest(Regex exp, Record stack, Path path, Node start, Node src) {
- boolean b = true;
- if (start != null) {
- b = test(exp.getExpr().getFilter(), path, regexNode, start);
- }
- if (b) {
- eval(stack, path, start, src);
- }
- stack.push(exp);
- }
-
- private void evalSeq(Regex exp, Record stack, Path path, Node start, Node src) {
- int fst = 0;
- int rst = 1;
- if (isReverse) {
- fst = 1;
- rst = 0;
- }
-
- stack.push(exp.getArg(rst));
- stack.push(exp.getArg(fst));
-
- eval(stack, path, start, src);
-
- stack.pop();
- stack.pop();
- stack.push(exp);
- }
-
- private void evalPara(Regex exp, Record stack, Path path, Node start, Node src) {
- if (start != null) {
- stack.pushStart(start);
- }
- stack.push(exp.getArg(2));
- stack.push(exp.getArg(0));
- eval(stack, path, start, src);
- stack.pop();
- stack.pop();
-
- if (start != null) {
- stack.popStart();
- }
- stack.push(exp);
- }
-
- private void evalCheck(Regex exp, Record stack, Path path, Node start, Node src) {
- Regex test = exp.getArg(0);
-
- if (test.retype() == Regex.PARA) {
- Record st = new Record(Visit.create(isReverse, isCountPath));
- st.push(test.getArg(1));
- st.setTarget(start);
- Node prev = stack.getStart();
-
- eval(st, path, prev, src);
-
- if (st.isSuccess()) {
- eval(stack, path, start, src);
- }
-
- stack.push(exp);
- } else if (test.retype() == Regex.OPTION) {
- if (!stack.getVisit().nloop(test, start)) {
- eval(stack, path, start, src);
- }
-
- stack.push(exp);
- }
- }
-
- private void evalAlt(Regex exp, Record stack, Path path, Node start, Node src) {
- stack.push(exp.getArg(0));
- eval(stack, path, start, src);
- stack.pop();
-
- stack.push(exp.getArg(1));
- eval(stack, path, start, src);
- stack.pop();
-
- stack.push(exp);
- }
-
- private void evalLabel(Regex exp, Record stack, Path path, Node start, Node src) {
- if (path.size() >= path.getMax()) {
- stack.push(exp);
- return;
- }
-
- Node previous = null;
- for (Edge ent : producer.getEdges(gNode, from, edge, memory, exp, src, start, index)) {
- if (isStop || stack.isSuccess()) {
- stack.push(exp);
- return;
- }
- previous = processEdge(ent, exp, stack, path, start, src, previous);
- }
-
- stack.push(exp);
- }
-
- private Node processEdge(Edge ent, Regex exp, Record stack, Path path, Node start, Node src, Node previous) {
- if (ent == null) {
- return previous;
- }
-
- Edge rel = ent;
- Node node = rel.getNode(index);
- if (exp.isInverse() || exp.isReverse()) {
- EdgeInv ei = new EdgeInv(ent);
- rel = ei;
- ent = ei;
- node = rel.getNode(index);
- }
-
- if (filter != null && start == null && !test(rel.getNode(index))) {
- return previous;
- }
-
- Node currentSrc = src;
- if (path.size() == 0 && src == null && gNode != null) {
- currentSrc = ent.getGraph();
- } else if (currentSrc != null && !ent.getGraph().match(currentSrc)) {
- return previous;
- }
-
- Visit visit = stack.getVisit();
- boolean isStart = start == null;
- if (isStart) {
- previous = recordStartNode(node, previous, visit, stack);
- }
-
- if (isShort && !checkShortestPath(visit, rel.getNode(other), exp, path.weight() + exp.getWeight())) {
- return previous;
- }
-
- stepPath(ent, rel, exp, stack, path, currentSrc);
-
- if (isStart) {
- visit.nleave(node);
- stack.popStart();
- }
-
- return previous;
- }
-
- private Node recordStartNode(Node node, Node previous, Visit visit, Record stack) {
- boolean isNew = previous == null || !previous.match(node);
- if (isNew) {
- visit.start();
- }
- visit.nstart(node);
- stack.pushStart(node);
- if (isShort && isNew) {
- producer.initPath(edge, 0);
- visit.initPath();
- }
- return node;
- }
-
- private boolean checkShortestPath(Visit visit, Node otherNode, Regex exp, int length) {
- Integer l = visit.getLength(otherNode, exp);
- if (l == null) {
- visit.setLength(otherNode, exp, length);
- return true;
- }
- if (length > l || (isOne && length == l)) {
- return false;
- }
- visit.setLength(otherNode, exp, length);
- return true;
- }
-
- private void stepPath(Edge ent, Edge rel, Regex exp, Record stack, Path path, Node currentSrc) {
- if (hasListener) {
- listener.enter(ent, exp, path.size());
- }
- path.add(ent, exp.getWeight());
- boolean suc = kgram.getVisitor().step(kgram, currentSrc, edge, path, path.firstNode(), path.lastNode());
- if (suc) {
- eval(stack, path, rel.getNode(other), currentSrc);
- }
- path.remove(exp.getWeight());
- if (hasListener) {
- listener.leave(ent, exp, path.size());
- }
- }
-
- boolean isDistinct(Record stack, Node start, Node target) {
- boolean b = stack.getVisit().isDistinct(start, target);
- if (b) {
- stack.getVisit().addDistinct(start, target);
- }
- return b;
- }
-
- void result(Record stack, Path path, Node start, Node src) {
- if (path.size() > 0) {
- if (isDistinct && !isDistinct(stack, path.firstNode(), path.lastNode())) {
- // distinct (start,target)
- return;
- }
-
- boolean shouldStore = true;
- if (hasListener) {
- shouldStore = listener.process(path);
- }
-
- if (shouldStore) {
- Mapping map = result(path, gNode, src, start, isReverse);
- if (map != null) {
- result(src, map);
- }
- }
- } else {
- resultNode(path, start, src);
- }
- }
-
- void resultNode(Path path, Node start, Node src) {
- for (Node node : getNodeIterator(start)) {
- if (isStop) {
- return;
- }
-
- if (node != null) {
- if (gNode != null) {
- src = node.getGraph();
- }
- Mapping m = result(path, gNode, src, node, isReverse);
-
- if (m != null) {
- result(src, m);
- }
- }
- }
- }
-
- void result(Node src, Mapping map) {
- kgram.getVisitor().path(kgram, src, edge, map.getPath(2), map.getNode(0), map.getNode(1));
- if (isList) {
- lMap.add(map);
- } else {
- mbuffer.put(map, true);
- }
- }
-
- void option(Regex exp, Record stack, Path path, Node start, Node src) {
- if (stack.getVisit().nloop(exp, start)) {
- stack.push(exp);
- return;
- }
-
- // skip option
- eval(stack, path, start, src);
-
- // with option
- // push check:
- stack.push(exp.getArg(1));
- stack.push(exp.getArg(0));
- eval(stack, path, start, src);
-
- // pop exp and check
- stack.pop();
- stack.pop();
- stack.push(exp);
-
- stack.getVisit().nunset(exp);
- }
-
- /**
- * exp*
- */
- void star(Regex exp, Record stack, Path path, Node start, Node src) {
- // start is the first node of exp*
- boolean isFirst = stack.getVisit().nfirst(exp);
-
- if (stack.getVisit().nloop(exp, start)) {
- // start already met in exp path: stop
- stack.push(exp);
- return;
- }
-
- // use case: (p*/q)*
- // we must save each visited of p*
- // because it expands to p*/q . p*/q ...
- // and each occurrence of p* must have its own visited
- Visit.VisitedNode save = stack.getVisit().nunset(exp);
- eval(stack, path, start, src);
- stack.getVisit().nset(exp, save);
-
- // restore exp*
- stack.push(exp);
- // push exp
- stack.push(exp.getArg(0));
- // second step: eval exp once more
- eval(stack, path, start, src);
- // restore stack (exp* on top)
- stack.pop();
-
- stack.getVisit().nremove(exp, start);
- if (isFirst) {
- stack.getVisit().nunset(exp);
- }
- }
-
- /**
- * exp = exp+ ; stack = rest
- * Distinguish 1. first execution where index(exp+) = 0 and 2. next executions where index(exp+) = 1
- * 1. stack := (exp, exp+, rest) ; eval(stack)
- * 2. a) stack = (rest) ; eval(stack) b) stack := (exp, exp+, rest) ; eval(stack)
- * In addition there are two cases whether start node is bound or not
- * If start is bound, OK
- * If start is not bound, it will be bound by case LABEL: above
- * ?x p+ ?y
- * When index(p+) = 0, start is not bound, execute(p/p+). When we come back to p+ in the stack,
- */
- void plus(Regex exp, Record stack, Path path, Node start, Node src) {
- Visit visit = stack.getVisit();
- // start is the first node of exp+
- visit.nfirst(exp);
-
- if (visit.count(exp) == 0) {
- // case 1: first execution of exp+
-
- // declare exp such that when start node changes (in case LABEL:)
- // the visitedNode table of exp be cleared by visit.start()
- visit.declare(exp);
-
- // push exp+ again in stack to loop later
- stack.push(exp);
- // assign index=1 to exp+, hence exp+ will be executed by case 2 below
- visit.count(exp, +1);
- // push exp to execute it now
- stack.push(exp.getArg(0));
- // execute exp ; stack = (exp, exp+, rest) ; exp+ will be executed by case 2 below
- eval(stack, path, start, src);
- stack.pop();
- visit.count(exp, -1);
-
- if (!isCountPath) {
- // std sparql
- // leave exp+
- visit.nremove(exp, start);
- }
- } else {
- // case 2:
- // next execution of exp+ after first one
- if (visit.nloop(exp, start)) {
- stack.push(exp);
- return;
- }
- // we have executed exp at least once
- // exp = exp+ ; stack = (rest)
- // (1) evaluate the stack if any and store result
- // use case: eval rest in: exp+ / rest
- // switch off exp+ visitedNode table in case: (exp1+/exp2+)+
- // exp1 would be ready to start "again" with "new" visitedNode table
- Visit.VisitedNode save = visit.nunset(exp);
- visit.set(exp, 0);
-
- // eval rest
- eval(stack, path, start, src);
-
- // switch on exp+ index and visitedNode table
- visit.set(exp, 1);
- visit.nset(exp, save);
-
- // (2) loop again
- // push exp+
- stack.push(exp);
- // push exp
- stack.push(exp.getArg(0));
- // stack = (exp, exp+, rest) ; eval(stack)
- eval(stack, path, start, src);
- stack.pop();
-
- visit.nremove(exp, start);
- }
- }
-
- /**
- * exp{n,m} exp{n,}
- */
- void count(Regex exp, Record stack, Path path, Node start, Node src) {
- if (stack.getVisit().count(exp) >= exp.getMin()) {
- countMinReached(exp, stack, path, start, src);
- } else {
- countMinNotReached(exp, stack, path, start, src);
- }
- }
-
- private void countMinReached(Regex exp, Record stack, Path path, Node start, Node src) {
- if (checkLoop(exp) && stack.getVisit().nloop(exp, start)) {
- stack.push(exp);
- return;
- }
-
- // min length is reached, can leave
- int save = stack.getVisit().count(exp);
- stack.getVisit().set(exp, 0);
- eval(stack, path, start, src);
- stack.getVisit().set(exp, save);
-
- stack.push(exp);
-
- if (stack.getVisit().count(exp) < exp.getMax()) {
- // max length not reached, can continue
- stack.getVisit().count(exp, +1);
- stack.push(exp.getArg(0));
- eval(stack, path, start, src);
- stack.pop();
- stack.getVisit().count(exp, -1);
- }
-
- if (checkLoop(exp)) {
- stack.getVisit().nremove(exp, start);
- }
- }
-
- private void countMinNotReached(Regex exp, Record stack, Path path, Node start, Node src) {
- if (isReverse) {
- if (checkLoop(exp)) {
- // use case: ?x exp[2,]
- // path goes backward
- stack.getVisit().ninsert(exp, start);
- }
- } else if (checkLoop && stack.getVisit().nloop(exp, start)) {
- stack.push(exp);
- return;
- }
-
- stack.push(exp);
-
- stack.getVisit().count(exp, +1);
- stack.push(exp.getArg(0));
- eval(stack, path, start, src);
- stack.pop();
- stack.getVisit().count(exp, -1);
-
- if (isReverse) {
- if (checkLoop(exp)) {
- // use case: ?x exp[2,]
- // path goes backward
- stack.getVisit().nremove(exp, start);
- }
- } else if (checkLoop) {
- stack.getVisit().nremove(exp, start);
- }
- }
-
- boolean hasMax(Regex exp) {
- return exp.getMax() != -1 && exp.getMax() != Integer.MAX_VALUE;
- }
-
- // for count exp [n,m]
- boolean checkLoop(Regex exp) {
- return checkLoop || !hasMax(exp);
- }
-}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/PathMappingBuffer.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/PathMappingBuffer.java
deleted file mode 100644
index c94694f9b..000000000
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/PathMappingBuffer.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package fr.inria.corese.core.next.query.impl.engine.path;
-
-import fr.inria.corese.core.next.query.impl.engine.solution.Mapping;
-
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-/**
- * Synchronized buffer to put/get path edges
- * Edges are consumed by an iterator
- *
- * @author Olivier Corby, Edelweiss, INRIA 2010
- *
- */
-// A producer/consumer stream is consumed once; replay would require unbounded buffering.
-@SuppressWarnings("java:S4348")
-public class PathMappingBuffer implements Iterable, Iterator {
-
- private Mapping map;
- private boolean hasNext = true;
- private boolean available = false;
-
- public synchronized Mapping next() {
- if (!hasNext()) {
- throw new NoSuchElementException("Path enumeration has ended");
- }
- available = false;
- notifyAll();
- return map;
- }
-
- public synchronized boolean hasNext() {
- while (!available) {
- try {
- wait();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- return false;
- }
- }
- notifyAll();
- return hasNext;
- }
-
- public synchronized void put(Mapping val, boolean next) {
- while (available) {
- try {
- wait();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- return;
- }
- }
- map = val;
- hasNext = next;
- available = true;
- notifyAll();
- }
-
- @Override
- public Iterator iterator() {
- return this;
- }
-
-}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/Record.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/Record.java
deleted file mode 100644
index 3b6b88516..000000000
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/Record.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package fr.inria.corese.core.next.query.impl.engine.path;
-
-import fr.inria.corese.core.next.query.impl.engine.model.Node;
-import fr.inria.corese.core.next.query.impl.engine.model.Regex;
-
-import java.util.ArrayList;
-
-/**
- * Stack of Regex + Visit for loop check
- * @author Olivier Corby, Edelweiss, INRIA 2011
- *
- */
-// Execution metadata does not participate in List equality or serialization.
-@SuppressWarnings("java:S2160")
-public class Record extends ArrayList {
-
- transient Visit visit;
- transient Node target;
-
- transient ArrayList stack;
-
- boolean success = false;
-
- Record(Visit v){
- visit = v;
- stack = new ArrayList<>();
- }
-
-
- Visit getVisit(){
- return visit;
- }
-
-
- void push(Regex exp){
- if (exp != null){
- add(exp);
- }
- }
-
-
- Regex pop(){
- if (size() == 0){
- return null;
- }
- Regex exp = get(size()-1);
- remove(size()-1);
- return exp;
- }
-
-
- /*************************************************************
- * Stack dedicated to PARA: e1 || e2
- * e1 goes from start to target
- * then e2 checks that there is a path from start to target
- * pushStart() store start node
- * setTarget() store target node in a fresh new Record,
- * hence there is no stack for target
- * New Record is created for loop check specific to e2 path
- *
- *************************************************************/
-
- void pushStart(Node start){
- stack.add(start);
- }
-
- Node getStart(){
- return stack.getLast();
- }
-
- void popStart(){
- stack.getLast();
- stack.removeLast();
- }
-
- void setTarget(Node n){
- target = n;
- }
-
- Node getTarget(){
- return target;
- }
-
- void setSuccess(){
- success = true;
- }
-
- boolean isSuccess(){
- return success;
- }
-
-
-}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/Visit.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/Visit.java
deleted file mode 100644
index fabd47d26..000000000
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/Visit.java
+++ /dev/null
@@ -1,425 +0,0 @@
-package fr.inria.corese.core.next.query.impl.engine.path;
-
-import fr.inria.corese.core.next.query.impl.engine.model.Node;
-import fr.inria.corese.core.next.query.impl.engine.model.Regex;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.TreeMap;
-
-/**
- * Record intermediate nodes that are visited by a loop path expression such as
- * exp+ exp* exp{n,} Prevent loop among intermediate nodes
- *
- * @author Olivier Corby, Edelweiss, INRIA 2010
- *
- */
-public class Visit {
- int nextVisitId = 0;
-
- ExpVisitedNode visitedNode;
- HashMap ctable;
- HashMap startNode;
- DTable tdistinct;
- LTable ltable;
- ArrayList regexList;
-
- boolean isReverse;
- // isCounting = false: SPARQL semantics without path counting
- boolean isCounting;
-
- Visit(boolean isRev, boolean isCount) {
- isCounting = isCount;
- isReverse = isRev && isCount;
-
- visitedNode = new ExpVisitedNode(isReverse);
- startNode = new HashMap<>();
- ctable = new HashMap<>();
- regexList = new ArrayList<>();
- tdistinct = new DTable();
- ltable = new LTable();
- }
-
- static class NodeTable extends TreeMap {
-
- NodeTable() {
- super(new Compare());
- }
- }
-
- // Traversal direction is metadata; equality must retain the Map contract.
- @SuppressWarnings("java:S2160")
- class ExpVisitedNode extends HashMap {
- boolean reverse;
-
- ExpVisitedNode(boolean b) {
- reverse = b;
- }
-
- void add(Regex exp, Node n) {
- VisitedNode t = get(exp);
- if (t == null) {
- t = new VisitedNode(reverse, nextVisitId++);
- put(exp, t);
- }
- t.add(n);
- }
-
- void remove(Regex exp, Node n) {
- VisitedNode t = get(exp);
- t.remove(n);
- }
-
- boolean loop(Regex exp, Node start) {
- VisitedNode table = get(exp);
- if (table == null) {
- return false;
- }
- return table.loop(exp, start);
- }
-
- boolean exist(Regex exp) {
- return containsKey(exp);
- }
-
- }
-
- static class VisitedNode {
-
- NodeTable table;
- List list;
- boolean isReverse;
- int n;
-
-
- VisitedNode(boolean b, int n) {
- this.n = n;
- isReverse = b;
- if (isReverse) {
- list = new ArrayList<>();
- } else {
- table = new NodeTable();
- }
- }
-
- void clear() {
- if (table != null) table.clear();
- if (list != null) list.clear();
- }
-
- @Override
- public String toString() {
- if (table == null) {
- return list.toString();
- }
- return table.toString();
- }
-
-
- void add(Node n) {
- if (isReverse) {
- list.add(n);
- } else {
- table.put(n, n);
- }
- }
-
- void remove(Node n) {
- if (isReverse) {
- list.removeLast();
- } else {
- table.remove(n);
- }
- }
-
- boolean loop(Regex exp, Node n) {
- if (isReverse) {
- // @deprecated, used for count only
- if (list == null || list.size() < 2) {
- return false;
- }
- int last = list.size() - 1;
-
- // use case: ?s {2,} ex:uri
- // Walk through the list backward (from to ?s), skip 2 first nodes
- // cf W3C pp test case
- Node node = null;
- int count = 0;
- int min = exp.getMin();
- if (min == -1) {
- min = 0;
- }
-
- for (int i = last; i >= 0; i--) {
- if (count == min) {
- node = list.get(i);
- } else if (list.get(i).equals(node)) {
- return true;
- }
- count++;
- }
-
- return false;
- } else {
- return table.containsKey(n);
- }
- }
-
- }
-
- static class DTable extends TreeMap {
-
- DTable() {
- super(new Compare());
- }
- }
-
- static class Compare implements Comparator {
-
- // Use the node ordering consistently for loop detection.
- public int compare(Node o1, Node o2) {
- return o1.compare(o2);
- }
- }
-
- static Visit create(boolean isReverse, boolean isCount) {
- return new Visit(isReverse, isCount);
- }
-
-
- /*
- * ************************************
- * With Regex
- *
- *************************************
- */
- /**
- * Test whether path loops
- */
- boolean nloop(Regex exp, Node start) {
- if (isReverse) {
- ninsert(exp, start);
- if (visitedNode.loop(exp, start)) {
- visitedNode.remove(exp, start);
- return true;
- }
- } else if (visitedNode.loop(exp, start)) {
- return true;
- } else {
- ninsert(exp, start);
- }
-
- return false;
- }
-
- VisitedNode getVisitedNode(Regex exp) {
- return visitedNode.get(exp);
- }
-
-
-
- boolean nfirst(Regex exp) {
- return !visitedNode.exist(exp);
- }
-
- void ninsert(Regex exp, Node start) {
- if (start == null) {
- // subscribe exp to start() above because start node is not bound yet
- declare(exp);
- } else {
- visitedNode.add(exp, start);
- }
- }
-
- void declare(Regex exp) {
- if (!regexList.contains(exp)) {
- regexList.add(exp);
- }
- }
-
- void undeclare(Regex exp) {
- regexList.remove(exp);
- }
-
- boolean knows(Regex exp) {
- return regexList.contains(exp);
- }
-
- void nset(Regex exp, VisitedNode t) {
- if (t != null) {
- visitedNode.put(exp, t);
- }
- }
-
-
- VisitedNode nunset(Regex exp) {
- VisitedNode t = visitedNode.get(exp);
- visitedNode.remove(exp);
- return t;
- }
-
- /**
- * use case: ?x p+ ?y
- * ?x is not bound when path starts first occurrence of p
- * binds ?x node is ?x exp is p+
- */
- void nstart(Node node) {
- for (Regex exp : regexList) {
- nstart(exp, node);
- }
- }
-
- void nstart(Regex exp, Node node) {
- if (exp.retype() != Regex.PLUS) {
- visitedNode.add(exp, node);
- }
- }
-
- /**
- * same use case as nstart() above leave the binding
- */
- void nleave(Node node) {
- for (Regex exp : regexList) {
- nleave(exp, node);
- }
- }
-
- void nleave(Regex exp, Node node) {
- if (exp.retype() != Regex.PLUS) {
- visitedNode.remove(exp, node);
- }
- }
-
- /**
- * Remove node from visited list
- */
- void nremove(Regex exp, Node node) {
- if (node == null) {
- undeclare(exp);
- return;
- }
- if (isCounting) {
- visitedNode.remove(exp, node);
- }
- }
-
- void set(Regex exp, int n) {
- ctable.put(exp, n);
- }
-
- void count(Regex exp, int n) {
- Integer i = ctable.get(exp);
- if (i == null) {
- i = 0;
- }
- ctable.put(exp, i + n);
- }
-
- int count(Regex exp) {
- Integer i = ctable.get(exp);
- if (i == null) {
- return 0;
- }
- return i;
- }
-
- /**
- * **************************************
- * ?x distinct(exp+) ?y
- *
- */
- boolean isDistinct(Node start, Node node) {
- NodeTable t = tdistinct.get(start);
- if (t == null) {
- return true;
- }
- return !t.containsKey(node);
- }
-
- void addDistinct(Node start, Node node) {
- NodeTable t = tdistinct.computeIfAbsent(start, key -> new NodeTable());
- t.put(node, node);
- }
-
- /*
- * ********************************************
- * ?x short(regex) ?y ?x distinct(regex) ?y
- *
- */
- /**
- * Table for managing path length for each node
- */
- static class LTable extends TreeMap {
-
- LTable() {
- super(new Compare());
- }
-
- void setLength(Node n, Regex exp, int l) {
- Record r = get(n);
- if (r == null) {
- r = new Record(exp, l);
- put(n, r);
- } else if (r.exp == exp) {
- r.len = l;
- }
- }
-
- Integer getLength(Node n, Regex exp) {
- Record r = get(n);
- if (r != null && r.exp == exp) {
- return r.len;
- } else {
- return null;
- }
- }
- }
-
- static class Record {
-
- Regex exp;
- Integer len;
-
- Record(Regex e, Integer l) {
- exp = e;
- len = l;
- }
-
- }
-
- void initPath() {
- ltable.clear();
- }
-
- /**
- * New start node: clean the table of visited nodes
- */
- void start() {
- if (!isCounting) {
- clearall();
- }
- }
-
-
- /**
- * New start node: clean the table of visited nodes
- */
- void clearall() {
- for (Regex exp : regexList) {
- VisitedNode t = getVisitedNode(exp);
- if (t != null) {
- t.clear();
- }
- }
- }
-
- void setLength(Node n, Regex exp, int l) {
- ltable.setLength(n, exp, l);
- }
-
- Integer getLength(Node n, Regex exp) {
- return ltable.getLength(n, exp);
- }
-
-}
diff --git a/src/test/java/fr/inria/corese/core/next/data/api/model/RdfValueOrderTest.java b/src/test/java/fr/inria/corese/core/next/data/api/model/RdfValueOrderTest.java
index 34d65c878..e2ce0018e 100644
--- a/src/test/java/fr/inria/corese/core/next/data/api/model/RdfValueOrderTest.java
+++ b/src/test/java/fr/inria/corese/core/next/data/api/model/RdfValueOrderTest.java
@@ -4,9 +4,12 @@
import fr.inria.corese.core.next.data.api.factory.ValueFactory;
import fr.inria.corese.core.next.data.api.literal.XSDDatatype;
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.Value;
import fr.inria.corese.core.next.data.api.vocabulary.XSD;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
import java.math.BigDecimal;
@@ -46,6 +49,21 @@ void comparesNumericLiteralsByValueBeforeLexicalForm() {
assertTrue(two.compare(ten) < 0);
}
+ @ParameterizedTest
+ @EnumSource(value = XSDDatatype.class, names = {"INTEGER", "DECIMAL", "FLOAT", "DOUBLE"})
+ void genericTypedNumbersUseTheSameOrderingWithoutChangingTheirLexicalForm(XSDDatatype datatype) {
+ var two = new SimpleLiteral("02", datatype.getIRI());
+ var ten = new SimpleLiteral("10", datatype.getIRI());
+ Literal specializedTwo = values.createLiteral("02", datatype.getIRI());
+ Literal specializedTen = values.createLiteral("10", datatype.getIRI());
+ assertTrue(two.compare(ten) < 0);
+ assertTrue(ten.compare(two) > 0);
+ assertEquals(0, two.compare(specializedTwo));
+ assertEquals(Integer.signum(specializedTen.compare(two)), Integer.signum(ten.compare(specializedTwo)));
+ assertEquals("02", two.getLabel());
+ assertEquals(datatype.getIRI(), two.getDatatype());
+ }
+
@Test
void usesRdfTermTieBreakersForEqualNumericValues() {
DatatypeValue integer = values.createLiteral("1", XSD.xsdInteger.getIRI());
diff --git a/src/test/java/fr/inria/corese/core/next/data/impl/io/parser/nquads/NQuadsListenerTest.java b/src/test/java/fr/inria/corese/core/next/data/impl/io/parser/nquads/NQuadsListenerTest.java
index 3e709c436..425d0791b 100644
--- a/src/test/java/fr/inria/corese/core/next/data/impl/io/parser/nquads/NQuadsListenerTest.java
+++ b/src/test/java/fr/inria/corese/core/next/data/impl/io/parser/nquads/NQuadsListenerTest.java
@@ -3,7 +3,7 @@
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.impl.io.parser.support.NTriplesQuadsTermDecoder;
import fr.inria.corese.core.next.data.api.exception.ParsingException;
import fr.inria.corese.core.next.generated.antlr.NQuadsParser;
import org.antlr.v4.runtime.ParserRuleContext;
@@ -34,12 +34,10 @@ class NQuadsListenerTest {
@Mock
private ValueFactory mockValueFactory;
- @Mock
- private IOOptions mockIOOptions;
+ private NTriplesQuadsTermDecoder termDecoder;
private NQuadsListener listener;
-
@Mock
private IRI mockIRI;
@Mock
@@ -55,7 +53,8 @@ class NQuadsListenerTest {
@BeforeEach
void setUp() {
- listener = new NQuadsListener(mockModel, mockValueFactory, mockIOOptions);
+ termDecoder = new NTriplesQuadsTermDecoder(mockValueFactory);
+ listener = new NQuadsListener(mockModel, mockValueFactory);
lenient().when(mockValueFactory.createIRI(anyString())).thenAnswer(invocation -> {
String uri = invocation.getArgument(0);
@@ -106,7 +105,6 @@ void testEnterExitStatementAddsToModelWithIRIGraph() {
NQuadsParser.ObjectContext objectCtx = mockRuleContext(NQuadsParser.ObjectContext.class);
NQuadsParser.GraphLabelContext graphLabelCtx = mockRuleContext(NQuadsParser.GraphLabelContext.class);
-
TerminalNode subjectIriRef = mockTerminalNode("");
when(subjectCtx.IRIREF()).thenReturn(subjectIriRef);
@@ -119,7 +117,6 @@ void testEnterExitStatementAddsToModelWithIRIGraph() {
TerminalNode graphIriRef = mockTerminalNode("");
when(graphLabelCtx.IRIREF()).thenReturn(graphIriRef);
-
when(statementCtx.subject()).thenReturn(subjectCtx);
when(statementCtx.predicate()).thenReturn(predicateCtx);
when(statementCtx.object()).thenReturn(objectCtx);
@@ -134,7 +131,6 @@ void testEnterExitStatementAddsToModelWithIRIGraph() {
when(mockValueFactory.createIRI("http://example.org/object")).thenReturn(actualObjectIRI);
when(mockValueFactory.createIRI("http://example.org/graph")).thenReturn(actualGraphIRI);
-
listener.enterStatement(statementCtx);
listener.exitStatement(statementCtx);
@@ -171,7 +167,6 @@ void testEnterExitStatementAddsToModelDefaultGraph() {
when(mockValueFactory.createIRI("http://example.org/predicate")).thenReturn(actualPredicateIRI);
when(mockValueFactory.createIRI("http://example.org/object")).thenReturn(actualObjectIRI);
-
listener.enterStatement(statementCtx);
listener.exitStatement(statementCtx);
@@ -277,7 +272,6 @@ void testExtractObjectLiteralLang() {
verify(mockValueFactory).createLiteral("hello", "en");
}
-
@Test
@DisplayName("extractGraph should return IRI for IRIREF context")
void testExtractGraphIRI() {
@@ -302,64 +296,47 @@ void testExtractGraphBNode() {
verify(mockValueFactory).createBNode("graph1");
}
-
-
-
@Test
- @DisplayName("unescapeLiteral should throw IllegalArgumentException for invalid \\UXXXXXXXX")
- @SuppressWarnings("deprecation")
- void testUnescapeLiteralInvalidUx() throws NoSuchMethodException {
+ @DisplayName("unescapeLiteral should throw ParsingException for invalid \\UXXXXXXXX")
+ void testUnescapeLiteralInvalidUx() {
String input = "\"Invalid\\U0000XXX\"";
- java.lang.reflect.Method method = NQuadsListener.class.getDeclaredMethod("unescapeLiteral", String.class);
- method.setAccessible(true);
assertThrows(ParsingException.class,
- () -> listener.unescapeLiteral(input),
+ () -> termDecoder.unescapeLiteral(input),
"Should throw for malformed \\UXXXXXXXX escape sequence");
}
-
@Test
@DisplayName("unescapeUri should handle basic escape sequences")
- void testUnescapeUriBasicEscapes() throws NoSuchMethodException, java.lang.reflect.InvocationTargetException, IllegalAccessException {
+ void testUnescapeUriBasicEscapes() {
String input = "http://example.org/path\\>with\\ listener.unescapeLiteral(input),
- "Should throw unescapeUri should throw IllegalArgumentException for invalid \\uXXXX");
+ () -> termDecoder.unescapeUri(input),
+ "Malformed Unicode escapes in IRIs must fail");
}
-
}
diff --git a/src/test/java/fr/inria/corese/core/next/data/impl/io/parser/ntriples/NTriplesListenerTest.java b/src/test/java/fr/inria/corese/core/next/data/impl/io/parser/ntriples/NTriplesListenerTest.java
index 497af9420..12ab2783c 100644
--- a/src/test/java/fr/inria/corese/core/next/data/impl/io/parser/ntriples/NTriplesListenerTest.java
+++ b/src/test/java/fr/inria/corese/core/next/data/impl/io/parser/ntriples/NTriplesListenerTest.java
@@ -3,7 +3,7 @@
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.impl.io.parser.support.NTriplesQuadsTermDecoder;
import fr.inria.corese.core.next.data.api.exception.ParsingException;
import fr.inria.corese.core.next.generated.antlr.NTriplesParser;
import org.antlr.v4.runtime.ParserRuleContext;
@@ -20,7 +20,7 @@
import static org.mockito.Mockito.*;
/**
- * Unit tests for the NTriplesListenerImpl class.
+ * Unit tests for the NTriplesListener class.
* These tests verify that the listener correctly processes ANTLR parse tree contexts
* to extract and unescape RDF terms (IRIs, Blank Nodes, Literals) and add them to the model.
*/
@@ -33,8 +33,7 @@ class NTriplesListenerTest {
@Mock
private ValueFactory mockValueFactory;
- @Mock
- private IOOptions mockIOOptions;
+ private NTriplesQuadsTermDecoder termDecoder;
private NTriplesListener listener;
@@ -49,7 +48,8 @@ class NTriplesListenerTest {
@BeforeEach
void setUp() {
- listener = new NTriplesListener(mockModel, mockValueFactory, mockIOOptions);
+ termDecoder = new NTriplesQuadsTermDecoder(mockValueFactory);
+ listener = new NTriplesListener(mockModel, mockValueFactory);
lenient().when(mockValueFactory.createIRI(anyString())).thenAnswer(invocation -> {
String uri = invocation.getArgument(0);
@@ -117,7 +117,6 @@ void testEnterExitTripleAddsToModel() {
when(mockValueFactory.createIRI("http://example.org/predicate")).thenReturn(actualPredicateIRI);
when(mockValueFactory.createIRI("http://example.org/object")).thenReturn(actualObjectIRI);
-
listener.enterTriple(tripleCtx);
listener.exitTriple(tripleCtx);
@@ -223,63 +222,47 @@ void testExtractObjectLiteralLang() {
verify(mockValueFactory).createLiteral("hello", "en");
}
-
@Test
@DisplayName("unescapeLiteral should throw ParsingException for invalid \\uXXXX")
- @SuppressWarnings("deprecation")
- void testUnescapeLiteralInvalidUx() throws NoSuchMethodException {
+ void testUnescapeLiteralInvalidUx() {
String input = "\"Invalid\\uXXXX\"";
- java.lang.reflect.Method method = NTriplesListener.class.getDeclaredMethod("unescapeLiteral", String.class);
- method.setAccessible(true);
-
assertThrows(ParsingException.class,
- () -> listener.unescapeLiteral(input),
- "Should throw unescapeLiteral should throw ParsingException for invalid \\uXXXX");
+ () -> termDecoder.unescapeLiteral(input),
+ "Malformed Unicode escapes must fail");
}
@Test
@DisplayName("unescapeLiteral should throw ParsingException for invalid \\UXXXXXXXX")
- @SuppressWarnings("deprecation")
- void testUnescapeLiteralInvalid() throws NoSuchMethodException {
+ void testUnescapeLiteralInvalid() {
String input = "\"Invalid\\U0000XXX\"";
- java.lang.reflect.Method method = NTriplesListener.class.getDeclaredMethod("unescapeLiteral", String.class);
- method.setAccessible(true);
-
assertThrows(ParsingException.class,
- () -> listener.unescapeLiteral(input),
- "Should throw unescapeLiteral should throw ParsingException for invalid \\UXXXXXXXX");
+ () -> termDecoder.unescapeLiteral(input),
+ "Malformed Unicode escapes must fail");
}
-
@Test
@DisplayName("unescapeUri should handle basic escape sequences")
- void testUnescapeUriBasicEscapes() throws NoSuchMethodException, java.lang.reflect.InvocationTargetException, IllegalAccessException {
+ void testUnescapeUriBasicEscapes() {
String input = "http://example.org/path\\>with\\ <%s> \"%s\"",
mockExPerson.stringValue(),
mockExName.stringValue(),
- escapeNQuadsString(lexJohn)) + " .\n";
+ escapeNQuadsString(LEX_JOHN)) + " .\n";
assertEquals(expected, writer.toString());
}
@@ -102,10 +103,8 @@ void writeShouldHandleBlankNodes() throws SerializationException {
StringWriter writer = new StringWriter();
nQuadsSerializer.write(writer);
- String expected = String.format("_:%s <%s> _:%s",
- mockBNode1.stringValue(),
- mockExKnows.stringValue(),
- mockBNode2.stringValue()) + " .\n";
+ String expected = "_:" + mockBNode1.stringValue() + " <" + mockExKnows.stringValue()
+ + "> _:" + mockBNode2.stringValue() + " .\n";
assertEquals(expected, writer.toString());
}
@@ -214,7 +213,7 @@ void writeShouldHandleNullContext() throws SerializationException {
String expected = String.format("<%s> <%s> \"%s\"",
mockExPerson.stringValue(),
mockExName.stringValue(),
- escapeNQuadsString(lexJohn)) + " .\n";
+ escapeNQuadsString(LEX_JOHN)) + " .\n";
assertEquals(expected, writer.toString());
}
@@ -272,7 +271,7 @@ void shouldHandleLiteralsWithLanguageTags() throws SerializationException {
String expectedOutput = String.format("<%s> <%s> \"%s\"@%s",
mockExPerson.stringValue(),
factory.createIRI("http://example.org/greeting").stringValue(),
- escapeNQuadsString(hello),
+ escapeNQuadsString(HELLO),
languageTag) + " .\n";
assertEquals(expectedOutput, writer.toString());
@@ -358,6 +357,9 @@ public boolean hasNext() {
@Override
public Statement next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
return statements[index++];
}
}
diff --git a/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/ntriples/NTriplesSerializerTest.java b/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/ntriples/NTriplesSerializerTest.java
index dd76586a2..4a793d55b 100644
--- a/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/ntriples/NTriplesSerializerTest.java
+++ b/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/ntriples/NTriplesSerializerTest.java
@@ -17,6 +17,7 @@
import java.io.StringWriter;
import java.io.Writer;
import java.util.Iterator;
+import java.util.NoSuchElementException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -24,6 +25,9 @@
class NTriplesSerializerTest {
+ private static final String LEX_JOHN = "John Doe";
+ private static final String HELLO = "Hello";
+
private Model model;
private NTriplesSerializer nTriplesSerializer;
private MockStatementFactory factory;
@@ -32,9 +36,6 @@ class NTriplesSerializerTest {
private IRI mockExName;
private IRI mockExKnows;
- private final String lexJohn = "John Doe";
- private final String hello = "Hello";
-
private Literal mockLiteralJohn;
private Literal mockLiteralHelloEn;
private Resource mockBNode1;
@@ -51,8 +52,8 @@ void setUp() {
mockExName = factory.createIRI("http://example.org/name");
mockExKnows = factory.createIRI("http://example.org/knows");
- mockLiteralJohn = factory.createLiteral(lexJohn, null, null);
- mockLiteralHelloEn = factory.createLiteral(hello, null, "en");
+ mockLiteralJohn = factory.createLiteral(LEX_JOHN, null, null);
+ mockLiteralHelloEn = factory.createLiteral(HELLO, null, "en");
mockBNode1 = factory.createBlankNode("b1");
mockBNode2 = factory.createBlankNode("b2");
@@ -86,7 +87,7 @@ void writeShouldSerializeSimpleStatement() throws SerializationException {
String expected = String.format("<%s> <%s> \"%s\"",
mockExPerson.stringValue(),
mockExName.stringValue(),
- escapeNTriplesString(lexJohn)) + " .\n";
+ escapeNTriplesString(LEX_JOHN)) + " .\n";
assertEquals(expected, writer.toString());
}
@@ -109,7 +110,7 @@ void writeShouldSerializeStatementWithContext() throws SerializationException {
String expected = String.format("<%s> <%s> \"%s\"",
mockExPerson.stringValue(),
mockExName.stringValue(),
- escapeNTriplesString(lexJohn)) + " .\n";
+ escapeNTriplesString(LEX_JOHN)) + " .\n";
assertEquals(expected, writer.toString());
}
@@ -127,10 +128,8 @@ void writeShouldHandleBlankNodes() throws SerializationException {
StringWriter writer = new StringWriter();
nTriplesSerializer.write(writer);
- String expected = String.format("_:%s <%s> _:%s",
- mockBNode1.stringValue(),
- mockExKnows.stringValue(),
- mockBNode2.stringValue()) + " .\n";
+ String expected = "_:" + mockBNode1.stringValue() + " <" + mockExKnows.stringValue()
+ + "> _:" + mockBNode2.stringValue() + " .\n";
assertEquals(expected, writer.toString());
}
@@ -221,7 +220,7 @@ void writeShouldHandleNullContext() throws SerializationException {
String expected = String.format("<%s> <%s> \"%s\"",
mockExPerson.stringValue(),
mockExName.stringValue(),
- escapeNTriplesString(lexJohn)) + " .\n";
+ escapeNTriplesString(LEX_JOHN)) + " .\n";
assertEquals(expected, writer.toString());
}
@@ -280,7 +279,7 @@ void shouldHandleLiteralsWithLanguageTags() throws SerializationException {
String expectedOutput = String.format("<%s> <%s> \"%s\"@%s",
mockExPerson.stringValue(),
factory.createIRI("http://example.org/greeting").stringValue(),
- escapeNTriplesString(hello),
+ escapeNTriplesString(HELLO),
languageTag) + " .\n";
assertEquals(expectedOutput, writer.toString());
@@ -365,6 +364,9 @@ public boolean hasNext() {
@Override
public Statement next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
return statements[index++];
}
}
diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/path/GraphPathTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/path/GraphPathTest.java
deleted file mode 100644
index 6df745042..000000000
--- a/src/test/java/fr/inria/corese/core/next/query/impl/engine/path/GraphPathTest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package fr.inria.corese.core.next.query.impl.engine.path;
-
-import fr.inria.corese.core.next.query.impl.engine.model.Node;
-import fr.inria.corese.core.next.query.impl.engine.spi.Environment;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.mockito.Mockito.*;
-
-class GraphPathTest {
- @Test
- void runsPathEnumerationOnTheWorkerThread() throws InterruptedException {
- PathFinder finder = mock(PathFinder.class);
- Environment environment = mock(Environment.class);
- Node start = mock(Node.class);
- when(finder.getIndex()).thenReturn(1);
- when(finder.get(environment, 1)).thenReturn(start);
- GraphPath worker = new GraphPath(finder, environment);
-
- worker.start();
- worker.join(2000);
-
- assertFalse(worker.isAlive());
- verify(finder).process(start, environment);
- }
-}
diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/path/PathMappingBufferTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/path/PathMappingBufferTest.java
deleted file mode 100644
index 2e50be92f..000000000
--- a/src/test/java/fr/inria/corese/core/next/query/impl/engine/path/PathMappingBufferTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package fr.inria.corese.core.next.query.impl.engine.path;
-
-import fr.inria.corese.core.next.query.impl.engine.solution.Mapping;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.Timeout;
-
-import java.util.NoSuchElementException;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-@Timeout(5)
-class PathMappingBufferTest {
- @Test
- void consumesProducedMappingsAndKeepsTheEndOfStream() throws Exception {
- PathMappingBuffer buffer = new PathMappingBuffer();
- Mapping mapping = Mapping.create();
- try (var executor = Executors.newSingleThreadExecutor()) {
- var producer = executor.submit(() -> {
- buffer.put(mapping, true);
- buffer.put(null, false);
- });
- assertTrue(buffer.hasNext());
- assertSame(mapping, buffer.next());
- assertFalse(buffer.hasNext());
- assertThrows(NoSuchElementException.class, buffer::next);
- assertFalse(buffer.hasNext());
- producer.get(2, TimeUnit.SECONDS);
- }
- }
-
- @Test
- void interruptionEndsIterationAndPreservesTheInterruptFlag() {
- PathMappingBuffer buffer = new PathMappingBuffer();
- Thread.currentThread().interrupt();
- try {
- assertThrows(NoSuchElementException.class, buffer::next);
- assertTrue(Thread.currentThread().isInterrupted());
- } finally {
- Thread.interrupted();
- }
- }
-}
From b1a2c688aa71da7532d09cc5c2f4a17bce754309 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9mi=20C=C3=A9r=C3=A8s?=
Date: Mon, 19 Oct 2026 15:14:57 +0200
Subject: [PATCH 2/3] refactor(engine): modernize query execution and eliminate
legacy KGRAM debt
- Remove obsolete KGRAM artifacts: Type.PATH, initPath, clean, min/max bounds, isGraphPath
- Strip unused query path flags (isCheckLoop, isListPath, isStorePath, isCachePath, isCountPath) and pathFilter
- Unify SPARQL execution pipeline with SparqlQueryExecutor, SparqlUpdateExecutor, and AstBackedFilter
- Add immutable compiled query plans with cache, top-k selection, and progressive streaming evaluation
- Expand execution diagnostics and memory storage snapshot query isolation tests
Resolves #617
---
.../exception/ConfigurationException.java | 3 +
.../corese/core/next/query/api/Query.java | 18 +
.../query/api/diagnostics/QueryExecution.java | 40 +
.../diagnostics/QueryExecutionProfile.java | 191 +++++
.../query/api/diagnostics/package-info.java | 2 +
.../exception/QueryEvaluationException.java | 3 +
.../query/api/exception/QueryException.java | 3 +
.../api/exception/QuerySyntaxException.java | 3 +
.../api/exception/QueryTimeoutException.java | 3 +
.../exception/QueryTypeErrorException.java | 3 +
.../exception/QueryValidationException.java | 3 +
.../api/exception/RepositoryException.java | 3 +
.../UnsupportedQueryFeatureException.java | 3 +
.../query/api/result/TupleQueryResult.java | 16 +-
.../diagnostics/DiagnosticProducer.java | 74 ++
.../diagnostics/ExecutionDiagnostics.java | 268 +++++++
.../impl/engine/diagnostics/package-info.java | 2 +
.../engine/eval/ApproximateSearchEnv.java | 104 ---
.../impl/engine/eval/CompleteSPARQL.java | 173 -----
.../impl/engine/eval/EnvironmentImpl.java | 135 ++--
.../next/query/impl/engine/eval/Eval.java | 684 ++++-------------
.../query/impl/engine/eval/EvalGraph.java | 6 +-
.../next/query/impl/engine/eval/EvalJoin.java | 226 +++---
.../query/impl/engine/eval/EvalOptional.java | 8 +-
.../query/impl/engine/eval/EvalSPARQL.java | 219 ------
...ecker.java => QueryDefinitionChecker.java} | 40 +-
.../query/impl/engine/eval/ResultsImpl.java | 46 --
.../query/impl/engine/eval/SelectCursor.java | 724 ++++++++++++++++++
.../impl/engine/eval/SparqlException.java | 28 -
.../next/query/impl/engine/eval/Stack.java | 59 +-
.../next/query/impl/engine/event/Event.java | 62 +-
.../query/impl/engine/event/EventImpl.java | 204 ++---
.../impl/engine/event/EventListener.java | 8 +-
.../engine/event/KgramEventDispatcher.java | 1 -
.../impl/engine/event/ResultListener.java | 28 +-
.../next/query/impl/engine/event/User.java | 16 -
.../query/impl/engine/filter/Checker.java | 216 ------
.../query/impl/engine/filter/Compile.java | 254 ------
.../filter/ExpressionPatternMatcher.java | 230 ++++++
.../impl/engine/filter/FilterOptimizer.java | 252 ++++++
.../impl/engine/filter/FilterPattern.java | 455 +++++------
.../query/impl/engine/filter/MatchBind.java | 82 +-
.../query/impl/engine/filter/Matcher.java | 230 ------
.../next/query/impl/engine/model/EdgeInv.java | 2 +-
.../next/query/impl/engine/model/ExpType.java | 84 +-
.../next/query/impl/engine/model/Expr.java | 63 +-
.../query/impl/engine/model/ExprType.java | 1 -
.../query/impl/engine/model/NodeImpl.java | 16 +-
.../next/query/impl/engine/package-info.java | 6 +-
.../next/query/impl/engine/path/Path.java | 4 +-
.../query/impl/engine/path/PropertyPath.java | 19 +-
.../impl/engine/path/PropertyPathEdge.java | 13 +-
.../impl/engine/pattern/BgpGenerator.java | 7 -
.../engine/pattern/CompiledQueryPlan.java | 56 ++
.../next/query/impl/engine/pattern/Exp.java | 181 ++---
.../query/impl/engine/pattern/ExpEdge.java | 1 +
.../impl/engine/pattern/ExpNodeCollector.java | 2 +-
.../next/query/impl/engine/pattern/Group.java | 6 +-
.../query/impl/engine/pattern/PlanCopy.java | 70 ++
.../impl/engine/pattern/PlanCopyable.java | 18 +
.../next/query/impl/engine/pattern/Query.java | 500 +++++-------
.../BindingPatternOrder.java} | 31 +-
.../planning/CardinalityPatternOrdering.java | 160 ++++
.../ConnectedPatternOrdering.java} | 19 +-
.../GreedyPatternOrdering.java} | 94 ++-
.../HeuristicPatternOrdering.java} | 48 +-
.../HeuristicSelectivityEstimator.java} | 44 +-
.../JoinCostModel.java} | 55 +-
.../NodeCostModel.java} | 67 +-
.../PatternOrderingStrategy.java} | 10 +-
.../PlanningConstants.java} | 8 +-
.../QueryPlanEdge.java} | 26 +-
.../QueryPlanGraph.java} | 95 ++-
.../QueryPlanNode.java} | 32 +-
.../QueryPlanner.java} | 85 +-
.../impl/engine/planning/QueryStatistics.java | 52 ++
.../SelectivityEstimator.java} | 9 +-
.../{sorter => planning}/package-info.java | 2 +-
.../query/impl/engine/solution/Mapping.java | 17 +-
.../impl/engine/solution/MappingSet.java | 13 +-
.../query/impl/engine/solution/Mappings.java | 50 +-
.../query/impl/engine/solution/Memory.java | 287 ++++---
.../impl/engine/sorter/AbstractCostModel.java | 35 -
.../query/impl/engine/sorter/IProducerQP.java | 24 -
.../core/next/query/impl/engine/spi/AST.java | 19 -
.../spi/DistributedQueryPlanFactory.java | 12 -
.../query/impl/engine/spi/Environment.java | 2 -
.../next/query/impl/engine/spi/Graphable.java | 15 -
.../next/query/impl/engine/spi/Plugin.java | 15 -
.../next/query/impl/engine/spi/Producer.java | 60 +-
.../next/query/impl/engine/spi/Provider.java | 4 +-
.../next/query/impl/engine/spi/Result.java | 16 -
.../next/query/impl/engine/spi/Results.java | 18 -
.../query/impl/engine/spi/SPARQLEngine.java | 23 -
.../impl/engine/storage/ProducerDefault.java | 16 -
.../storage/StorageBindingEnvironment.java | 6 -
.../storage/StorageManagerProducer.java | 67 +-
.../query/impl/query/AbstractCoreseQuery.java | 45 +-
.../query/impl/query/CoreseBooleanQuery.java | 28 +-
.../query/impl/query/CoreseGraphQuery.java | 28 +-
.../query/impl/query/CoreseTupleQuery.java | 28 +-
.../next/query/impl/query/CoreseUpdate.java | 4 +-
.../CoreseRepositoryConnection.java | 10 +-
.../query/impl/repository/package-info.java | 2 +-
.../query/impl/sparql/ast/AddRequestAst.java | 2 +-
.../query/impl/sparql/ast/AggregateAst.java | 2 +-
.../query/impl/sparql/ast/AskQueryAst.java | 2 +-
.../next/query/impl/sparql/ast/BgpAst.java | 2 +-
.../next/query/impl/sparql/ast/BindAst.java | 2 +-
.../impl/sparql/ast/ClearRequestAst.java | 2 +-
.../impl/sparql/ast/ConstructQueryAst.java | 2 +-
.../impl/sparql/ast/ConstructTemplateAst.java | 4 +-
.../query/impl/sparql/ast/CopyRequestAst.java | 2 +-
.../impl/sparql/ast/CreateRequestAst.java | 2 +-
.../impl/sparql/ast/DatasetClauseAst.java | 4 +-
.../impl/sparql/ast/DeleteDataRequestAst.java | 2 +-
.../sparql/ast/DeleteWhereRequestAst.java | 2 +-
.../impl/sparql/ast/DescribeQueryAst.java | 2 +-
.../query/impl/sparql/ast/DropRequestAst.java | 2 +-
.../next/query/impl/sparql/ast/FilterAst.java | 2 +-
.../next/query/impl/sparql/ast/GraphAst.java | 2 +-
.../query/impl/sparql/ast/GraphRefAst.java | 4 +-
.../query/impl/sparql/ast/GroupByAst.java | 4 +-
.../impl/sparql/ast/GroupGraphPatternAst.java | 2 +-
.../next/query/impl/sparql/ast/HavingAst.java | 4 +-
.../impl/sparql/ast/InsertDataRequestAst.java | 2 +-
.../next/query/impl/sparql/ast/IriAst.java | 2 +-
.../query/impl/sparql/ast/LiteralAst.java | 2 +-
.../query/impl/sparql/ast/LoadRequestAst.java | 2 +-
.../next/query/impl/sparql/ast/MinusAst.java | 2 +-
.../impl/sparql/ast/ModifyRequestAst.java | 2 +-
.../query/impl/sparql/ast/MoveRequestAst.java | 2 +-
.../impl/sparql/ast/NamedGraphQuadsAst.java | 4 +-
.../query/impl/sparql/ast/OptionalAst.java | 2 +-
.../impl/sparql/ast/OrderConditionAst.java | 4 +-
.../query/impl/sparql/ast/PatternAst.java | 4 +-
.../impl/sparql/ast/PrefixDeclarationAst.java | 4 +-
.../query/impl/sparql/ast/ProjectionAst.java | 4 +-
.../next/query/impl/sparql/ast/QuadsAst.java | 4 +-
.../next/query/impl/sparql/ast/QueryAst.java | 2 +-
.../impl/sparql/ast/QueryPrologueAst.java | 4 +-
.../query/impl/sparql/ast/SelectQueryAst.java | 2 +-
.../query/impl/sparql/ast/ServiceAst.java | 2 +-
.../impl/sparql/ast/SolutionModifierAst.java | 4 +-
.../query/impl/sparql/ast/SubQueryAst.java | 2 +-
.../next/query/impl/sparql/ast/TermAst.java | 2 +-
.../impl/sparql/ast/TriplePatternAst.java | 4 +-
.../next/query/impl/sparql/ast/UnionAst.java | 2 +-
.../impl/sparql/ast/UpdateRequestAst.java | 2 +-
.../impl/sparql/ast/UpdateRequestUnitAst.java | 2 +-
.../impl/sparql/ast/ValueMappingAst.java | 4 +-
.../next/query/impl/sparql/ast/ValuesAst.java | 2 +-
.../next/query/impl/sparql/ast/VarAst.java | 2 +-
.../AbstractBinaryConstraintAst.java | 6 +-
.../AbstractChainableOperatorAst.java | 6 +-
.../AbstractUnaryConstraintAst.java | 2 +-
...AbstractUnlimitedArgumentsFunctionAst.java | 2 +-
.../impl/sparql/ast/constraint/AddAst.java | 6 +-
.../impl/sparql/ast/constraint/AndAst.java | 6 +-
.../impl/sparql/ast/constraint/BnodeAst.java | 2 +-
.../sparql/ast/constraint/DifferentAst.java | 6 +-
.../impl/sparql/ast/constraint/DivideAst.java | 6 +-
.../impl/sparql/ast/constraint/EqualsAst.java | 6 +-
.../impl/sparql/ast/constraint/ExistsAst.java | 2 +-
.../ast/constraint/FunctionCallAst.java | 2 +-
.../ast/constraint/GreaterOrEqualThanAst.java | 6 +-
.../sparql/ast/constraint/GreaterThanAst.java | 6 +-
.../impl/sparql/ast/constraint/IfAst.java | 2 +-
.../impl/sparql/ast/constraint/InAst.java | 2 +-
.../ast/constraint/LowerOrEqualThanAst.java | 6 +-
.../sparql/ast/constraint/LowerThanAst.java | 6 +-
.../sparql/ast/constraint/MultiplyAst.java | 6 +-
.../sparql/ast/constraint/NotExistsAst.java | 2 +-
.../impl/sparql/ast/constraint/NotInAst.java | 2 +-
.../impl/sparql/ast/constraint/NowAst.java | 2 +-
.../impl/sparql/ast/constraint/OrAst.java | 6 +-
.../impl/sparql/ast/constraint/RandAst.java | 2 +-
.../sparql/ast/constraint/ReplaceAst.java | 2 +-
.../sparql/ast/constraint/StrUuidAst.java | 2 +-
.../impl/sparql/ast/constraint/SubstrAst.java | 2 +-
.../sparql/ast/constraint/SubtractAst.java | 6 +-
.../ast/constraint/TrinaryRegexAst.java | 2 +-
.../impl/sparql/ast/constraint/UuidAst.java | 2 +-
.../sparql/ast/path/AlternativePathAst.java | 2 +-
.../impl/sparql/ast/path/InversePathAst.java | 2 +-
.../ast/path/NegatedPropertySetPathAst.java | 2 +-
.../sparql/ast/path/OneOrMorePathAst.java | 2 +-
.../impl/sparql/ast/path/OptionalPathAst.java | 2 +-
.../query/impl/sparql/ast/path/PathAst.java | 2 +-
.../sparql/ast/path/PredicatePathAst.java | 2 +-
.../impl/sparql/ast/path/SequencePathAst.java | 2 +-
.../sparql/ast/path/ZeroOrMorePathAst.java | 2 +-
.../visitor}/AbstractAstVisitor.java | 2 +-
.../support => ast/visitor}/AstVisitor.java | 2 +-
.../impl/sparql/ast/visitor/VisitableAst.java | 6 +
.../impl/sparql/ast/visitor/package-info.java | 5 +
.../impl/sparql/bridge/AstBackedEdge.java | 14 +-
.../impl/sparql/bridge/AstBackedExpr.java | 159 +---
...ilterFromAst.java => AstBackedFilter.java} | 17 +-
.../sparql/bridge/CoreseAstQueryBuilder.java | 1 +
.../bridge/NativeEvaluationContext.java | 9 +-
.../bridge/SolutionModifierCompiler.java | 2 +
.../sparql/bridge/SparqlTermResolver.java | 20 +-
.../impl/sparql/bridge/WhereCompiler.java | 23 +-
.../sparql/execution/CoreseBindingSet.java | 43 +-
.../sparql/execution/PreparedQueryPlan.java | 116 +++
...Executor.java => SparqlQueryExecutor.java} | 278 ++++++-
.../execution/StreamingTupleQueryResult.java | 256 +++++++
.../impl/sparql/execution/TopKSelector.java | 104 +++
.../impl/sparql/parser/SparqlAstBuilder.java | 4 +-
.../parser/SparqlQuadTemplateBuilder.java | 1 +
.../impl/sparql/parser/SparqlTermBuilder.java | 15 +-
.../parser/listener/ValuesAstListener.java | 2 +
.../rule/FilterArgumentsValidationRule.java | 2 +-
...randArgumentBooleanTypeValidationRule.java | 2 +-
.../OperandArgumentIRITypeValidationRule.java | 2 +-
...randArgumentNumericTypeValidationRule.java | 2 +-
.../parser/semantic/support/VisitableAst.java | 6 -
.../parser/semantic/support/package-info.java | 5 +-
...xecutor.java => SparqlUpdateExecutor.java} | 10 +-
.../core/next/storage/api/StorageManager.java | 16 +
.../api/exception/StorageException.java | 3 +
.../api/operations/QueryOperations.java | 19 +
.../storage/api/plugin/PluginException.java | 3 +
.../api/plugin/PluginNotFoundException.java | 3 +
.../impl/memory/IndexedStatementStore.java | 54 +-
.../impl/memory/MemoryStorageManager.java | 6 +
.../memory/plugin/MemoryStoragePlugin.java | 4 +-
.../ExecutionDeliveryTimingTest.java | 116 +++
.../QueryDeliveryDiagnosticsTest.java | 306 ++++++++
.../QueryExecutionDiagnosticsTest.java | 436 +++++++++++
.../query/impl/engine/eval/EvalJoinTest.java | 98 +++
.../next/query/impl/engine/eval/EvalTest.java | 47 --
.../engine/pattern/CompiledQueryPlanTest.java | 96 +++
.../query/impl/engine/pattern/ExpTest.java | 74 --
.../query/impl/engine/pattern/QueryTest.java | 75 +-
.../planning/CardinalityQueryPlannerTest.java | 343 +++++++++
.../engine/storage/GraphIsolationTest.java | 20 +-
.../query/impl/sparql/ast/SparqlAstTest.java | 1 +
.../impl/sparql/bridge/AstBackedExprTest.java | 8 +-
.../execution/CoreseBindingSetTest.java | 48 ++
.../sparql/execution/PipelineTestSupport.java | 4 +-
.../execution/PreparedPlanCacheTest.java | 331 ++++++++
.../execution/ProgressiveSelectTest.java | 569 ++++++++++++++
.../sparql/execution/ScalarFunctionsTest.java | 10 +-
...Test.java => SparqlQueryExecutorTest.java} | 46 +-
.../sparql/execution/TopKLifecycleTest.java | 204 +++++
.../impl/sparql/execution/TopKQueryTest.java | 155 ++++
.../sparql/execution/TopKSelectorTest.java | 75 ++
.../support/AbstractResultSerializerTest.java | 2 +-
.../support/ResultSerializerTestUtils.java | 2 +-
.../xml/XmlResultSerializerOptionsTest.java | 17 +-
.../AbstractSparqlParserFeatureTest.java | 2 +-
.../parser/SparqlExpressionBuilderTest.java | 3 +-
.../parser/SparqlParserSelectQueryTest.java | 68 +-
.../sparql/parser/SparqlPathBuilderTest.java | 2 +-
.../sparql/parser/SparqlTermBuilderTest.java | 2 +-
.../storage/impl/memory/LazyProducerTest.java | 7 +-
.../memory/ProducerDeduplicationTest.java | 4 +-
.../impl/memory/StorageDataVersionTest.java | 103 +++
.../impl/memory/StorageReadSnapshotTest.java | 70 ++
261 files changed, 8132 insertions(+), 4548 deletions(-)
create mode 100644 src/main/java/fr/inria/corese/core/next/query/api/diagnostics/QueryExecution.java
create mode 100644 src/main/java/fr/inria/corese/core/next/query/api/diagnostics/QueryExecutionProfile.java
create mode 100644 src/main/java/fr/inria/corese/core/next/query/api/diagnostics/package-info.java
create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/diagnostics/DiagnosticProducer.java
create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/diagnostics/ExecutionDiagnostics.java
create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/diagnostics/package-info.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/ApproximateSearchEnv.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/CompleteSPARQL.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalSPARQL.java
rename src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/{Checker.java => QueryDefinitionChecker.java} (75%)
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/ResultsImpl.java
create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/SelectCursor.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/SparqlException.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/event/User.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/Checker.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/Compile.java
create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/ExpressionPatternMatcher.java
create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/FilterOptimizer.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/Matcher.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/BgpGenerator.java
create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/CompiledQueryPlan.java
create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/PlanCopy.java
create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/PlanCopyable.java
rename src/main/java/fr/inria/corese/core/next/query/impl/engine/{sorter/BasicPatternGenerator.java => planning/BindingPatternOrder.java} (67%)
create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/planning/CardinalityPatternOrdering.java
rename src/main/java/fr/inria/corese/core/next/query/impl/engine/{sorter/Sorter.java => planning/ConnectedPatternOrdering.java} (86%)
rename src/main/java/fr/inria/corese/core/next/query/impl/engine/{sorter/DepthFirstBestSearch.java => planning/GreedyPatternOrdering.java} (65%)
rename src/main/java/fr/inria/corese/core/next/query/impl/engine/{sorter/SorterNew.java => planning/HeuristicPatternOrdering.java} (60%)
rename src/main/java/fr/inria/corese/core/next/query/impl/engine/{sorter/HeuristicsBasedEstimation.java => planning/HeuristicSelectivityEstimator.java} (52%)
rename src/main/java/fr/inria/corese/core/next/query/impl/engine/{sorter/QPGEdgeCostModel.java => planning/JoinCostModel.java} (57%)
rename src/main/java/fr/inria/corese/core/next/query/impl/engine/{sorter/QPGNodeCostModel.java => planning/NodeCostModel.java} (78%)
rename src/main/java/fr/inria/corese/core/next/query/impl/engine/{sorter/ISort.java => planning/PatternOrderingStrategy.java} (52%)
rename src/main/java/fr/inria/corese/core/next/query/impl/engine/{sorter/QuerySorterConst.java => planning/PlanningConstants.java} (83%)
rename src/main/java/fr/inria/corese/core/next/query/impl/engine/{sorter/QPGEdge.java => planning/QueryPlanEdge.java} (74%)
rename src/main/java/fr/inria/corese/core/next/query/impl/engine/{sorter/QPGraph.java => planning/QueryPlanGraph.java} (53%)
rename src/main/java/fr/inria/corese/core/next/query/impl/engine/{sorter/QPGNode.java => planning/QueryPlanNode.java} (90%)
rename src/main/java/fr/inria/corese/core/next/query/impl/engine/{sorter/QuerySorter.java => planning/QueryPlanner.java} (84%)
create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/planning/QueryStatistics.java
rename src/main/java/fr/inria/corese/core/next/query/impl/engine/{sorter/IEstimate.java => planning/SelectivityEstimator.java} (61%)
rename src/main/java/fr/inria/corese/core/next/query/impl/engine/{sorter => planning}/package-info.java (58%)
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/sorter/AbstractCostModel.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/sorter/IProducerQP.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/spi/AST.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/spi/DistributedQueryPlanFactory.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/spi/Graphable.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/spi/Plugin.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/spi/Result.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/spi/Results.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/spi/SPARQLEngine.java
rename src/main/java/fr/inria/corese/core/next/query/impl/sparql/{parser/semantic/support => ast/visitor}/AbstractAstVisitor.java (95%)
rename src/main/java/fr/inria/corese/core/next/query/impl/sparql/{parser/semantic/support => ast/visitor}/AstVisitor.java (93%)
create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/visitor/VisitableAst.java
create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/visitor/package-info.java
rename src/main/java/fr/inria/corese/core/next/query/impl/sparql/bridge/{NextFilterFromAst.java => AstBackedFilter.java} (80%)
create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/sparql/execution/PreparedQueryPlan.java
rename src/main/java/fr/inria/corese/core/next/query/impl/sparql/execution/{NextSparqlPipelineExecutor.java => SparqlQueryExecutor.java} (65%)
create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/sparql/execution/StreamingTupleQueryResult.java
create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/sparql/execution/TopKSelector.java
delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/sparql/parser/semantic/support/VisitableAst.java
rename src/main/java/fr/inria/corese/core/next/query/impl/sparql/update/{NextSparqlUpdateExecutor.java => SparqlUpdateExecutor.java} (94%)
create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/diagnostics/ExecutionDeliveryTimingTest.java
create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/diagnostics/QueryDeliveryDiagnosticsTest.java
create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/diagnostics/QueryExecutionDiagnosticsTest.java
create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalJoinTest.java
create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/CompiledQueryPlanTest.java
create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/planning/CardinalityQueryPlannerTest.java
create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/sparql/execution/CoreseBindingSetTest.java
create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/sparql/execution/PreparedPlanCacheTest.java
create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/sparql/execution/ProgressiveSelectTest.java
rename src/test/java/fr/inria/corese/core/next/query/impl/sparql/execution/{NextSparqlPipelineExecutorTest.java => SparqlQueryExecutorTest.java} (85%)
create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/sparql/execution/TopKLifecycleTest.java
create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/sparql/execution/TopKQueryTest.java
create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/sparql/execution/TopKSelectorTest.java
create mode 100644 src/test/java/fr/inria/corese/core/next/storage/impl/memory/StorageDataVersionTest.java
create mode 100644 src/test/java/fr/inria/corese/core/next/storage/impl/memory/StorageReadSnapshotTest.java
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/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