Skip to content

Add MINUS patterns and bulk delete/update operations#27

Merged
flyon merged 20 commits intodevfrom
claude/setup-and-summarize-GQoTY
Mar 11, 2026
Merged

Add MINUS patterns and bulk delete/update operations#27
flyon merged 20 commits intodevfrom
claude/setup-and-summarize-GQoTY

Conversation

@flyon
Copy link
Member

@flyon flyon commented Mar 11, 2026

Summary

Implements three core features for the linked data DSL:

  1. MINUS patterns.minus() on QueryBuilder to exclude results matching a pattern
  2. Bulk delete operations.deleteAll() and .deleteWhere() for deleting multiple entities
  3. Conditional update operations.update().where() and .update().forAll() for bulk updates

All features follow the existing IR → Algebra → SPARQL pipeline and reuse existing serialization logic.

Key Changes

MINUS Patterns (QueryBuilder)

  • Added .minus() method supporting multiple call styles:
    • .minus(Shape) — exclude by shape type
    • .minus(p => p.prop.equals(val)) — exclude by condition
    • .minus(p => p.prop) — exclude by property existence
    • .minus(p => [p.prop1, p.nested.prop2]) — multi-property with nested path support
  • New IRMinusPattern type in IR representing MINUS blocks
  • PropertyPathSegment type threads through desugaring/canonicalization pipeline untransformed
  • IRLower converts property paths to chained IRTraversePattern sequences
  • selectToAlgebra wraps algebra in SparqlMinus nodes for each MINUS pattern

Bulk Delete Operations

  • DeleteBuilder now supports three modes: 'ids' (existing), 'all' (new), 'where' (new)
  • New .all() and .where() methods on DeleteBuilder
  • Shape.deleteAll() and Shape.deleteWhere(fn) static convenience methods
  • New IRDeleteAllMutation and IRDeleteWhereMutation types
  • deleteAllToAlgebra() includes schema-aware blank node cleanup via walkBlankNodeTree() — recursively deletes blank nodes referenced by blank-node-typed properties
  • deleteWhereToAlgebra() adds filter conditions to WHERE clause

Conditional Update Operations

  • UpdateBuilder now supports three modes: 'for' (existing), 'forAll' (new), 'where' (new)
  • New .forAll() and .where() methods on UpdateBuilder
  • New IRUpdateWhereMutation type
  • Extracted shared field processing into processUpdateFields() helper, parameterized by subject term (IRI vs variable)
  • updateWhereToAlgebra() reuses field processing logic with variable subject

API Cleanup

  • Deprecated sortBy() — users should use orderBy() instead
  • Shape.update() now requires data parameter (no longer optional)
  • DeleteBuilder.from(shape, ids) replaces .for() for ID-based deletion

Infrastructure

  • Added lazy-loading helpers for ShapeClass and shacl ontology to avoid circular dependencies
  • New wrapOldValueOptionals() helper wraps old-value triples in OPTIONAL (LEFT JOIN) for UPDATE operations
  • SparqlStore routes mutations by kind field to appropriate conversion functions
  • Comprehensive test fixtures and golden SPARQL assertions for all new features

Implementation Details

  • Property path threading: PropertyPathSegment[][] passes through Raw → Desugared → Canonical stages untransformed; only IRLower converts to traverse patterns (requires alias generation)
  • MINUS runtime type detection: Callback return type detection in toRawInput() distinguishes between Evaluation (WHERE), QueryBuilderObject (single property), and QueryBuilderObject[] (multi-property) without separate method names
  • Blank node cleanup: Schema-aware deletion recursively walks SHACL shape definitions to identify and clean up blank node references, handling nested blank node types
  • Shared update logic: processUpdateFields() and wrapOldValueOptionals() extracted to support both ID-based and condition-based updates with consistent field handling

claude added 20 commits March 10, 2026 10:58
…ures

Rewrote the ideation doc to reflect the current 2.0 codebase state
(DeleteBuilder, UpdateBuilder, .for()/.forAll() patterns, existing NOT EXISTS
support). Restructured into 3 features with multiple implementation routes:
MINUS/NOT EXISTS, bulk delete, and conditional update.

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
- Implementation plan for SPARQL MINUS support on select queries
- Implementation plan for where-clause-based delete mutations

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
- Accepts ShapeConstructor or WhereClause<S> (same callback types as .select())
- Supports shape, single/multi property, boolean, and nested callbacks
- Reuses existing callback processing, new IRMinusPattern through pipeline
- Chainable: .minus(A).minus(B) produces separate MINUS blocks

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
…leanup

- .deleteAll() for bulk, .delete().where(cb) for conditional, .deleteWhere() as sugar
- Returns void, no safety gate needed
- Schema-aware blank node cleanup using explicit property paths from shape tree
- FILTER(isBlank()) for sh:BlankNodeOrIRI properties
- Recursive depth determined at codegen by walking shape tree

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
…lder

