From 9a41b950a820cc83c32d88046477d323ab945dfa Mon Sep 17 00:00:00 2001 From: Tim Shedor Date: Mon, 13 Jan 2025 12:00:59 -0800 Subject: [PATCH] feat(supabase): define upsertMethod within query for supabase (#529) --- packages/brick_supabase/CHANGELOG.md | 6 ++++ .../brick_supabase/lib/brick_supabase.dart | 1 + .../lib/src/query_supabase_transformer.dart | 4 ++- .../lib/src/supabase_provider.dart | 3 ++ .../lib/src/supabase_provider_query.dart | 30 +++++++++++++++++++ packages/brick_supabase/pubspec.yaml | 2 +- .../test/query_supabase_transformer_test.dart | 2 +- .../test/supabase_provider_test.dart | 24 +++++++++++++++ 8 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 packages/brick_supabase/lib/src/supabase_provider_query.dart diff --git a/packages/brick_supabase/CHANGELOG.md b/packages/brick_supabase/CHANGELOG.md index 8de48e44..ede7f17e 100644 --- a/packages/brick_supabase/CHANGELOG.md +++ b/packages/brick_supabase/CHANGELOG.md @@ -1,5 +1,11 @@ ## Unreleased +## 1.4.1+1 + +- Add `SupabaseProviderQuery` +- Support defining `upsertMethod` via `SupabaseProviderQuery` +- Fix `orderBy` queries to use the column name instead of the field name when constructing PostgREST queries + ## 1.4.0 - **DEPRECATION** remove `Supabase#nullable`. Builders should evaluate the nullable suffix of the field instead diff --git a/packages/brick_supabase/lib/brick_supabase.dart b/packages/brick_supabase/lib/brick_supabase.dart index 40d1f743..de88d5f0 100644 --- a/packages/brick_supabase/lib/brick_supabase.dart +++ b/packages/brick_supabase/lib/brick_supabase.dart @@ -6,3 +6,4 @@ export 'package:brick_supabase/src/supabase_adapter.dart'; export 'package:brick_supabase/src/supabase_model.dart'; export 'package:brick_supabase/src/supabase_model_dictionary.dart'; export 'package:brick_supabase/src/supabase_provider.dart'; +export 'package:brick_supabase/src/supabase_provider_query.dart'; diff --git a/packages/brick_supabase/lib/src/query_supabase_transformer.dart b/packages/brick_supabase/lib/src/query_supabase_transformer.dart index e8a48e24..a5cc039a 100644 --- a/packages/brick_supabase/lib/src/query_supabase_transformer.dart +++ b/packages/brick_supabase/lib/src/query_supabase_transformer.dart @@ -247,10 +247,12 @@ class QuerySupabaseTransformer<_Model extends SupabaseModel> { return query!.orderBy.fold(withProviderArgs, (acc, orderBy) { final definition = adapter.fieldsToSupabaseColumns[orderBy.evaluatedField]; final tableName = modelDictionary.adapterFor[definition?.associationType]?.supabaseTableName; + final columnName = adapter + .fieldsToSupabaseColumns[orderBy.associationField ?? orderBy.evaluatedField]?.columnName; final url = acc.appendSearchParams( tableName == null ? 'order' : '$tableName.order', - '${orderBy.associationField ?? orderBy.evaluatedField}.${orderBy.ascending ? 'asc' : 'desc'}.nullslast', + '$columnName.${orderBy.ascending ? 'asc' : 'desc'}.nullslast', ); return acc.copyWithUrl(url); }); diff --git a/packages/brick_supabase/lib/src/supabase_provider.dart b/packages/brick_supabase/lib/src/supabase_provider.dart index be4d8f76..b7ff1ecb 100644 --- a/packages/brick_supabase/lib/src/supabase_provider.dart +++ b/packages/brick_supabase/lib/src/supabase_provider.dart @@ -2,6 +2,7 @@ import 'package:brick_core/core.dart'; import 'package:brick_supabase/src/query_supabase_transformer.dart'; import 'package:brick_supabase/src/supabase_model.dart'; import 'package:brick_supabase/src/supabase_model_dictionary.dart'; +import 'package:brick_supabase/src/supabase_provider_query.dart'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:supabase/supabase.dart'; @@ -149,9 +150,11 @@ class SupabaseProvider implements Provider { }) async { final adapter = modelDictionary.adapterFor[TModel]!; final output = await adapter.toSupabase(instance, provider: this, repository: repository); + final providerQuery = query?.providerQueries[SupabaseProvider] as SupabaseProviderQuery?; return await recursiveAssociationUpsert( output, + method: providerQuery?.upsertMethod ?? UpsertMethod.upsert, type: TModel, query: query, repository: repository, diff --git a/packages/brick_supabase/lib/src/supabase_provider_query.dart b/packages/brick_supabase/lib/src/supabase_provider_query.dart new file mode 100644 index 00000000..bb134071 --- /dev/null +++ b/packages/brick_supabase/lib/src/supabase_provider_query.dart @@ -0,0 +1,30 @@ +import 'package:brick_core/core.dart'; +import 'package:brick_supabase/src/supabase_provider.dart'; + +/// [SupabaseProvider]-specific options for a [Query] +class SupabaseProviderQuery extends ProviderQuery { + /// An internal definition for remote requests. + /// In rare cases, a specific `update` or `insert` is preferable to `upsert`; + /// this enum explicitly declares the desired behavior. + final UpsertMethod? upsertMethod; + + /// [SupabaseProvider]-specific options for a [Query] + const SupabaseProviderQuery({ + this.upsertMethod, + }); + + @override + Map toJson() => { + if (upsertMethod != null) 'upsertMethod': upsertMethod?.name, + }; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is SupabaseProviderQuery && + runtimeType == other.runtimeType && + upsertMethod == other.upsertMethod; + + @override + int get hashCode => upsertMethod.hashCode; +} diff --git a/packages/brick_supabase/pubspec.yaml b/packages/brick_supabase/pubspec.yaml index 2896fe01..c5e98ad0 100644 --- a/packages/brick_supabase/pubspec.yaml +++ b/packages/brick_supabase/pubspec.yaml @@ -4,7 +4,7 @@ homepage: https://github.com/GetDutchie/brick/tree/main/packages/brick_supabase issue_tracker: https://github.com/GetDutchie/brick/issues repository: https://github.com/GetDutchie/brick -version: 1.4.0 +version: 1.4.1+1 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/packages/brick_supabase/test/query_supabase_transformer_test.dart b/packages/brick_supabase/test/query_supabase_transformer_test.dart index 291ca4d5..26245ec8 100644 --- a/packages/brick_supabase/test/query_supabase_transformer_test.dart +++ b/packages/brick_supabase/test/query_supabase_transformer_test.dart @@ -246,7 +246,7 @@ void main() { expect( transformBuilder.query, - 'select=id,nested_column:demo_associations(id,name,assoc_id:demos!assoc_id(id,name,custom_age),assocs:demos(id,name,custom_age))&demo_associations.order=nested.desc.nullslast', + 'select=id,nested_column:demo_associations(id,name,assoc_id:demos!assoc_id(id,name,custom_age),assocs:demos(id,name,custom_age))&demo_associations.order=nested_column.desc.nullslast', ); }); diff --git a/packages/brick_supabase/test/supabase_provider_test.dart b/packages/brick_supabase/test/supabase_provider_test.dart index 1130a40d..f571b878 100644 --- a/packages/brick_supabase/test/supabase_provider_test.dart +++ b/packages/brick_supabase/test/supabase_provider_test.dart @@ -1,6 +1,8 @@ // ignore_for_file: unawaited_futures +import 'package:brick_core/query.dart'; import 'package:brick_supabase/src/supabase_provider.dart'; +import 'package:brick_supabase/src/supabase_provider_query.dart'; import 'package:brick_supabase/testing.dart'; import 'package:test/test.dart'; @@ -141,6 +143,28 @@ void main() { expect(inserted.assoc.id, instance.assoc.id); expect(inserted.name, instance.name); }); + + test('with non-default method from query', () async { + const req = SupabaseRequest( + requestMethod: 'PATCH', + filter: 'id=eq.1', + limit: 1, + ); + final instance = Demo(age: 1, name: 'Demo 1', id: '1'); + final resp = SupabaseResponse(await mock.serialize(instance)); + mock.handle({req: resp}); + + final provider = SupabaseProvider(mock.client, modelDictionary: supabaseModelDictionary); + final inserted = await provider.upsert( + instance, + query: const Query( + forProviders: [SupabaseProviderQuery(upsertMethod: UpsertMethod.update)], + ), + ); + expect(inserted.id, instance.id); + expect(inserted.age, instance.age); + expect(inserted.name, instance.name); + }); }); }); }