SparqlMinusScopingTests.java
/*******************************************************************************
* Copyright (c) 2025 Eclipse RDF4J contributors.
*
* 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.testsuite.sparql.tests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.io.StringReader;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.MalformedQueryException;
import org.eclipse.rdf4j.query.QueryResults;
import org.eclipse.rdf4j.query.TupleQuery;
import org.eclipse.rdf4j.query.TupleQueryResult;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.testsuite.sparql.AbstractComplianceTest;
import org.junit.jupiter.api.DynamicTest;
public class SparqlMinusScopingTests extends AbstractComplianceTest {
public SparqlMinusScopingTests(Supplier<Repository> repo) {
super(repo);
}
private static final String NS = "http://ex/";
private static final String PREFIX = "PREFIX : <http://ex/>\n";
private static final String TTL = String.join("\n",
"@prefix : <http://ex/> .",
":a :p 1 . :a :q 10 . :a :r 100 .",
":b :p 2 . :b :q 20 . :b :r 200 .",
":c :p 3 . :c :r 300 .",
":d :q 40 . :d :r 400 .",
":e :p 5 . :e :q 50 ."
);
// ---------- Helpers
private static List<BindingSet> select(RepositoryConnection conn, String body) throws IOException {
String sparql = PREFIX + body;
conn.add(new StringReader(TTL), "", RDFFormat.TURTLE);
TupleQuery q = conn.prepareTupleQuery(sparql);
try (TupleQueryResult r = q.evaluate()) {
return QueryResults.asList(r);
}
}
/**
* Run a SELECT with a custom dataset (clears the connection first).
*/
private static List<BindingSet> selectWithData(RepositoryConnection conn, String data, RDFFormat fmt, String body)
throws IOException {
String sparql = PREFIX + body;
conn.clear();
conn.add(new StringReader(data), NS, fmt);
TupleQuery q = conn.prepareTupleQuery(sparql);
try (TupleQueryResult r = q.evaluate()) {
return QueryResults.asList(r);
}
}
private static Set<String> names(List<BindingSet> rows, String var) {
return rows.stream()
.map(bs -> bs.getValue(var))
.filter(Objects::nonNull)
.map(SparqlMinusScopingTests::name)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
private static Set<String> pairs(List<BindingSet> rows, String var1, String var2) {
return rows.stream()
.map(bs -> {
Value v1 = bs.getValue(var1);
Value v2 = bs.getValue(var2);
return (v1 != null && v2 != null) ? name(v1) + "|" + name(v2) : null;
})
.filter(Objects::nonNull)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
private static Set<String> triples(List<BindingSet> rows, String v1, String v2, String v3) {
return rows.stream()
.map(bs -> {
Value a = bs.getValue(v1), b = bs.getValue(v2), c = bs.getValue(v3);
return (a != null && b != null && c != null) ? name(a) + "|" + name(b) + "|" + name(c) : null;
})
.filter(Objects::nonNull)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
private static String name(Value v) {
if (v instanceof IRI) {
IRI iri = (IRI) v;
return iri.getLocalName(); // ex:a -> "a"
}
return v.stringValue();
}
private static Set<String> setOf(String... items) {
return new LinkedHashSet<>(Arrays.asList(items));
}
void T1_bindCreatesFreshVarInRight_NoOverlap_NoEffect(RepositoryConnection conn) throws IOException {
List<BindingSet> rows = select(conn,
"SELECT ?s ?pVal WHERE {\n" +
" ?s :p ?pVal .\n"
+ " MINUS { ?x :q ?qVal . BIND(?qVal*2 AS ?fresh) }\n" +
"}"
);
assertEquals(4, rows.size());
assertEquals(setOf("a", "b", "c", "e"), names(rows, "s"));
}
void T3_bindBeforeUseIntroducesOverlap_EverythingRemoved(RepositoryConnection conn) throws IOException {
List<BindingSet> rows = select(conn,
"SELECT ?s ?qVal WHERE {\n" +
" ?s :q ?qVal .\n" +
" MINUS { ?t :q ?x . BIND(?x AS ?qVal) }\n" +
"}"
);
assertTrue(rows.isEmpty(), "All ?qVal values appear on the right after BIND, so MINUS removes all left rows");
}
void T4_renamedVarsInsideRight_NoTrueOverlap_NoEffect(RepositoryConnection conn) throws IOException {
List<BindingSet> rows = select(conn,
"SELECT ?s ?pVal WHERE {\n" +
" ?s :p ?pVal .\n"
+ " MINUS { ?s2 :p ?pVal2 . BIND(?pVal2 AS ?pVal_tmp) }\n" +
"}"
);
assertEquals(4, rows.size());
assertEquals(setOf("a", "b", "c", "e"), names(rows, "s"));
}
void T5_randInsideDisjointRight_MinusHasNoEffect(RepositoryConnection conn) throws IOException {
List<BindingSet> rows = select(conn,
"SELECT ?s WHERE {\n" +
" ?s :p ?v .\n" +
" MINUS { ?x :q ?w . FILTER(RAND() < 2) }\n" + // disjoint vars -> MINUS no effect by spec
"}"
);
assertEquals(4, rows.size(), "Disjoint-variable MINUS must not remove any rows, regardless of RAND()");
assertEquals(setOf("a", "b", "c", "e"), names(rows, "s"));
}
void T8_projectionExprOnLeftDoesNotAffectMinusOverlap_NoEffect(RepositoryConnection conn) throws IOException {
List<BindingSet> rows = select(conn,
"SELECT ?s ?pVal (STR(?s) AS ?z) WHERE {\n" +
" ?s :p ?pVal .\n"
+ " MINUS { ?x :q ?q . BIND(STR(?x) AS ?z) }\n" + // ?z only exists on the right (left ?z is
// projection-time)
"}"
);
assertEquals(4, rows.size());
assertEquals(setOf("a", "b", "c", "e"), names(rows, "s"));
}
void T9_projectionBeforeMinus_NoSharedVarsAfterSubselect_NoEffect(RepositoryConnection conn) throws IOException {
List<BindingSet> rows = select(conn,
"SELECT ?s WHERE {\n" +
" { SELECT ?s WHERE { ?s :p ?v } }\n" +
" MINUS { ?x :p ?v }\n" + // ?v not projected to the outer level; disjoint wrt left (?s)
"}"
);
assertEquals(4, rows.size());
assertEquals(setOf("a", "b", "c", "e"), names(rows, "s"));
}
void T10_minusVsNotExists_WithThisDataTheyCoincide(RepositoryConnection conn) throws IOException {
List<BindingSet> minusRows = select(conn,
"SELECT ?s WHERE {\n" +
" ?s :p ?v .\n" +
" MINUS { ?s :q ?w }\n" +
"}"
);
assertEquals(setOf("c"), names(minusRows, "s"));
List<BindingSet> notExistsRows = select(conn,
"SELECT ?s WHERE {\n" +
" ?s :p ?v .\n" +
" FILTER NOT EXISTS { ?s :q ?w }\n" +
"}"
);
assertEquals(setOf("c"), names(notExistsRows, "s"));
}
void T11_multipleMinus_sharedThenIndependent_onlyFirstMatters(RepositoryConnection conn) throws IOException {
List<BindingSet> rows = select(conn,
"SELECT ?s WHERE {\n" +
" ?s :p ?v .\n" +
" MINUS { ?s :q ?w } # removes a, b, e\n"
+ " MINUS { ?x :r ?r } # no shared vars -> no further effect\n" +
"}"
);
assertEquals(setOf("c"), names(rows, "s"));
}
void T12_minusInsideOptional_affectsOnlyOptionalGroup(RepositoryConnection conn) throws IOException {
List<BindingSet> rows = select(conn,
"SELECT ?s ?maybe WHERE {\n" +
" ?s :p ?v .\n" +
" OPTIONAL {\n"
+ " BIND(1 AS ?maybe)\n" +
" MINUS { ?s :q ?w }\n" +
" }\n" +
"}"
);
// Build subject -> hasMaybe mapping
Map<String, Boolean> hasMaybe = new LinkedHashMap<>();
for (BindingSet bs : rows) {
String s = name(bs.getValue("s"));
boolean bound = bs.hasBinding("maybe");
hasMaybe.put(s, bound);
}
// With the dataset, only :c lacks :q, so OPTIONAL survives only for c.
assertEquals(4, rows.size());
assertEquals(Boolean.FALSE, hasMaybe.get("a"));
assertEquals(Boolean.FALSE, hasMaybe.get("b"));
assertEquals(Boolean.TRUE, hasMaybe.get("c"));
assertEquals(Boolean.FALSE, hasMaybe.get("e"));
}
void T13_minus_no_shared_vars_is_noop_select(RepositoryConnection conn) throws IOException {
String ttl = "@prefix : <http://ex/> .\n" +
":a :p 1 . :a :q 1 . :b :p 1 .";
List<BindingSet> rows = selectWithData(conn, ttl, RDFFormat.TURTLE,
"SELECT ?s WHERE { ?s :p 1 MINUS { ?x :q 1 } }");
assertEquals(setOf("a", "b"), names(rows, "s"),
"MINUS with disjoint var-sets must keep the LHS intact (��8.3).");
}
void T14_not_exists_contrast_to_minus_no_shared_vars(RepositoryConnection conn) throws IOException {
String ttl = "@prefix : <http://ex/> .\n" +
":a :p 1 . :a :q 1 . :b :p 1 .";
List<BindingSet> rows = selectWithData(conn, ttl, RDFFormat.TURTLE,
"SELECT ?s WHERE { ?s :p 1 FILTER NOT EXISTS { ?x :q 1 } }");
assertTrue(rows.isEmpty(), "NOT EXISTS is correlated and removes all rows when { ?x :q 1 } exists (��8.3).");
}
void T15_rhs_filter_referencing_outer_var_is_unbound_and_ignored(RepositoryConnection conn) throws IOException {
String ttl = "@prefix : <http://ex/> .\n" +
":a :p 1 ; :q 1, 2 .\n" +
":b :p 3 ; :q 4 .";
List<BindingSet> rows = selectWithData(conn, ttl, RDFFormat.TURTLE,
"SELECT ?x ?n WHERE {\n" +
" ?x :p ?n .\n" +
" MINUS { ?x :q ?m . FILTER(?m = ?n) } # ?n unbound on RHS ��� filter errors ��� RHS empty\n" +
"} ORDER BY ?x");
assertEquals(setOf("a|1", "b|3"), pairs(rows, "x", "n"),
"RHS filter sees no outer vars under MINUS; subtract nothing (��8.3).");
}
void T16_rhs_bind_of_outer_var_produces_unbound_then_overremoves_on_shared_subset(RepositoryConnection conn)
throws IOException {
String ttl = "@prefix : <http://ex/> .\n" +
":a :p 1 ; :q 1, 2 .\n" +
":b :p 3 ; :q 4 .\n" +
":c :p 7 .";
List<BindingSet> rows = selectWithData(conn, ttl, RDFFormat.TURTLE,
"SELECT ?x WHERE {\n" +
" ?x :p ?n .\n" +
" MINUS { BIND(?n AS ?k) ?x :q ?k }\n" +
"} ORDER BY ?x");
assertEquals(setOf("c"), names(rows, "x"),
"RHS BIND on unbound outer var must not correlate; shared-vars logic should remove :a,:b only.");
}
void T17_rhs_bind_creates_intentional_shared_var_equalities(RepositoryConnection conn) throws IOException {
String ttl = "@prefix : <http://ex/> .\n" +
":e :p 10 ; :q 42 .\n" +
":f :p 20 ; :q 20 .\n" +
":g :p 30 ; :q 99 .";
List<BindingSet> rows = selectWithData(conn, ttl, RDFFormat.TURTLE,
"SELECT ?x WHERE {\n" +
" ?x :p ?v .\n" +
" MINUS { ?x :q ?m . BIND(?m AS ?v) } # removes only when q==p\n" +
"} ORDER BY ?x");
assertEquals(setOf("e", "g"), names(rows, "x"),
"Only :f should be removed (q==p). Early projection must NOT change shared vars.");
}
void T18_early_projection_should_not_change_minus_semantics(RepositoryConnection conn) throws IOException {
String ttl = "@prefix : <http://ex/> .\n" +
":e :p 10 ; :q 42 .\n" +
":f :p 20 ; :q 20 .\n" +
":g :p 30 ; :q 99 .";
List<BindingSet> rows = selectWithData(conn, ttl, RDFFormat.TURTLE,
"SELECT ?x WHERE { ?x :p ?v MINUS { ?x :q ?v } } ORDER BY ?x");
assertEquals(setOf("e", "g"), names(rows, "x"),
"Pushing projection before MINUS would wrongly remove :e and :g; don't do that.");
}
void T19_subquery_pins_shared_var_against_optimizer(RepositoryConnection conn) throws IOException {
String ttl = "@prefix : <http://ex/> .\n" +
":e :p 10 ; :q 42 .\n" +
":f :p 20 ; :q 20 .\n" +
":g :p 30 ; :q 99 .";
List<BindingSet> rows = selectWithData(conn, ttl, RDFFormat.TURTLE,
"SELECT ?x WHERE {\n" +
" { SELECT ?x ?v WHERE { ?x :p ?v } } # box the LHS\n" +
" MINUS { ?x :q ?v }\n" +
"} ORDER BY ?x");
assertEquals(setOf("e", "g"), names(rows, "x"), "Subquery must preserve shared vars until MINUS.");
}
void T20_optional_inside_minus_only_removes_when_optional_matches(RepositoryConnection conn) throws IOException {
String ttl = "@prefix : <http://ex/> .\n" +
":e :name \"Alice\" ; :formerName \"Alice\" .\n" +
":f :name \"Carol\" .";
List<BindingSet> rows = selectWithData(conn, ttl, RDFFormat.TURTLE,
"SELECT ?x WHERE {\n" +
" ?x :name ?n .\n" +
" MINUS { OPTIONAL { ?x :formerName ?n } }\n" +
"} ORDER BY ?x");
assertEquals(setOf("f"), names(rows, "x"),
"OPTIONAL inside MINUS: only rows for which the OPTIONAL binds compatibly are removed.");
}
void T21_not_exists_over_optional_is_always_false_here(RepositoryConnection conn) throws IOException {
String ttl = "@prefix : <http://ex/> .\n" +
":e :name \"Alice\" ; :formerName \"Alice\" .\n" +
":f :name \"Carol\" .";
List<BindingSet> rows = selectWithData(conn, ttl, RDFFormat.TURTLE,
"SELECT ?x WHERE {\n" +
" ?x :name ?n .\n" +
" FILTER NOT EXISTS { OPTIONAL { ?x :formerName ?n } }\n" +
"}");
assertEquals(List.of(), rows);
}
void T22_graph_isolation_same_g_on_both_sides_no_removal_when_values_differ(RepositoryConnection conn)
throws IOException {
String trig = "@prefix : <http://ex/> .\n" +
"GRAPH :g1 { :a :p 1 . }\n" +
"GRAPH :g2 { :a :q 1 . :a :p 2 . }";
List<BindingSet> rows = selectWithData(conn, trig, RDFFormat.TRIG,
"SELECT ?g ?x ?n WHERE {\n" +
" GRAPH ?g { ?x :p ?n }\n" +
" MINUS { GRAPH ?g { ?x :q ?n } }\n" +
"} ORDER BY ?g ?x ?n");
assertEquals(setOf("g1|a|1", "g2|a|2"), triples(rows, "g", "x", "n"),
"Active graph must be respected on the RHS as well (��13.3).");
}
void T23_graph_isolation_removes_only_in_graph_where_match_exists(RepositoryConnection conn) throws IOException {
String trig = "@prefix : <http://ex/> .\n" +
"GRAPH :g1 { :a :p 1 . }\n" +
"GRAPH :g2 { :a :q 1 . :a :p 2 . :a :q 2 . }";
List<BindingSet> rows = selectWithData(conn, trig, RDFFormat.TRIG,
"SELECT ?g ?x ?n WHERE {\n" +
" GRAPH ?g { ?x :p ?n }\n" +
" MINUS { GRAPH ?g { ?x :q ?n } }\n" +
"} ORDER BY ?g");
assertEquals(setOf("g1|a|1"), triples(rows, "g", "x", "n"),
"Only the :g2 row should be removed because :q 2 exists in :g2.");
}
void T24_minus_disjoint_varsets_is_noop_even_with_union_on_lhs(RepositoryConnection conn) throws IOException {
String ttl = "@prefix : <http://ex/> .\n" +
":a :p 1 ; :q 1 .\n" +
":b :p 1 .\n" +
":c :p 2 .";
List<BindingSet> rows = selectWithData(conn, ttl, RDFFormat.TURTLE,
"SELECT ?x WHERE {\n" +
" { ?x :p 1 } UNION { ?x :p 2 }\n" +
" MINUS { ?y :q 1 }\n" +
"} ORDER BY ?x");
assertEquals(setOf("a", "b", "c"), names(rows, "x"), "No shared vars ��� MINUS must be a no-op (��8.3).");
}
void T25_values_left_only_no_shared_vars_is_noop(RepositoryConnection conn) throws IOException {
String ttl = "@prefix : <http://ex/> . :a :q 1 .";
List<BindingSet> rows = selectWithData(conn, ttl, RDFFormat.TURTLE,
"SELECT ?a WHERE { VALUES ?a { 1 2 } MINUS { ?x :q 1 } } ORDER BY ?a");
assertEquals(setOf("1", "2"), names(rows, "a"),
"VALUES introduces no shared vars with RHS, so MINUS removes nothing.");
}
void T26_minus_shared_subset_only_subject_shared_removes_all_rows_for_that_subject(RepositoryConnection conn)
throws IOException {
String ttl = "@prefix : <http://ex/> .\n" +
":a :p 1 ; :q 99 .\n" +
":b :p 2 .";
List<BindingSet> rows = selectWithData(conn, ttl, RDFFormat.TURTLE,
"SELECT ?x ?n WHERE { ?x :p ?n MINUS { ?x :q ?m } } ORDER BY ?x");
assertEquals(setOf("b|2"), pairs(rows, "x", "n"),
"Since only ?x is shared, any :q for :a kills *all* its :p rows.");
}
void T27_rhs_subselect_order_by_limit_one_global_elimination(RepositoryConnection conn) throws IOException {
String ttl = "@prefix : <http://ex/> .\n" +
":a :p 1 ; :q 1 .\n" +
":b :p 2 ; :q 2 .\n" +
":c :p 3 ; :q 3 .";
List<BindingSet> rows = selectWithData(conn, ttl, RDFFormat.TURTLE,
"SELECT ?x WHERE {\n" +
" ?x :p ?n .\n" +
" MINUS {\n" +
" { SELECT ?x WHERE { ?x :q ?m } ORDER BY ?m LIMIT 1 }\n" +
" }\n" +
"} ORDER BY ?x");
assertEquals(setOf("b", "c"), names(rows, "x"),
"Flattening/pushing ORDER BY/LIMIT across MINUS would change which row is removed.");
}
void T28_bnode_function_on_rhs_cannot_match_data_terms(RepositoryConnection conn) throws IOException {
String ttl = "@prefix : <http://ex/> .\n" +
"_:b1 a [] . _:b2 a [] .\n" +
":k :p _:b1 . :l :p _:b2 .";
List<BindingSet> rows = selectWithData(conn, ttl, RDFFormat.TURTLE,
"SELECT ?s WHERE { ?s :p ?id MINUS { BIND(BNODE() AS ?id) } } ORDER BY ?s");
assertEquals(setOf("k", "l"), names(rows, "s"),
"BNODE() creates fresh, distinct bnodes ��� cannot match dataset objects, so MINUS is a no-op here.");
}
void T29_syntax_error_rebinding_in_rhs_must_fail_to_parse(RepositoryConnection conn) {
String body = "SELECT * WHERE {\n" +
" ?x :p ?v .\n" +
" MINUS { ?x :q ?v . BIND(1 AS ?v) } # re-binding ?v inside same RHS group is illegal\n" +
"}";
assertThrows(MalformedQueryException.class, () -> conn.prepareTupleQuery(PREFIX + body),
"BIND target must not have been used earlier in the same group; parser should reject.");
}
public Stream<DynamicTest> tests() {
return Stream.of(
makeTest("T1_bindCreatesFreshVarInRight_NoOverlap_NoEffect",
this::T1_bindCreatesFreshVarInRight_NoOverlap_NoEffect),
makeTest("T3_bindBeforeUseIntroducesOverlap_EverythingRemoved",
this::T3_bindBeforeUseIntroducesOverlap_EverythingRemoved),
makeTest("T4_renamedVarsInsideRight_NoTrueOverlap_NoEffect",
this::T4_renamedVarsInsideRight_NoTrueOverlap_NoEffect),
makeTest("T5_randInsideDisjointRight_MinusHasNoEffect",
this::T5_randInsideDisjointRight_MinusHasNoEffect),
makeTest("T8_projectionExprOnLeftDoesNotAffectMinusOverlap_NoEffect",
this::T8_projectionExprOnLeftDoesNotAffectMinusOverlap_NoEffect),
makeTest("T9_projectionBeforeMinus_NoSharedVarsAfterSubselect_NoEffect",
this::T9_projectionBeforeMinus_NoSharedVarsAfterSubselect_NoEffect),
makeTest("T10_minusVsNotExists_WithThisDataTheyCoincide",
this::T10_minusVsNotExists_WithThisDataTheyCoincide),
makeTest("T11_multipleMinus_sharedThenIndependent_onlyFirstMatters",
this::T11_multipleMinus_sharedThenIndependent_onlyFirstMatters),
makeTest("T12_minusInsideOptional_affectsOnlyOptionalGroup",
this::T12_minusInsideOptional_affectsOnlyOptionalGroup),
makeTest("T13_minus_no_shared_vars_is_noop_select", this::T13_minus_no_shared_vars_is_noop_select),
makeTest("T14_not_exists_contrast_to_minus_no_shared_vars",
this::T14_not_exists_contrast_to_minus_no_shared_vars),
makeTest("T15_rhs_filter_referencing_outer_var_is_unbound_and_ignored",
this::T15_rhs_filter_referencing_outer_var_is_unbound_and_ignored),
makeTest("T16_rhs_bind_of_outer_var_produces_unbound_then_overremoves_on_shared_subset",
this::T16_rhs_bind_of_outer_var_produces_unbound_then_overremoves_on_shared_subset),
makeTest("T17_rhs_bind_creates_intentional_shared_var_equalities",
this::T17_rhs_bind_creates_intentional_shared_var_equalities),
makeTest("T18_early_projection_should_not_change_minus_semantics",
this::T18_early_projection_should_not_change_minus_semantics),
makeTest("T19_subquery_pins_shared_var_against_optimizer",
this::T19_subquery_pins_shared_var_against_optimizer),
makeTest("T20_optional_inside_minus_only_removes_when_optional_matches",
this::T20_optional_inside_minus_only_removes_when_optional_matches),
makeTest("T21_not_exists_over_optional_is_always_false_here",
this::T21_not_exists_over_optional_is_always_false_here),
makeTest("T22_graph_isolation_same_g_on_both_sides_no_removal_when_values_differ",
this::T22_graph_isolation_same_g_on_both_sides_no_removal_when_values_differ),
makeTest("T23_graph_isolation_removes_only_in_graph_where_match_exists",
this::T23_graph_isolation_removes_only_in_graph_where_match_exists),
makeTest("T24_minus_disjoint_varsets_is_noop_even_with_union_on_lhs",
this::T24_minus_disjoint_varsets_is_noop_even_with_union_on_lhs),
makeTest("T25_values_left_only_no_shared_vars_is_noop",
this::T25_values_left_only_no_shared_vars_is_noop),
makeTest("T26_minus_shared_subset_only_subject_shared_removes_all_rows_for_that_subject",
this::T26_minus_shared_subset_only_subject_shared_removes_all_rows_for_that_subject),
makeTest("T27_rhs_subselect_order_by_limit_one_global_elimination",
this::T27_rhs_subselect_order_by_limit_one_global_elimination),
makeTest("T28_bnode_function_on_rhs_cannot_match_data_terms",
this::T28_bnode_function_on_rhs_cannot_match_data_terms),
makeTest("T29_syntax_error_rebinding_in_rhs_must_fail_to_parse",
this::T29_syntax_error_rebinding_in_rhs_must_fail_to_parse)
);
}
}