diff --git a/rdflib/plugins/sparql/_contrib/valuesToTheLeftOfTheJoins.py b/rdflib/plugins/sparql/_contrib/valuesToTheLeftOfTheJoins.py
new file mode 100644
index 000000000..f2193bb8c
--- /dev/null
+++ b/rdflib/plugins/sparql/_contrib/valuesToTheLeftOfTheJoins.py
@@ -0,0 +1,36 @@
+"""
+Move a VALUES clause to the left of the join.
+This is normally smart as this is often a much shorter list than what is generated
+by the other expression.
+"""
+
+from typing import Any
+
+from rdflib.plugins.sparql.sparql import Query
+
+
+class ValuesToTheLeftOfTheJoin:
+ @classmethod
+ def translate(cls, query: Query) -> Query:
+ main = query.algebra
+ query.algebra = ValuesToTheLeftOfTheJoin._optimize_node(main)
+ return query
+
+ @classmethod
+ def _optimize_node(cls, cv: Any) -> Any:
+ if cv.name == "Join":
+ if cv.p1.name != "ToMultiSet" and "ToMultiSet" == cv.p2.name:
+ cv.update(p1=cv.p2, p2=cv.p1)
+ else:
+ op1 = ValuesToTheLeftOfTheJoin._optimize_node(cv.p1)
+ op2 = ValuesToTheLeftOfTheJoin._optimize_node(cv.p2)
+ cv.update(op1, op2)
+ return cv
+ elif cv.p is not None:
+ cv.p.update(ValuesToTheLeftOfTheJoin._optimize_node(cv.p))
+ elif cv.p1 is not None and cv.p2 is not None:
+ cv.p1.update(ValuesToTheLeftOfTheJoin._optimize_node(cv.p1))
+ cv.p2.update(ValuesToTheLeftOfTheJoin._optimize_node(cv.p2))
+ elif cv.p1 is not None:
+ cv.p1.update(ValuesToTheLeftOfTheJoin._optimize_node(cv.p1))
+ return cv
diff --git a/rdflib/plugins/sparql/optimizer.py b/rdflib/plugins/sparql/optimizer.py
new file mode 100644
index 000000000..2556543f6
--- /dev/null
+++ b/rdflib/plugins/sparql/optimizer.py
@@ -0,0 +1,23 @@
+from __future__ import annotations
+
+"""
+This contains standard optimizers for sparql
+
+"""
+import re
+from typing import Any, Callable
+
+from rdflib import Literal
+from rdflib.plugins.sparql.algebra import CompValue, Expr, Join, Values
+from rdflib.plugins.sparql.operators import Builtin_CONTAINS, Builtin_REGEX
+from rdflib.plugins.sparql.sparql import Query
+
+"""
+An interface for having optimizers that transform a query algebra hopefully
+in an faster to evaluate version.
+"""
+
+
+class SPARQLOptimizer:
+ def optimize(self, query: Query) -> Query:
+ return query
diff --git a/rdflib/plugins/sparql/processor.py b/rdflib/plugins/sparql/processor.py
index de97d80bd..a8677d340 100644
--- a/rdflib/plugins/sparql/processor.py
+++ b/rdflib/plugins/sparql/processor.py
@@ -7,7 +7,7 @@
from __future__ import annotations
-from typing import Any, Mapping, Optional, Union
+from typing import Any, Callable, List, Mapping, Optional, Union
from rdflib.graph import Graph
from rdflib.plugins.sparql.algebra import translateQuery, translateUpdate
@@ -106,9 +106,13 @@ def update(
return evalUpdate(self.graph, strOrQuery, initBindings)
+_QueryTranslatorType = Callable[[Query], Query]
+
+
class SPARQLProcessor(Processor):
- def __init__(self, graph):
+ def __init__(self, graph, translators: Optional[List[_QueryTranslatorType]] = None):
self.graph = graph
+ self.translators = translators
# NOTE on type error: this is because the super type constructor does not
# accept base argument and thie position of the DEBUG argument is
@@ -144,4 +148,7 @@ def query( # type: ignore[override]
if isinstance(strOrQuery, str):
strOrQuery = translateQuery(parseQuery(strOrQuery), base, initNs)
+ for translator in self.translators:
+ strOrQuery = translator(strOrQuery)
+
return evalQuery(self.graph, strOrQuery, initBindings, base)
diff --git a/test/test_sparql/test_contrib_query_translators.py b/test/test_sparql/test_contrib_query_translators.py
new file mode 100644
index 000000000..f9de73cb3
--- /dev/null
+++ b/test/test_sparql/test_contrib_query_translators.py
@@ -0,0 +1,71 @@
+from rdflib import Graph
+from rdflib.plugins.sparql._contrib.valuesToTheLeftOfTheJoins import (
+ ValuesToTheLeftOfTheJoin,
+)
+from rdflib.plugins.sparql.parser import *
+
+# from rdflib.plugins.sparql.processor import prepareQuery
+from rdflib.plugins.sparql.processor import parseQuery, translateQuery
+
+query_slow = """
+PREFIX ex:
+
+SELECT ?x {
+ ?x ?y ?z .
+ VALUES (?x) {
+ (ex:1)
+ (ex:2)
+ (ex:3)
+ }
+}
+"""
+
+query_fast = """
+PREFIX ex:
+
+SELECT ?x {
+ VALUES (?x) {
+ (ex:1)
+ (ex:2)
+ (ex:3)
+ }
+ ?x ?y ?z .
+}
+"""
+
+query_regex = """
+PREFIX ex:
+
+SELECT ?x {
+ ?x ?y ?z .
+ FILTER(regex("?z", "hi"))
+}
+"""
+
+query_contains = """
+PREFIX ex:
+
+SELECT ?x {
+ ?x ?y ?z .
+ FILTER(contains("?z", "hi"))
+}
+"""
+
+
+def test_values_to_left():
+ qs = _prepare_query(query_slow)
+ qf = _prepare_query(query_fast)
+ assert qs != qf
+ qso = ValuesToTheLeftOfTheJoin.translate(qs)
+
+ assert qso.algebra == qf.algebra
+
+
+def _prepare_query(str_or_query):
+ parse_tree = parseQuery(str_or_query)
+ query = translateQuery(parse_tree, None, {})
+ return query
+
+
+if __name__ == "__main__":
+ test_values_to_left()