feat: v0.9.0 — null semantics (setNull, isNull filters, m2m set) + remove deprecated raw helpers - #76
Conversation
…op raw helpers
Closes the typed-surface gaps found during the familiarise backend migration:
- setNull: List<{Model}ScalarField>? on update/updateMany — listed fields are
injected as explicit NULL assignments (typed inputs drop nulls otherwise).
- isNull: bool? on every filter class — true -> IS NULL, false -> IS NOT NULL
(compiler operators already existed; now typed).
- Nested `set` on to-many relation write inputs — m2m replace semantics
(junction clear for the parent + connects); throws UnsupportedError on
1:N/1:1 instead of silently dropping data.
- Null-tolerant array decode: required list columns hydrate SQL NULL as
const [] instead of crashing fromJson (dirty-data tolerance).
- REMOVED findManyRaw/findFirstRaw (deprecated 0.8.0, scheduled for 0.9.0).
464 unit tests green; flutter analyze --fatal-infos clean; format clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
📝 WalkthroughWalkthroughVersion 0.9.0 adds typed null filters, explicit null updates, many-to-many nested ChangesVersion 0.9.0 semantics
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant RelationWriteInput
participant SqlCompiler
participant JunctionTable
RelationWriteInput->>SqlCompiler: submit many-to-many set targets
SqlCompiler->>JunctionTable: clear existing parent links
SqlCompiler->>JunctionTable: insert replacement links
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
lib/src/generator/cb_delegate_generator.dart (1)
505-518: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExtract shared
setNullinjection snippet.The null-injection block (
final data0 = data.toJson(); if (setNull != null) { for (final f in setNull) { data0[f.fieldName] = null; } }) is duplicated verbatim in_updateand_updateMany. Consider a small generator-side helper (e.g._setNullInjectionCode()) that both methods call, so any future change to this semantics (e.g. adding validation against relation fields, or conflict detection withdata) only needs updating in one place.♻️ Proposed refactor
+ String _setNullInjectionSnippet() => ''' + if (setNull != null) { + for (final f in setNull) { + data0[f.fieldName] = null; + } + } + ''';Then reference
${_setNullInjectionSnippet()}inside both_update's and_updateMany's body templates.Also applies to: 590-606
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/src/generator/cb_delegate_generator.dart` around lines 505 - 518, Extract the duplicated `data.toJson()` and `setNull` iteration block into a generator-side helper such as `_setNullInjectionSnippet()`. Update both `_update` and `_updateMany` body templates to interpolate that helper instead of embedding the block directly, preserving the existing null-field injection behavior.lib/src/generator/cb_model_generator.dart (1)
718-738: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy liftGate nested relation
setto many-to-many relations only.
lib/src/generator/cb_model_generator.dart:718-738emitssetfor anyf.isList && canConnect, but nestedsetis compiled only forRelationType.manyToManythrough junction-table clear/connect operations. For the existing 1:NUser.postsrelation, the generatedPostWriteInputexposesseteven though the runtime only handles m2m, so invalidset: [...]calls can reach the type-level API before the SQL compiler supports them. Add a derived many-to-many flag/metadata and restrictsetgeneration to that branch.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/src/generator/cb_model_generator.dart` around lines 718 - 738, In the nested relation parameter generation for list fields, derive or reuse metadata identifying RelationType.manyToMany and emit the set parameter and JSON entry only when that flag is true. Keep connect and disconnect generation unchanged for other canConnect list relations, including 1:N relations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/src/runtime/query/sql_compiler.dart`:
- Around line 1436-1444: Extend the relation-type guard in the nested mutation
handling branch to include RelationType.manyToOne alongside oneToMany and
oneToOne. Preserve the existing UnsupportedError message and behavior for
unsupported nested set operations.
- Around line 1353-1357: Update the relation-operation detection used by
_atomicUpdateOp so a set key is recognized as a relation operation only when
relation metadata/context confirms it is relational. Preserve scalar {set: ...}
values as scalar assignments, ensuring _compileCleanMainMutation retains those
fields in cleanData when no relation metadata is present.
- Around line 1495-1507: The _compileJunctionClear method must validate all
many-to-many junction metadata, including inverseJoinColumn, before generating a
DELETE query. Throw an error when joinTable, joinColumn, or inverseJoinColumn is
missing; only return the clear query for fully configured relations, preventing
incomplete configurations from silently clearing links or becoming no-ops.
- Around line 1410-1418: Validate every normalized selector in the `set` branch
before invoking or appending `_compileJunctionClear`, rejecting any target
missing its required primary key. Only perform the destructive clear and
subsequent `_compileConnectOperations` call after all selectors pass validation,
while preserving valid set replacement behavior.
- Around line 1408-1417: Update the set-handling path in the relation mutation
compiler to pass the raw parentId through _compileJunctionClear and
_compileConnectOperations instead of converting it with toString(). Ensure those
helpers bind the parent identifier using its actual primary-key type rather than
forcing ArgType.string, while preserving the existing clear-then-reconnect
behavior.
---
Nitpick comments:
In `@lib/src/generator/cb_delegate_generator.dart`:
- Around line 505-518: Extract the duplicated `data.toJson()` and `setNull`
iteration block into a generator-side helper such as
`_setNullInjectionSnippet()`. Update both `_update` and `_updateMany` body
templates to interpolate that helper instead of embedding the block directly,
preserving the existing null-field injection behavior.
In `@lib/src/generator/cb_model_generator.dart`:
- Around line 718-738: In the nested relation parameter generation for list
fields, derive or reuse metadata identifying RelationType.manyToMany and emit
the set parameter and JSON entry only when that flag is true. Keep connect and
disconnect generation unchanged for other canConnect list relations, including
1:N relations.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 2acfa813-5c99-47de-a141-d41500b3934f
📒 Files selected for processing (8)
CHANGELOG.mdlib/src/generator/cb_delegate_generator.dartlib/src/generator/cb_filter_types_generator.dartlib/src/generator/cb_model_generator.dartlib/src/runtime/query/sql_compiler.dartpubspec.yamltest/unit/typed_projection_test.darttest/unit/v090_null_semantics_test.dart
| value is Map<String, dynamic> && | ||
| (value.containsKey('connect') || | ||
| value.containsKey('disconnect') || | ||
| value.containsKey('set') || | ||
| value.containsKey('create')); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Keep scalar {set: ...} out of relation-op detection.
_atomicUpdateOp treats {set: 5} as a scalar assignment, but this change classifies it as a relation operation. _compileCleanMainMutation then drops scalar fields without relation metadata from cleanData, silently omitting the update. Make relation detection context-aware before adding set.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/src/runtime/query/sql_compiler.dart` around lines 1353 - 1357, Update the
relation-operation detection used by _atomicUpdateOp so a set key is recognized
as a relation operation only when relation metadata/context confirms it is
relational. Preserve scalar {set: ...} values as scalar assignments, ensuring
_compileCleanMainMutation retains those fields in cleanData when no relation
metadata is present.
| // `set`: replace the full relation — clear all junction rows for the | ||
| // parent, then connect exactly the given targets. | ||
| if (v.containsKey('set')) { | ||
| final clear = _compileJunctionClear(relation, parentId.toString()); | ||
| if (clear != null) mutations.add(clear); | ||
| mutations.addAll(_compileConnectOperations( | ||
| parentId: parentId.toString(), | ||
| relation: relation, | ||
| connectItems: _normalizeConnectDisconnect(v['set']), | ||
| effectiveSchema: effectiveSchema, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Preserve primary-key types in relation mutations.
parentId is dynamic but this path converts it to String, while the clear and reconnect queries bind ArgType.string. Many-to-many relations with non-string primary keys can fail or use incorrect parameter types. Preserve raw IDs and bind their actual types.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/src/runtime/query/sql_compiler.dart` around lines 1408 - 1417, Update the
set-handling path in the relation mutation compiler to pass the raw parentId
through _compileJunctionClear and _compileConnectOperations instead of
converting it with toString(). Ensure those helpers bind the parent identifier
using its actual primary-key type rather than forcing ArgType.string, while
preserving the existing clear-then-reconnect behavior.
| if (v.containsKey('set')) { | ||
| final clear = _compileJunctionClear(relation, parentId.toString()); | ||
| if (clear != null) mutations.add(clear); | ||
| mutations.addAll(_compileConnectOperations( | ||
| parentId: parentId.toString(), | ||
| relation: relation, | ||
| connectItems: _normalizeConnectDisconnect(v['set']), | ||
| effectiveSchema: effectiveSchema, | ||
| )); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Validate every set selector before clearing the junction.
_compileConnectOperations skips targets missing their primary key, so {set: [{}]} deletes all existing links and reconnects none. Validate all targets before appending the destructive DELETE and reject malformed selectors.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/src/runtime/query/sql_compiler.dart` around lines 1410 - 1418, Validate
every normalized selector in the `set` branch before invoking or appending
`_compileJunctionClear`, rejecting any target missing its required primary key.
Only perform the destructive clear and subsequent `_compileConnectOperations`
call after all selectors pass validation, while preserving valid set replacement
behavior.
| } else if (relation.type == RelationType.oneToMany || | ||
| relation.type == RelationType.oneToOne) { | ||
| if (v.containsKey('set')) { | ||
| throw UnsupportedError( | ||
| 'Nested `set` on the ${relation.type.name} relation ' | ||
| '"${entry.key}" is only supported for many-to-many relations ' | ||
| '(1:N re-parenting is not implemented).', | ||
| ); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Reject set on manyToOne relations too.
The guard covers only oneToMany and oneToOne. A manyToOne {set: ...} is recognized, stripped from the main mutation, and never handled by _buildRelationMutations, so the update succeeds without applying the relation change. Include RelationType.manyToOne in the rejection path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/src/runtime/query/sql_compiler.dart` around lines 1436 - 1444, Extend the
relation-type guard in the nested mutation handling branch to include
RelationType.manyToOne alongside oneToMany and oneToOne. Preserve the existing
UnsupportedError message and behavior for unsupported nested set operations.
| /// DELETE all junction rows for [parentId] on an m2m relation (the clear | ||
| /// half of a nested `set`). Returns null when the relation lacks junction | ||
| /// metadata. | ||
| SqlQuery? _compileJunctionClear(RelationInfo relation, String parentId) { | ||
| if (relation.joinTable == null || relation.joinColumn == null) return null; | ||
| return SqlQuery( | ||
| sql: 'DELETE FROM ${_quoteIdentifier(relation.joinTable!)} ' | ||
| 'WHERE ${_quoteIdentifier(relation.joinColumn!)} = ${_placeholder(1)}', | ||
| args: [parentId], | ||
| argTypes: const [ArgType.string], | ||
| ); | ||
| } | ||
|
|
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Fail loudly when many-to-many metadata is incomplete.
This helper permits missing inverseJoinColumn; the subsequent connect compiler then returns no queries, leaving the DELETE to clear all links permanently. Missing joinTable or joinColumn instead makes the entire set a silent no-op. Validate all junction metadata before clearing and throw an error for invalid relation configuration.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/src/runtime/query/sql_compiler.dart` around lines 1495 - 1507, The
_compileJunctionClear method must validate all many-to-many junction metadata,
including inverseJoinColumn, before generating a DELETE query. Throw an error
when joinTable, joinColumn, or inverseJoinColumn is missing; only return the
clear query for fully configured relations, preventing incomplete configurations
from silently clearing links or becoming no-ops.
prisma_flutter_connector v0.9.0 — null semantics
Closes the typed-surface gaps discovered while migrating the familiarise backend to a fully typed data layer (the 4 documented JQB-exempt sites), and completes the raw-helper deprecation cycle.
Added
setNullon typed updates — typed inputs drop null fields, so null-clears were inexpressible:isNullon every filter class —isNull: true→IS NULL,isNull: false→IS NOT NULL:Nested
setfor many-to-many relations — replace semantics (junction clear + connects):seton 1:N/1:1 throwsUnsupportedError(re-parenting unimplemented) rather than silently dropping data.Changed
String[]-style columns hydrate SQLNULLasconst []instead of crashingfromJson(observed with dirty data violating a non-null array column).Removed
findManyRaw/findFirstRaw— deprecated in 0.8.0, removed as scheduled. Use the projected finders or typedfindMany+toJson().Verified
flutter analyze --fatal-infosclean; format clean.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
isNullandisNotNullfiltering for typed queries.setNullsupport for clearing fields in typed updates.setoperations for many-to-many relationships.Bug Fixes
NULLvalues as empty lists.Breaking Changes
findManyRawandfindFirstRaw; use projected or typed finders instead.setis unsupported for one-to-one and one-to-many relationships.