Skip to content

feat: SPARQL conversion layer with SparqlStore base class#14

Merged
flyon merged 33 commits intodevfrom
flyon/nashville-v1
Mar 2, 2026
Merged

feat: SPARQL conversion layer with SparqlStore base class#14
flyon merged 33 commits intodevfrom
flyon/nashville-v1

Conversation

@flyon
Copy link
Copy Markdown
Member

@flyon flyon commented Mar 2, 2026

Summary

Adds a complete SPARQL conversion pipeline to @_linked/core — compiles the canonical IR into executable SPARQL and maps results back to fully typed DSL objects.

What's new

  • Three-layer pipeline: IR → SPARQL algebra (typed AST) → SPARQL string → execute → result mapping
  • SparqlStore abstract base class: Concrete stores only implement executeSparqlSelect() and executeSparqlUpdate() — the base class handles everything else
  • SPARQL algebra types: Formal algebra aligned with SPARQL 1.1 spec §18 (BGP, LeftJoin, Filter, Union, Minus, Extend, Graph)
  • Prefix-aware serialization: Automatic PREFIX block generation from registered ontologies
  • Result mapping: SPARQL JSON → typed DSL objects with XSD coercion, nested grouping, and literal traversal detection
  • Fully inferred result types: TypeScript return type is automatically inferred from selected paths — highlighted as a top-level feature in README

Test coverage

  • ~200 unit tests (algebra, serialization, result mapping, golden SPARQL comparison, negative/edge cases)
  • 80 Fuseki integration tests (all query types end-to-end through SparqlStore)

Documentation

  • New documentation/sparql-algebra.md — full layer reference
  • Updated documentation/intermediate-representation.md — SparqlStore as reference implementation
  • Updated README.md — pipeline walkthrough, type inference highlight, SparqlStore section

Future work (ideation docs)

  • Named graph support (docs/ideas/005)
  • Computed expressions & update functions (docs/ideas/006)
  • Advanced query patterns: MINUS, DELETE WHERE (docs/ideas/007)

Test plan

  • All unit tests pass (394 passing)
  • TypeScript compiles cleanly (npx tsc --noEmit)
  • Fuseki integration tests pass (80/80, requires Docker)
  • Documentation reviewed and consistent with implementation
  • Changeset: major bump for @_linked/core

🤖 Generated with Claude Code

René Verheij and others added 27 commits February 27, 2026 15:34
Complete design workflow for IR-to-SPARQL conversion:
- Ideation docs exploring package placement, architecture, result mapping,
  and layered conversion with SPARQL 1.2 algebra intermediate
- Architecture plan with full type definitions, translation rules, and
  inter-component contracts for parallel implementation
- Task breakdown with 7 phases (1 → 2a-2d parallel → 3 → 4), each with
  detailed test specifications and explicit assertions
- Updated workflow skill to auto-enter ideation mode
- Updated plan skill with inter-component contracts requirement
- Updated tasks skill with parallel execution and test specification sections

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 1 of SPARQL conversion layer:
- SparqlAlgebra.ts: full type definitions for algebra nodes, plans,
  expressions, terms, triples, projection items, and order conditions
- sparqlUtils.ts: formatUri, formatLiteral, collectPrefixes,
  generateEntityUri with ULID-based URI generation
- Barrel exports via src/sparql/index.ts and src/index.ts
- 15 unit tests covering all utility functions
- Added ulid dependency
- Removed originating ideation doc (implementation started)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Renamed "Test specification" to "Validation specification" to clarify
that phase acceptance criteria can include compilation checks, runtime
checks, HTTP checks, and structural checks — not only coded tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 2a: IR→algebra for SELECT queries (27 tests)
Phase 2b: algebra→SPARQL string serialization (61 tests)
Phase 2c: SPARQL JSON result mapping (25 tests)
Phase 2d: IR→algebra for mutations (16 tests)

All 291 tests pass, compilation clean, no regressions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire selectToSparql, createToSparql, updateToSparql, deleteToSparql
to call through the full IR→algebra→string pipeline.

Add 55 select golden tests and 19 mutation golden tests covering
all query-fixtures factories end-to-end.

365/365 tests pass, compilation clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add test helper for Fuseki dataset management (create, load, query,
cleanup) and 19 integration tests covering the full SPARQL pipeline
(16 select + 3 mutation). Tests skip gracefully when Fuseki is
unavailable.

384/384 tests pass, compilation clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…phase) and add Phase 5-6

