Skip to content

GH-4362: Make OpDatasetNames work on in-memory and wrapped TDB2 datasets - #4387

Open
krickert wants to merge 1 commit into
apache:mainfrom
ai-pipestream:claude/project-thread-ag4zua
Open

krickert wants to merge 1 commit into
apache:mainfrom
ai-pipestream:claude/project-thread-ag4zua

Conversation

@krickert

@krickert krickert commented Nov 5, 2026

Copy link
Copy Markdown

GitHub issue resolved #4362

Pull request Description:

GRAPH ?g { } and GRAPH <iri> { } become OpDatasetNames when a query is turned into quad form. Today that only executes through QueryEngineTDB. The general OpExecutor throws ARQNotImplemented, and OpExecutorTDB2 casts the execution dataset to DatasetGraphTDB, which fails with a ClassCastException when the query runs against a DatasetGraphSwitchable or another wrapper.

A runnable example of the failure, as asked for on the issue. It routes the query through the general quad engine, which is what happens for a wrapped dataset:

DatasetGraph dsg = DatabaseMgr.createDatasetGraph();   // TDB2, in memory
Txn.executeWrite(dsg, () -> dsg.add(Quad.create(
        NodeFactory.createURI("http://example/g"),
        NodeFactory.createURI("http://example/s"),
        NodeFactory.createURI("http://example/p"),
        NodeFactory.createURI("http://example/o"))));

Context cxt = Context.setupContextForDataset(null, dsg);
QueryEngineRegistry reg = new QueryEngineRegistry();
reg.add(QueryEngineMainQuad.getFactory());
QueryEngineRegistry.set(cxt, reg);

Txn.executeRead(dsg, () -> {
    try ( QueryExec qExec = QueryExec.dataset(dsg).query("SELECT ?g { GRAPH ?g { } }").context(cxt).build() ) {
        qExec.select().forEachRemaining(System.out::println);
    }
});

On main this throws ClassCastException: DatasetGraphSwitchable cannot be cast to DatasetGraphTDB. With DatasetGraphFactory.createTxnMem() in place of TDB2 it throws ARQNotImplemented: execute/OpDatasetNames. With this change both print ( ?g = <http://example/g> ).

Changes:

  • OpExecutor now evaluates OpDatasetNames. A concrete graph name passes the input bindings through when the graph exists and yields nothing otherwise. A variable is bound to each graph name in the dataset, or checked against the dataset if an earlier part of the query already bound it.
  • OpExecutorTDB2 unwraps the execution dataset with TDBInternal.getDatasetGraphTDB and falls back to the general implementation when the dataset is not TDB2 storage (for example DatasetGraphWrapper or DatasetGraphFilteredView). This mirrors how QueryEngineFactoryTDB already unwraps before building its engine.
  • SolverLibTDB.graphNames keeps the incoming bindings instead of replacing them, and checks an already-bound graph variable against the quads table. Before, { ?x :p ?s . GRAPH ?g { } } on TDB2 dropped ?s from the results.

Tests:

  • TestOpDatasetNames (added to TS_Engine): empty dataset, unbound and pre-bound graph variables, a literal bound to the graph variable, concrete graph names, wrapped and filtered datasets, and preservation of unrelated input bindings.
  • Test_SPARQL_TDB: the same cases on TDB2 through the quad engine (switchable, wrapped and filtered datasets), plus a binding-preservation test through QueryEngineTDB.
  • Without the production change, all of the new tests fail (ARQNotImplemented, ClassCastException, or lost bindings).

Verified with mvn test -pl jena-arq,jena-tdb2 -am -Dtest=TS_Engine,TS_Store,TS_SolverTDB (294 tests, 0 failures) on Java 21, and a full mvn install build.

This change was written with help from an AI coding assistant. I reviewed and tested it and take responsibility for it.


  • Tests are included.
  • Documentation change and updates are provided for the Apache Jena website (none needed: bug fix, no new user-facing API)
  • Commits have been squashed to remove intermediate development commit messages.
  • Key commit messages start with the issue number (GH-xxxx)

By submitting this pull request, I acknowledge that I am making a contribution to the Apache Software Foundation under the terms and conditions of the Contributor's Agreement.


See the Apache Jena "Contributing" guide.

… datasets

An empty graph pattern such as GRAPH ?g { } becomes OpDatasetNames in
quad form. Until now only QueryEngineTDB could execute it: the general
OpExecutor threw ARQNotImplemented, and OpExecutorTDB2 cast the dataset
to DatasetGraphTDB, which fails with a ClassCastException when the
query runs against DatasetGraphSwitchable or any other wrapper.

OpExecutor now evaluates OpDatasetNames itself. A concrete graph name
passes the input bindings through if that graph exists in the dataset,
and yields nothing otherwise. A variable is bound to each graph name in
the dataset, or checked against the dataset if an earlier part of the
query already bound it.

OpExecutorTDB2 unwraps the dataset with TDBInternal.getDatasetGraphTDB
and falls back to the general implementation when the dataset is not
TDB2 storage. SolverLibTDB.graphNames keeps the incoming bindings
instead of replacing them, so GRAPH ?g { } composes with the rest of
the query the same way on TDB2 as elsewhere.

Tests cover both executors, bound and unbound graph variables, and
wrapped datasets.

Generated-by: Claude Code, reviewed and tested by the author.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@TelicentPaul TelicentPaul left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

@afs

afs commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

This will take a long time to review properly.

Remove the fiddling around with a QueryEngineRegistry and the example works, and it works if a DatasetGraphWrapper is added.

TDB2 execution does not go direct to the quad engine.

This is the problem with AI. It solves a problem; the question is whether it is solving the problem.

@krickert

krickert commented Nov 5, 2026

Copy link
Copy Markdown
Author

This will take a long time to review properly.

Remove the fiddling around with a QueryEngineRegistry and the example works, and it works if a DatasetGraphWrapper is added.

TDB2 execution does not go direct to the quad engine.

This is the problem with AI. It solves a problem; the question is whether it is solving the problem.

No rush on my side. I was able to fix the CCE, but know how this can cascade

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpDatasetNames (empty GRAPH ?g {} in quad form) fails with ClassCastException on TDB2 (& ARQNotImplemented elsewhere)

3 participants