Skip to content

feat(postgrest)!: order and limit inside a to-many embed on the typed surface - #1960

Merged
spydon merged 1 commit into
mainfrom
lukasklingsbo/sdk-1758-embedded-scope
Oct 15, 2026
Merged

spydon merged 1 commit into
mainfrom
lukasklingsbo/sdk-1758-embedded-scope

Conversation

@spydon

@spydon spydon commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The typed order() had no way to address an embed, and the typed limit() and range() still took an untyped String? referencedTable. Sorting the rows inside a to-many embed was therefore impossible on the typed surface, so limit(1, referencedTable: 'books') trimmed an unsorted embed and returned an arbitrary row.

This adds embedded() to the typed transform builder, following the scope Swift shipped in supabase/supabase-swift#1458:

await client
    .table(Authors.table)
    .selectOnly([Authors.name, Authors.books.select([Books.title])])
    .embedded(
      Authors.books,
      (books) => books.order(Books.createdAt.desc()).limit(1),
    );
// select=name,books(title)&books.order=created_at.desc&books.limit=1
  • embedded() takes a PostgrestToManyRelation<Row, Target>. The callback receives a PostgrestEmbeddedScope<Target>, so ordering by a column of another table does not compile.
  • The scope offers order, limit, range and embedded. Nested scopes render dotted paths, such as books.reviews.limit=3.
  • An aliased embed is addressed by its alias.
  • Repeated scopes of one embed append their sort keys, and the last limit or range wins.
  • Scopes also apply to the rows a mutation returns.

Breaking

The typed limit() and range() no longer take referencedTable. The typed surface is experimental, so the change is covered in the "Moving from from() to the typed table API" section of MIGRATION.md and not as its own breaking section.

Out of scope

Filtering inside an embed and the requiring (!inner) counterpart will ship together in a follow-up. An embedded filter without requiring silently keeps every parent row.

Other changes

  • PostgrestTableRequest is now @internal. It was already hidden from the package export, and the annotation keeps its fields out of the compliance symbol check.
  • Removed PostgrestEmbeddedPage. It was internal, and the scope replaces it.
  • Registered under database.using_modifiers.relationship_embed in sdk-compliance.yaml.

Testing

  • Unit tests in typed_query_test.dart cover rendering, nesting, aliases, repeated scopes and mutations.
  • A live test in resource_embedding_test.dart checks ordering, limit and range against the local stack.
  • The full postgrest suite passes (601 tests). melos analyze, dcm analyze and the local compliance symbol check are clean.

Summary by CodeRabbit

  • New Features

    • Configure ordering, limits, and ranges for rows within to-many embeds, including nested and aliased embeds. These options also work with rows returned by mutations.
    • PostgrestEmbeddedPage is now available through the public library exports.
  • Changes

    • limit() and range() now apply to top-level results only; use embedded options to paginate embedded rows.
    • Embedded rows without a limit are no longer offset.

… surface

Adds `embedded()` to the typed transform builder. It takes a to-many
relation and a callback that receives a scope typed against the embedded
table, offering `order`, `limit`, `range` and nested `embedded` calls:

    .embedded(
      Authors.books,
      (books) => books.order(Books.createdAt.desc()).limit(1),
    )
    // books.order=created_at.desc&books.limit=1

The typed `limit()` and `range()` drop their untyped `referencedTable`
parameter in favor of the scope.

Fixes SDK-1758
@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: supabase/supabase-flutter/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0777a813-60e8-46fa-bc0c-2c92034efe85
📥 Commits

Reviewing files that changed from the base of the PR and between b223d07 and 2972e00.

📒 Files selected for processing: 12
  • MIGRATION.md
  • packages/postgrest/lib/postgrest.dart
  • packages/postgrest/lib/src/postgrest_embedded_relation.dart
  • packages/postgrest/lib/src/postgrest_embedded_scope.dart
  • packages/postgrest/lib/src/postgrest_table_executor.dart
  • packages/postgrest/lib/src/postgrest_table_request.dart
  • packages/postgrest/lib/src/postgrest_transform_builder.dart
  • packages/postgrest/lib/src/postgrest_typed_builder.dart
  • packages/postgrest/lib/src/postgrest_typed_transform_builder.dart
  • packages/postgrest/test/resource_embedding_test.dart
  • packages/postgrest/test/typed_query_test.dart
  • sdk-compliance.yaml
💤 Files with no reviewable changes: 1
  • packages/postgrest/lib/postgrest.dart

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

📝 Walkthrough

