/*
* 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.algebra;
import static org.junit.Assert.assertEquals ;
import static org.junit.Assert.assertFalse ;
import static org.junit.Assert.assertTrue ;
import java.util.Map ;
import org.apache.jena.atlas.lib.StrUtils ;
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.apache.jena.sparql.sse.SSE ;
import org.junit.Assert ;
import org.junit.Test ;
/**
* Tests for {@link OpAsQuery}
*/
public class TestOpAsQuery {
// Basic stuff
@Test public void testBasic01() { test_roundTripQuery("SELECT * { }") ; }
@Test public void testBasic02() { test_roundTripQuery("SELECT * { ?s ?p ?o }") ; }
@Test public void testBasic03() { test_roundTripQuery("SELECT * { ?s ?p ?o FILTER(?o > 5) }") ; }
@Test public void testBasic04() { test_roundTripQuery("SELECT ?s { ?s ?p ?o FILTER(?o > 5) }") ; }
@Test public void testBasic05() { test_roundTripQuery("SELECT ?s (?o + 5 AS ?B) { ?s ?p ?o }") ; }
@Test public void testOptional01()
{ test_roundTripQuery("SELECT * WHERE { ?s ?p ?o OPTIONAL { ?s ?q ?z FILTER (?foo) } }") ; }
// Double {{...}} matter here in SPARQL.
@Test public void testOptional02()
{ test_roundTripQuery("SELECT * WHERE { ?s ?p ?o OPTIONAL { { ?s ?q ?z FILTER (?foo) } } }") ; }
@Test public void testOptional03()
// Don't currently unnest the LHS of the second optional. See testOptional03a
{ test_roundTripQuery("SELECT * WHERE { ?s ?p ?o OPTIONAL { ?s ?p1 ?o1 } OPTIONAL { ?s ?p2 ?o2 } } ") ; }
@Test public void testOptional04()
{ test_roundTripQuery("SELECT * WHERE { ?s ?p ?o OPTIONAL { ?s ?p1 ?o1 } OPTIONAL { ?s ?p2 ?o2 } OPTIONAL { ?s ?p3 ?o3 }} ") ; }
@Test
public void testCountStar() {
test_roundTripQuery("select (count(*) as ?cs) { ?s ?p ?o }");
}
@Test
public void testCountGroup() {
test_roundTripQuery("select (count(?p) as ?cp) { ?s ?p ?o } group by ?s");
}
@Test
public void testCountGroupAs() {
test_roundTripQuery("select (count(?p) as ?cp) { ?s ?p ?o }");
}
@Test
public void testDoubleCount() {
Query[] result = test_roundTripQuery("select (count(?s) as ?sc) (count(?p) as ?pc) { ?s ?p ?o }") ;
assertEquals(2, result[1].getResultVars().size());
assertTrue(result[1].getResultVars().contains("sc"));
assertTrue(result[1].getResultVars().contains("pc"));
}
/* JENA-166 */
@Test
public void testGroupWithExpression() {
test_roundTripQuery("SELECT (sample(?a) + 1 AS ?c) {} GROUP BY ?x");
}
/* Coverage developed for JENA-963 : GROUP BY*/
@Test public void testGroupBy_01()
{ test_roundTripQuery("SELECT ?s { ?s ?p ?o } GROUP BY ?s"); }
@Test public void testGroupBy_02()
{ test_roundTripQuery("SELECT (count(?p) as ?cp) { ?s ?p ?o } GROUP BY ?s"); }
@Test public void testGroupBy_03()
{ test_roundTripQuery("SELECT ?s { ?s ?p ?o } GROUP BY ?s HAVING (count(*) > 1 )"); }
@Test public void testGroupBy_04()
{ test_roundTripQuery("SELECT ?s { ?s ?p ?o } GROUP BY ?s HAVING (?s > 1 )"); }
@Test public void testGroupBy_05()
{ test_roundTripQuery("SELECT (count(?p) as ?cp) { ?s ?p ?o } GROUP BY ?s HAVING (?cp > 1 )"); }
@Test public void testGroupBy_06()
{ test_roundTripQuery("SELECT (count(?p) as ?cp) { ?s ?p ?o } GROUP BY (abs(?o)) HAVING (?cp > 1 )"); }
@Test public void testGroupBy_07()
{ test_roundTripQuery("SELECT (?X+2 AS ?Y) (count(?p) as ?cp) ?Z (1/?X AS ?X1) { ?s ?p ?o } GROUP BY ?Z (abs(?o) AS ?X) HAVING (?cp > 1 )"); }
@Test public void testGroupBy_08()
{ test_roundTripQuery("SELECT (count(?p) as ?cp) { ?s ?p ?o } GROUP BY (abs(?o)) HAVING (?cp > 1 )"); }
@Test public void testGroupBy_09()
{ test_roundTripQuery("SELECT (count(?p) as ?cp) { ?s ?p ?o } GROUP BY (abs(?o)) ORDER BY (COUNT(*))"); }
@Test public void testGroupBy_10()
{ test_roundTripQuery("SELECT (7+count(?p) as ?cp) { ?s ?p ?o } GROUP BY (abs(?o)) HAVING (?cp > 1 && SUM(?o) > 99 ) ORDER BY (6+COUNT(*))"); }
@Test public void testGroupBy_11()
{ test_roundTripQuery("SELECT ?X { ?s ?p ?o } GROUP BY (abs(?o) AS ?X) HAVING (?cp > 1 )"); }
@Test public void testGroupBy_12()
{ test_roundTripQuery("SELECT * { ?s ?q ?z {SELECT DISTINCT * { ?s ?p ?o }} }"); }
@Test public void testSubQuery_01()
{ test_roundTripQuery("SELECT ?s { SELECT (count(*) as ?cp) { ?s ?p ?o } }") ; }
@Test public void testSubQuery_02()
{ test_roundTripQuery("SELECT ?s { ?s ?p ?o { SELECT (count(*) as ?cp) { ?s ?p ?o } }}") ; }
@Test public void testSubQuery_03()
{ test_roundTripQuery("SELECT ?s { { SELECT (count(*) as ?cp) { ?s ?p ?o } } ?s ?p ?o }") ; }
@Test public void testSubQuery_04()
{ test_roundTripQuery("SELECT * WHERE { ?s ?p ?o . BIND(?o AS ?x) }") ; }
@Test public void testSubQuery_05()
{ test_roundTripQuery("SELECT (?o AS ?x) WHERE { ?s ?p ?o .}") ; }
@Test
public void testProject1() {
test_roundTripQuery("SELECT (?x + 1 AS ?c) {}");
}
@Test
public void testProject2() {
Query[] result = test_roundTripQuery("SELECT (?x + 1 AS ?c) ?d {}");
assertEquals(2, result[1].getResultVars().size());
assertTrue(result[1].getResultVars().contains("c"));
assertTrue(result[1].getResultVars().contains("d"));
}
@Test
public void testNestedBind() {
test_roundTripQuery("SELECT ?c { { } UNION { BIND(?x + 1 AS ?c) } }");
}
@Test
public void testNestedProject() {
test_roundTripQuery("SELECT (?x + 1 AS ?c) { { } UNION { } }");
}
@Test
public void testGroupExpression() {
test_roundTripQuery("SELECT ?z { } GROUP BY (?x + ?y AS ?z)");
}
@Test
public void testNestedProjectWithGroup() {
test_roundTripQuery("SELECT (SAMPLE(?c) as ?s) { {} UNION {BIND(?x + 1 AS ?c)} } GROUP BY ?x");
}
@Test
public void testQuadPatternInDefaultGraph() {
test_roundTripQueryQuads("SELECT * WHERE { ?s a ?type }");
}
@Test
public void testGraphClauseUri() {
test_roundTripQuery("SELECT * WHERE { GRAPH <http://example> { ?s a ?type } }");
}
@Test
public void testGraphClauseComplex() {
test_roundTripQuery("SELECT * WHERE { GRAPH <http://example> { ?s a ?type . OPTIONAL { ?s <http://label> ?label } } }");
}
@Test
public void testQuadPatternInGraph() {
test_roundTripQueryQuads("SELECT * WHERE { GRAPH <http://example> { ?s a ?type } }");
}
@Test
public void testQuadPatternInGraphComplex01() {
//This fails because OpQuadPattern's are converted back to individual GRAPH clauses
Object[] result = roundTripQueryQuad("SELECT * WHERE { GRAPH <http://example> { ?s a ?type . OPTIONAL { ?s <http://label> ?label } } }");
assertFalse(result[0].equals(result[1]));
}
@Test
public void testQuadPatternInGraphComplex02() {
//This succeeds since each OpQuadPattern is from a single simple GRAPH clause
test_roundTripQueryQuads("SELECT * WHERE { GRAPH <http://example> { ?s a ?type } OPTIONAL { GRAPH <http://example> { ?s <http://label> ?label } } }");
}
@Test
public void testExtend1() {
// Top Level BIND should now be round trippable
test_roundTripQuery("SELECT * WHERE { ?s ?p ?o . BIND(?o AS ?x) }");
}
@Test
public void testExtend2() {
// Nested BIND should always have been round trippable
test_roundTripQuery("SELECT * WHERE { GRAPH ?g { ?s ?p ?o . BIND(?o AS ?x) } }");
}
@Test
public void testExtend3() {
//JENA-429
String query = StrUtils.strjoinNL
("PREFIX : <http://www.cipe.accamargo.org.br/ontologias/h2tc.owl#>" ,
"PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>" ,
"PREFIX mylib: <java:dateadd.lib.pkgfor.arq.>",
"",
"SELECT ?yearmonth ( count(?document) as ?total )",
"{" ,
" ?document a :Document;",
" :documentDateOfCreation ?date ;",
" :documentType \"exam results\" ." ,
" BIND( mylib:DateFormat( xsd:string(?date), \"yyyy-MM\" ) as ?yearmonth )",
"} group by ?yearmonth") ;
test_roundTripQuery(query);
}
@Test
public void testExtend4() {
//Simplified repo of JENA-429
test_roundTripQuery("SELECT ?key (COUNT(?member) AS ?total) WHERE { ?s ?p ?o . BIND(LCASE(?o) AS ?key) } GROUP BY ?key");
}
@Test
public void testExtendInService() {
//Original test case from JENA-422
Query[] result = test_roundTripQuery("SELECT * WHERE { SERVICE <http://example/endpoint> { ?s ?p ?o . BIND(?o AS ?x) } }");
assertTrue(result[1].toString().contains("BIND"));
}
@Test
public void testSubQuery1() {
test_roundTripQuery("SELECT ?s WHERE { SELECT ?s ?p WHERE { ?s ?p ?o } }");
}
@Test
public void testSubQuery2() {
String query = "SELECT ?s ?x WHERE { { SELECT ?s ?p WHERE { ?s ?p ?o } } { SELECT ?x WHERE { ?x ?p ?o } } }";
// The second inner sub-query is specially fixed up in OpJoin processing.
// Not all cases of sub-query have unnecessary {} removed.
test_roundTripQuery(query) ;
}
@Test
public void testSubQuery3() {
String query = "SELECT * WHERE { { SELECT ?s ?p WHERE { ?s ?p ?o } } { SELECT ?x WHERE { ?x ?p ?o } } }";
test_roundTripQuery(query) ;
}
@Test
public void testAggregatesInSubQuery1() {
//Simplified form of a test case provided via the mailing list (JENA-445)
String query = "SELECT ?key ?agg WHERE { SELECT ?key (COUNT(*) AS ?agg) { ?key ?p ?o } GROUP BY ?key }";
test_roundTripQuery(query);
}
@Test
public void testAggregatesInSubQuery2() {
//Simplified form of a test case provided via the mailing list (JENA-445)
test_roundTripAlegbra("SELECT * WHERE { { SELECT ?key (COUNT(*) AS ?agg) { ?key ?p ?o } GROUP BY ?key } }");
}
@Test
public void testAggregatesInSubQuery3() {
//Actual test case from JENA-445 bug report
String queryString =
"PREFIX dcterms: <http://purl.org/dc/terms/> \n" +
"PREFIX dbpedia: <http://dbpedia.org/resource/> \n" +
"SELECT ?num_of_holidays ?celebrate_Chinese_New_Year WHERE { \n" +
"{" +
"SELECT ?country_cat (COUNT(?holiday) as ?num_of_holidays) \n" +
"WHERE {" +
"?country_cat <http://www.w3.org/2004/02/skos/core#broader> <http://dbpedia.org/resource/Category:Public_holidays_by_country>. \n" +
"?holiday dcterms:subject ?country_cat \n" +
"}GROUP by ?country_cat \n" +
"} \n" +
"{ \n" +
"SELECT ?country_cat (COUNT(?holiday) as ?celebrate_Chinese_New_Year) \n" +
"WHERE { \n" +
"?country_cat <http://www.w3.org/2004/02/skos/core#broader> <http://dbpedia.org/resource/Category:Public_holidays_by_country>. \n" +
"?holiday dcterms:subject ?country_cat \n" +
"FILTER(?holiday=\"http://dbpedia.org/resource/Lunar_New_Year\'s_Day\") \n" +
"}GROUP by ?country_cat \n" +
"} \n" +
"}\n";
test_roundTripQuery(queryString);
}
@Test
public void testModifiersOnSubQuery1() {
// From JENA-954
String query = StrUtils.strjoinNL("SELECT (COUNT(*) as ?count) {",
" SELECT DISTINCT ?uri ?graph WHERE {",
" GRAPH ?graph {",
" ?uri ?p ?o .",
" }",
" } LIMIT 1",
"}");
test_roundTripQuery(query) ;
}
@Test
public void testModifiersOnSubQuery2() {
// From JENA-954
String query = StrUtils.strjoinNL("SELECT (COUNT(*) as ?count) {",
" SELECT REDUCED ?uri ?graph WHERE {",
" GRAPH ?graph {",
" ?uri ?p ?o .",
" }",
" } LIMIT 1",
"}");
test_roundTripQuery(query);
}
@Test
public void testModifiersOnSubQuery3() {
// From JENA-954
String query = StrUtils.strjoinNL("SELECT (COUNT(*) as ?count) {",
" SELECT ?uri ?graph WHERE {",
" GRAPH ?graph {",
" ?uri ?p ?o .",
" }",
" } LIMIT 1",
"}");
test_roundTripQuery(query);
}
@Test
public void testModifiersOnSubQuery4() {
// From JENA-954
String query = StrUtils.strjoinNL("SELECT (COUNT(*) as ?count) {",
" SELECT ?uri ?graph WHERE {",
" GRAPH ?graph {",
" ?uri ?p ?o .",
" }",
" } OFFSET 1",
"}");
test_roundTripQuery(query);
}
@Test
public void testPathExpressions1() {
String query = "PREFIX : <http://example/> SELECT * { ?s :p* ?o . ?x :r 123 . }" ;
test_roundTripQuery(query);
}
@Test
public void testPathExpressions2() {
String query = "PREFIX : <http://example/> SELECT * { ?s :p*/:q ?o . ?x :r 123 . }" ;
test_roundTripQuery(query);
}
@Test
public void testMinus1() {
test_roundTripQuery("PREFIX : <http://example/> SELECT * { ?s :p ?o MINUS { ?s :q ?v .FILTER(?v<5) } }") ;
}
@Test
public void testMinus2() {
// query gains a level of {} but the meaning is the same.
String query = "PREFIX : <http://example/> SELECT * { ?s :p ?o OPTIONAL { ?s :x ?2 } MINUS { ?s :q ?v .FILTER(?v<5) } }" ;
test_roundTripAlegbra(query) ;
}
@Test
public void testValues1() {
String query = "SELECT * { VALUES ?x {1 2} ?s ?p ?x }" ;
test_roundTripQuery(query) ;
}
@Test
public void testValues2() {
String query = "SELECT * { ?s ?p ?x VALUES ?x {1 2} }" ;
test_roundTripQuery(query) ;
}
// Algebra to query : optimization cases OpAsQuery can handle.
@Test
public void testAlgebra01() {
String opStr = "(sequence (bgp (?s1 ?p1 ?o1)) (bgp (?s2 ?p2 ?o2)) )" ;
String query = "SELECT * { ?s1 ?p1 ?o1. ?s2 ?p2 ?o2}" ;
test_AlgebraToQuery(opStr, query);
}
@Test
public void testAlgebra02() {
String opStr = "(sequence (bgp (?s1 ?p1 ?o1)) (path ?x (path* :p) ?z) )" ;
String query = "PREFIX : <http://example/> SELECT * { ?s1 ?p1 ?o1. ?x :p* ?z}" ;
test_AlgebraToQuery(opStr, query);
}
@Test
public void testAlgebra03() {
String opStr = "(sequence (path ?x (path* :p) ?z) (bgp (?s1 ?p1 ?o1)) )" ;
String query = "PREFIX : <http://example/> SELECT * { ?x :p* ?z . ?s1 ?p1 ?o1. }" ;
test_AlgebraToQuery(opStr, query);
}
// There 3 classes of transformations: there are 3 main test operations.
// test_roundTripQuery: The same query is recovered from OpAsQuery
// test_roundTripAlegbra: Different queries with the same alegra forms
// test_equivalentQuery: Different equivalent queries - same answers, different algebra.
// test_algebraToQuery: algebra to query (e.g. optimization shapes)
//
// test_roundTripQuery is test_equivalentQuery with same input and expected.
// + quad variants.
public static void test_equivalentQuery(String input, String expected) {
Query orig = QueryFactory.create(input, Syntax.syntaxSPARQL_11);
Op toReconstruct = Algebra.compile(orig);
Query got = OpAsQuery.asQuery(toReconstruct);
Query result = QueryFactory.create(expected, Syntax.syntaxSPARQL_11);
assertEquals(result, got);
}
// Test for queries that do query->algebra->OpAsQuery->query
// to produce an output that is .equals the input.
public static Query[] test_roundTripQuery(String query) {
Query[] r = roundTripQuery(query) ;
stripNamespacesAndBase(r[0]) ;
stripNamespacesAndBase(r[1]) ;
assertEquals(r[0], r[1]) ;
return r ;
}
// Test via quads
public static Query[] test_roundTripQueryQuads(String query) {
Query[] r = roundTripQueryQuad(query) ;
assertEquals(r[0], r[1]) ;
return r ;
}
// Compare A1 and A2 where
// query[Q1]->algebra[A1]->OpAsQuery->query[Q2]->algebra[A2]
// Sometimes Q1 and Q2 are equivalent but not .equals.
public void test_roundTripAlegbra(String query) {
Query[] r = roundTripQuery(query);
// Even if the strings come out as non-equal because of the translation from algebra to query
// the algebras should be equal
// i.e. the queries should remain semantically equivalent
Op a1 = Algebra.compile(r[0]);
Op a2 = Algebra.compile(r[1]);
Assert.assertEquals(a1, a2);
}
// algebra->OpAsQuery->query
public static void test_AlgebraToQuery(String input, String expected) {
Op op = SSE.parseOp(input) ;
Query orig = QueryFactory.create(expected, Syntax.syntaxSPARQL_11);
stripNamespacesAndBase(orig) ;
Query got = OpAsQuery.asQuery(op);
Assert.assertEquals(orig, got) ;
}
// query->algebra->OpAsQuery->query
private static Query[] roundTripQuery(String query) {
Query orig = QueryFactory.create(query, Syntax.syntaxSPARQL_11);
Op toReconstruct = Algebra.compile(orig);
Query got = OpAsQuery.asQuery(toReconstruct);
Query[] r = { orig, got };
return r;
}
// query->algebra/quads->OpAsQuery->query
private static Query[] roundTripQueryQuad(String query) {
Query orig = QueryFactory.create(query, Syntax.syntaxSPARQL_11);
Op toReconstruct = Algebra.compile(orig);
toReconstruct = Algebra.toQuadForm(toReconstruct);
Query got = OpAsQuery.asQuery(toReconstruct);
Query[] r = { orig, got };
return r;
}
protected static void stripNamespacesAndBase(Query q) {
Map<String, String> prefixes = q.getPrefixMapping().getNsPrefixMap();
for (String prefix : prefixes.keySet()) {
q.getPrefixMapping().removeNsPrefix(prefix);
}
q.setBaseURI((String)null);
}
}