Skip to content

Fix SPARQL queries failing on Virtuoso endpoints - #171

Open
kvistgaard wants to merge 1 commit into
reactodia:masterfrom
kvistgaard:fix/virtuoso-compat
Open

Fix SPARQL queries failing on Virtuoso endpoints#171
kvistgaard wants to merge 1 commit into
reactodia:masterfrom
kvistgaard:fix/virtuoso-compat

Conversation

@kvistgaard

Copy link
Copy Markdown

Fix SPARQL queries failing on Virtuoso endpoints

Summary

Three queries generated by SparqlDataProvider fail against Virtuoso (tested on
version 08.03.3332): two hang until the server's timeout, one is rejected outright by the
cost estimator. Each fix keeps the query semantics identical for standard SPARQL
1.1 endpoints, so behaviour elsewhere does not change. Verified against a
Virtuoso endpoint holding an 11M-triple graph and against Comunica via a new
unit test.

Symptoms

On a Virtuoso endpoint, with OwlStatsSettings:

  1. The connections dialog ("Navigate to connected elements") shows an error.
  2. "Search for connected elements" never finishes loading.
  3. Both symptoms appear on any dataset large enough that a full scan is slow;
    on an 11M-triple graph the affected queries take >20s instead of ~0.1s.

Causes and fixes

1. FILTER(isIri(...)) on incoming-link patterns (sparqlDataProvider.ts)

createRefQueryPart emitted FILTER(isIri(?inst)) on both branches of the
connected-elements union, and connectedLinkStats emitted
FILTER IsIri(?inObject) for the statistics query's incoming branch. On an
incoming pattern (?inst ?link <element>) that filter makes Virtuoso abandon
the object-anchored index and scan the graph:

query shape time
{ ?inst ?link <e> FILTER(isIri(?inst)) } in union timeout (>20s)
{ ?inst ?link <e> FILTER(!isBlank(?inst)) } in union 0.19s

In the subject position a standard SPARQL 1.1 term cannot be a literal, so
!isBlank accepts the same solutions as isIri there. Outgoing patterns keep
isIri (their object can be a literal, and the subject-anchored plan is not
affected). Known trade-off: on an RDF-star endpoint a quoted-triple subject now
passes the filter where isIri excluded it, and mapSparqlResponseIntoRdfJs
throws on "type": "triple" bindings; there is no SPARQL 1.1-portable filter
that excludes quoted triples without also breaking on non-star endpoints
(isTRIPLE is SPARQL-star syntax). If RDF-star tolerance is wanted, skipping
unmappable term types in the response mapper would be a follow-up.

2. Joined aggregate subqueries in linkTypesStatisticsQuery (sparqlDataProviderSettings.ts)

The per-link-type count query in OwlRdfsSettings joined two aggregate
subqueries. Virtuoso's cost estimator rejects that shape with HTTP 400:

Virtuoso 42000 Error The estimated execution time 0 (sec) exceeds the limit of 190800 (sec).

Rewritten as a UNION of two row subqueries with an outer sum(), which the
estimator accepts (an aggregation over ~47k rows answers in 0.12s). Counts
stay exact; the previous LIMIT 101 is dropped because it applied to a
single-row aggregate result and so never limited anything. Constants in the
projection of an aggregate query are permitted by SPARQL 1.1 (§18.2.4.1). The
helper variables are named ?__out/?__in to avoid capturing variables from
user-authored LinkConfiguration.path patterns, which are substituted verbatim
into the subqueries.

3. Unbound counts crash getLinkStatistics (responseHandler.ts, sparqlModels.ts)

When a counted pattern matches nothing, Virtuoso returns the aggregate variable
unbound rather than 0, and parseCount crashed reading .value of
undefined. parseCount now treats a missing binding as 0, the default query
wraps the sums in COALESCE(..., 0), and LinkCountBinding declares
inCount/outCount optional so consumers cannot type-check past the case.

Testing

  • New unit test: connectedLinkStats() with exact counts over the org ontology
    fixture, which runs the rewritten statistics query through Comunica.
  • Full suite passes; npm run lint, npm run typecheck, and the library +
    examples build pass.
  • End-to-end against a Virtuoso 08.03.3332 endpoint holding an 11M-triple
    graph: the connections dialog lists all link types with counts,
    connected-element search returns, links render between placed elements.

🤖 Generated with Claude Code

- Use FILTER(!isBlank(...)) instead of FILTER(isIri(...)) on incoming-link
  patterns, where a literal subject is not possible; isIri() there makes
  Virtuoso choose a catastrophic query plan scanning the whole graph.
- Rewrite the OwlRdfsSettings link type statistics query as a UNION with
  an outer sum() instead of joining two aggregate sub-queries, which the
  Virtuoso cost estimator rejects; drop the LIMIT that applied to a
  single-row aggregate result and so never limited anything.
- Treat unbound link counts as 0: some endpoints return the aggregate
  variable unbound when it counts an empty solution group.
- Add a connectedLinkStats() unit test covering the statistics query.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant