Skip to content

Commit

Permalink
eng: add update and insert to SupabaseProvider (#486)
Browse files Browse the repository at this point in the history
  • Loading branch information
tshedor authored Dec 11, 2024
1 parent 0ae0932 commit 6d3d971
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 8 deletions.
3 changes: 3 additions & 0 deletions packages/brick_offline_first/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
## Unreleased

## 3.4.0

- Change `OfflineFirstRepository#exists` behavior: the check against memory cache will only return `true` if results have been found, otherwise it will continue to the SQLite provider
- Forward errors from `OfflineFirstRepository#subscribe` streams to their callers (@sonbs21 #484)

## 3.3.0

Expand Down
2 changes: 1 addition & 1 deletion packages/brick_offline_first/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ homepage: https://github.com/GetDutchie/brick/tree/main/packages/brick_offline_f
issue_tracker: https://github.com/GetDutchie/brick/issues
repository: https://github.com/GetDutchie/brick

version: 3.3.0
version: 3.4.0

environment:
sdk: ">=2.18.0 <4.0.0"
Expand Down
5 changes: 5 additions & 0 deletions packages/brick_supabase/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## Unreleased

## 1.2.0

- Add `SupabaseProvider#update` and `SupabaseProvider#insert` to conform to Supabase policy restrictions
- Use `columnName` instead of `evaluatedField` in `QuerySupabaseTransformer` when searching for non null associations

## 1.1.3

- Add `query:` to `@Supabase` to override the generated query at runtime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ class QuerySupabaseTransformer<_Model extends SupabaseModel> {
condition.value as WhereCondition, associationAdapter, newLeadingAssociations, [
if (!definition.associationIsNullable)
{
condition.evaluatedField: 'not.is.null',
definition.columnName: 'not.is.null',
},
...associationConditions,
]);
Expand Down
58 changes: 54 additions & 4 deletions packages/brick_supabase/lib/src/supabase_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:supabase/supabase.dart';

enum UpsertMethod {
insert,
update,
upsert,
}

/// Retrieves from an HTTP endpoint
class SupabaseProvider implements Provider<SupabaseModel> {
final SupabaseClient client;
Expand Down Expand Up @@ -77,6 +83,36 @@ class SupabaseProvider implements Provider<SupabaseModel> {
);
}

/// In almost all cases, use [upsert]. This method is provided for cases when a table's
/// policy permits inserts without updates.
Future<TModel> insert<TModel extends SupabaseModel>(instance, {query, repository}) async {
final adapter = modelDictionary.adapterFor[TModel]!;
final output = await adapter.toSupabase(instance, provider: this, repository: repository);

return await recursiveAssociationUpsert(
output,
method: UpsertMethod.insert,
type: TModel,
query: query,
repository: repository,
) as TModel;
}

/// In almost all cases, use [upsert]. This method is provided for cases when a table's
/// policy permits updates without inserts.
Future<TModel> update<TModel extends SupabaseModel>(instance, {query, repository}) async {
final adapter = modelDictionary.adapterFor[TModel]!;
final output = await adapter.toSupabase(instance, provider: this, repository: repository);

return await recursiveAssociationUpsert(
output,
method: UpsertMethod.update,
type: TModel,
query: query,
repository: repository,
) as TModel;
}

/// Association models are upserted recursively before the requested instance is upserted.
/// Because it's unknown if there has been any change from the local association to the remote
/// association, all associations and their associations are upserted on a parent's upsert.
Expand All @@ -101,6 +137,7 @@ class SupabaseProvider implements Provider<SupabaseModel> {
@protected
Future<SupabaseModel> upsertByType(
Map<String, dynamic> serializedInstance, {
UpsertMethod method = UpsertMethod.upsert,
required Type type,
Query? query,
ModelRepository<SupabaseModel>? repository,
Expand All @@ -112,10 +149,20 @@ class SupabaseProvider implements Provider<SupabaseModel> {
final queryTransformer =
QuerySupabaseTransformer(adapter: adapter, modelDictionary: modelDictionary, query: query);

final builder = adapter.uniqueFields.fold(
client
.from(adapter.supabaseTableName)
.upsert(serializedInstance, onConflict: adapter.onConflict), (acc, uniqueFieldName) {
final builderFilter = () {
switch (method) {
case UpsertMethod.insert:
return client.from(adapter.supabaseTableName).insert(serializedInstance);
case UpsertMethod.update:
return client.from(adapter.supabaseTableName).update(serializedInstance);
case UpsertMethod.upsert:
return client
.from(adapter.supabaseTableName)
.upsert(serializedInstance, onConflict: adapter.onConflict);
}
}();

final builder = adapter.uniqueFields.fold(builderFilter, (acc, uniqueFieldName) {
final columnName = adapter.fieldsToSupabaseColumns[uniqueFieldName]!.columnName;
if (serializedInstance.containsKey(columnName)) {
return acc.eq(columnName, serializedInstance[columnName]);
Expand All @@ -136,6 +183,7 @@ class SupabaseProvider implements Provider<SupabaseModel> {
@protected
Future<SupabaseModel> recursiveAssociationUpsert(
Map<String, dynamic> serializedInstance, {
UpsertMethod method = UpsertMethod.upsert,
required Type type,
Query? query,
ModelRepository<SupabaseModel>? repository,
Expand All @@ -157,6 +205,7 @@ class SupabaseProvider implements Provider<SupabaseModel> {

await recursiveAssociationUpsert(
Map<String, dynamic>.from(serializedInstance[association.columnName]),
method: method,
type: association.associationType!,
query: query,
repository: repository,
Expand All @@ -166,6 +215,7 @@ class SupabaseProvider implements Provider<SupabaseModel> {

return await upsertByType(
serializedInstance,
method: method,
type: type,
query: query,
repository: repository,
Expand Down
2 changes: 1 addition & 1 deletion packages/brick_supabase/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.1.3
version: 1.2.0

environment:
sdk: ">=3.0.0 <4.0.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ void main() {

expect(
select.query,
'select=id,name,assoc_id:demos!assoc_id(id,name,custom_age),assocs:demos(id,name,custom_age)&demos.name=eq.Thomas&assoc=not.is.null',
'select=id,name,assoc_id:demos!assoc_id(id,name,custom_age),assocs:demos(id,name,custom_age)&demos.name=eq.Thomas&assoc_id=not.is.null',
);
});
});
Expand Down
34 changes: 34 additions & 0 deletions packages/brick_supabase/test/supabase_provider_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,40 @@ void main() {
expect(retrieved[1].age, 2);
});

test('#insert', () async {
final req = SupabaseRequest<Demo>(
requestMethod: 'POST',
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.insert<Demo>(instance);
expect(inserted.id, instance.id);
expect(inserted.age, instance.age);
expect(inserted.name, instance.name);
});

test('#update', () async {
final req = SupabaseRequest<Demo>(
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.update<Demo>(instance);
expect(inserted.id, instance.id);
expect(inserted.age, instance.age);
expect(inserted.name, instance.name);
});

group('#upsert', () {
test('no associations', () async {
final req = SupabaseRequest<Demo>(
Expand Down

0 comments on commit 6d3d971

Please sign in to comment.