Skip to content

Feature: De-fork sparql-client, vanilla 3.2.2 + goo-owned bolt-ons (Ship 1) - #194

Merged
mdorf merged 4 commits into
developmentfrom
feature/sparql-client-defork
Jul 28, 2026
Merged

mdorf merged 4 commits into
developmentfrom
feature/sparql-client-defork

Conversation

@mdorf

@mdorf mdorf commented Jul 28, 2026 •

Copy link
Copy Markdown
Member

What this does

goo used to get its SPARQL client from an NCBO fork of the sparql-client gem, with our custom features edited directly into the gem's source. This PR switches goo to the standard sparql-client 3.2.2 from rubygems and moves every NCBO customization into goo itself, where it is tested and maintained like any other goo code.

Why

The fork was expensive to carry. Its diff against upstream was mostly whitespace, which buried the real changes, so every upstream security or bug fix meant a risky manual merge, and one past merge silently lost a feature. With the customizations living in goo, upgrading the gem becomes a normal dependency bump.

What moved into goo

  • Redis query cache -> lib/goo/sparql/cache.rb. Same Redis key format and same caching scope as before, so existing cache contents stay valid. Two ordering bugs were fixed in the move: results are now cached exactly as returned, and invalidation happens after a write commits instead of before.
  • Custom query syntax goo uses to fetch attributes in one query -> lib/goo/sparql/ext/query_extensions.rb, along with support for multiple FROM clauses and nested UNIONs.
  • Triple store quirks (xsd:string typing for 4store/Virtuoso, Virtuoso's INSERT DATA limitation, tolerance for empty result bindings) -> lib/goo/sparql/ext/virtuoso_compat.rb.
  • Cache switch hardening: Goo.use_cache is now respected regardless of configuration order, and OP_USE_CACHE=true can enable caching from the environment. goo's default remains off.
  • File-based query logging is removed. A reworked version ships separately (see below).
  • goo.gemspec now pins sparql-client = 3.2.2, so consuming apps resolve exactly the version goo was tested against.

How we know behavior did not change

  • Characterization tests pin the exact SPARQL text goo generates. They were recorded while goo still ran the fork, and the identical tests pass on this branch. The recorded comparison is in docs/sparql-defork-ab-record.md.
  • CI is green on all four backends (4store, AllegroGraph, Virtuoso, GraphDB), and the full suite passes locally on this branch merged with current development.

Action needed in consuming repos

Each downstream repo (ontologies_linked_data, ncbo_annotator, ncbo_cron, ncbo_ontology_recommender, ontologies_api) pins the fork in its own Gemfile. Those pins must be removed when picking up this goo version; otherwise bundler keeps installing the fork and its built-in caching runs on top of goo's.

Not in this PR (planned follow-ups)

  • feature/sparql-resilience: circuit breakers so a Redis or triple store outage fails fast instead of hanging requests. Opt-in via env flag.
  • feature/sparql-observability: reworked query logging, cache hit-rate, and query-count reporting. Opt-in.

Design and full review notes: docs/sparql-client-defork-proposal.md and docs/sparql-defork-review.md.

Work by @alexskr; review, verification, and shipping by @mdorf.

alexskr and others added 4 commits June 26, 2026 16:54
…into goo

Stop depending on the ncbo/sparql-client hard fork; pin vanilla sparql-client 3.2.2
and carry the NCBO-specific behavior as goo-owned bolt-ons.

Re-homed:
- form-urlencoded query transport (Client#make_post_request)
- multiple-FROM + nested-UNION serialization (Ext::QuerySerialization)
- union-with-bind DSL as a goo QueryElement (Ext::UnionWithBind, QueryBuilder)
- xsd:string forcing, empty-binding tolerance, INSERT/INSERT DATA toggle (Ext::VirtuosoCompat)
- Redis read-through caching (Goo::SPARQL::Cache + query/update overrides, invalidate-after-write)

Removes the fork-era query-logging wiring (vanilla lacks it; goo-owned logging is
reintroduced separately). Offline characterization tests pin the generated SPARQL as
byte-identical across the swap.

Re-implemented onto ncbo's minitest-6 base (not merged from the alexskr POC fork).
…parity, §3 tests)

Implements the parity-pure gates from docs/sparql-defork-review.md (D13 Ship 1):

- D3/H-1: match the fork's cache scope — only JSON SELECT/ASK results (Solutions or
  the ASK boolean) are cached; CONSTRUCT/DESCRIBE graph results are not (the fork wrote
  the cache solely from the RESULT_JSON branch of parse_response).
- D8/M-2: restore the fork's raise ("Unsupported cacheable query") when caching is on
  and an update has no graph (incl. plain string updates) — fail loudly before the
  write rather than silently leave stale cache.
- D5/§6: make use_cache authoritative regardless of add_redis_backend vs
  add_sparql_backend order (set_sparql_cache at the end of add_sparql_backend), and add
  the OP_USE_CACHE env opt-in (goo default remains OFF).
- M-5 parity restore: reinstate the fork's inner DEL rescue in cache invalidation —
  a failed DEL warns and moves on instead of silently entering the sleep(5)×3 retry
  loop (the port had dropped the fork's log line and its two-layer rescue).
- M-1: reword the invalidate-after-write comments — narrows, not eliminates, the
  cache-aside race.

Tests (review §3): new test/test_cache_unit.rb — 17 backend-free unit tests covering
key format, store/get round-trip, inert-when-off, reload/bypass, stale-entry eviction
(the allkeys-lru fail-safe), the 50MB guard, cache scope (T-7), invalidation mechanics
+ DEL-failure path, Marshal round-trip, empty-binding tolerance, xsd:string forcing,
form-urlencoded transport, the D8 raise, UnionWithBind contract, and use_cache
authority (T-24). Plus T-12 backend-matrix locks in the query characterization
(graphdb BIND branch, allegrograph FILTER branch).
…/B parity

Carried over from the POC branch so the review + A/B parity record ship with the
de-fork PR (resolves review nit N-1).
goo's Gemfile pin only governs goo's own bundle. Consumers (OLD,
ontologies_api, ncbo_cron, annotator, recommender) resolve sparql-client
through goo.gemspec, which was unversioned; once they drop their fork
Gemfile pins they would pick up the latest rubygems release (3.3.0
today), while the de-fork bolt-ons copy and prepend 3.2.2 internals
(QuerySerialization#to_s, make_post_request, parse_json_value; review
M-3 / section 7.5). Pin the gemspec dependency to 3.2.2 so every
consumer resolves the reviewed version; bump deliberately with a
re-review of the ext/ prepends.
@mdorf

mdorf commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

Local AllegroGraph verification of the merge preview (this branch merged into current development 752fb6b), against AG in Docker + clean Redis: 216 runs, 3216 assertions, 0 failures, 0 errors, 2 skips (backend-specific skips). Together with the 4store run recorded in the description and the green 4-backend CI matrix, all pre-merge verification is complete.

@mdorf
mdorf merged commit 78ff860 into development Jul 28, 2026
10 checks passed
@mdorf mdorf changed the title De-fork sparql-client: vanilla 3.2.2 + goo-owned bolt-ons (Ship 1) Feature: De-fork sparql-client, vanilla 3.2.2 + goo-owned bolt-ons (Ship 1) Jul 28, 2026
@alexskr
alexskr deleted the feature/sparql-client-defork branch August 1, 2026 09:09
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.

2 participants