Add MINUS patterns and bulk delete/update operations#27
Merged
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements three core features for the linked data DSL:
.minus()onQueryBuilderto exclude results matching a pattern.deleteAll()and.deleteWhere()for deleting multiple entities.update().where()and.update().forAll()for bulk updatesAll features follow the existing IR → Algebra → SPARQL pipeline and reuse existing serialization logic.
Key Changes
MINUS Patterns (QueryBuilder)
.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 supportIRMinusPatterntype in IR representing MINUS blocksPropertyPathSegmenttype threads through desugaring/canonicalization pipeline untransformedIRLowerconverts property paths to chainedIRTraversePatternsequencesselectToAlgebrawraps algebra inSparqlMinusnodes for each MINUS patternBulk Delete Operations
DeleteBuildernow supports three modes:'ids'(existing),'all'(new),'where'(new).all()and.where()methods onDeleteBuilderShape.deleteAll()andShape.deleteWhere(fn)static convenience methodsIRDeleteAllMutationandIRDeleteWhereMutationtypesdeleteAllToAlgebra()includes schema-aware blank node cleanup viawalkBlankNodeTree()— recursively deletes blank nodes referenced by blank-node-typed propertiesdeleteWhereToAlgebra()adds filter conditions to WHERE clauseConditional Update Operations
UpdateBuildernow supports three modes:'for'(existing),'forAll'(new),'where'(new).forAll()and.where()methods onUpdateBuilderIRUpdateWhereMutationtypeprocessUpdateFields()helper, parameterized by subject term (IRI vs variable)updateWhereToAlgebra()reuses field processing logic with variable subjectAPI Cleanup
sortBy()— users should useorderBy()insteadShape.update()now requiresdataparameter (no longer optional)DeleteBuilder.from(shape, ids)replaces.for()for ID-based deletionInfrastructure
ShapeClassandshaclontology to avoid circular dependencieswrapOldValueOptionals()helper wraps old-value triples in OPTIONAL (LEFT JOIN) for UPDATE operationsSparqlStoreroutes mutations bykindfield to appropriate conversion functionsImplementation Details
PropertyPathSegment[][]passes through Raw → Desugared → Canonical stages untransformed; onlyIRLowerconverts to traverse patterns (requires alias generation)toRawInput()distinguishes betweenEvaluation(WHERE),QueryBuilderObject(single property), andQueryBuilderObject[](multi-property) without separate method namesprocessUpdateFields()andwrapOldValueOptionals()extracted to support both ID-based and condition-based updates with consistent field handling