I once started with query containment for a caching system, but in the end I settled for the simpler and more tractable approach the led to development of the Service Enhancer [1]: Instead of the system trying to guess what might be worth caching, the user declares it explicitly with a SERVICE <cache:> { } block.

So as an idea, the same concept could be adapted to your use case:
You could use the SERVICE block to request authorization of the given pattern:

SELECT ?o {
  { SERVICE <authorize:> { # Request authorization to execute the following pattern:

    SELECT ?s ?o {
      ?s <foo> ?o
     }
  } }
  FILTER(regex(?s), 'foo') # Post-filtering outside of the authorization request.
}

From there one, all operations become quite straight forward:
If you want a standard SPARQL query back, you can easily get rid of those SERVICE block using an ElementTransformBase subclass that returns the body for SERVICE <authorize:> {} patterns.

You can then also visitors (Element or Op) to reject queries based on the used features. For example, you may want to reject any triple pattern / quad that appears outside of an authorize block. You could also reject queries with more than 1 authorize block (no joins allowed).

You could easily extend authorize to allow for structural equivalence:
Iterate the mentions of variables in order, rename each variable to a fresh one based on the ordinal of its first mention.

So the pattern above would be normalized to:
SERVICE <authorize:> {
  SELECT ?v1 ?v2 {
    ?v1 <foo> ?v2
  }
}
This way you can check authorization based on the canonical variable naming, without having to resort to checking semantic equivalence of algebra expressions.

Because trying to match two queries with query containment is quite hard to make it work in a smart way. Should a different order of UNION members still match? What about these cases:
{ BIND(:o AS ?o) ?s ?p ?o }
{ ?s ?p ?o FILTER(?o = :o) }
{ ?s ?p :o }
{ ?s ?p ?o FILTER(sameTerm(?o, :o)) }
{ ?s ?p ?o FILTER(?o = :o || ?o = :x) }
{ ?s ?p ?o FILTER(?o IN (:o, :x)) }


Related: Stardog supports "stored queries" - which is essentially a map from name to query. SERVICE <query://NAME> can be used to "transclude" stored queries.
A small custom vocab allows to map variables. See [2].

SELECT (COUNT(*) AS ?c) {
  SERVICE <query://salaries> {  []  sqs:var:department ?d }
  FILTER (?d NOT IN (:marketing))
}

Cheers,
Claus

[1] https://jena.apache.org/documentation/query/service_enhancer.html
[2] https://docs.stardog.com/query-stardog/stored-query-service



On 3/13/26 10:25, Martynas Jusevičius wrote:
I want to do this at the algebra level :) The use case is a DDoS protection
request filter that only allows "instantiations" of pre-defined query
templates.

On Fri, Mar 6, 2026 at 12:53 AM Justin Dowdy <[email protected]> wrote:

It seems like one query is "isomorphic" to another query if and only if
they produce the same bindings when evaluated against all possible data
sets.

If you want to do this in the general case you might need a way to simulate
all possible data sets then evaluate the queries against them, right?

On Thu, Mar 5, 2026, 2:37 PM Martynas Jusevičius <[email protected]>
wrote:

Hi,

What would be the way to check programatically using Jena that

{ <http://localhost/> rdf:type ?x }

is a substitution of a binding (?s = <http://localhost/>) or VALUES (?s)
{ <
http://localhost/> }

on

{ ?s rdf:type ?type }

but

{ <http://localhost/> another:property ?x }

is not?

Basically check if a query is "isomorphic" to a given query template?

Claude calls this "pattern containment" or "query subsumption".


Martynas

Reply via email to