- implementation skill: add parallel execution section for sub-agent spawning
- tasks skill: strengthen integration phase requirement after parallel work
- review skill: require follow-up questions before mode switch, enforce tasks-first flow
- plan: add Phase 5 (Fuseki Docker) and Phase 6 (negative tests)
- ideation: defer SPARQL CONSTRUCT support to 004

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ation test assertions

Phase 5: Added docker-compose.test.yml for ephemeral Fuseki, test:fuseki npm
script, fixed 6 test assertion issues (singleResult handling, test data gaps).
Documented two known limitations: nested result grouping needs traversal aliases
in SELECT projection; FILTER uses string literals for URI entity references.

Phase 6: Added 12 negative/error-path tests, fixed silent failure in
convertExistsPattern(), updated outdated "stubs" comments.

All 396 tests pass, 19 Fuseki integration tests pass against live Fuseki.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…coverage to 75 fixtures

Phase 7: Add IRReferenceExpression type to distinguish URI references from
string literals in FILTER comparisons. Entity refs now emit <IRI> instead
of "literal" in generated SPARQL.

Phase 8: Add collectTraversalAliases() and inject traversal alias variables
into SELECT projection so mapNestedRows() can group nested results by
traversed entity.

Phase 9: Expand sparql-fuseki.test.ts from 19 to 75 tests covering all
query factories (56 SELECT + 19 mutation). Add Employee test data,
categorize tests by SPARQL validity, use try/finally for mutation cleanup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 10: Recursive nesting in result mapping
Phase 11: Tighten Fuseki assertions to OLD test depth
Phase 12: Inline where filter lowering (IR pipeline)
Phase 13: Fix invalid SPARQL generation (3 fixtures)
Phase 14: Fix edge cases (eval projection, context path)

Each phase includes root cause analysis, approach options,
files changed, and open questions for user decision.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each phase now includes:
- Exact code locations and line numbers for changes
- Before/after code examples with concrete implementations
- Expected SPARQL output after fixes
- Precise root cause analysis with code path traces
- Architectural considerations and edge cases

Key findings from deep dive:
- Phase 13b: root cause is line 158 of IRCanonicalize.ts calling
  toComparison() instead of checking for SOME/EVERY on the `first`
  element of a where_boolean compound
- Phase 13c: SparqlSelectPlan already has `having` field and
  algebraToString.ts already serializes it — only irToAlgebra.ts
  needs changes
- Phase 14b: arg_path.subject already carries context entity IRI —
  fix is to use it as a fixed triple subject instead of using the
  query's root alias

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks mode: defines concrete tasks, dependency graph, parallel
execution strategy, stub boundaries, and detailed validation
specifications with test data assertions for each phase.

Key structure:
- Phases 10, 12, 13, 14 run in parallel (different files)
- Phase 11 runs last (assertion tightening after fixes land)
- Shared file risk in irToAlgebra.ts documented with section ranges
- Integration verification checklist after all phases merge

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrites resultMapping.ts to support arbitrary nesting depth in
SPARQL result grouping. Introduces buildAliasChain() to walk the
traversal map, insertIntoTree() for recursive NestedGroup construction,
and collectNestedGroup() for recursive binding collection. All 452
tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
13a: Wrap NOT expressions in parentheses in algebraToString.ts to
     fix precedence (!(expr) instead of !expr).
13b: Add canonicalizeComparison() in IRCanonicalize.ts to detect
     SOME/EVERY quantifiers in the first element of boolean compounds.
13c: Route aggregate-containing WHERE filters to HAVING in
     irToAlgebra.ts via containsAggregate() detection.

All 452 tests pass with updated golden expectations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
14a: Add 'expression' variant to SparqlProjectionItem for computed
     projections like (expr AS ?alias). Fixes customResultEqualsBoolean
     which now correctly projects the boolean comparison.
14b: Add context_property_expr IR expression type to resolve context
     entity property paths via separate triples instead of reusing
     the same variable. Fixes whereWithContextPath tautology.

All 452 tests pass with updated golden expectations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Process DesugaredPropertyStep.where predicates that were silently
dropped during IR lowering. When a property step has .where(), force
a traversal creation, canonicalize and lower the where predicate, and
attach the resulting filter to the traverse pattern. In irToAlgebra,
filtered traversals produce OPTIONAL blocks with the traverse triple,
filter property triples, and FILTER co-located inside.