- Thin layer over existing DELETE/INSERT/WHERE SPARQL generation
- Swap hardcoded entity IRI for variable, add type triple + filter conditions
- Field-level scoping: only touches fields in update data
- .forAll() keeps OPTIONAL for old bindings; .where() drops it (filter implies existence)
- No .updateWhere() sugar — just .update().where()

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
…te, conditional update)

Covers all three features from ideation with inter-component contracts,
file change matrix, IR types, algebra conversion signatures, and pitfalls.

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
- IRMinusPattern added to IRGraphPattern union
- IRDeleteAllMutation, IRDeleteWhereMutation, IRUpdateWhereMutation types
- Canonical IR builder functions for new mutation types
- Widened DeleteQuery and UpdateQuery type unions
- SparqlStore dispatches by kind with stubs for new mutation types

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
- QueryBuilder.minus(ShapeConstructor | WhereClause) with immutable chaining
- IRMinusPattern threaded through desugar → canonicalize → lower pipeline
- selectToAlgebra wraps algebra in SparqlMinus nodes
- Supports shape exclusion, condition-based exclusion, and chained MINUS blocks
- 3 new golden tests: minusShape, minusCondition, minusChained

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
…deleteAll/deleteWhere

Phase 3 of advanced query patterns:
- DeleteBuilder rewritten with mode-based dispatch (ids/all/where)
- Shape.deleteAll() and Shape.deleteWhere() static convenience methods
- deleteAllToAlgebra() with schema-aware blank node cleanup via walkBlankNodeTree()
- deleteWhereToAlgebra() with WHERE filter condition support
- SparqlStore dispatch wired up for delete_all and delete_where
- Golden tests for both deleteAll and deleteWhere

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
…forAll()

Phase 4 of advanced query patterns:
- UpdateBuilder extended with .where(fn) and .forAll() fluent methods
- updateWhereToAlgebra() generates DELETE/INSERT/WHERE with variable subject
  (?a0) instead of hardcoded entity IRI, adds type triple and filter conditions
- SparqlStore dispatch wired up for update_where
- Golden tests for both updateForAll and updateWhere

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
Export toWhere from IRDesugar and add lowerWhereToIR to IRLower —
these changes support the WHERE processing in mutation builders.

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
…nce tests

- Extract processUpdateFields() and wrapOldValueOptionals() from
  updateToAlgebra, shared with updateWhereToAlgebra (eliminates duplication)
- deleteAll/deleteWhere/updateWhere now resolve to void (not placeholder objects)
  via typed R parameter on DeleteBuilder<S, R> and UpdateBuilder<S, U, R>
- Add builder equivalence tests: Person.deleteAll() === DeleteBuilder.from().all()
  and Person.deleteWhere(fn) === DeleteBuilder.from().where(fn)

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
Plan for .minus(p => [p.hobby, p.name]) — exclude entities where all
listed properties exist. Threads propertyPaths through the pipeline
without touching WhereClause or processWhereClause.

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
…erty

Update the Phase 6 plan to support .minus(p => [p.bestFriend.name]) with
nested path traversal, not just flat properties. Reuses existing
FieldSet.collectPropertySegments() and createProxiedPathBuilder() for
maximum code reuse.

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
Add .minus(p => [p.hobby, p.bestFriend.name]) syntax for property
existence exclusion in SPARQL MINUS blocks. Supports:
- Multi-property: .minus(p => [p.hobby, p.name])
- Nested paths: .minus(p => [p.bestFriend.name])
- Mixed: .minus(p => [p.hobby, p.bestFriend.name])
- Single property (no array): .minus(p => p.hobby)

Reuses existing FieldSet.collectPropertySegments() for path extraction
and createProxiedPathBuilder() for proxy creation. Threads
PropertyPathSegment[][] through IRDesugar → IRCanonicalize → IRLower,
where segments are converted to chained IRTraversePatterns.

4 new golden tests with exact SPARQL string matching.

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
…elete API

1. Deprecate sortBy() on QueryBuilder — use orderBy() instead. Updated
   all fixtures and tests to use orderBy.

2. Require data in Shape.update() — removed no-arg overload, data is
   now a required parameter. UpdateBuilder.set() jsdoc updated to
   "Replace the update data".

3. Simplify delete API — removed .for() from DeleteBuilder. ID-based
   deletes use DeleteBuilder.from(shape, ids). deleteAll() is strictly
   no-arg (null cannot trigger it). deleteWhere(fn) for conditional.

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
- Add MINUS examples (shape, property, multi-property, nested path, condition)
- Update sortBy → orderBy in examples
- Update delete section with deleteAll/deleteWhere
- Update update section with .where()/.forAll() and required data
- Update mutation builders to show DeleteBuilder.from(shape, id) instead of .for()
- Remove docs/ideas/007 and docs/plans/001 (work complete, report at docs/reports/009)

https://claude.ai/code/session_01TGZykDVZBEimHNvwXiAq2D
@flyon flyon merged commit c6f5a36 into dev Mar 11, 2026
3 checks passed
@flyon flyon deleted the claude/setup-and-summarize-GQoTY branch March 11, 2026 07:19
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