From 10a136096c69adfb42597c42d1302980d8104e48 Mon Sep 17 00:00:00 2001 From: Claus Stadler Date: Sun, 8 Dec 2024 00:44:36 +0100 Subject: [PATCH] GH-2883: Fix for an NPE in OpAsQuery. --- .../apache/jena/sparql/algebra/OpAsQuery.java | 19 ++++ .../ExprTransformApplyElementTransform.java | 11 ++- .../syntaxtransform/QueryTransformOps.java | 9 +- .../jena/sparql/algebra/TestOpAsQuery.java | 90 ++++++++++++++++++- 4 files changed, 122 insertions(+), 7 deletions(-) diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/OpAsQuery.java b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/OpAsQuery.java index 594139040cf..9be1eb36ab2 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/OpAsQuery.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/OpAsQuery.java @@ -81,6 +81,25 @@ public static Query asQuery(Op op) { return converter.convert() ; } + public static Element asElement(Op op) { + Query query = asQuery(op); + Element elt = isPrimitiveQuery(query) + ? query.getQueryPattern() + : new ElementSubQuery(query); + return elt; + } + + /** Whether the query is a plain SELECT * { pattern }. */ + private static boolean isPrimitiveQuery(Query query) { + boolean isNonPrimitive = + !query.isQueryResultStar() || query.isDistinct() || query.isReduced() || + query.hasLimit() || query.hasOffset() || + query.hasAggregators() || query.hasGroupBy() || query.hasHaving() || + query.hasOrderBy() || + query.hasValues(); + return !isNonPrimitive; + } + static class /* struct */ QueryLevelDetails { // The stack of processing in a query is: // slice-distinct/reduce-project-order-filter[having]-extend*[AS and aggregate naming]-group-pattern diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/syntax/syntaxtransform/ExprTransformApplyElementTransform.java b/jena-arq/src/main/java/org/apache/jena/sparql/syntax/syntaxtransform/ExprTransformApplyElementTransform.java index 9e4e3f5806d..698f04492e9 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/syntax/syntaxtransform/ExprTransformApplyElementTransform.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/syntax/syntaxtransform/ExprTransformApplyElementTransform.java @@ -20,6 +20,7 @@ import org.apache.jena.sparql.ARQInternalErrorException; import org.apache.jena.sparql.algebra.Op; +import org.apache.jena.sparql.algebra.OpAsQuery; import org.apache.jena.sparql.expr.*; import org.apache.jena.sparql.syntax.Element; @@ -43,7 +44,15 @@ public ExprTransformApplyElementTransform(ElementTransform transform, boolean al @Override public Expr transform(ExprFunctionOp funcOp, ExprList args, Op opArg) { - Element el2 = ElementTransformer.transform(funcOp.getElement(), transform); + // If the element is null then an attempt is made to obtain it from the algebra. + // A given element takes precedence over the algebra. + Element el1 = funcOp.getElement(); + if (el1 == null) { + Op op = funcOp.getGraphPattern(); + el1 = op == null ? null : OpAsQuery.asElement(op); + } + // ElementTransformer will warn should it happen that null is passed to it. + Element el2 = ElementTransformer.transform(el1, transform, this); if ( el2 == funcOp.getElement() ) return super.transform(funcOp, args, opArg); 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 313f75245b5..b4f987a3bf8 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 @@ -69,7 +69,7 @@ public static Query transform(Query query, ElementTransform transform, ExprTrans Query q2 = QueryTransformOps.shallowCopy(query); // Mutate the q2 structures which are already allocated and no other code can access yet. - mutateByQueryType(q2, transform, exprTransform); + mutateByQueryType(q2, exprTransform); mutateVarExprList(q2.getGroupBy(), exprTransform); mutateExprList(q2.getHavingExprs(), exprTransform); if (q2.getOrderBy() != null) @@ -116,7 +116,7 @@ private static void setAggregators(Query newQuery, Query query, ExprTransform ex } // Do the result form part of the cloned query. - private static void mutateByQueryType(Query q2, ElementTransform transform, ExprTransform exprTransform) { + private static void mutateByQueryType(Query q2, ExprTransform exprTransform) { switch(q2.queryType()) { case ASK : break; case CONSTRUCT : @@ -125,7 +125,7 @@ private static void mutateByQueryType(Query q2, ElementTransform transform, Expr Template template = q2.getConstructTemplate(); QuadAcc acc = new QuadAcc(); List quads = template.getQuads(); - template.getQuads().forEach(q->{ + quads.forEach(q->{ Node g = transform(q.getGraph(), exprTransform); Node s = transform(q.getSubject(), exprTransform); Node p = transform(q.getPredicate(), exprTransform); @@ -145,7 +145,8 @@ private static void mutateByQueryType(Query q2, ElementTransform transform, Expr case CONSTRUCT_JSON : throw new UnsupportedOperationException("Transform of JSON template queries"); case UNKNOWN : - throw new JenaException("Unknown qu ery type"); + default : + throw new JenaException("Unknown query type"); } } diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestOpAsQuery.java b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestOpAsQuery.java index 3d91b293b9b..af58d9c09db 100644 --- a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestOpAsQuery.java +++ b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestOpAsQuery.java @@ -329,6 +329,11 @@ public void testSubQuery3() { test_roundTripQuery(query) ; } + @Test + public void testSubQuery4() { + test_roundTripQuery("SELECT ?z { SELECT ?x { } }"); + } + @Test public void testAggregatesInSubQuery1() { //Simplified form of a test case provided via the mailing list (JENA-445) @@ -482,6 +487,83 @@ public void testMinus02() { "SELECT * { GRAPH ?g { { ?x ?y ?z FILTER(EXISTS { ?s ?p ?o }) } ?x ?y ?z } }", syntaxARQ); } + @Test + public void testExists07a() { + String query = """ + SELECT ?x { + ?x a ?y . + FILTER EXISTS { ?y a ?z } + } + """; + test_roundTripQuery(query); + } + + @Test + public void testExists07b() { + String input = """ + SELECT ?x { + ?x a ?y + FILTER EXISTS { SELECT * { ?y a ?z } } + } + """; + // Going through the algebra is expected to lose the SELECT * { } part within EXISTS. + String expected = """ + SELECT ?x { + ?x a ?y + FILTER EXISTS { ?y a ?z } + } + """; + test_roundTripQueryViaAlgebra(input, expected); + } + + @Test + public void testExists08a() { + String input = """ + SELECT * { + ?x a ?y + FILTER EXISTS { SELECT * { ?y a ?z } LIMIT 1 } + } + """; + test_roundTripQuery(input); + test_roundTripQueryViaAlgebra(input, input); + } + + // Tests exists with a subquery within a subquery. + @Test + public void testExists09a() { + String input = """ + SELECT ?x { + SELECT ?x { + ?x a ?y + FILTER EXISTS { SELECT * { ?y a ?z } } + } + } + """; + // Going through the algebra is expected to lose the SELECT * { } part within EXISTS. + String expected = """ + SELECT ?x { + SELECT ?x { + ?x a ?y + FILTER EXISTS { ?y a ?z } + } + } + """; + test_roundTripQueryViaAlgebra(input, expected); + } + + @Test + public void testExists09b() { + String queryStr = """ + SELECT ?x { + SELECT ?x { + ?x a ?y + FILTER EXISTS { SELECT * { ?y a ?z } LIMIT 1 } + } + } + """; + test_roundTripQuery(queryStr, Syntax.syntaxARQ); + } + @Test public void testNotExists01() { test_roundTripQuery("SELECT * { ?x ?y ?z NOT EXISTS { ?s ?p ?o } }", "SELECT * { ?x ?y ?z FILTER NOT EXISTS { ?s ?p ?o } }", syntaxARQ); } @@ -503,8 +585,6 @@ public void testMinus02() { "SELECT * { GRAPH ?g { { ?x ?y ?z FILTER(NOT EXISTS { ?s ?p ?o }) } ?x ?y ?z } }", syntaxARQ); } - - @Test public void testTable1() { String query = "SELECT * WHERE { ?x ?p ?z . VALUES ?y { } }" ; @@ -603,6 +683,12 @@ private static void test_roundTripQuery(String query, String outcome) { test_roundTripQuery(query, outcome, Syntax.syntaxSPARQL_11); } + /** query->algebra->OpAsQuery->assert equality with outcome */ + private static void test_roundTripQueryViaAlgebra(String query, String outcome) { + String opStr = Algebra.compile(QueryFactory.create(query)).toString(); + test_AlgebraToQuery(opStr, outcome); + } + private static void test_roundTripQuery(String query, String outcome, Syntax syntax) { // This must also be true. test_roundTripAlegbra(query, syntax);