The typed query API adds embedded() scopes for ordering, limiting, and ranging to-many embeds. Scopes can contain nested embeds. The HTTP table executor converts the scope settings into request parameters.

Changes: Typed embedded scopes

Layer / File(s) Summary
Define and expose embedded scopes
packages/postgrest/lib/src/postgrest_embedded_scope.dart, packages/postgrest/lib/src/postgrest_typed_transform_builder.dart, packages/postgrest/lib/src/postgrest_transform_builder.dart, packages/postgrest/lib/src/postgrest_table_request.dart, packages/postgrest/lib/postgrest.dart, packages/postgrest/lib/src/postgrest_typed_builder.dart, MIGRATION.md, packages/postgrest/lib/src/postgrest_embedded_relation.dart, sdk-compliance.yaml
PostgrestEmbeddedScope stores ordering keys, limits, offsets, and nested scopes. Typed limit() and range() now set only top-level request values. embedded() configures selected to-many relations. The request stores embedded scopes, and the public export and documentation describe the API.
Build embedded request parameters
packages/postgrest/lib/src/postgrest_table_executor.dart, packages/postgrest/test/resource_embedding_test.dart, packages/postgrest/test/typed_query_test.dart
The executor applies each flattened scope’s ordering and row window to its relation path. Tests cover ordering, limits, ranges, nested and aliased embeds, repeated scopes, and mutation results.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant PostgrestTypedTransformBuilder
  participant PostgrestEmbeddedScope
  participant PostgrestTableRequest
  participant PostgrestHttpTableExecutor
  PostgrestTypedTransformBuilder->>PostgrestEmbeddedScope: Configure ordering, limits, ranges, and nested scopes
  PostgrestTypedTransformBuilder->>PostgrestTableRequest: Append embedded scope
  PostgrestHttpTableExecutor->>PostgrestEmbeddedScope: Flatten scope transforms
  PostgrestHttpTableExecutor->>PostgrestTableRequest: Apply ordering and row windows to relation paths
Loading

Suggested reviewers: grdsdev, androidpoet

Merge Risk: ⚪ Minimal · up to 2972e

This adds scoped ordering and pagination for to-many embeds in the typed query API. I found no concrete merge-blocking risk. The removal of referencedTable from limit() and range() is a deliberate API change that the migration guide covers.

Pre-merge checks: Passed 5
Passed 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 clearly and concisely describes the main change: adding typed-surface support to order and limit rows inside to-many embeds. The breaking-change marker is also appropriate because typed limi…
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
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)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@spydon
spydon merged commit 973de4f into main Oct 15, 2026
44 checks passed
@spydon
spydon deleted the lukasklingsbo/sdk-1758-embedded-scope branch October 15, 2026 13:10
spydon added a commit that referenced this pull request Oct 15, 2026
…select list (#1962)

## Summary

Follow-up to #1960, from a review of that PR.

A relation with a foreign key hint and no alias, such as a hand-written
`PostgrestToOneRelation('users!messages_username_fkey', ...)`, was
addressed by its hinted name everywhere outside the `select` list.
PostgREST matches those paths against the alias or the table name only.
Checked against the local stack:

| Request | Result |
|---|---|
| `messages.limit=1` | applied, 1 row |
| `messages!username.limit=1` | silently ignored, every row |
| `order=users(username).desc` | applied |
| `order=users!username(username).desc` | PGRST100 parse error |

Outside the `select` list, the embed is now addressed by its alias, or
by the table name without the hint. This fixes `embedded()` scopes, and
also the to-one `order` keys and `embeddedFilterName`, which had the
same bug before #1960. Typegen always pairs a hint with an alias, so
generated tables were not affected.

## Other changes

- The `embedded()` dartdoc now says exactly what a repeated call does: a
later limit replaces only the limit, and a later range replaces both the
offset and the limit.
- The untyped `order()` merges its key through `appendOrderKey` instead
of duplicating the merge logic.
- In `MIGRATION.md`, the foreign key paragraph is back next to the
relation text it explains, and the snippet is wrapped to 80 characters.

## Testing

- New unit tests cover a hinted embed with no alias in a scope, in an
order key and in a filter name.
- The full postgrest suite passes against the local stack (623 tests).
`melos analyze` and `dcm analyze` are clean.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Embedded ordering and filters now correctly use the referenced table
name when a foreign-key hint is present without an alias, while
preserving the hint in the select expression.

* **Documentation**
* Clarified how to address embedded relations when multiple foreign keys
point to the same table.
* Updated guidance on how repeated embedded sort keys, limits, and
ranges are applied.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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