SPARQLParserTest.java
/*******************************************************************************
* Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.query.parser.sparql;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.StringReader;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Namespace;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.impl.SimpleNamespace;
import org.eclipse.rdf4j.model.util.Values;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.MalformedQueryException;
import org.eclipse.rdf4j.query.algebra.AggregateFunctionCall;
import org.eclipse.rdf4j.query.algebra.ArbitraryLengthPath;
import org.eclipse.rdf4j.query.algebra.DeleteData;
import org.eclipse.rdf4j.query.algebra.Extension;
import org.eclipse.rdf4j.query.algebra.Filter;
import org.eclipse.rdf4j.query.algebra.Group;
import org.eclipse.rdf4j.query.algebra.InsertData;
import org.eclipse.rdf4j.query.algebra.Join;
import org.eclipse.rdf4j.query.algebra.Modify;
import org.eclipse.rdf4j.query.algebra.Order;
import org.eclipse.rdf4j.query.algebra.Projection;
import org.eclipse.rdf4j.query.algebra.ProjectionElem;
import org.eclipse.rdf4j.query.algebra.ProjectionElemList;
import org.eclipse.rdf4j.query.algebra.QueryModelNode;
import org.eclipse.rdf4j.query.algebra.QueryRoot;
import org.eclipse.rdf4j.query.algebra.Slice;
import org.eclipse.rdf4j.query.algebra.StatementPattern;
import org.eclipse.rdf4j.query.algebra.Sum;
import org.eclipse.rdf4j.query.algebra.TupleExpr;
import org.eclipse.rdf4j.query.algebra.Union;
import org.eclipse.rdf4j.query.algebra.UpdateExpr;
import org.eclipse.rdf4j.query.algebra.Var;
import org.eclipse.rdf4j.query.parser.ParsedBooleanQuery;
import org.eclipse.rdf4j.query.parser.ParsedGraphQuery;
import org.eclipse.rdf4j.query.parser.ParsedQuery;
import org.eclipse.rdf4j.query.parser.ParsedTupleQuery;
import org.eclipse.rdf4j.query.parser.ParsedUpdate;
import org.eclipse.rdf4j.query.parser.sparql.aggregate.AggregateCollector;
import org.eclipse.rdf4j.query.parser.sparql.aggregate.AggregateFunction;
import org.eclipse.rdf4j.query.parser.sparql.aggregate.AggregateFunctionFactory;
import org.eclipse.rdf4j.query.parser.sparql.aggregate.CustomAggregateFunctionRegistry;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.Rio;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* @author jeen
*/
public class SPARQLParserTest {
private SPARQLParser parser;
@BeforeEach
public void setUp() {
parser = new SPARQLParser();
}
@AfterEach
public void tearDown() {
parser = null;
}
/**
* Test method for
* {@link org.eclipse.rdf4j.query.parser.sparql.SPARQLParser#parseQuery(java.lang.String, java.lang.String)} .
*/
@Test
public void testSourceStringAssignment() {
String simpleSparqlQuery = "SELECT * WHERE {?X ?P ?Y }";
ParsedQuery q = parser.parseQuery(simpleSparqlQuery, null);
assertThat(q.getSourceString()).isEqualTo(simpleSparqlQuery);
}
@Test
public void testInsertDataLineNumberReporting() {
String insertDataString = "INSERT DATA {\n incorrect reference }";
try {
ParsedUpdate u = parser.parseUpdate(insertDataString, null);
fail("should have resulted in parse exception");
} catch (MalformedQueryException e) {
assertThat(e.getMessage()).contains("line 2,");
}
}
@Test
public void testDeleteDataLineNumberReporting() {
String deleteDataString = "DELETE DATA {\n incorrect reference }";
try {
ParsedUpdate u = parser.parseUpdate(deleteDataString, null);
fail("should have resulted in parse exception");
} catch (MalformedQueryException e) {
assertThat(e.getMessage()).contains("line 2,");
}
}
@Test
public void testSES1922PathSequenceWithValueConstant() {
StringBuilder qb = new StringBuilder();
qb.append("ASK {?A (<foo:bar>)/<foo:foo> <foo:objValue>} ");
ParsedQuery q = parser.parseQuery(qb.toString(), null);
TupleExpr te = q.getTupleExpr();
assertNotNull(te);
assertTrue(te instanceof QueryRoot);
te = ((QueryRoot) te).getArg();
assertTrue(te instanceof Slice);
Slice s = (Slice) te;
assertTrue(s.getArg() instanceof Join);
Join j = (Join) s.getArg();
assertTrue(j.getLeftArg() instanceof StatementPattern);
assertTrue(j.getRightArg() instanceof StatementPattern);
StatementPattern leftArg = (StatementPattern) j.getLeftArg();
StatementPattern rightArg = (StatementPattern) j.getRightArg();
assertTrue(leftArg.getObjectVar().equals(rightArg.getSubjectVar()));
assertEquals(leftArg.getObjectVar().getName(), rightArg.getSubjectVar().getName());
}
@Test
public void testParsedBooleanQueryRootNode() {
StringBuilder qb = new StringBuilder();
qb.append("ASK {?a <foo:bar> \"test\"}");
ParsedBooleanQuery q = (ParsedBooleanQuery) parser.parseQuery(qb.toString(), null);
TupleExpr te = q.getTupleExpr();
assertTrue(te instanceof QueryRoot);
te = ((QueryRoot) te).getArg();
assertNotNull(te);
assertTrue(te instanceof Slice);
assertTrue(te.getParentNode() instanceof QueryRoot);
}
/**
* Verify that an INSERT with a subselect using a wildcard correctly adds vars to projection
*
* @see <a href="https://github.com/eclipse/rdf4j/issues/686">#686</a>
*/
@Test
public void testParseWildcardSubselectInUpdate() {
StringBuilder update = new StringBuilder();
update.append("INSERT { <urn:a> <urn:b> <urn:c> . } WHERE { SELECT * {?s ?p ?o } }");
ParsedUpdate parsedUpdate = parser.parseUpdate(update.toString(), null);
List<UpdateExpr> exprs = parsedUpdate.getUpdateExprs();
assertEquals(1, exprs.size());
UpdateExpr expr = exprs.get(0);
assertTrue(expr instanceof Modify);
verifySerializable(expr);
Modify m = (Modify) expr;
TupleExpr whereClause = m.getWhereExpr();
assertTrue(whereClause instanceof Projection);
ProjectionElemList projectionElemList = ((Projection) whereClause).getProjectionElemList();
assertNotNull(projectionElemList);
List<ProjectionElem> elements = projectionElemList.getElements();
assertNotNull(elements);
assertThat(elements).hasSize(3);
}
@Test
public void testParseIntegerObjectValue() {
// test that the parser correctly parses the object value as an integer, instead of as a decimal.
String query = "select ?Concept where { ?Concept a 1. ?Concept2 a 1. } ";
ParsedTupleQuery q = (ParsedTupleQuery) parser.parseQuery(query, null);
verifySerializable(q.getTupleExpr());
// all we're verifying is that the query is parsed without error. If it doesn't parse as integer but as a
// decimal, the
// parser will fail, because the statement pattern doesn't end with a full-stop.
assertNotNull(q);
}
@Test
public void testParsedTupleQueryRootNode() {
StringBuilder qb = new StringBuilder();
qb.append("SELECT * {?a <foo:bar> \"test\"}");
ParsedTupleQuery q = (ParsedTupleQuery) parser.parseQuery(qb.toString(), null);
TupleExpr tupleExpr = q.getTupleExpr();
assertTrue(tupleExpr instanceof QueryRoot);
verifySerializable(tupleExpr);
tupleExpr = ((QueryRoot) tupleExpr).getArg();
assertNotNull(tupleExpr);
assertTrue(tupleExpr instanceof Projection);
assertTrue(tupleExpr.getParentNode() instanceof QueryRoot);
}
@Test
public void testParsedGraphQueryRootNode() {
StringBuilder qb = new StringBuilder();
qb.append("CONSTRUCT WHERE {?a <foo:bar> \"test\"}");
ParsedGraphQuery q = (ParsedGraphQuery) parser.parseQuery(qb.toString(), null);
TupleExpr te = q.getTupleExpr();
verifySerializable(te);
assertTrue(te instanceof QueryRoot);
te = ((QueryRoot) te).getArg();
assertNotNull(te);
assertTrue(te instanceof Projection);
assertTrue(te.getParentNode() instanceof QueryRoot);
}
@Test
public void testOrderByWithAliases1() {
String queryString = " SELECT ?x (SUM(?v1)/COUNT(?v1) as ?r) WHERE { ?x <urn:foo> ?v1 } GROUP BY ?x ORDER BY ?r";
ParsedQuery query = parser.parseQuery(queryString, null);
assertNotNull(query);
TupleExpr te = query.getTupleExpr();
verifySerializable(te);
assertTrue(te instanceof QueryRoot);
te = ((QueryRoot) te).getArg();
assertTrue(te instanceof Projection);
te = ((Projection) te).getArg();
assertTrue(te instanceof Order);
te = ((Order) te).getArg();
assertTrue(te instanceof Extension);
}
@Test
public void testOrderByWithAliases2() {
String queryString = "SELECT (?l AS ?v)\n"
+ "WHERE { ?s rdfs:label ?l. }\n"
+ "ORDER BY ?v";
ParsedQuery query = parser.parseQuery(queryString, null);
TupleExpr te = query.getTupleExpr();
verifySerializable(te);
assertThat(te).isInstanceOf(QueryRoot.class);
te = ((QueryRoot) te).getArg();
assertThat(te).isInstanceOf(Projection.class);
te = ((Projection) te).getArg();
assertThat(te).isInstanceOf(Order.class);
te = ((Order) te).getArg();
assertThat(te).isInstanceOf(Extension.class);
}
@Test
public void testSES1927UnequalLiteralValueConstants1() {
StringBuilder qb = new StringBuilder();
qb.append("ASK {?a <foo:bar> \"test\". ?a <foo:foo> \"test\"@en .} ");
ParsedQuery q = parser.parseQuery(qb.toString(), null);
TupleExpr te = q.getTupleExpr();
verifySerializable(te);
assertTrue(te instanceof QueryRoot);
te = ((QueryRoot) te).getArg();
assertNotNull(te);
assertTrue(te instanceof Slice);
Slice s = (Slice) te;
assertTrue(s.getArg() instanceof Join);
Join j = (Join) s.getArg();
assertTrue(j.getLeftArg() instanceof StatementPattern);
assertTrue(j.getRightArg() instanceof StatementPattern);
StatementPattern leftArg = (StatementPattern) j.getLeftArg();
StatementPattern rightArg = (StatementPattern) j.getRightArg();
assertFalse(leftArg.getObjectVar().equals(rightArg.getObjectVar()));
assertNotEquals(leftArg.getObjectVar().getName(), rightArg.getObjectVar().getName());
}
@Test
public void testSES1927UnequalLiteralValueConstants2() {
StringBuilder qb = new StringBuilder();
qb.append("ASK {?a <foo:bar> \"test\". ?a <foo:foo> \"test\"^^<foo:bar> .} ");
ParsedQuery q = parser.parseQuery(qb.toString(), null);
TupleExpr te = q.getTupleExpr();
verifySerializable(te);
assertTrue(te instanceof QueryRoot);
te = ((QueryRoot) te).getArg();
assertNotNull(te);
assertTrue(te instanceof Slice);
Slice s = (Slice) te;
assertTrue(s.getArg() instanceof Join);
Join j = (Join) s.getArg();
assertTrue(j.getLeftArg() instanceof StatementPattern);
assertTrue(j.getRightArg() instanceof StatementPattern);
StatementPattern leftArg = (StatementPattern) j.getLeftArg();
StatementPattern rightArg = (StatementPattern) j.getRightArg();
assertFalse(leftArg.getObjectVar().equals(rightArg.getObjectVar()));
assertNotEquals(leftArg.getObjectVar().getName(), rightArg.getObjectVar().getName());
}
@Test
public void testLongUnicode() {
ParsedUpdate ru = parser.parseUpdate("insert data {<urn:test:foo> <urn:test:bar> \"\\U0001F61F\" .}",
"urn:test");
InsertData insertData = (InsertData) ru.getUpdateExprs().get(0);
String[] lines = insertData.getDataBlock().split("\n");
assertEquals("\uD83D\uDE1F", lines[lines.length - 1].replaceAll(".*\"(.*)\".*", "$1"));
}
@Test
public void testAdditionalWhitespace_Not_In() {
String query = "SELECT * WHERE { ?s ?p ?o. FILTER(?o NOT IN (1, 2, 3)) }";
ParsedQuery parsedQuery = parser.parseQuery(query, null);
// parsing should not throw exception
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
}
@Test
public void testAdditionalWhitespace_Not_Exists() {
String query = "SELECT * WHERE { ?s ?p ?o. FILTER NOT EXISTS { ?s ?p ?o } }";
ParsedQuery parsedQuery = parser.parseQuery(query, null);
// parsing should not throw exception
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
}
@Test
public void testWildCardPathFixedEnd() {
String query = "PREFIX : <http://example.org/>\n ASK {:IBM ((:|!:)|(^:|!^:))* :Jane.} ";
ParsedQuery parsedQuery = parser.parseQuery(query, null);
// parsing should not throw exception
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
}
@Test
public void testWildCardPathPushNegation() {
String query = "PREFIX : <http://example.org/>\n ASK {:IBM ^(:|!:) ?jane.} ";
ParsedQuery parsedQuery = parser.parseQuery(query, null);
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
assertTrue(tupleExpr instanceof QueryRoot);
tupleExpr = ((QueryRoot) tupleExpr).getArg();
Slice slice = (Slice) tupleExpr;
Union union = (Union) slice.getArg();
Var leftSubjectVar = ((StatementPattern) union.getLeftArg()).getSubjectVar();
Var rightSubjectVar = ((StatementPattern) ((Filter) union.getRightArg()).getArg()).getSubjectVar();
assertEquals(leftSubjectVar, rightSubjectVar);
}
@Test
public void testWildCardPathPushNegation2() {
String query = "PREFIX : <http://example.org/>\n ASK {:IBM ^(:|!:) :Jane.} ";
ParsedQuery parsedQuery = parser.parseQuery(query, null);
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
assertTrue(tupleExpr instanceof QueryRoot);
tupleExpr = ((QueryRoot) tupleExpr).getArg();
assertTrue(tupleExpr instanceof Slice);
Slice slice = (Slice) tupleExpr;
assertTrue(slice.getArg() instanceof Union);
Union union = (Union) slice.getArg();
Var leftSubjectVar = ((StatementPattern) union.getLeftArg()).getSubjectVar();
Var rightSubjectVar = ((StatementPattern) ((Filter) union.getRightArg()).getArg()).getSubjectVar();
assertEquals(leftSubjectVar, rightSubjectVar);
}
@Test
public void testWildCardPathComplexSubjectHandling() {
String query = "PREFIX : <http://example.org/>\n ASK { ?a (:comment/^(:subClassOf|(:type/:label))/:type)* ?b } ";
ParsedQuery parsedQuery = parser.parseQuery(query, null);
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
assertTrue(tupleExpr instanceof QueryRoot);
tupleExpr = ((QueryRoot) tupleExpr).getArg();
assertTrue(tupleExpr instanceof Slice);
Slice slice = (Slice) tupleExpr;
ArbitraryLengthPath path = (ArbitraryLengthPath) slice.getArg();
Var pathStart = path.getSubjectVar();
Var pathEnd = path.getObjectVar();
assertThat(pathStart.getName()).isEqualTo("a");
assertThat(pathEnd.getName()).isEqualTo("b");
Join pathSequence = (Join) path.getPathExpression();
Join innerJoin = (Join) pathSequence.getLeftArg();
Var commentObjectVar = ((StatementPattern) innerJoin.getLeftArg()).getObjectVar();
Union union = (Union) innerJoin.getRightArg();
Var subClassOfSubjectVar = ((StatementPattern) union.getLeftArg()).getSubjectVar();
assertThat(subClassOfSubjectVar).isNotEqualTo(commentObjectVar);
Var subClassOfObjectVar = ((StatementPattern) union.getLeftArg()).getObjectVar();
assertThat(subClassOfObjectVar).isEqualTo(commentObjectVar);
}
@Test
public void testGroupByProjectionHandling_NoAggregate() {
String query = "SELECT DISTINCT ?s (?o AS ?o1) \n"
+ "WHERE {\n"
+ " ?s ?p ?o \n"
+ "} GROUP BY ?s ?o";
// should parse without error
ParsedQuery parsedQuery = parser.parseQuery(query, null);
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
}
@Test
public void testProjectionHandling_CustomAggregateFunction() {
var factory = buildDummyFactory();
try {
CustomAggregateFunctionRegistry.getInstance().add(factory);
String query = "prefix rj: <https://www.rdf4j.org/aggregate#>"
+ "SELECT (rj:x(distinct ?o) AS ?o1) \n"
+ "WHERE {\n"
+ " ?s ?p ?o \n"
+ "} GROUP BY ?s ?o";
var tupleExpr = parser.parseQuery(query, null).getTupleExpr();
verifySerializable(tupleExpr);
assertTrue(tupleExpr instanceof QueryRoot);
tupleExpr = ((QueryRoot) tupleExpr).getArg();
assertTrue(tupleExpr instanceof Projection);
tupleExpr = ((Projection) tupleExpr).getArg();
assertTrue(tupleExpr instanceof Extension);
var extensionElements = ((Extension) tupleExpr).getElements();
assertEquals(1, extensionElements.size());
assertTrue(extensionElements.get(0).getExpr() instanceof AggregateFunctionCall);
assertEquals(factory.getIri(), ((AggregateFunctionCall) extensionElements.get(0).getExpr()).getIRI());
tupleExpr = ((Extension) tupleExpr).getArg();
assertTrue(tupleExpr instanceof Group);
assertEquals(1, ((Group) tupleExpr).getGroupElements().size());
assertEquals(extensionElements.get(0).getExpr(),
((Group) tupleExpr).getGroupElements().get(0).getOperator());
assertTrue(((Group) tupleExpr).getGroupElements().get(0).getOperator().isDistinct());
} finally {
CustomAggregateFunctionRegistry.getInstance().remove(factory);
}
}
@Test
public void testProjectionHandling_HavingCustomAggregateFunction() {
var factory = buildDummyFactory();
try {
CustomAggregateFunctionRegistry.getInstance().add(factory);
String query = "prefix rj: <https://www.rdf4j.org/aggregate#>"
+ "SELECT (rj:x(distinct ?o) AS ?o1) \n"
+ "WHERE {\n"
+ " ?s ?p ?o \n"
+ "} GROUP BY ?s \nHAVING (rj:x(distinct ?o) > 50) ";
var tupleExpr = parser.parseQuery(query, null).getTupleExpr();
verifySerializable(tupleExpr);
assertTrue(tupleExpr instanceof QueryRoot);
tupleExpr = ((QueryRoot) tupleExpr).getArg();
assertTrue(tupleExpr instanceof Projection);
tupleExpr = ((Projection) tupleExpr).getArg();
assertTrue(tupleExpr instanceof Extension);
var extensionElements = ((Extension) tupleExpr).getElements();
assertEquals(1, extensionElements.size());
assertTrue(extensionElements.get(0).getExpr() instanceof AggregateFunctionCall);
assertEquals(factory.getIri(), ((AggregateFunctionCall) extensionElements.get(0).getExpr()).getIRI());
tupleExpr = ((Extension) tupleExpr).getArg();
assertTrue(tupleExpr instanceof Filter);
tupleExpr = ((Filter) tupleExpr).getArg();
assertTrue(tupleExpr instanceof Extension);
tupleExpr = ((Extension) tupleExpr).getArg();
assertTrue(tupleExpr instanceof Group);
assertEquals(2, ((Group) tupleExpr).getGroupElements().size());
assertEquals(extensionElements.get(0).getExpr(),
((Group) tupleExpr).getGroupElements().get(0).getOperator());
assertEquals(extensionElements.get(0).getExpr(),
((Group) tupleExpr).getGroupElements().get(1).getOperator());
assertTrue(((Group) tupleExpr).getGroupElements().get(0).getOperator().isDistinct());
} finally {
CustomAggregateFunctionRegistry.getInstance().remove(factory);
}
}
@Test
public void testProjectionHandling_MultipleAggregateFunction() {
var factory = buildDummyFactory();
try {
CustomAggregateFunctionRegistry.getInstance().add(factory);
String query = "prefix rj: <https://www.rdf4j.org/aggregate#>"
+ "SELECT (rj:x(distinct ?o) AS ?o1) (sum(?o) AS ?o2)\n"
+ "WHERE {\n"
+ " ?s ?p ?o \n"
+ "} GROUP BY ?s ?o";
var tupleExpr = parser.parseQuery(query, null).getTupleExpr();
verifySerializable(tupleExpr);
assertTrue(tupleExpr instanceof QueryRoot);
tupleExpr = ((QueryRoot) tupleExpr).getArg();
assertTrue(tupleExpr instanceof Projection);
tupleExpr = ((Projection) tupleExpr).getArg();
assertTrue(tupleExpr instanceof Extension);
var extensionElements = ((Extension) tupleExpr).getElements();
assertEquals(2, extensionElements.size());
assertTrue(extensionElements.get(0).getExpr() instanceof AggregateFunctionCall);
assertTrue(extensionElements.get(1).getExpr() instanceof Sum);
assertEquals(factory.getIri(), ((AggregateFunctionCall) extensionElements.get(0).getExpr()).getIRI());
tupleExpr = ((Extension) tupleExpr).getArg();
assertTrue(tupleExpr instanceof Group);
assertEquals(2, ((Group) tupleExpr).getGroupElements().size());
assertEquals(extensionElements.get(0).getExpr(),
((Group) tupleExpr).getGroupElements().get(0).getOperator());
assertTrue(((Group) tupleExpr).getGroupElements().get(0).getOperator().isDistinct());
} finally {
CustomAggregateFunctionRegistry.getInstance().remove(factory);
}
}
@Test
public void testProjectionHandling_UnknownFunctionCallWithDistinct() {
String query = "prefix rj: <https://www.rdf4j.org/aggregate#>"
+ "SELECT (rj:x(distinct ?o) AS ?o1) \n"
+ "WHERE {\n"
+ " ?s ?p ?o \n"
+ "} GROUP BY ?s ?o";
assertDoesNotThrow(() -> parser.parseQuery(query, null));
}
@Test
public void testProjectionHandling_FunctionCallWithArgsFails() {
var factory = buildDummyFactory();
String query = "prefix rj: <https://www.rdf4j.org/aggregate#>"
+ "SELECT (rj:x(?o, ?p) AS ?o1) \n"
+ "WHERE {\n"
+ " ?s ?p ?o \n"
+ "} GROUP BY ?s ?o";
try {
CustomAggregateFunctionRegistry.getInstance().add(factory);
parser.parseQuery(query, null);
fail("Should not be able to parse function calls with multiple args");
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
} finally {
CustomAggregateFunctionRegistry.getInstance().remove(factory);
}
}
@Test
public void testGroupByProjectionHandling_Aggregate_NonSimpleExpr() {
String query = "SELECT (COUNT(?s) as ?count) (?o + ?s AS ?o1) \n"
+ "WHERE {\n"
+ " ?s ?p ?o \n"
+ "} GROUP BY ?o";
assertThatExceptionOfType(MalformedQueryException.class).isThrownBy(() -> parser.parseQuery(query, null))
.withMessageStartingWith("non-aggregate expression 'MathExpr (+)");
}
@Test
public void testGroupByProjectionHandling_Aggregate_Alias() {
String query = "SELECT (COUNT(?s) as ?count) (?o AS ?o1) \n"
+ "WHERE {\n"
+ " ?s ?p ?o \n"
+ "} GROUP BY ?o";
// should parse without error
ParsedQuery parsedQuery = parser.parseQuery(query, null);
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
}
@Test
public void testGroupByProjectionHandling_Aggregate_Alias2() {
String query = "SELECT (COUNT(?s) as ?count) (?o AS ?o1) \n"
+ "WHERE {\n"
+ " ?s ?p ?o \n"
+ "} GROUP BY ?p";
assertThatExceptionOfType(MalformedQueryException.class).isThrownBy(() -> parser.parseQuery(query, null))
.withMessageStartingWith("non-aggregate expression 'Var (name=o)");
}
@Test
public void testGroupByProjectionHandling_Aggregate_SimpleExpr() {
String query = "SELECT (COUNT(?s) as ?count) ?o \n"
+ "WHERE {\n"
+ " ?s ?p ?o \n"
+ "} GROUP BY ?p";
assertThatExceptionOfType(MalformedQueryException.class).isThrownBy(() -> parser.parseQuery(query, null))
.withMessageStartingWith("variable 'o' in projection not present in GROUP BY.");
}
@Test
public void testGroupByProjectionHandling_Aggregate_SimpleExpr2() {
String query = "SELECT (COUNT(?s) as ?count) ?o \n"
+ "WHERE {\n"
+ " ?s ?p ?o \n"
+ "} GROUP BY ?o";
// should parse without error
ParsedQuery parsedQuery = parser.parseQuery(query, null);
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
}
@Test
public void testGroupByProjectionHandling_Aggregate_Constant() {
String query = "SELECT (COUNT(?s) as ?count) (<foo:constant> as ?constant) \n"
+ "WHERE {\n"
+ " ?s ?p ?o \n"
+ "} GROUP BY ?o";
// should parse without error
ParsedQuery parsedQuery = parser.parseQuery(query, null);
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
}
@Test
public void testGroupByProjectionHandling_variableEffectivelyAggregationResult() {
String query = "SELECT (COUNT (*) AS ?count) (?count / ?count AS ?result) (?result AS ?temp) (?temp / 2 AS ?temp2) {\n"
+
" ?s a ?o .\n" +
"}";
// should parse without error
ParsedQuery parsedQuery = parser.parseQuery(query, null);
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
}
@Test
public void testGroupByProjectionHandling_effectivelyConstant() {
String query = "SELECT (2 AS ?constant1) (?constant1 AS ?constant2) (?constant2/2 AS ?constant3){\n" +
" ?o ?p ?o .\n" +
"} GROUP BY ?o";
// should parse without error
ParsedQuery parsedQuery = parser.parseQuery(query, null);
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
}
@Test
public void testGroupByProjectionHandling_renameVariable() {
String query = "SELECT ?o (?o AS ?o2) (?o2 AS ?o3) (?o3/2 AS ?o4){\n" +
" ?o ?p ?o .\n" +
"} GROUP BY ?o";
// should parse without error
ParsedQuery parsedQuery = parser.parseQuery(query, null);
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
}
@Test
public void testGroupByProjectionHandling_renameVariableWithAggregation() {
String query = "SELECT ?o (?o AS ?o2) (COUNT (*) AS ?count) (?o2/?count AS ?newCount){\n" +
" ?o ?p ?o .\n" +
"} GROUP BY ?o";
// should parse without error
ParsedQuery parsedQuery = parser.parseQuery(query, null);
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
}
@Test
public void testDefaultPrefixes() {
String query = "SELECT ?s {?s ex:aaa ex:ooo}";
Set<Namespace> defaultPrefixes = new HashSet<>();
defaultPrefixes.add(new SimpleNamespace("ex", "http://example.org/"));
SPARQLParser parser = new SPARQLParser(defaultPrefixes);
ParsedQuery parsedQuery = parser.parseQuery(query, null);
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
}
@Test
public void testDefaultPrefixesOverride() {
final String namespaceEx1 = "http://example1.org/";
final String namespaceEx2 = "http://example2.org/";
assertNotEquals(namespaceEx1, namespaceEx2);
String query = "PREFIX ex: <" + namespaceEx1 + "> CONSTRUCT {?s ex:bbb ex:ccc} WHERE {?s ex:aaa ex:ooo}";
Set<Namespace> defaultPrefixes = new HashSet<>();
defaultPrefixes.add(new SimpleNamespace("ex", namespaceEx2));
SPARQLParser parser = new SPARQLParser(defaultPrefixes);
ParsedQuery parsedQuery = parser.parseQuery(query, null);
assertInstanceOf(ParsedGraphQuery.class, parsedQuery);
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
ParsedGraphQuery parsedGraphQuery = (ParsedGraphQuery) parsedQuery;
assertEquals(namespaceEx1, parsedGraphQuery.getQueryNamespaces().get("ex"));
}
@Test
public void testDefaultPrefixesNoOverride() {
final String namespaceEx1 = "http://example1.org/";
String query = "CONSTRUCT {?s ex:bbb ex:ccc} WHERE {?s ex:aaa ex:ooo}";
Set<Namespace> defaultPrefixes = new HashSet<>();
defaultPrefixes.add(new SimpleNamespace("ex", namespaceEx1));
SPARQLParser parser = new SPARQLParser(defaultPrefixes);
ParsedQuery parsedQuery = parser.parseQuery(query, null);
assertInstanceOf(ParsedGraphQuery.class, parsedQuery);
TupleExpr tupleExpr = parsedQuery.getTupleExpr();
verifySerializable(tupleExpr);
ParsedGraphQuery parsedGraphQuery = (ParsedGraphQuery) parsedQuery;
assertEquals(namespaceEx1, parsedGraphQuery.getQueryNamespaces().get("ex"));
}
@Test
public void testNoDefaultPrefixes() {
assertThrows(MalformedQueryException.class, () -> {
String query = "SELECT ?s {?s ex:aaa ex:ooo}";
HashSet<Namespace> customPrefixes = new HashSet<>();
SPARQLParser parser = new SPARQLParser(customPrefixes);
parser.parseQuery(query, null);
});
}
@Test
public void testDefaultPrefixesInsertDataOverride() throws IOException {
final String namespaceEx1 = "http://example1.org/";
final String namespaceEx2 = "http://example2.org/";
String query = "PREFIX ex: <" + namespaceEx2 + "> INSERT DATA { ex:A ex:P ex:B . }";
Set<Namespace> defaultPrefixes = new HashSet<>();
defaultPrefixes.add(new SimpleNamespace("ex", namespaceEx1));
SPARQLParser parser = new SPARQLParser(defaultPrefixes);
ParsedUpdate parsedUpdate = parser.parseUpdate(query, null);
assertEquals(1, parsedUpdate.getUpdateExprs().size());
UpdateExpr expr = parsedUpdate.getUpdateExprs().get(0);
verifySerializable(expr);
assertInstanceOf(InsertData.class, expr);
Model parse = Rio.parse(new StringReader(((InsertData) expr).getDataBlock()), RDFFormat.TURTLE);
Iterable<Statement> iterable = parse.getStatements(null, null, null);
Iterator<Statement> it = iterable.iterator();
assertTrue(it.hasNext());
Statement line = it.next();
assertFalse(it.hasNext());
assertEquals(namespaceEx2 + "A", line.getSubject().stringValue());
assertEquals(namespaceEx2 + "P", line.getPredicate().stringValue());
assertEquals(namespaceEx2 + "B", line.getObject().stringValue());
}
@Test
public void testDefaultPrefixesInsertDataInsideOverride() throws IOException {
final String namespaceEx1 = "http://example1.org/";
final String namespaceEx2 = "http://example2.org/";
String query = "INSERT DATA { PREFIX ex: <" + namespaceEx2 + "> ex:A ex:P ex:B . }";
Set<Namespace> defaultPrefixes = new HashSet<>();
defaultPrefixes.add(Values.namespace("ex", namespaceEx1));
SPARQLParser parser = new SPARQLParser(defaultPrefixes);
ParsedUpdate parsedUpdate = parser.parseUpdate(query, null);
assertEquals(1, parsedUpdate.getUpdateExprs().size());
UpdateExpr expr = parsedUpdate.getUpdateExprs().get(0);
verifySerializable(expr);
assertInstanceOf(InsertData.class, expr);
Model parse = Rio.parse(new StringReader(((InsertData) expr).getDataBlock()), RDFFormat.TURTLE);
Iterable<Statement> iterable = parse.getStatements(null, null, null);
Iterator<Statement> it = iterable.iterator();
assertTrue(it.hasNext());
Statement line = it.next();
assertFalse(it.hasNext());
assertEquals(namespaceEx2 + "A", line.getSubject().stringValue());
assertEquals(namespaceEx2 + "P", line.getPredicate().stringValue());
assertEquals(namespaceEx2 + "B", line.getObject().stringValue());
}
@Test
public void testDefaultPrefixesInsertDataNoOverride() throws IOException {
final String namespaceEx1 = "http://example1.org/";
String query = "INSERT DATA { ex:A ex:P ex:B . }";
Set<Namespace> defaultPrefixes = new HashSet<>();
defaultPrefixes.add(Values.namespace("ex", namespaceEx1));
SPARQLParser parser = new SPARQLParser(defaultPrefixes);
ParsedUpdate parsedUpdate = parser.parseUpdate(query, null);
assertEquals(1, parsedUpdate.getUpdateExprs().size());
UpdateExpr expr = parsedUpdate.getUpdateExprs().get(0);
verifySerializable(expr);
assertInstanceOf(InsertData.class, expr);
Model parse = Rio.parse(new StringReader(((InsertData) expr).getDataBlock()), RDFFormat.TURTLE);
Iterable<Statement> iterable = parse.getStatements(null, null, null);
Iterator<Statement> it = iterable.iterator();
assertTrue(it.hasNext());
Statement line = it.next();
assertFalse(it.hasNext());
assertEquals(namespaceEx1 + "A", line.getSubject().stringValue());
assertEquals(namespaceEx1 + "P", line.getPredicate().stringValue());
assertEquals(namespaceEx1 + "B", line.getObject().stringValue());
}
@Test
public void testDefaultPrefixesDeleteDataOverride() throws IOException {
final String namespaceEx1 = "http://example1.org/";
final String namespaceEx2 = "http://example2.org/";
String query = "PREFIX ex: <" + namespaceEx2 + "> DELETE DATA { ex:A ex:P ex:B . }";
Set<Namespace> defaultPrefixes = new HashSet<>();
defaultPrefixes.add(Values.namespace("ex", namespaceEx1));
SPARQLParser parser = new SPARQLParser(defaultPrefixes);
ParsedUpdate parsedUpdate = parser.parseUpdate(query, null);
assertEquals(1, parsedUpdate.getUpdateExprs().size());
UpdateExpr expr = parsedUpdate.getUpdateExprs().get(0);
verifySerializable(expr);
assertInstanceOf(DeleteData.class, expr);
Model parse = Rio.parse(new StringReader(((DeleteData) expr).getDataBlock()), RDFFormat.TURTLE);
Iterable<Statement> iterable = parse.getStatements(null, null, null);
Iterator<Statement> it = iterable.iterator();
assertTrue(it.hasNext());
Statement line = it.next();
assertFalse(it.hasNext());
assertEquals(namespaceEx2 + "A", line.getSubject().stringValue());
assertEquals(namespaceEx2 + "P", line.getPredicate().stringValue());
assertEquals(namespaceEx2 + "B", line.getObject().stringValue());
}
@Test
public void testDefaultPrefixesDeleteDataInsideOverride() throws IOException {
final String namespaceEx1 = "http://example1.org/";
final String namespaceEx2 = "http://example2.org/";
String query = "DELETE DATA { PREFIX ex: <" + namespaceEx2 + "> ex:A ex:P ex:B . }";
Set<Namespace> defaultPrefixes = new HashSet<>();
defaultPrefixes.add(Values.namespace("ex", namespaceEx1));
SPARQLParser parser = new SPARQLParser(defaultPrefixes);
ParsedUpdate parsedUpdate = parser.parseUpdate(query, null);
assertEquals(1, parsedUpdate.getUpdateExprs().size());
UpdateExpr expr = parsedUpdate.getUpdateExprs().get(0);
verifySerializable(expr);
assertInstanceOf(DeleteData.class, expr);
Model parse = Rio.parse(new StringReader(((DeleteData) expr).getDataBlock()), RDFFormat.TURTLE);
Iterable<Statement> iterable = parse.getStatements(null, null, null);
Iterator<Statement> it = iterable.iterator();
assertTrue(it.hasNext());
Statement line = it.next();
assertFalse(it.hasNext());
assertEquals(namespaceEx2 + "A", line.getSubject().stringValue());
assertEquals(namespaceEx2 + "P", line.getPredicate().stringValue());
assertEquals(namespaceEx2 + "B", line.getObject().stringValue());
}
@Test
public void testDefaultPrefixesDeleteDataNoOverride() throws IOException {
final String namespaceEx1 = "http://example1.org/";
String query = "DELETE DATA { ex:A ex:P ex:B . }";
Set<Namespace> defaultPrefixes = new HashSet<>();
defaultPrefixes.add(Values.namespace("ex", namespaceEx1));
SPARQLParser parser = new SPARQLParser(defaultPrefixes);
ParsedUpdate parsedUpdate = parser.parseUpdate(query, null);
assertEquals(1, parsedUpdate.getUpdateExprs().size());
UpdateExpr expr = parsedUpdate.getUpdateExprs().get(0);
verifySerializable(expr);
assertInstanceOf(DeleteData.class, expr);
Model parse = Rio.parse(new StringReader(((DeleteData) expr).getDataBlock()), RDFFormat.TURTLE);
Iterable<Statement> iterable = parse.getStatements(null, null, null);
Iterator<Statement> it = iterable.iterator();
assertTrue(it.hasNext());
Statement line = it.next();
assertFalse(it.hasNext());
assertEquals(namespaceEx1 + "A", line.getSubject().stringValue());
assertEquals(namespaceEx1 + "P", line.getPredicate().stringValue());
assertEquals(namespaceEx1 + "B", line.getObject().stringValue());
}
@Test
public void testGroupByProjectionHandling_function() {
String query = "select (strlen(concat(strlen(?s)+2, \"abc\", count(?o))) as ?len) where { \n" +
"?s ?p ?o .\n" +
"} group by ?s";
// should parse without error
ParsedQuery parsedQuery = parser.parseQuery(query, null);
verifySerializable(parsedQuery.getTupleExpr());
}
@Test
public void testApostrophe() {
String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n" +
"select * where { \n" +
" rdf:Test\\'s ?p1 ?o1\n" +
"}";
HashSet<Namespace> customPrefixes = new HashSet<>();
SPARQLParser parser = new SPARQLParser(customPrefixes);
parser.parseQuery(query, null);
}
@Test
public void testApostropheInsertData() {
String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n" +
"INSERT DATA { \n" +
" rdf:Test\\'s a <http://example.com/Test> .\n" +
"}";
HashSet<Namespace> customPrefixes = new HashSet<>();
SPARQLParser parser = new SPARQLParser(customPrefixes);
parser.parseUpdate(query, null);
}
private AggregateFunctionFactory buildDummyFactory() {
return new AggregateFunctionFactory() {
@Override
public String getIri() {
return "https://www.rdf4j.org/aggregate#x";
}
@Override
public AggregateFunction buildFunction(Function<BindingSet, Value> evaluationStep) {
return null;
}
@Override
public AggregateCollector getCollector() {
return null;
}
};
}
private void verifySerializable(QueryModelNode tupleExpr) {
byte[] bytes = objectToBytes(tupleExpr);
QueryModelNode parsed = (QueryModelNode) bytesToObject(bytes);
assertEquals(tupleExpr, parsed);
}
private byte[] objectToBytes(Serializable object) {
try (var byteArrayOutputStream = new ByteArrayOutputStream()) {
try (var objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) {
objectOutputStream.writeObject(object);
}
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private Object bytesToObject(byte[] str) {
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(str)) {
try (ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)) {
return objectInputStream.readObject();
}
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}