Fixes 6 fixtures: whereFriendsNameEquals, whereHobbyEquals, whereAnd,
whereOr, whereAndOrAnd, whereAndOrAndNested.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 3-level nesting and alias_expr tests to sparql-result-mapping.test.ts.
Tighten Fuseki test assertions from defensive patterns (>=1, toBeDefined)
to precise values matching test data. Update inline where section (now
lowered via Phase 12) and invalid SPARQL section (now valid via Phase 13).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ested create with predefined ID (Phases 15-17)

- Fix DELETE variable reuse via SparqlDeleteInsertPlan refactor
- Fix context variable naming with sanitizeVarName()
- Fix aggregate alias collision in irToAlgebra
- Fix UPDATE OPTIONAL wrapping for safe WHERE clause
- Add operator parenthesization for OR inside AND expressions
- Fix literal property inline where result mapping with detectLiteralTraversals()
- Fix isNodeReference() to distinguish references from nested creates
- Handle predefined ID in convertNodeDescription()
- All 456 tests pass, 75/75 Fuseki end-to-end

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add comprehensive review section to plan doc with remaining gaps categorized by priority
- Create documentation/sparql-algebra.md covering the full SPARQL pipeline:
  algebra types, IR→algebra conversion, string serialization, result mapping,
  public API, store implementation guide, and known limitations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…sion detection (Phases 18-20)

Phase 18: Add escapeSparqlString() for SPARQL literal escaping (quotes,
backslashes, newlines, tabs, CRs). Fix language-tagged literal bypass in
serializeTerm(). Remove unused varCounter in updateToAlgebra().

Phase 19: Rewrite convertExistsPattern() with full recursive support for
optional (→ left_join), union (→ union), and nested exists patterns.
Fix join case to handle mixed sub-pattern types instead of only traverse.

Phase 20: Improve detectLiteralTraversals() to scan all bindings and
handle mixed types safely. Add duplicate result key detection in
buildNestingDescriptor(). Fix context property collision by using raw IRI
as registry key + variable name deduplication with counter.

All 471 tests pass, 75/75 Fuseki, TypeScript clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove string literal escaping and EXISTS limitations (now fixed).
Add escapeSparqlString to utilities table. Update known limitations
to reflect current state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…pe casts

- Add SparqlStore abstract base class (IR → SPARQL → execute → map pipeline)
- Add FusekiStore concrete implementation with 5 integration tests (80/80 Fuseki)
- Replace hardcoded XSD/RDF URI strings with ontology module imports
- Fix `as any` casts: use `as never` for exhaustive switches, explicit ResultRow[] for arrays
- Remove dead commented code in MutationQuery.ts
- Update plan review section with resolved nice-to-have items

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dvanced patterns

- 005: Named graph support (.from()/.into() for GRAPH clauses)
- 006: Computed expressions & update functions (L.times, L.concat, etc.)
- 007: Advanced query patterns (MINUS set difference + DELETE WHERE bulk delete)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Walks through Shape → DSL → IR → SPARQL Algebra → SPARQL String → Result
with abbreviated examples at each stage. Links to IR and SPARQL algebra docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…type inference

- README: highlight fully inferred result types as top feature, add AST optimization
  mention, remove stray code block, update store packages list
- sparql-algebra.md: fix "no base classes" statement, add SparqlStore.ts to file
  structure, rewrite store implementation section to use SparqlStore base class,
  add SparqlStore to public API section
- intermediate-representation.md: update reference implementations to describe
  built-in SparqlStore instead of "coming soon"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 2, 2026

⚠️ No changeset found. If this PR should trigger a release, run npx changeset and commit the generated file.

If this change doesn't need a release (docs, CI config, etc.), you can ignore this message.

@socket-security
Copy link
Copy Markdown

socket-security bot commented Mar 2, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedulid@​3.0.210010010084100

View full report

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 2, 2026

⚠️ No changeset found. If this PR should trigger a release, run npx changeset and commit the generated file.

If this change doesn't need a release (docs, CI config, etc.), you can ignore this message.

René Verheij and others added 5 commits March 2, 2026 14:28
- Rewrote report with full architecture, design decisions, API surface,
  conversion rules, resolved gaps, and test coverage
- Added changeset (minor) with detailed user-facing changelog
- Updated wrapup skill with report quality and changeset guidelines

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@flyon flyon merged commit f029534 into dev Mar 2, 2026
3 checks passed
@flyon flyon deleted the flyon/nashville-v1 branch March 2, 2026 07:21
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