diff --git a/jena-arq/src/main/java/org/apache/jena/http/sys/ExecHTTPBuilder.java b/jena-arq/src/main/java/org/apache/jena/http/sys/ExecHTTPBuilder.java index 9c3b032d44b..a7b3f6c4713 100644 --- a/jena-arq/src/main/java/org/apache/jena/http/sys/ExecHTTPBuilder.java +++ b/jena-arq/src/main/java/org/apache/jena/http/sys/ExecHTTPBuilder.java @@ -328,7 +328,7 @@ public X build() { throw new QueryException("Substitution only supported for Query objects. Failed to parse the given string as a Query object.", e); } } - queryActual = QueryTransformOps.transform(queryActual, substitutionMap); + queryActual = QueryTransformOps.replaceVars(queryActual, substitutionMap); queryStringActual = queryActual.toString(); } Context cxt = contextAcc.context(); diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/VarExprList.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/VarExprList.java index 185eb23f255..7a6f1d7f094 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/core/VarExprList.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/VarExprList.java @@ -158,6 +158,17 @@ public void remove(Var var) { exprs.remove(var); } + /** + * If the variable is already in the VarExprList, replace the expression. + * This retains the list order. + * Otherwise, add the variable and expression. + */ + public void update(Var var, Expr newExpr) { + exprs.put(var, newExpr); + if ( ! vars.contains(var) ) + vars.add(var); + } + public void clear() { vars.clear(); exprs.clear(); diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExecDatasetBuilder.java b/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExecDatasetBuilder.java index f028893669f..42e2dcf7f5f 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExecDatasetBuilder.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExecDatasetBuilder.java @@ -204,7 +204,7 @@ public QueryExec build() { String queryStringActual = queryString; if ( substitutionMap != null && ! substitutionMap.isEmpty() ) { - queryActual = QueryTransformOps.transform(query, substitutionMap); + queryActual = QueryTransformOps.replaceVars(query, substitutionMap); queryStringActual = null; } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/syntax/syntaxtransform/QueryScopeException.java b/jena-arq/src/main/java/org/apache/jena/sparql/syntax/syntaxtransform/QueryScopeException.java new file mode 100644 index 00000000000..a49547a7ba1 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/sparql/syntax/syntaxtransform/QueryScopeException.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.sparql.syntax.syntaxtransform; + +import org.apache.jena.query.QueryException; + +public class QueryScopeException extends QueryException { + public QueryScopeException(String msg) { super(msg) ; } + public QueryScopeException(String msg, Throwable cause) { super(msg, cause) ; } +} diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/syntax/syntaxtransform/QuerySyntaxSubstituteScope.java b/jena-arq/src/main/java/org/apache/jena/sparql/syntax/syntaxtransform/QuerySyntaxSubstituteScope.java new file mode 100644 index 00000000000..923396daab8 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/sparql/syntax/syntaxtransform/QuerySyntaxSubstituteScope.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.sparql.syntax.syntaxtransform; + +import java.util.Collection; + +import org.apache.jena.query.Query; +import org.apache.jena.sparql.core.Var; +import org.apache.jena.sparql.core.VarExprList; +import org.apache.jena.sparql.syntax.*; + +/** + * Variable usage check for {@link QueryTransformOps#transform}. + */ +public class QuerySyntaxSubstituteScope { + + /** + * Check that the query can be transformed by replacing the variables with values. + * For example, an assigned variables ({@code AS ?var}) can not be replace by a value. + */ + public static void scopeCheck(Query query, Collection vars) { + checkLevel(query, vars); + checkPattern(query.getQueryPattern(), vars); + } + + public static void checkPattern(Element element, Collection vars) { + ElementVisitor visitor = new SubstituteScopeVisitor(vars); + ElementWalker.walk(element, visitor); + } + + private static void checkLevel(Query query, Collection vars) { + checkAssignments("Query project expression", vars, query.getProject()); + checkAssignments("GROUP BY ", vars, query.getGroupBy()); + query.getAggregators().forEach(agg->{ + checkAssignment("Aggregator", vars, agg.getVar()); + }); + } + + private static void checkAssignments(String context, Collection vars, VarExprList varExprList) { + varExprList.forEachVarExpr((v,e)->{ + if ( e != null ) + checkAssignment(context, vars, v); + }); + } + + private static void checkAssignment(String context, Collection vars, Var assignedVar) { + if ( vars.contains(assignedVar) ) + reject("BIND", assignedVar); + } + + private static void reject(String elementName, Var badVar) { + throw new QueryScopeException("Can not use "+badVar+" in this query"); + } + + private static class SubstituteScopeVisitor extends ElementVisitorBase { + + private Collection vars; + + SubstituteScopeVisitor(Collection vars) { + this.vars = vars; + } + + // BOUND(?x) with no ?x in scope. +// @Override +// public void visit(ElementFilter el) { +// Set mentioned = el.getExpr().getVarsMentioned(); +// // EXISTS +// } + + @Override + public void visit(ElementAssign el) { + Var assignedVar = el.getVar(); + checkAssignment("LET", vars, assignedVar); + } + + @Override + public void visit(ElementBind el) { + Var assignedVar = el.getVar(); + checkAssignment("BIND", vars, assignedVar); + } + + @Override + public void visit(ElementData el) { + var assignedVars = el.getVars(); + assignedVars.forEach(v->checkAssignment("VALUES", vars, v)); + } + +// @Override +// public void visit(ElementExists el) { } +// +// @Override +// public void visit(ElementNotExists el) { } + + @Override + public void visit(ElementSubQuery el) { + // Check project + el.getQuery().getQueryPattern().visit(this); + } + } +} diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/syntax/syntaxtransform/QueryTransformOps.java b/jena-arq/src/main/java/org/apache/jena/sparql/syntax/syntaxtransform/QueryTransformOps.java index b4f987a3bf8..34081c0fae8 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/syntax/syntaxtransform/QueryTransformOps.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/syntax/syntaxtransform/QueryTransformOps.java @@ -18,6 +18,8 @@ package org.apache.jena.sparql.syntax.syntaxtransform; +import static org.apache.jena.sparql.syntax.syntaxtransform.QuerySyntaxSubstituteScope.scopeCheck; + import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -43,24 +45,73 @@ /** Support for transformation of query abstract syntax. */ public class QueryTransformOps { - /** Transform a query based on a mapping from {@link Var} variable to replacement {@link Node}. */ + + /** + * Replace variables in a query by RDF terms. + * The replacements are added to the return queries SELECT clause (if a SELECT query). + *

+ * @throws QueryScopeException if the query contains variables used in a + * way that does not allow substitution (.e.g {@code AS ?var} or used in + * {@code VALUES}). + */ + public static Query syntaxSubstitute(Query input, Map substitutions) { + scopeCheck(input, substitutions.keySet()); + Query output = transformTopLevel(input, substitutions); + return output; + } + + // Call transform, add in the substitutions as top-level SELECT expressions/ + private static Query transformTopLevel(Query query, Map substitutions) { + Query query2 = transformSubstitute(query, substitutions); + // Include substitutions + if ( query.isSelectType() ) { + query2.setQueryResultStar(false); + substitutions.forEach((v, n) -> { + var nv = NodeValue.makeNode(n); + query2.getProject().update(v, NodeValue.makeNode(n)); + }); + } + return query2; + } + + /** @deprecated Use {@link #queryReplaceVars} */ + @Deprecated public static Query transform(Query query, Map substitutions) { - ElementTransform eltrans = new ElementTransformSubst(substitutions); - NodeTransform nodeTransform = new NodeTransformSubst(substitutions); - ExprTransform exprTrans = new ExprTransformNodeElement(nodeTransform, eltrans); - return transform(query, eltrans, exprTrans); + return replaceVars(query, substitutions); + } + + /** Transform a query based on a mapping from {@link Var} variable to replacement {@link Node}. */ + public static Query replaceVars(Query query, Map substitutions) { + return transformSubstitute(query, substitutions); + } + + + /** @deprecated Use {@link #queryReplaceVars} */ + @Deprecated + public static Query transformQuery(Query query, Map substitutions) { + return queryReplaceVars(query, substitutions); } /** * Transform a query based on a mapping from variable name to replacement * {@link RDFNode} (a {@link Resource} (or blank node) or a {@link Literal}). */ - public static Query transformQuery(Query query, Map substitutions) { + public static Query queryReplaceVars(Query query, Map substitutions) { // Must have a different name because of Java's erasure of parameterised types. Map map = TransformElementLib.convert(substitutions); - return transform(query, map); + return replaceVars(query, map); + } + + private static Query transformSubstitute(Query query, Map substitutions) { + scopeCheck(query, substitutions.keySet()); + ElementTransform eltrans = new ElementTransformSubst(substitutions); + NodeTransform nodeTransform = new NodeTransformSubst(substitutions); + ExprTransform exprTrans = new ExprTransformNodeElement(nodeTransform, eltrans); + return transform(query, eltrans, exprTrans); } + // ---------------- + /** * Transform a query using {@link ElementTransform} and {@link ExprTransform}. * It is the responsibility of these transforms to transform to a legal SPARQL query. @@ -68,7 +119,6 @@ public static Query transformQuery(Query query, Map s public static Query transform(Query query, ElementTransform transform, ExprTransform exprTransform) { Query q2 = QueryTransformOps.shallowCopy(query); // Mutate the q2 structures which are already allocated and no other code can access yet. - mutateByQueryType(q2, exprTransform); mutateVarExprList(q2.getGroupBy(), exprTransform); mutateExprList(q2.getHavingExprs(), exprTransform); diff --git a/jena-arq/src/test/java/org/apache/jena/riot/system/TS_RiotSystem.java b/jena-arq/src/test/java/org/apache/jena/riot/system/TS_RiotSystem.java index b796647e77f..097202a8151 100644 --- a/jena-arq/src/test/java/org/apache/jena/riot/system/TS_RiotSystem.java +++ b/jena-arq/src/test/java/org/apache/jena/riot/system/TS_RiotSystem.java @@ -18,9 +18,6 @@ package org.apache.jena.riot.system; - - -// 2024-10 Using Junit5 can confuse Eclipse testing. // Test classes get missed import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/syntax/TS_Syntax.java b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/TS_Syntax.java index 91764e2432a..4394c0e0356 100644 --- a/jena-arq/src/test/java/org/apache/jena/sparql/syntax/TS_Syntax.java +++ b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/TS_Syntax.java @@ -18,19 +18,25 @@ package org.apache.jena.sparql.syntax; -import org.apache.jena.sparql.syntax.syntaxtransform.TestFlattenSyntax ; -import org.apache.jena.sparql.syntax.syntaxtransform.TestQueryOps ; -import org.apache.jena.sparql.syntax.syntaxtransform.TestQuerySyntaxTransform ; -import org.junit.runner.RunWith ; -import org.junit.runners.Suite ; -import org.junit.runners.Suite.SuiteClasses ; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; -@RunWith(Suite.class) -@SuiteClasses( { +import org.apache.jena.sparql.syntax.syntaxtransform.*; + +@Suite +@SelectClasses({ + +//import org.junit.runner.RunWith; +//import org.junit.runners.Suite; +//import org.junit.runners.Suite.SuiteClasses; +//@RunWith(Suite.class) +//@SuiteClasses({ TestQueryParser.class , TestSerialization.class - , TestQueryOps.class + , TestQueryShallowCopy.class + , TestQuerySubstituteScope.class , TestQuerySyntaxTransform.class + , TestQuerySyntaxSubstitute.class , TestFlattenSyntax.class }) diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/syntax/TestQueryParser.java b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/TestQueryParser.java index 8e25334fc21..4b93c8dcca5 100644 --- a/jena-arq/src/test/java/org/apache/jena/sparql/syntax/TestQueryParser.java +++ b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/TestQueryParser.java @@ -18,11 +18,14 @@ package org.apache.jena.sparql.syntax; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + import org.apache.jena.atlas.logging.LogCtl; import org.apache.jena.query.QueryFactory; import org.apache.jena.query.QueryParseException; import org.apache.jena.sparql.lang.QueryParserBase; -import org.junit.Test; import org.slf4j.Logger; /** @@ -49,34 +52,33 @@ private static void silent(Runnable action) { testParseIRIs(""); } - @Test(expected = QueryParseException.class) + @Test public void syntax_uri_brace_1() { - testParseIRIs(""); + assertThrows(QueryParseException.class, ()->testParseIRIs("")); } - @Test(expected = QueryParseException.class) - public void syntax_uri_brace_2() { - testParseIRIs(""); + @Test public void syntax_uri_brace_2() { + assertThrows(QueryParseException.class, ()->testParseIRIs("")); } - @Test(expected = QueryParseException.class) + @Test public void syntax_uri_space_1() { - testParseIRIs(""); + assertThrows(QueryParseException.class, ()->testParseIRIs("")); } - @Test(expected = QueryParseException.class) + @Test public void syntax_uri_space_2() { - testParseIRIs(""); + assertThrows(QueryParseException.class, ()->testParseIRIs("")); } - @Test(expected = QueryParseException.class) + @Test public void syntax_uri_space_3() { - testParseIRIs("< http://example/abc>"); + assertThrows(QueryParseException.class, ()->testParseIRIs("< http://example/abc>")); } - @Test(expected = QueryParseException.class) + @Test public void syntax_uri_space_4() { - testParseIRIs(""); + assertThrows(QueryParseException.class, ()->testParseIRIs("")); } // Test that a URI string can be used in Turtle data diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/syntax/TestSerialization.java b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/TestSerialization.java index 6efb1eaef46..ba3b0f1eba1 100644 --- a/jena-arq/src/test/java/org/apache/jena/sparql/syntax/TestSerialization.java +++ b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/TestSerialization.java @@ -18,7 +18,12 @@ package org.apache.jena.sparql.syntax; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; import org.apache.jena.query.Query ; import org.apache.jena.query.QueryFactory ; @@ -29,7 +34,6 @@ import org.apache.jena.sparql.algebra.OpAsQuery ; import org.apache.jena.sparql.sse.SSE ; import org.apache.jena.sparql.util.FmtUtils ; -import org.junit.Test ; public class TestSerialization { diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestFlattenSyntax.java b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestFlattenSyntax.java index cbf28f0afd1..204284b2dfb 100644 --- a/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestFlattenSyntax.java +++ b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestFlattenSyntax.java @@ -18,19 +18,21 @@ package org.apache.jena.sparql.syntax.syntaxtransform; -import static org.junit.Assert.assertEquals; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; import org.apache.jena.query.Query ; import org.apache.jena.query.QueryFactory ; import org.apache.jena.query.Syntax ; import org.apache.jena.sparql.algebra.Algebra ; import org.apache.jena.sparql.algebra.Op ; -import org.junit.Test ; public class TestFlattenSyntax { static String PRE = "PREFIX : \n" ; - - @Test public void test_flatten_basic_01() + + @Test public void test_flatten_basic_01() { test(":s0 :p :o .", null) ; } @Test public void test_flatten_basic_02() @@ -42,7 +44,7 @@ public class TestFlattenSyntax { @Test public void test_flatten_basic_04() { test("{{{ :s3 :p :o }}}", ":s3 :p :o") ; } - @Test public void test_flatten_filter_01() + @Test public void test_flatten_filter_01() { test(":s0 :p :o .{FILTER(?x)}", null) ; } @Test public void test_flatten_fileter_02() @@ -53,19 +55,19 @@ public class TestFlattenSyntax { @Test public void test_flatten_optional_01() { test("OPTIONAL{ ?s1 :q ?z }", null) ; } - + @Test public void test_flatten_optional_02() { test("OPTIONAL{{?s2 :q ?z}}", "OPTIONAL{?s2 :q ?z}") ; } - + @Test public void test_flatten_optional_03() { test("OPTIONAL{?s1f :q ?z FILTER(?z) }", null) ; } - + @Test public void test_flatten_optional_04() { test("OPTIONAL{{?S2 :q ?z FILTER(?z) }}", null); } - + @Test public void test_flatten_optional_05() { test("OPTIONAL{{{?S3 :q ?z FILTER(?z) }}}", "OPTIONAL{{?S3 :q ?z FILTER(?z) }}") ; } - + @Test public void test_flatten_optional_06() { test("OPTIONAL{?sx :q ?z {FILTER(?z)} }", null) ; } @@ -74,7 +76,7 @@ public class TestFlattenSyntax { @Test public void test_flatten_pattern_02() { test("{{?s :q ?z}} UNION {?s :q ?z }", "{?s :q ?z} UNION {?s :q ?z }") ; } - + @Test public void test_flatten_pattern_03() { test("{ ?s :q ?z} UNION {{?s :q ?z}}", "{?s :q ?z} UNION {?s :q ?z }") ; } @@ -92,26 +94,26 @@ public class TestFlattenSyntax { @Test public void test_flatten_arq_02() { test("EXISTS {{ :s :p :o }}", "EXISTS { :s :p :o }") ; } - + private static void test(String input, String expected) { if ( expected == null ) expected = input ; String qs = gen(PRE, input) ; String qsExpected = gen(PRE, expected) ; - + Query query = QueryFactory.create(qs, Syntax.syntaxARQ) ; Query query2 = QueryTransformOps.transform(query, new ElementTransformCleanGroupsOfOne()) ; Query queryExpected = QueryFactory.create(qsExpected, Syntax.syntaxARQ) ; - + Op op1 = Algebra.compile(query) ; Op op2 = Algebra.compile(query2) ; - assertEquals("Algebra different", op1, op2) ; - + assertEquals(op1, op2, "Algebra different") ; + boolean modified = ! query.equals(query2) ; boolean expectModification = !queryExpected.equals(query) ; - assertEquals("Expect query modifed?", expectModification, modified) ; + assertEquals(expectModification, modified, "Expect query modifed?") ; } - + private static String gen(String PRE, String string) { return PRE+"\nSELECT * { "+string+"\n}" ; } diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestQueryOps.java b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestQueryShallowCopy.java similarity index 94% rename from jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestQueryOps.java rename to jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestQueryShallowCopy.java index 0604b5fca7b..740a961313c 100644 --- a/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestQueryOps.java +++ b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestQueryShallowCopy.java @@ -18,13 +18,14 @@ package org.apache.jena.sparql.syntax.syntaxtransform; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; -import org.junit.Test ; import org.apache.jena.query.Query ; import org.apache.jena.query.QueryFactory ; -public class TestQueryOps +public class TestQueryShallowCopy { @Test public void queryOp_01() { testShallowCopy("SELECT * { }") ; } diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestQuerySubstituteScope.java b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestQuerySubstituteScope.java new file mode 100644 index 00000000000..55aa5183a11 --- /dev/null +++ b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestQuerySubstituteScope.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.sparql.syntax.syntaxtransform; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import org.apache.jena.query.QueryFactory; +import org.apache.jena.sparql.core.Var; + +/** + * Variable usage for {@link QueryTransformOps}. + */ +public class TestQuerySubstituteScope { + + private Var varX = Var.alloc("X"); + private Var varY = Var.alloc("Y"); + private Var varZ = Var.alloc("Z"); + + @Test public void valid_scope_01() { + testScopeRestriction("SELECT * {}"); + } + + @Test public void valid_scope_02() { + testScopeRestriction("SELECT * {}", varX); + } + + @Test public void valid_scope_03() { + testScopeRestriction("SELECT * { ?s ?p ?X}", varX); + } + + @Test public void valid_scope_04() { + testScopeRestriction("SELECT * { ?s ?p ?o BIND(123 AS ?n)}", varZ); + } + + @Test public void valid_scope_05() { + testScopeRestriction("SELECT * { VALUES ?z { 123 } }", varZ); + } + + @Test public void valid_scope_06() { + testScopeRestriction("SELECT * { VALUES ?z { 123 } } GROUP BY (str(?z) AS ?str)", varZ); + } + + @Test public void valid_scope_07() { + testScopeRestriction("SELECT (?z + 1 AS ?z2) { VALUES ?z { 123 } }", varX); + } + + @Test public void valid_scope_08() { + testScopeRestriction("SELECT ?X { ?s ?p ?o }", varX); + } + + @Test public void valid_scope_10() { + testScopeRestriction("SELECT * { ?s ?p ?o { SELECT ?X { ?a ?b ?c }}}", varX, varY, varZ); + } + + // subquery + + @Test public void invalid_scope_01() { + testScopeRestrictionBad("SELECT * { BIND(123 AS ?X) }", varX); + } + + @Test public void invalid_scope_02() { + testScopeRestrictionBad("SELECT (123 AS ?X) { ?s ?p ?o }", varX); + } + + @Test public void invalid_scope_03() { + testScopeRestrictionBad("SELECT * { VALUES ?X { 123 } ?s ?p ?o }", varX); + } + + @Test public void invalid_scope_04() { + testScopeRestrictionBad("SELECT * { ?s ?p ?o } GROUP BY (str(?p) AS ?X)", varX); + } + + @Test public void invalid_scope_05() { + testScopeRestrictionBad("SELECT (count(*) as ?Z) { ?s ?p ?o } GROUP BY ?s", varX, varY, varZ); + } + + @Test public void invalid_scope_06() { + testScopeRestriction("SELECT * { ?s ?p ?o { SELECT (?c +1 AS ?X) { ?a ?b ?c }}}", varX, varY, varZ); + } + + private void testScopeRestriction(String queryString, Var...vars ) { + var query = QueryFactory.create(queryString); + var varsList = Arrays.asList(vars); + QuerySyntaxSubstituteScope.scopeCheck(query, varsList); + } + + private void testScopeRestrictionBad(String queryString, Var...vars ) { + assertThrows(QueryScopeException.class, ()->testScopeRestriction(queryString, vars)); + } + +} diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestQuerySyntaxSubstitute.java b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestQuerySyntaxSubstitute.java new file mode 100644 index 00000000000..03af530ff96 --- /dev/null +++ b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestQuerySyntaxSubstitute.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.sparql.syntax.syntaxtransform; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.apache.jena.graph.Node; +import org.apache.jena.graph.NodeFactory; +import org.apache.jena.query.Query; +import org.apache.jena.query.QueryFactory; +import org.apache.jena.sparql.core.Var; + +public class TestQuerySyntaxSubstitute { + + private static Map substitutions1 = Map.of(Var.alloc("x"), NodeFactory.createURI("http://example/xxx")); + private static Map substitutions2 = Map.of(Var.alloc("x"), NodeFactory.createURI("http://example/xxx"), + Var.alloc("y"), NodeFactory.createURI("http://example/yyy")); + + @Test public void syntaxSubstitue_01() { + testSubstitue("SELECT * { ?x :p ?z }", substitutions1, + "SELECT ?z (:xxx AS ?x) { :xxx :p ?z }" + ); + } + + @Test public void syntaxSubstitue_02() { + testSubstitue("SELECT ?x { ?x :p ?z }", substitutions1, + "SELECT (:xxx AS ?x) { :xxx :p ?z }" + ); + } + + @Test public void syntaxSubstitue_03() { + testSubstitue("SELECT ?z { :a :p ?z }", substitutions1, + "SELECT ?z (:xxx AS ?x) { :a :p ?z }" + ); + } + + @Test public void syntaxSubstitue_04() { + testSubstitue("SELECT ?x ?z { ?x :p ?z }", substitutions1, + "SELECT (:xxx AS ?x) ?z { :xxx :p ?z }" + ); + } + + @Test public void syntaxSubstitue_10() { + testSubstitue("SELECT ?y ?x { ?x :p ?y }", substitutions2, + "SELECT (:yyy AS ?y) (:xxx AS ?x) { :xxx :p :yyy }" + ); + } + + @Test public void syntaxSubstitue_11() { + testSubstitue("SELECT ?y ?p ?x { ?x ?p ?y }", substitutions2, + "SELECT (:yyy AS ?y) ?p (:xxx AS ?x) { :xxx ?p :yyy }" + ); + } + + // GH-2799: Sub-queries not yet ready. +// // Sub-query visible variable. +// @Test public void syntaxSubstitue_12() { +// testSubstitue("SELECT * { ?s ?p ?o { SELECT ?x { ?x :p ?y } } }", substitutions1, +// "SELECT (:yyy AS ?y) ?p (:xxx AS ?x) { ?s ?p ?o { SELECT * { :xxx :p ?y } }}" +// ); +// } +// +// // Sub-query hidden variable. +// @Test public void syntaxSubstitue_13() { +// testSubstitue("SELECT * { ?s ?p ?o { SELECT ?y { ?x :p ?y } } }", substitutions1, +// "SELECT ?s ?p ?o (:xxx AS ?x) { ?s ?p ?o { SELECT * { :xxx :p ?y } }}" +// ); +// } +// +// // Multi-level variable. +// @Test public void syntaxSubstitue_14() { +// testSubstitue("SELECT * { ?x ?p ?o { SELECT * { ?x :p ?y } } }", substitutions2, +// "" //"SELECT (:yyy AS ?y) ?p (:xxx AS ?x) { ?s ?p ?o { SELECT * { :xxx :p ?y } }}" +// ); +// } + + @Test public void syntaxSubstitue_50() { + assertThrows(QueryScopeException.class, ()-> + testSubstitue("SELECT (456 AS ?x) { ?y :p ?z }", substitutions1, + "" + )); + } + + @Test public void syntaxSubstitue_51() { + assertThrows(QueryScopeException.class, ()-> + testSubstitue("SELECT * { ?y :p ?z BIND(789 AS ?x)}", substitutions1, + "" + )); + } + + private void testSubstitue(String qs, Map substitutions, String outcome) { + String prologue = "PREFIX : "; + String queryString = prologue+qs; + Query query = QueryFactory.create(queryString); + Query query2 = QueryTransformOps.syntaxSubstitute(query, substitutions); + Query queryOutcome = QueryFactory.create(prologue+outcome); + assertEquals(queryOutcome, query2); + } +} diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestSyntaxTransform.java b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestQuerySyntaxTransform.java similarity index 84% rename from jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestSyntaxTransform.java rename to jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestQuerySyntaxTransform.java index 510692c3401..7c7e4cd8c2e 100644 --- a/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestSyntaxTransform.java +++ b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/syntaxtransform/TestQuerySyntaxTransform.java @@ -18,11 +18,13 @@ package org.apache.jena.sparql.syntax.syntaxtransform; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.HashMap; import java.util.Map; +import org.junit.jupiter.api.Test; + import org.apache.jena.graph.Node; import org.apache.jena.query.Query; import org.apache.jena.query.QueryFactory; @@ -32,153 +34,152 @@ import org.apache.jena.sparql.util.ModelUtils; import org.apache.jena.update.UpdateFactory; import org.apache.jena.update.UpdateRequest; -import org.junit.Test; /** Test of variable replaced by value */ public class TestQuerySyntaxTransform { - @Test public void subst_query_01() { + @Test public void transformTransformReplace_01() { testQuery("SELECT * { }", "SELECT * {}", "o", "1"); } - @Test public void subst_query_02() { + @Test public void transformTransformReplace_02() { testQuery("SELECT ?x { }", "SELECT ?x {}", "o", "1"); } - @Test public void subst_query_03() { + @Test public void transformTransformReplace_03() { testQuery("SELECT ?o { }", "SELECT (1 as ?o) {}", "o", "1"); } - @Test public void subst_query_04() { + @Test public void transformTransformReplace_04() { testQuery("SELECT (?o AS ?z) { }", "SELECT (1 AS ?z) {}", "o", "1"); } - @Test public void subst_query_05() { + @Test public void transformTransformReplace_05() { testQuery("SELECT (?o+2 AS ?z) { }", "SELECT (1+2 AS ?z) {}", "o", "1"); } - @Test public void subst_query_09() { + @Test public void transformTransformReplace_09() { testQuery("SELECT * {?s ?p ?o}", "SELECT * {?s ?p 1}", "o", "1"); } - @Test public void subst_query_10() { + @Test public void transformTransformReplace_10() { testQuery("SELECT * { SELECT ?o {} }", "SELECT * { SELECT (1 as ?o) {}}", "o", "1"); } - @Test public void subst_query_11() { + @Test public void transformTransformReplace_11() { testQuery("SELECT * { ?s ?p ?o { SELECT ?x { ?x ?p ?o } } }", "SELECT * { ?s ?p 1 { SELECT ?x { ?x ?p 1 } } }", "o", "1"); } - @Test public void subst_query_20() { + @Test public void transformTransformReplace_20() { testQuery("SELECT * { ?s ?p ?g GRAPH ?g { ?s ?p ?g } }", "SELECT * { ?s ?p GRAPH { ?s ?p } }", "g", ""); } - @Test public void subst_query_21() { + @Test public void transformTransformReplace_21() { testQuery("SELECT * { ?s ?p ?srv SERVICE ?srv { ?s ?p ?srv}}", "SELECT * { ?s ?p SERVICE { ?s ?p }}", "srv", ""); } - @Test public void subst_query_30() { + @Test public void transformTransformReplace_30() { testQuery("SELECT * { ?s ?p ?o } ORDER BY ?s", "SELECT * { ?p ?o } ORDER BY ()", "s", ""); } // GH-2650 - @Test public void subst_query_31() { + @Test public void transformTransformReplace_31() { testQuery("PREFIX : SELECT (SUM(?a + ?b) AS ?c) WHERE { ?s :p ?a }", "PREFIX : SELECT (SUM(123 + ?b) AS ?c) WHERE { ?s :p 123 }", "a", "123"); } // GH-2650 - @Test public void subst_query_32() { + @Test public void transformTransformReplace_32() { testQuery("PREFIX : SELECT (SUM(?a + ?b) AS ?c) WHERE { }", "PREFIX : SELECT (SUM(123 + ?b) AS ?c) WHERE { }", "a", "123"); } // GH-2650 - @Test public void subst_query_33() { + @Test public void transformTransformReplace_33() { testQuery("SELECT * WHERE { ?s ?p ?o { SELECT (count(?a) as ?C) WHERE {} } }", "SELECT * WHERE { ?s ?p ?o { SELECT (count(123) as ?C) WHERE {} } }", "a", "123"); } // Same except use the Model API. - @Test public void subst_query_model_2() { + @Test public void transformTransformReplace_model_2() { testQueryModel("SELECT * { ?s ?p ?o } ORDER BY ?s", "SELECT * { ?p ?o } ORDER BY ()", "s", ""); } - @Test public void subst_query_40() { + @Test public void transformTransformReplace_40() { testQueryModel("CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }", "CONSTRUCT { ?p ?o } WHERE { ?p ?o }", "s", ""); } - @Test public void subst_query_41() { + @Test public void transformTransformReplace_41() { testQueryModel("CONSTRUCTWHERE { ?s ?p ?o }", "CONSTRUCT { ?p ?o } WHERE { ?p ?o }", "s", ""); } - @Test public void subst_query_42() { + @Test public void transformTransformReplace_42() { testQueryModel("CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }", "CONSTRUCT { ?p 57 } WHERE { ?p 57 }", "s", "", "o", "57"); } - @Test public void subst_query_43() { + @Test public void transformTransformReplace_43() { testQueryModel("CONSTRUCT { GRAPH ?g {?s ?p ?o } } WHERE { GRAPH ?g {?s ?p ?o } }", "CONSTRUCT { GRAPH { ?p ?o } } WHERE { GRAPH { ?p ?o } }", "s", "", "g", ""); } - @Test public void subst_query_44() { + @Test public void transformTransformReplace_44() { testQueryModel("CONSTRUCTWHERE { GRAPH ?g {?s ?p ?o } }", "CONSTRUCT { GRAPH { ?p ?o } } WHERE { GRAPH { ?p ?o } }", "s", "", "g", ""); } - @Test public void subst_update_01() { + @Test public void transformSubstituteupdate_01() { testUpdate("DELETE { ?s ?x } WHERE {}", "DELETE { ?s } WHERE {}", "x", ""); } - @Test public void subst_update_02() { + @Test public void transformSubstituteupdate_02() { testUpdate("DELETE { ?s ?x } WHERE { ?s ?x }", "DELETE { ?s } WHERE { ?s }", "x", ""); } - @Test public void subst_update_03() { + @Test public void transformSubstituteupdate_03() { testUpdate("DELETE { ?s ?x } INSERT { ?s ?x } WHERE { ?s ?x }", "DELETE { ?s } INSERT { ?s } WHERE { ?s }", "x", ""); } - @Test public void subst_update_09() { + @Test public void transformSubstituteupdate_09() { testUpdate("DELETE WHERE { ?s ?x }", "DELETE WHERE { ?s }", "x", ""); } - @Test public void subst_update_10() { + @Test public void transformSubstituteupdate_10() { testUpdateModel("DELETE WHERE { ?s ?x }", "DELETE WHERE { ?s }", "x", ""); @@ -195,7 +196,7 @@ private void testQuery(String input, String output, String varStr, String valStr Map map = new HashMap<>(); map.put(Var.alloc(varStr), SSE.parseNode(valStr)); - Query qTrans = QueryTransformOps.transform(q1, map); + Query qTrans = QueryTransformOps.replaceVars(q1, map); if ( ! qExpected.equals(qTrans) ) { System.out.println(qExpected.getProject()); @@ -212,7 +213,7 @@ private void testQueryModel(String input, String output, String varStr, String v Query qExpected = QueryFactory.create(PREFIX+output); Map map = Map.of(varStr, fromString(valStr)); - Query qTrans = QueryTransformOps.transformQuery(q1, map) ; + Query qTrans = QueryTransformOps.queryReplaceVars(q1, map) ; assertEquals(qExpected, qTrans) ; } @@ -222,7 +223,7 @@ private void testQueryModel(String input, String output, String varStr1, String Map map = Map.of(varStr1, fromString(valStr1), varStr2, fromString(valStr2)); - Query qTrans = QueryTransformOps.transformQuery(q1, map) ; + Query qTrans = QueryTransformOps.queryReplaceVars(q1, map) ; assertEquals(qExpected, qTrans) ; } diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/SparqlValidation.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/SparqlValidation.java index 47f3dc4bc02..6f4a725010a 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/SparqlValidation.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/SparqlValidation.java @@ -115,7 +115,7 @@ private static boolean validateMap(ValidationContext vCxt, Graph data, Shape sha Map substitutions = parameterMapToSyntaxSubstitutions(parameterMap, focusNode, path); if ( query.isAskType() ) addSubstition(substitutions, "value", valueNode); - Query query2 = QueryTransformOps.transform(query, substitutions); + Query query2 = QueryTransformOps.replaceVars(query, substitutions); qExec = QueryExecutionFactory.create(query2, model); } else { // Done with pre-binding. diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/validation/EvalSparql.java b/jena-shacl/src/main/java/org/apache/jena/shacl/validation/EvalSparql.java index 26375f60275..67e6302d562 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/validation/EvalSparql.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/validation/EvalSparql.java @@ -62,7 +62,7 @@ public static Collection evalSparqlComponent(Graph data, Node node, Sparql // Done with QueryTransformOps.transform DatasetGraph dsg = DatasetGraphFactory.wrap(data); Map substitutions = parametersToSyntaxSubstitutions(data, node, sparqlComponent.getParams()); - Query query2 = QueryTransformOps.transform(query, substitutions); + Query query2 = QueryTransformOps.replaceVars(query, substitutions); try ( QueryExecution qExec = QueryExecutionFactory.create(query2, dsg)) { return evalSparqlOneVar(qExec); }