Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package fr.inria.corese.core.next.query.impl.sparql.bridge;

import fr.inria.corese.core.next.data.api.Value;
import fr.inria.corese.core.next.data.impl.adapter.CoreseValueFactory;
import fr.inria.corese.core.next.query.kgram.api.core.Node;
import fr.inria.corese.core.sparql.api.IDatatype;

/**
* Converts KGRAM {@link Node} constants to API {@link Value} instances.
*
* <p>This is the single point in the bridge layer that is allowed to inspect
* {@link IDatatype} on behalf of callers outside {@code next.query.kgram}.</p>
*/
public final class KgramNodeConverter {

private KgramNodeConverter() {}

/**
* Converts a KGRAM constant {@link Node} to the corresponding API {@link Value}.
*
* @param node the KGRAM node to convert (must not be null)
* @param factory the value factory used to create API term instances
* @return the API value, or {@code null} when the node kind is not supported
*/
public static Value nodeToValue(Node node, CoreseValueFactory factory) {
IDatatype dt = node.getDatatypeValue();
if (dt.isURI()) {
return factory.createIRI(dt.getLabel());
}
if (dt.isBlank()) {
return factory.createBNode(dt.getLabel());
}
if (dt.isLiteral()) {
String lang = dt.getLang();
if (lang != null && !lang.isEmpty()) {
return factory.createLiteral(dt.getLabel(), lang);
}
String datatypeUri = dt.getDatatypeURI();
if (datatypeUri != null) {
return factory.createLiteral(dt.getLabel(), factory.createIRI(datatypeUri));
}
return factory.createLiteral(dt.getLabel());
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import fr.inria.corese.core.next.query.impl.sparql.ast.QueryAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.SelectQueryAst;
import fr.inria.corese.core.next.query.impl.sparql.bridge.CoreseAstQueryBuilder;
import fr.inria.corese.core.next.query.impl.sparql.bridge.KgramNodeConverter;
import fr.inria.corese.core.next.query.kgram.api.core.Edge;
import fr.inria.corese.core.next.query.kgram.api.core.Node;
import fr.inria.corese.core.next.query.kgram.core.Eval;
Expand All @@ -36,10 +37,6 @@
import fr.inria.corese.core.next.query.kgram.tool.NodeImpl;
import fr.inria.corese.core.next.query.kgram.tool.StorageManagerProducer;
import fr.inria.corese.core.next.storagemanager.api.StorageManager;
import fr.inria.corese.core.sparql.api.IDatatype;
import fr.inria.corese.core.sparql.datatype.DatatypeMap;
import fr.inria.corese.core.sparql.triple.parser.Constant;
import fr.inria.corese.core.sparql.triple.parser.Variable;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -348,28 +345,13 @@ private Node resolveTemplateNode(Node templateNode, Mapping mapping) {
/**
* Converts a KGRAM constant {@link Node} to the corresponding API {@link Value}.
*
* @return the API value, or {@code null} when the datatype kind is not supported
* <p>Delegates to {@link KgramNodeConverter} so that this class does not depend on
* {@code IDatatype} directly.</p>
*
* @return the API value, or {@code null} when the node kind is not supported
*/
private Value kgramNodeToApiValue(Node node, CoreseValueFactory factory) {
IDatatype dt = node.getDatatypeValue();
if (dt.isURI()) {
return factory.createIRI(dt.getLabel());
}
if (dt.isBlank()) {
return factory.createBNode(dt.getLabel());
}
if (dt.isLiteral()) {
String lang = dt.getLang();
if (lang != null && !lang.isEmpty()) {
return factory.createLiteral(dt.getLabel(), lang);
}
String datatypeUri = dt.getDatatypeURI();
if (datatypeUri != null) {
return factory.createLiteral(dt.getLabel(), factory.createIRI(datatypeUri));
}
return factory.createLiteral(dt.getLabel());
}
return null;
return KgramNodeConverter.nodeToValue(node, factory);
}

// -------------------------------------------------------------------------
Expand Down Expand Up @@ -399,7 +381,7 @@ private void applyDataset(Query kgramQuery, Dataset dataset) {
private List<Node> urisToKgramNodes(List<String> uris) {
List<Node> nodes = new ArrayList<>(uris.size());
for (String uri : uris) {
nodes.add(new NodeImpl(Constant.create(DatatypeMap.newResource(uri))));
nodes.add(NodeImpl.forIRI(uri));
}
return nodes;
}
Expand Down Expand Up @@ -429,7 +411,7 @@ private Mapping buildInitialMapping(BindingSet bindings) {
for (Binding b : bindings) {
Node targetNode = valueToKgramNode(b.value());
if (targetNode != null) {
queryNodes.add(new NodeImpl(new Variable(b.name())));
queryNodes.add(NodeImpl.forVariable(b.name()));
targetNodes.add(targetNode);
}
}
Expand All @@ -442,24 +424,20 @@ private Mapping buildInitialMapping(BindingSet bindings) {
* @return a constant node, or {@code null} when the value type is not supported
*/
private Node valueToKgramNode(Value value) {
IDatatype dt;
if (value instanceof IRI iri) {
dt = DatatypeMap.newResource(iri.stringValue());
return NodeImpl.forIRI(iri.stringValue());
} else if (value instanceof BNode bNode) {
dt = DatatypeMap.createBlank(bNode.getID());
return NodeImpl.forBlank(bNode.getID());
} else if (value instanceof Literal literal) {
String lang = literal.getLanguage().orElse(null);
if (lang != null && !lang.isEmpty()) {
dt = DatatypeMap.createLiteral(literal.getLabel(), null, lang);
} else {
String datatypeUri = literal.getDatatype() != null
? literal.getDatatype().stringValue()
: null;
dt = DatatypeMap.createLiteral(literal.getLabel(), datatypeUri, null);
return NodeImpl.forLiteral(literal.getLabel(), null, lang);
}
} else {
return null;
String datatypeUri = literal.getDatatype() != null
? literal.getDatatype().stringValue()
: null;
return NodeImpl.forLiteral(literal.getLabel(), datatypeUri, null);
}
return new NodeImpl(Constant.create(dt));
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import fr.inria.corese.core.next.query.kgram.api.core.Node;
import fr.inria.corese.core.next.query.kgram.api.core.TripleStore;
import fr.inria.corese.core.sparql.api.IDatatype;
import fr.inria.corese.core.sparql.datatype.DatatypeMap;
import fr.inria.corese.core.sparql.triple.parser.Atom;
import fr.inria.corese.core.sparql.triple.parser.Constant;
import fr.inria.corese.core.sparql.triple.parser.Variable;

public class NodeImpl implements Node {

Expand All @@ -19,6 +21,32 @@ public NodeImpl(Atom at) {
atom = at;
}

/** Creates a constant node for an IRI. */
public static NodeImpl forIRI(String iri) {
return new NodeImpl(Constant.create(DatatypeMap.newResource(iri)));
}

/** Creates a constant node for a blank node. */
public static NodeImpl forBlank(String id) {
return new NodeImpl(Constant.create(DatatypeMap.createBlank(id)));
}

/**
* Creates a constant node for a literal.
*
* @param label lexical value
* @param datatypeUri datatype IRI, or {@code null}
* @param lang language tag, or {@code null}
*/
public static NodeImpl forLiteral(String label, String datatypeUri, String lang) {
return new NodeImpl(Constant.create(DatatypeMap.createLiteral(label, datatypeUri, lang)));
}

/** Creates a variable node with the given name. */
public static NodeImpl forVariable(String name) {
return new NodeImpl(new Variable(name));
}

@Override
public IDatatype getValue() {
return atom.getDatatypeValue();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package fr.inria.corese.core.next.util;

import fr.inria.corese.core.sparql.triple.parser.Processor;

public class StringUtils {

public static String trimChevronIRIs(String uri) {
Expand Down Expand Up @@ -101,7 +99,7 @@ public static String escapeForDisplay(String iri) {

/**
* Strips angle brackets if present, then returns the local part after {@code #}, {@code /}, or {@code :},
* lowercased for {@link Processor} lookup.
* lowercased.
*/
public static String localNameFromIriToken(String raw) {
String t = raw.trim();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package fr.inria.corese.core.next.query.impl.sparql.bridge;

import fr.inria.corese.core.next.data.api.BNode;
import fr.inria.corese.core.next.data.api.IRI;
import fr.inria.corese.core.next.data.api.Literal;
import fr.inria.corese.core.next.data.api.Value;
import fr.inria.corese.core.next.data.impl.adapter.CoreseValueFactory;
import fr.inria.corese.core.next.query.kgram.api.core.Node;
import fr.inria.corese.core.next.query.kgram.tool.NodeImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class KgramNodeConverterTest {

private CoreseValueFactory factory;

@BeforeEach
void setUp() {
factory = new CoreseValueFactory();
}

@Test
@DisplayName("IRI node converts to API IRI with the same string value")
void iriNodeConvertsToApiIRI() {
Node node = NodeImpl.forIRI("http://example.org/alice");

Value value = KgramNodeConverter.nodeToValue(node, factory);

assertInstanceOf(IRI.class, value);
assertEquals("http://example.org/alice", value.stringValue());
}

@Test
@DisplayName("Blank node converts to API BNode with the same ID")
void blankNodeConvertsToApiBNode() {
Node node = NodeImpl.forBlank("b1");

Value value = KgramNodeConverter.nodeToValue(node, factory);

assertInstanceOf(BNode.class, value);
assertEquals("b1", ((BNode) value).getID());
}

@Test
@DisplayName("Language-tagged literal converts to API Literal preserving label and lang")
void langLiteralConvertsToApiLiteral() {
Node node = NodeImpl.forLiteral("hello", null, "en");

Value value = KgramNodeConverter.nodeToValue(node, factory);

assertInstanceOf(Literal.class, value);
Literal lit = (Literal) value;
assertEquals("hello", lit.getLabel());
assertEquals("en", lit.getLanguage().orElse(null));
}

@Test
@DisplayName("Typed literal converts to API Literal preserving label and datatype IRI")
void typedLiteralConvertsToApiLiteral() {
String xsdInteger = "http://www.w3.org/2001/XMLSchema#integer";
Node node = NodeImpl.forLiteral("42", xsdInteger, null);

Value value = KgramNodeConverter.nodeToValue(node, factory);

assertInstanceOf(Literal.class, value);
Literal lit = (Literal) value;
assertEquals("42", lit.getLabel());
assertNotNull(lit.getDatatype());
assertEquals(xsdInteger, lit.getDatatype().stringValue());
assertTrue(lit.getLanguage().isEmpty());
}

@Test
@DisplayName("Plain literal (no lang, no explicit datatype) converts to API Literal with label")
void plainLiteralConvertsToApiLiteral() {
Node node = NodeImpl.forLiteral("bare", null, null);

Value value = KgramNodeConverter.nodeToValue(node, factory);

assertInstanceOf(Literal.class, value);
assertEquals("bare", ((Literal) value).getLabel());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import fr.inria.corese.core.next.data.api.IRI;
import fr.inria.corese.core.next.data.api.Resource;
import fr.inria.corese.core.next.data.api.Statement;
import fr.inria.corese.core.next.data.api.Value;
import fr.inria.corese.core.next.data.api.ValueFactory;
import fr.inria.corese.core.next.data.impl.adapter.CoreseValueFactory;
import fr.inria.corese.core.next.query.api.dataset.Dataset;
import fr.inria.corese.core.next.query.api.exception.QueryTimeoutException;
import fr.inria.corese.core.next.query.api.result.Binding;
import fr.inria.corese.core.next.query.api.result.BindingSet;
import fr.inria.corese.core.next.query.api.result.GraphQueryResult;
import fr.inria.corese.core.next.query.api.result.TupleQueryResult;
import fr.inria.corese.core.next.query.impl.dataset.CoreseDataset;
import fr.inria.corese.core.next.storagemanager.impl.memory.MemoryStorageManager;
Expand Down Expand Up @@ -194,6 +196,65 @@ void selectWithDatasetFromReturnsNothingForEmptyNamedGraph() {
assertFalse(result.hasNext(), "No results expected for an empty named graph");
}

// -------------------------------------------------------------------------
// CONSTRUCT / graph evaluation
// -------------------------------------------------------------------------

@Test
@DisplayName("CONSTRUCT query materialises triples from WHERE bindings")
void constructQueryMaterialisesTriples() {
GraphQueryResult result = executor.evaluateGraph("""
CONSTRUCT { ?s <http://example.org/knows> ?o }
WHERE { ?s <http://example.org/knows> ?o }
""");

assertTrue(result.hasNext());
Statement stmt = result.next();
assertEquals(ALICE, stmt.getSubject().stringValue());
assertEquals(KNOWS, stmt.getPredicate().stringValue());
assertEquals(BOB, stmt.getObject().stringValue());
assertFalse(result.hasNext());
}

@Test
@DisplayName("CONSTRUCT with no matching WHERE returns empty graph result")
void constructWithNoMatchReturnsEmptyResult() {
GraphQueryResult result = executor.evaluateGraph("""
CONSTRUCT { ?s <http://example.org/knows> ?o }
WHERE { ?s <http://example.org/likes> ?o }
""");

assertFalse(result.hasNext());
}

@Test
@DisplayName("Graph evaluation rejects non-CONSTRUCT/DESCRIBE queries")
void graphEvaluationRejectsSelectQuery() {
assertThrows(
IllegalArgumentException.class,
() -> executor.evaluateGraph("SELECT * WHERE { ?s ?p ?o }"));
}

// -------------------------------------------------------------------------
// Initial bindings — literal and blank-node values
// -------------------------------------------------------------------------

@Test
@DisplayName("SELECT with literal initial binding filters results by literal value")
void selectWithLiteralInitialBindingFiltersResults() {
insert(iri(BOB), iri(NAME), valueFactory.createLiteral("Bob"));
insert(iri(ALICE), iri(NAME), valueFactory.createLiteral("Alice"));

BindingSet bindings = singleBinding("name", valueFactory.createLiteral("Bob"));
TupleQueryResult result = executor.evaluateTuple(
"SELECT ?s WHERE { ?s <" + NAME + "> ?name }",
bindings, null, 0L);

assertTrue(result.hasNext());
assertEquals(BOB, result.next().getValue("s").stringValue());
assertFalse(result.hasNext(), "Only the triple with literal 'Bob' should match");
}

// -------------------------------------------------------------------------
// Timeout
// -------------------------------------------------------------------------
Expand Down
Loading
Loading