Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -2680,6 +2680,9 @@ final List<String> titles = [

A to-one relation reads back as a nullable partial row and a to-many relation as a list of them.

When two foreign keys point at the same table, the generator adds the constraint hint and an alias,
so each embed comes back under its own key without spelling `authors!books_author_id_fkey` by hand.

`embedded()` replaces `referencedTable` on `order()`, `limit()` and `range()`. Its callback receives
a scope typed against the embedded table, so ordering by a column of another table does not compile,
and a scope can reach an embed nested in it:
Expand All @@ -2698,7 +2701,10 @@ await supabase
.table(Authors.table)
.selectOnly([
Authors.name,
Authors.books.select([Books.title, Books.reviews.select([Reviews.rating])]),
Authors.books.select([
Books.title,
Books.reviews.select([Reviews.rating]),
]),
])
.embedded(
Authors.books,
Expand All @@ -2708,8 +2714,6 @@ await supabase
.embedded(Books.reviews, (reviews) => reviews.limit(3)),
);
```
When two foreign keys point at the same table, the generator adds the constraint hint and an alias,
so each embed comes back under its own key without spelling `authors!books_author_id_fkey` by hand.

The generated table definition also lists `primaryKey` and `relations`. Neither changes how a
request is sent; they describe the table to code that handles requests itself, such as a plugin
Expand Down
7 changes: 4 additions & 3 deletions packages/postgrest/lib/src/postgrest_embedded_relation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ sealed class PostgrestRelation<Row, Target> {
}) : columns = const [],
referencedColumns = const [];

/// The name PostgREST addresses the embed by: the table name, including
/// The name the embed is selected by: the table name, including
/// any disambiguating foreign key hint such as
/// `authors!books_author_id_fkey`, or the function name of a computed
/// relationship.
Expand Down Expand Up @@ -95,8 +95,9 @@ sealed class PostgrestRelation<Row, Target> {
/// How the embed is spelled in the `select` list.
String get _selectName => alias == null ? name : '$alias:$name';

/// How the embed is addressed outside the `select` list.
String get _reference => alias ?? name;
/// How the embed is addressed outside the `select` list, where PostgREST
/// matches the alias or the table name but not a foreign key hint.
String get _reference => key;

/// The columns of this table the relation joins on; empty for a computed
/// relationship.
Expand Down
11 changes: 4 additions & 7 deletions packages/postgrest/lib/src/postgrest_transform_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,15 @@ class PostgrestTransformBuilder<T> extends PostgrestBuilder<T> {
bool? nullsFirst,
String? referencedTable,
}) {
final key = referencedTable == null ? 'order' : '$referencedTable.order';
final existingOrder = _url.queryParameters[key];
final placement = switch (nullsFirst) {
null => '',
true => '.nullsfirst',
false => '.nullslast',
};
final value =
'${existingOrder == null ? '' : '$existingOrder,'}$column.'
'${ascending ? 'asc' : 'desc'}$placement';
final url = _url.overrideSearchParameters(key, value);
return PostgrestTransformBuilder(copyWithUrl(url));
return appendOrderKey(
'$column.${ascending ? 'asc' : 'desc'}$placement',
referencedTable: referencedTable,
);
}

/// Merges the rendered sort key [key] into the `order` parameter, or into
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ class PostgrestTypedTransformBuilder<Row, T> extends PostgrestTypedBuilder<T> {
/// ```
///
/// [relation] has to be in the `select` list. Repeated calls for the same
/// embed append their sort keys, and the last limit or range wins. See
/// embed append their sort keys, a later limit replaces the limit and a
/// later range replaces both the offset and the limit. See
/// [PostgrestEmbeddedScope] for what a scope offers.
PostgrestTypedTransformBuilder<Row, T> embedded<Target>(
PostgrestToManyRelation<Row, Target> relation,
Expand Down
15 changes: 15 additions & 0 deletions packages/postgrest/test/postgrest_embedded_relation_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,21 @@ void main() {
'parent_todo(orders(id))',
);
});

test('addresses a hinted embed without an alias by its table name', () {
const hintedTodo = PostgrestToOneRelation<Order, Todo>(
'todo!orders_parent_id_fkey',
columns: [Orders.parentId],
referencedTable: 'todos',
referencedColumns: [Todos.id],
);
expect(
hintedTodo(Todos.title).expression,
'todo!orders_parent_id_fkey(title)',
);
expect(hintedTodo(Todos.title).desc().orderKey, 'todo(title).desc');
expect(hintedTodo(Todos.title).embeddedFilterName, 'todo.title');
});
});

group('select', () {
Expand Down
20 changes: 20 additions & 0 deletions packages/postgrest/test/typed_query_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,26 @@ void main() {
});
});

test('a hinted embed without an alias is addressed by its table', () async {
const hintedBooks = PostgrestToManyRelation<Author, Book>(
'books!books_editor_id_fkey',
columns: [Authors.id],
referencedTable: 'books',
referencedColumns: [Books.editorId],
);
await client
.table(Authors.table)
.selectOnly([
hintedBooks.select([Books.title]),
])
.embedded(hintedBooks, (books) => books.limit(1));

expect(requestParameters(), {
'select': 'books!books_editor_id_fkey(title)',
'books.limit': '1',
});
});

test('repeated scopes of one embed append orderings', () async {
await client
.table(Authors.table)
Expand Down
Loading