Skip to content

feat: v0.9.0 — null semantics (setNull, isNull filters, m2m set) + remove deprecated raw helpers - #76

Merged
teetangh merged 1 commit into
mainfrom
feat/v0.9.0-null-semantics
Jul 23, 2026
Merged

feat: v0.9.0 — null semantics (setNull, isNull filters, m2m set) + remove deprecated raw helpers#76
teetangh merged 1 commit into
mainfrom
feat/v0.9.0-null-semantics

Conversation

@teetangh

@teetangh teetangh commented Jul 23, 2026

Copy link
Copy Markdown
Owner

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

setNull on typed updates — typed inputs drop null fields, so null-clears were inexpressible:

db.user.update(
  where: UserWhereUniqueInput(id: id),
  data: UpdateUserInput(),
  setNull: [UserScalarField.image],   // → SET "image" = NULL
);

isNull on every filter classisNull: trueIS NULL, isNull: falseIS NOT NULL:

where: MaintenanceWindowWhereInput(endedAt: DateTimeFilter(isNull: true))

Nested set for many-to-many relations — replace semantics (junction clear + connects):

db.consultantProfile.update(
  where: ...,
  data: UpdateConsultantProfileInput(
    subDomains: ConsultantProfileSubDomainsWriteInput(
      set: [for (final id in ids) SubDomainWhereUniqueInput(id: id)],
    ),
  ),
);

set on 1:N/1:1 throws UnsupportedError (re-parenting unimplemented) rather than silently dropping data.

Changed

  • Null-tolerant array decode — required String[]-style columns hydrate SQL NULL as const [] instead of crashing fromJson (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 typed findMany + toJson().

Verified

  • 464 unit tests green (new: setNull injection + NULL SET compilation; isNull filter emission + IS NULL/IS NOT NULL SQL; m2m set → junction clear + connects with exact args; 1:N set throws; null-tolerant list decode; raw helpers absent).
  • flutter analyze --fatal-infos clean; format clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added isNull and isNotNull filtering for typed queries.
    • Added setNull support for clearing fields in typed updates.
    • Added nested set operations for many-to-many relationships.
  • Bug Fixes

    • Array fields now safely decode SQL NULL values as empty lists.
  • Breaking Changes

    • Removed findManyRaw and findFirstRaw; use projected or typed finders instead.
    • Nested set is unsupported for one-to-one and one-to-many relationships.

…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>
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Version 0.9.0 adds typed null filters, explicit null updates, many-to-many nested set mutations, null-tolerant array decoding, and removal of raw finder methods. Tests cover generated code and SQL compilation for these behaviors.

Changes

Version 0.9.0 semantics

Layer / File(s) Summary
Null filters and typed updates
lib/src/generator/cb_filter_types_generator.dart, lib/src/generator/cb_delegate_generator.dart, test/unit/v090_null_semantics_test.dart
Generated filters serialize isNull and isNotNull, while update and updateMany accept setNull and emit explicit null assignments.
Many-to-many set mutations
lib/src/generator/cb_model_generator.dart, lib/src/runtime/query/sql_compiler.dart, test/unit/v090_null_semantics_test.dart
Connectable list relations accept set; many-to-many compilation clears existing junction rows before inserting replacement links, while to-one and one-to-many usage throws UnsupportedError.
Null-tolerant array decoding
lib/src/generator/cb_model_generator.dart, test/unit/v090_null_semantics_test.dart
Required enum-list decoding treats JSON null as an empty list.
Finder API removal and release metadata
lib/src/generator/cb_delegate_generator.dart, test/unit/typed_projection_test.dart, pubspec.yaml, CHANGELOG.md
Raw finder methods are no longer generated, tests assert their absence, and the package and changelog are updated to 0.9.0.

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
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main v0.9.0 null-semantics changes and raw helper removal.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/v0.9.0-null-semantics

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
lib/src/generator/cb_delegate_generator.dart (1)

505-518: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract shared setNull injection 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 _update and _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 with data) 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 lift

Gate nested relation set to many-to-many relations only.

lib/src/generator/cb_model_generator.dart:718-738 emits set for any f.isList && canConnect, but nested set is compiled only for RelationType.manyToMany through junction-table clear/connect operations. For the existing 1:N User.posts relation, the generated PostWriteInput exposes set even though the runtime only handles m2m, so invalid set: [...] calls can reach the type-level API before the SQL compiler supports them. Add a derived many-to-many flag/metadata and restrict set generation 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

📥 Commits

Reviewing files that changed from the base of the PR and between c1aa029 and 8224996.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • lib/src/generator/cb_delegate_generator.dart
  • lib/src/generator/cb_filter_types_generator.dart
  • lib/src/generator/cb_model_generator.dart
  • lib/src/runtime/query/sql_compiler.dart
  • pubspec.yaml
  • test/unit/typed_projection_test.dart
  • test/unit/v090_null_semantics_test.dart

Comment on lines 1353 to 1357
value is Map<String, dynamic> &&
(value.containsKey('connect') ||
value.containsKey('disconnect') ||
value.containsKey('set') ||
value.containsKey('create'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Comment on lines +1408 to +1417
// `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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Comment on lines +1410 to +1418
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,
));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Comment on lines 1436 to +1444
} 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).',
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines +1495 to +1507
/// 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],
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

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