/******************************************************************************* * Copyright (c) 2007 Cambridge Semantics Incorporated. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cambridge Semantics Incorporated *******************************************************************************/ package org.openanzo.glitter; import java.util.ArrayList; import java.util.List; import junit.framework.TestCase; import org.openanzo.glitter.query.QueryController; import org.openanzo.glitter.query.planning.LexicalOrderBasedExecutionPlan; import org.openanzo.rdf.utils.PrettyPrinter; /** * For a set of SPARQL queries intended to cover all of the language features, test that after the query is parsed that: * * <ol> * <li>The QueryController.getQueryString method will correctly return the "canonical format" of the query originally parsed</li> * <li>The QueryController.prettyPrint will correctly return the "canonical format" of the AST (Abstract Syntax Tree) for the query</li> * </ol> * * These test are performed by comparing the expected string representation of the query and AST with the one created by the QueryController. * * Note that the "canonical format" used here is completely informal and not intended for general use beyond these tests, heres some of the rules: * * <ul> * <li>The spacing must be exact</li> * <li>Prefixes are in-lined into the query (no PREFIX)</li> * <li>operator precedence must be explicit. E.g. FILTER(!?o1 && ?o2 || ?o3) must be written FILTER(((!?o1) && (?o2 || ?o3)))</li> * </ul> * * @author Joe Betz <jpbetz@cambridgesemantics.com> * */ public class TestQueryAndAstStrings extends TestCase { static final List<CanonicalQueryStrings> queries = new ArrayList<CanonicalQueryStrings>(); static { String query; String ast; // a basic projection query = "SELECT ?o ?o2 WHERE { <http://fake.url/s> <http://fake.url/p> ?o. <http://fake.url/s> <http://fake.url/p2> ?o2 }"; ast = "Query(Projection(?o, ?o2), GraphPattern(Group(BGP(TriplePatternNode(<http://fake.url/s> <http://fake.url/p> ?o), TriplePatternNode(<http://fake.url/s> <http://fake.url/p2> ?o2)))))"; queries.add(new CanonicalQueryStrings(query, ast)); // projection w/ optional query = "SELECT ?o WHERE { ?s <http://fake.url/p> ?o OPTIONAL { ?s <http://fake.url/p2> ?o2 } }"; ast = "Query(Projection(?o), GraphPattern(Group(Optional(Group(BGP(TriplePatternNode(?s <http://fake.url/p> ?o))), Group(BGP(TriplePatternNode(?s <http://fake.url/p2> ?o2)))))))"; queries.add(new CanonicalQueryStrings(query, ast)); /* union order is not deterministic, so can't verify AST this way... query = "SELECT ?o WHERE { {<http://fake.url/s> ?p ?o } UNION {<http://fake.url/s2> ?p ?o } }"; ast = "Query(Projection(?o), GraphPattern(Union(Group(Group(BGP(TriplePatternNode(<http://fake.url/s> ?p ?o)))), Group(Group(BGP(TriplePatternNode(<http://fake.url/s2> ?p ?o)))))))"; queries.add(new CanonicalQueryStrings(query, ast)); */ // graph query = "SELECT ?o WHERE { GRAPH ?g { <http://fake.url/s> ?p ?o } }"; ast = "Query(Projection(?o), GraphPattern(Group(Graph(?g, Group(BGP(TriplePatternNode(<http://fake.url/s> ?p ?o)))))))"; queries.add(new CanonicalQueryStrings(query, ast)); // filter infix operator: ?o > 0 query = "SELECT ?o WHERE { <http://fake.url/s> ?p ?o FILTER ((?o > \"\"\"0\"\"\"^^<http://www.w3.org/2001/XMLSchema#integer>)) }"; ast = "Query(Projection(?o), GraphPattern(Group(Filters(PolymorphicGt(?o, \"\"\"0\"\"\"^^<http://www.w3.org/2001/XMLSchema#integer>)), BGP(TriplePatternNode(<http://fake.url/s> ?p ?o)))))"; queries.add(new CanonicalQueryStrings(query, ast)); // filter prefix operator: !o query = "SELECT ?o WHERE { <http://fake.url/s> ?p ?o FILTER ((!?o)) }"; ast = "Query(Projection(?o), GraphPattern(Group(Filters(Not(?o)), BGP(TriplePatternNode(<http://fake.url/s> ?p ?o)))))"; queries.add(new CanonicalQueryStrings(query, ast)); // filter function: isIRI(?o) query = "SELECT ?o WHERE { <http://fake.url/s> ?p ?o FILTER (isIRI(?o)) }"; ast = "Query(Projection(?o), GraphPattern(Group(Filters(IsIRI(?o)), BGP(TriplePatternNode(<http://fake.url/s> ?p ?o)))))"; queries.add(new CanonicalQueryStrings(query, ast)); // nested filters: regex(str(?o), "@peter", "i") query = "SELECT ?o WHERE { <http://fake.url/s> ?p ?o FILTER (regex(str(?o), \"@peter\", \"i\")) }"; ast = "Query(Projection(?o), GraphPattern(Group(Filters(RegEx(Str(?o), \"@peter\", \"i\")), BGP(TriplePatternNode(<http://fake.url/s> ?p ?o)))))"; queries.add(new CanonicalQueryStrings(query, ast)); // filter operator precedence: !?o && ?p || ?o query = "SELECT ?o WHERE { <http://fake.url/s> ?p ?o FILTER (((!?o) && (?p || ?o))) }"; ast = "Query(Projection(?o), GraphPattern(Group(Filters(LogicalAnd(Not(?o), LogicalOr(?p, ?o))), BGP(TriplePatternNode(<http://fake.url/s> ?p ?o)))))"; queries.add(new CanonicalQueryStrings(query, ast)); // filter operator precidence, variation: (!?o && ?p) || ?o query = "SELECT ?o WHERE { <http://fake.url/s> ?p ?o FILTER ((((!?o) && ?p) || ?o)) }"; ast = "Query(Projection(?o), GraphPattern(Group(Filters(LogicalOr(LogicalAnd(Not(?o), ?p), ?o)), BGP(TriplePatternNode(<http://fake.url/s> ?p ?o)))))"; queries.add(new CanonicalQueryStrings(query, ast)); // select distinct query = "SELECT DISTINCT ?o WHERE { <http://fake.url/s> ?p ?o } ORDER BY ASC(?o)"; ast = "Query(OrderBy(?o), Projection(DISTINCT, ?o), GraphPattern(Group(BGP(TriplePatternNode(<http://fake.url/s> ?p ?o)))))"; queries.add(new CanonicalQueryStrings(query, ast)); // select reduced query = "SELECT REDUCED ?o WHERE { <http://fake.url/s> ?p ?o }"; ast = "Query(Projection(REDUCED, ?o), GraphPattern(Group(BGP(TriplePatternNode(<http://fake.url/s> ?p ?o)))))"; queries.add(new CanonicalQueryStrings(query, ast)); // select count query = "SELECT (COUNT(*) AS ?count) ?o WHERE { <http://fake.url/s> ?p ?o } GROUP BY ?o"; ast = "Query(Projection(ProjectAs(Count(*), ?count), ?o, GroupBy(?o)), GraphPattern(Group(BGP(TriplePatternNode(<http://fake.url/s> ?p ?o)))))"; queries.add(new CanonicalQueryStrings(query, ast)); // ask query = "ASK WHERE { <http://fake.url/s> <http://fake.url/p> \"\"\"0\"\"\"^^<http://www.w3.org/2001/XMLSchema#integer> }"; ast = "Query(Ask(), GraphPattern(Group(BGP(TriplePatternNode(<http://fake.url/s> <http://fake.url/p> \"\"\"0\"\"\"^^<http://www.w3.org/2001/XMLSchema#integer>)))))"; queries.add(new CanonicalQueryStrings(query, ast)); // construct query = "CONSTRUCT { ?o <http://fake.url/p2> ?o2. ?o2 <http://fake.url/p3> <http://fake.url/s> } WHERE { <http://fake.url/s> ?p ?o. ?o <http://fake.url/p> ?o2 }"; ast = "Query(Construct(TriplePatternNode(?o <http://fake.url/p2> ?o2), TriplePatternNode(?o2 <http://fake.url/p3> <http://fake.url/s>)), GraphPattern(Group(BGP(TriplePatternNode(<http://fake.url/s> ?p ?o), TriplePatternNode(?o <http://fake.url/p> ?o2)))))"; queries.add(new CanonicalQueryStrings(query, ast)); // complicated w/ optionals //query = "SELECT ?o1 ?o3 ?o4 ?o5 WHERE { ?s <http://fake.url/p1> ?o1 ; <http://fake.url/p2> [ <http://fake.url/p3> ?o3 ] OPTIONAL { ?s <http://fake.url/p2> [ <http://fake.url/p4> ?o4 ] } OPTIONAL { ?s <http://fake.url/p5> ?o5 } }"; query = "SELECT ?o1 ?o3 ?o4 ?o5 WHERE { ?s <http://fake.url/p1> ?o1. _:b1 <http://fake.url/p3> ?o3. ?s <http://fake.url/p2> _:b1 OPTIONAL { _:b2 <http://fake.url/p4> ?o4. ?s <http://fake.url/p2> _:b2 } OPTIONAL { ?s <http://fake.url/p5> ?o5 } }"; ast = "Query(Projection(?o1, ?o3, ?o4, ?o5), GraphPattern(Group(Optional(Group(Optional(Group(BGP(TriplePatternNode(?s <http://fake.url/p1> ?o1), TriplePatternNode(_:b1 <http://fake.url/p3> ?o3), TriplePatternNode(?s <http://fake.url/p2> _:b1))), Group(BGP(TriplePatternNode(_:b2 <http://fake.url/p4> ?o4), TriplePatternNode(?s <http://fake.url/p2> _:b2))))), Group(BGP(TriplePatternNode(?s <http://fake.url/p5> ?o5)))))))"; queries.add(new CanonicalQueryStrings(query, ast)); } String generateAbstractSyntax(String query) throws Exception { Engine engine = new Engine(new MockEngineConfig(LexicalOrderBasedExecutionPlan.class)); QueryController prepareQuery = engine.prepareQuery(null, query); return PrettyPrinter.print(prepareQuery); } String parseAndSerialize(String query) throws Exception { Engine engine = new Engine(new MockEngineConfig(LexicalOrderBasedExecutionPlan.class)); QueryController prepareQuery = engine.prepareQuery(null, query); return prepareQuery.getQueryString(true); } /** * Test abstract sparql syntax * * @throws Exception */ public void testSparqlToAbstractSyntax() throws Exception { for (CanonicalQueryStrings query : queries) { assertEquals(query.ast, generateAbstractSyntax(query.sparql)); } } /** * Test parse and serialize query * * @throws Exception */ public void testParseAndSerializeQuery() throws Exception { for (CanonicalQueryStrings query : queries) { assertEquals(query.sparql, parseAndSerialize(query.sparql)); } } static class CanonicalQueryStrings { public final String sparql; public final String ast; public CanonicalQueryStrings(String sparql, String ast) { this.sparql = sparql; this.ast = ast; } } }