44import fr .inria .corese .core .next .impl .parser .antlr .SparqlParser ;
55import fr .inria .corese .core .next .query .api .exception .QueryEvaluationException ;
66import fr .inria .corese .core .next .query .api .exception .QuerySyntaxException ;
7+ import fr .inria .corese .core .next .query .api .exception .QueryValidationException ;
78import fr .inria .corese .core .next .query .impl .sparql .ast .*;
89import fr .inria .corese .core .next .query .impl .sparql .ast .constraint .*;
910import org .antlr .v4 .runtime .tree .ParseTree ;
1011import org .antlr .v4 .runtime .tree .TerminalNode ;
1112
12-
1313import java .util .*;
1414
1515/**
@@ -112,6 +112,11 @@ public final class SparqlAstBuilder {
112112 */
113113 private ConstructTemplateAst constructTemplate ;
114114
115+ /**
116+ * Helper used to compute visible and referenced variables.
117+ */
118+ private final VariableScopeAnalyzer variableScopeAnalyzer = new VariableScopeAnalyzer ();
119+
115120 public SparqlAstBuilder (SparqlParserOptions options ) {
116121 this .options = options ;
117122 }
@@ -328,14 +333,10 @@ public QueryAst getResult() {
328333 }
329334 DatasetClauseAst datasetClauseAst = new DatasetClauseAst (datasetDefaultGraphs , datasetNamedGraphs );
330335 return switch (this .queryType ) {
331- case DESCRIBE -> new DescribeQueryAst (datasetClauseAst , describeResources , whereClause );
332- case CONSTRUCT -> new ConstructQueryAst (
333- constructTemplate != null ? constructTemplate : new ConstructTemplateAst (List .of ()),
334- datasetClauseAst ,
335- whereClause ,
336- buildSolutionModifier ());
337- case ASK -> new AskQueryAst (datasetClauseAst , whereClause );
338- case SELECT -> new SelectQueryAst (projection , datasetClauseAst , whereClause , buildSolutionModifier ());
336+ case ASK -> buildAskQueryAst (datasetClauseAst );
337+ case CONSTRUCT -> buildConstructQueryAst (datasetClauseAst );
338+ case DESCRIBE -> buildDescribeQueryAst (datasetClauseAst );
339+ case SELECT -> buildSelectQueryAst (datasetClauseAst );
339340 case UNDEFINED -> throw new QueryEvaluationException ("Could not determine the type of query during parsing" );
340341 };
341342 }
@@ -366,6 +367,116 @@ private void ensureNoOpenBgp() {
366367 }
367368 }
368369
370+ /**
371+ * Builds the AST for ASK queries.
372+ */
373+ private AskQueryAst buildAskQueryAst (DatasetClauseAst datasetClauseAst ) {
374+ return new AskQueryAst (datasetClauseAst , whereClause );
375+ }
376+
377+ /**
378+ * Builds the AST for SELECT queries.
379+ */
380+ private SelectQueryAst buildSelectQueryAst (DatasetClauseAst datasetClauseAst ) {
381+ validateSelectQueryScope ();
382+ return new SelectQueryAst (projection , datasetClauseAst , whereClause , buildSolutionModifier ());
383+ }
384+
385+ /**
386+ * Builds the AST for DESCRIBE queries.
387+ */
388+ private DescribeQueryAst buildDescribeQueryAst (DatasetClauseAst datasetClauseAst ) {
389+ // TODO #306: validate variable scope for DESCRIBE modifiers when DescribeQueryAst carries them.
390+ return new DescribeQueryAst (datasetClauseAst , describeResources , whereClause );
391+ }
392+
393+ /**
394+ * Builds the AST for CONSTRUCT queries.
395+ */
396+ private ConstructQueryAst buildConstructQueryAst (DatasetClauseAst datasetClauseAst ) {
397+ // TODO #306: validate variable scope for CONSTRUCT modifiers when ConstructQueryAst carries them.
398+ return new ConstructQueryAst (
399+ constructTemplate != null ? constructTemplate : new ConstructTemplateAst (List .of ()),
400+ datasetClauseAst ,
401+ whereClause ,
402+ buildSolutionModifier ());
403+ }
404+
405+ /**
406+ * Validates SELECT projection and ORDER BY variables against the WHERE clause scope.
407+ */
408+ private void validateSelectQueryScope () {
409+ // TODO #306: extend this validation to GROUP BY when it is supported by the next parser.
410+ Set <String > visibleVariables = variableScopeAnalyzer .collectVisibleVariables (whereClause );
411+
412+ // SELECT * still needs ORDER BY validation.
413+ if (!projection .selectAll ()) {
414+ validateProjectionVariables (visibleVariables );
415+ }
416+
417+ validateOrderVariables (collectOrderByAvailableVariables (visibleVariables ));
418+ }
419+
420+ /**
421+ * Validates explicit projection variables against the WHERE clause scope.
422+ *
423+ * @param visibleVariables variable names visible from the WHERE clause
424+ */
425+ private void validateProjectionVariables (Set <String > visibleVariables ) {
426+ for (VarAst projectedVar : projection .variables ()) {
427+ if (!visibleVariables .contains (projectedVar .name ())) {
428+ throw new QueryValidationException (buildOutOfScopeVariableMessage (
429+ projectedVar .name (),
430+ "SELECT projection" ));
431+ }
432+ }
433+ }
434+
435+ /**
436+ * Collects variables that may be referenced from ORDER BY.
437+ *
438+ * <p>SPARQL applies ORDER BY before the final projection step. In the current next parser,
439+ * explicit projection variables are already validated against the WHERE clause, so adding
440+ * them here mainly keeps the availability rule explicit while staying within the current
441+ * SPARQL 1.0 feature set.
442+ *
443+ * @param visibleVariables variable names visible from the WHERE clause
444+ * @return variable names available to ORDER BY validation
445+ */
446+ private Set <String > collectOrderByAvailableVariables (Set <String > visibleVariables ) {
447+ Set <String > availableVariables = new LinkedHashSet <>(visibleVariables );
448+ if (!projection .selectAll ()) {
449+ for (VarAst projectedVar : projection .variables ()) {
450+ availableVariables .add (projectedVar .name ());
451+ }
452+ }
453+ return availableVariables ;
454+ }
455+
456+ /**
457+ * Validates ORDER BY variables against the variables available at ORDER BY time.
458+ *
459+ * @param availableOrderVariables variable names available to ORDER BY
460+ */
461+ private void validateOrderVariables (Set <String > availableOrderVariables ) {
462+ for (OrderConditionAst orderCondition : orderConditions ) {
463+ Set <String > referencedVariables = variableScopeAnalyzer
464+ .collectReferencedVariables (orderCondition .expression ());
465+
466+ for (String variableName : referencedVariables ) {
467+ if (!availableOrderVariables .contains (variableName )) {
468+ throw new QueryValidationException (buildOutOfScopeVariableMessage (
469+ variableName ,
470+ "ORDER BY" ));
471+ }
472+ }
473+ }
474+ }
475+
476+ private String buildOutOfScopeVariableMessage (String variableName , String clause ) {
477+ return "Variable ?" + variableName + " used in " + clause + " is not visible in WHERE clause" ;
478+ }
479+
369480 /**
370481 * Signals the start of a {@code GroupOrUnionGraphPattern}.
371482 */
@@ -1021,7 +1132,6 @@ public TermAst termFromRegex(SparqlParser.RegexExpressionContext ctx) {
10211132 throw new QueryEvaluationException ("Unexpected arguments for REGEX call" );
10221133 }
10231134 }
1024-
10251135 /**
10261136 * Predicate as a property path.
10271137 * For simple triples without a composed path, this is just an iriRef or 'a'.
@@ -1054,4 +1164,4 @@ public List<TermAst> termListFromObjectListPath(SparqlParser.ObjectListPathConte
10541164 }
10551165 return out ;
10561166 }
1057- }
1167+ }
0 commit comments