Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ public AstBackedExpr(Expression delegate) {
this(delegate, Optional.empty());
}

public AstBackedExpr(Expression delegate, Optional<TermAst> sourceAst) {
this.delegate = Objects.requireNonNull(delegate);
this.sourceAst = sourceAst == null ? Optional.empty() : sourceAst;
this.filterView = new NextFilterFromAst(this);
}
public AstBackedExpr(Expression delegate, Optional<TermAst> sourceAst) {
this.delegate = Objects.requireNonNull(delegate, "delegate");
this.sourceAst = Objects.requireNonNull(sourceAst, "sourceAst");
this.filterView = new NextFilterFromAst(this);
}

/**
* The underlying SPARQL interpreter {@link Expression} (triple.parser), for metadata and {@link Filter#getFilterExpression()}.
Expand Down Expand Up @@ -300,8 +300,8 @@ public IDatatype evalWE(Evaluator eval, BindingContext b, Environment env, Produ
}

private static Binding bindingFrom(BindingContext b) {
if (b instanceof BindingAdapter ba) {
return ba.delegate();
if (b instanceof BindingAdapter(Binding delegate1)) {
return delegate1;
}
if (b instanceof Binding binding) {
return binding;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import fr.inria.corese.core.next.query.impl.sparql.ast.ASTConstants;
import fr.inria.corese.core.next.query.impl.sparql.ast.ConstraintAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.DatasetClauseAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.DescribeQueryAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.FilterAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.GroupGraphPatternAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.IriAst;
Expand Down Expand Up @@ -32,8 +33,8 @@
* Builds KGRAM {@code Exp} / {@code Query} structures from Corese-next query AST nodes.
*
* <p>This bridge sits between the parsed SPARQL AST and the KGRAM runtime query model:
* it does not execute queries, it translates syntax-level query forms ({@code ASK}, {@code SELECT})
* into runtime-ready {@link Query}/{@link Exp} structures.</p>
* it does not execute queries, it translates syntax-level query forms ({@code ASK}, {@code SELECT},
* {@code DESCRIBE}) into runtime-ready {@link Query}/{@link Exp} structures.</p>
*/
public final class CoreseAstQueryBuilder {

Expand Down Expand Up @@ -100,6 +101,30 @@ public Query toNextQuery(SelectQueryAst selectQueryAst) {
return query;
}

/**
* Builds a KGRAM {@link Query} from a {@link DescribeQueryAst}.
*
* <p>Reuses the shared query shell (compiled {@code WHERE} body, dataset, LIMIT/OFFSET) and
* {@code ORDER BY} like the other forms, then lowers {@code DESCRIBE} to the construct-like
* shape expected by the current KGRAM runtime. A described variable reuses its runtime node,
* a described IRI becomes a fresh constant node, and {@code DESCRIBE *} reuses the in-scope
* nodes of the body, matching {@code SELECT *}. Clauses that require dedicated aggregate or
* values handling are rejected explicitly.</p>
*/
public Query toNextQuery(DescribeQueryAst describeQueryAst) {
Objects.requireNonNull(describeQueryAst, "describeQueryAst");
rejectUnsupportedDescribeClauses(describeQueryAst);

Query query = createQuery(
describeQueryAst.whereClause(),
describeQueryAst.datasetClause(),
describeQueryAst.solutionModifier());
applyOrderBy(query, describeQueryAst.solutionModifier());
List<Node> describedNodes = describeNodes(query, describeQueryAst);
lowerDescribeToConstructQuery(query, describedNodes);
return query;
}

/**
* Converts a SPARQL {@code FILTER} clause by converting {@link FilterAst#operator()} the same way as
* {@link #toNextFilter(TermAst)}.
Expand Down Expand Up @@ -165,13 +190,11 @@ private static void rejectUnsupportedSelectClauses(SelectQueryAst selectQueryAst
if (!selectQueryAst.valuesClause().mappings().isEmpty()) {
throw new UnsupportedOperationException("VALUES is not supported yet when building a next Query");
}

ProjectionAst projection = selectQueryAst.projection();
if (!projection.expressionTerms().isEmpty() || !projection.expressionBoundVariables().isEmpty()) {
throw new UnsupportedOperationException(
"SELECT expressions are not supported yet when building a next Query");
}

SolutionModifierAst solutionModifier = selectQueryAst.solutionModifier();
if (solutionModifier.reduced()) {
throw new UnsupportedOperationException("REDUCED is not supported yet when building a next Query");
Expand All @@ -188,6 +211,21 @@ private static void rejectUnsupportedSelectClauses(SelectQueryAst selectQueryAst
// TODO(#387): REDUCED support should be aligned with the final next-pipeline query-form policy.
}

private static void rejectUnsupportedDescribeClauses(DescribeQueryAst describeQueryAst) {
if (!describeQueryAst.valuesClause().mappings().isEmpty()) {
throw new UnsupportedOperationException(
"Inline VALUES is not supported yet for DESCRIBE (values handling is a follow-up)");
}
SolutionModifierAst mod = describeQueryAst.solutionModifier();
if (mod.hasGroupBy() || mod.hasHaving() || mod.distinct() || mod.reduced()) {
throw new UnsupportedOperationException(
"Solution modifiers (GROUP BY, HAVING, DISTINCT, REDUCED) are not supported for DESCRIBE");
}
// TODO(#390): inline VALUES needs a dedicated runtime mapping.
// TODO(#390): GROUP BY / HAVING would require aggregate-aware DESCRIBE semantics, not just field copying.
// TODO(#390): REDUCED support should be aligned with the final query-form policy for the next pipeline.
}

/**
* Creates the runtime {@link Query} shell shared by every query form handled here.
*
Expand All @@ -200,7 +238,7 @@ private Query createQuery(
DatasetClauseAst datasetClause,
SolutionModifierAst solutionModifier) {
Query query = Query.create(whereCompiler.compile(whereClause));
// Collect visible nodes once so later clauses (projection, ORDER BY, GROUP BY)
// Collect visible nodes once so later clauses (projection, ORDER BY, DESCRIBE)
// can resolve variables against the compiled runtime body.
query.collect();
applyDataset(query, datasetClause);
Expand Down Expand Up @@ -243,11 +281,81 @@ private void applyProjection(Query query, ProjectionAst projection) {
selectExpressions.add(Exp.create(Type.NODE, node));
}
}

query.setSelectFun(selectExpressions);
query.setSelect(selectNodeList(selectExpressions));
}

/**
* Resolves the resources of a {@code DESCRIBE} against the compiled body.
*
* <p>{@code DESCRIBE *} reuses the in-scope nodes of the body (like {@code SELECT *}).
* A described variable reuses its runtime node (so it is the one bound by the body) and
* fails fast when it is not visible; a described IRI becomes a fresh constant node.</p>
*/
private List<Node> describeNodes(Query query, DescribeQueryAst describeQueryAst) {
if (describeQueryAst.isDescribeAll()) {
return query.selectNodesFromPattern();
}
List<Node> nodes = new ArrayList<>();
for (TermAst term : describeQueryAst.described()) {
if (term instanceof VarAst(String name)) {
Node node = query.getExtNode(name);
if (node == null) {
throw new IllegalArgumentException(
"DESCRIBE variable ?" + name + " is not visible in the compiled query body");
}
nodes.add(node);
} else {
nodes.add(toNode(term));
}
}
return nodes;
}

/**
* Lowers {@code DESCRIBE} to the construct-like shape expected by the current
* KGRAM-next runtime.
*
* <p>This keeps the current KGRAM contract, inherited from the historical pipeline:
* {@code DESCRIBE} is executed through the construct runtime path.</p>
*/
private void lowerDescribeToConstructQuery(Query query, List<Node> describedNodes) {
Exp constructTemplate = Exp.create(Type.BGP);
int syntheticIndex = 0;
for (Node describedNode : describedNodes) {
DescribePattern describePattern = describePattern(describedNode, syntheticIndex++);
// KGRAM-next currently represents DESCRIBE with outgoing and incoming construct triples.
constructTemplate.add(describePattern.outgoing().getEdge());
constructTemplate.add(describePattern.incoming().getEdge());
query.getBody().add(Exp.create(Type.OPTIONAL, Exp.create(Type.AND), describePattern.optionalBody()));
}
query.setConstruct(constructTemplate);
query.setConstruct(true);
query.setConstructNodes(constructTemplate.getNodes());
}

private DescribePattern describePattern(Node describedNode, int index) {
Node outgoingPredicate = createSyntheticDescribeNode("p", index, 0);
Node outgoingValue = createSyntheticDescribeNode("v", index, 0);
Node incomingPredicate = createSyntheticDescribeNode("p", index, 1);
Node incomingValue = createSyntheticDescribeNode("v", index, 1);

Exp outgoing = Exp.create(Type.EDGE, new AstBackedEdge(describedNode, outgoingPredicate, outgoingValue));
Exp incoming = Exp.create(Type.EDGE, new AstBackedEdge(incomingValue, incomingPredicate, describedNode));
Exp outgoingBgp = Exp.create(Type.BGP);
outgoingBgp.add(outgoing);
Exp incomingBgp = Exp.create(Type.BGP);
incomingBgp.add(incoming);
return new DescribePattern(outgoing, incoming, Exp.create(Type.UNION, outgoingBgp, incomingBgp));
}

private Node createSyntheticDescribeNode(String role, int describedIndex, int directionIndex) {
return new NodeImpl(Variable.create("__describe_" + role + "_" + describedIndex + "_" + directionIndex));
}

private record DescribePattern(Exp outgoing, Exp incoming, Exp optionalBody) {
}

/**
* Maps {@code ORDER BY} conditions that are already expressible in runtime KGRAM terms.
*
Expand All @@ -258,7 +366,6 @@ private void applyOrderBy(Query query, SolutionModifierAst solutionModifier) {
if (!solutionModifier.hasOrderBy()) {
return;
}

List<Exp> orderByExpressions = new ArrayList<>();
int syntheticIndex = 0;
for (OrderConditionAst orderCondition : solutionModifier.orderBy()) {
Expand All @@ -282,7 +389,6 @@ private Exp toOrderByExpression(Query query, OrderConditionAst orderCondition, i
}
return Exp.create(Type.NODE, node);
}

Filter filter = SparqlAstToExpression.toNextFilter(expression);
Exp exp = Exp.create(Type.NODE, createSyntheticOrderNode(syntheticIndex));
exp.setFilter(filter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,12 @@
/**
* Adapts a runtime {@link IDatatype} to the Corese-next {@link DatatypeValue} API.
*/
public final class NextDatatypeValueAdapter implements DatatypeValue {

private final IDatatype delegate;

public NextDatatypeValueAdapter(IDatatype delegate) {
this.delegate = delegate;
}
public record NextDatatypeValueAdapter(IDatatype delegate) implements DatatypeValue {

public static DatatypeValue ofNullable(IDatatype dt) {
return dt == null ? null : new NextDatatypeValueAdapter(dt);
}

public IDatatype delegate() {
return delegate;
}

@Override
public String getLabel() {
return delegate.getLabel();
Expand Down Expand Up @@ -90,8 +80,8 @@ public boolean isBlank() {
}

private static IDatatype unwrap(DatatypeValue other) throws CoreseDatatypeException {
if (other instanceof NextDatatypeValueAdapter a) {
return a.delegate;
if (other instanceof NextDatatypeValueAdapter(IDatatype delegate1)) {
return delegate1;
}
if (other instanceof IDatatype id) {
return id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ private SparqlBuiltinFunctionNameResolver() {
* Resolves the function name from a term that must be an {@link IriAst}.
*/
public static String fromFunctionTerm(TermAst functionName) {
if (!(functionName instanceof IriAst i)) {
if (!(functionName instanceof IriAst(String raw))) {
throw new IllegalArgumentException("Function name must be IriAst, got " + functionName);
}
return localNameFromIriToken(i.raw());
return localNameFromIriToken(raw);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public static DQPFactory getFactory() {
//selectWithExp,
orderBy, groupBy;
List<Filter> failure, pathFilter, funList;

List<String> errors, info;
Exp having, construct, delete;
// gNode is a local graph node when subquery has no ?g in its select
Expand Down Expand Up @@ -822,6 +823,14 @@ public Exp getInsert() {
return construct;
}

public List<Node> getConstructNodes() {
return constructNodes;
}

public void setConstructNodes(List<Node> constructNodes) {
this.constructNodes = constructNodes;
}

public void setDelete(Exp c) {
delete = c;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ void rejectsValuesClause() {
}

@Test
@DisplayName("toNextQuery(null) throws NullPointerException")
@DisplayName("toNextQuery((AskQueryAst) null) throws NullPointerException")
void rejectsNullAsk() {
assertThrows(NullPointerException.class, () -> builder.toNextQuery((AskQueryAst) null));
}
Expand Down
Loading
Loading