From 88cc90be5c2b9cbae367947f82d7ccd58914ce37 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Fri, 14 Jul 2023 13:44:15 +0200 Subject: [PATCH 01/14] feat: add all and any filter --- .../lib/src/postgrest_filter_builder.dart | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/packages/postgrest/lib/src/postgrest_filter_builder.dart b/packages/postgrest/lib/src/postgrest_filter_builder.dart index dc6efc6cd..7d4ba1e0e 100644 --- a/packages/postgrest/lib/src/postgrest_filter_builder.dart +++ b/packages/postgrest/lib/src/postgrest_filter_builder.dart @@ -152,6 +152,32 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return this; } + /// Match only rows where [column] matches all of [patterns] case-sensitively. + /// + /// ```dart + /// await supabase + /// .from('users') + /// .select() + /// .likeAllOf('username', ['%supa%', '%bot%']); + /// ``` + PostgrestFilterBuilder likeAllOf(String column, List patterns) { + appendSearchParams(column, 'like(all).{${patterns.join(',')}}'); + return this; + } + + /// Match only rows where [column] matches any of [patterns] case-sensitively. + /// + /// ```dart + /// await supabase + /// .from('users') + /// .select() + /// .likeAnyOf('username', ['%supa%', '%bot%']); + /// ``` + PostgrestFilterBuilder likeAnyOf(String column, List patterns) { + appendSearchParams(column, 'like(any).{${patterns.join(',')}}'); + return this; + } + /// Finds all rows whose value in the stated [column] matches the supplied [pattern] (case insensitive). /// /// ```dart @@ -165,6 +191,32 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return this; } + /// Match only rows where [column] matches all of [patterns] case-insensitively. + /// + /// ```dart + /// await supabase + /// .from('users') + /// .select() + /// .likeAllOf('username', ['%supa%', '%bot%']); + /// ``` + PostgrestFilterBuilder ilikeAllOf(String column, List patterns) { + appendSearchParams(column, 'ilike(all).{${patterns.join(',')}}'); + return this; + } + + /// Match only rows where [column] matches any of [patterns] case-insensitively. + /// + /// ```dart + /// await supabase + /// .from('users') + /// .select() + /// .likeAnyOf('username', ['%supa%', '%bot%']); + /// ``` + PostgrestFilterBuilder ilikeAnyOf(String column, List patterns) { + appendSearchParams(column, 'ilike(any).{${patterns.join(',')}}'); + return this; + } + /// A check for exact equality (null, true, false) /// /// Finds all rows whose value on the stated [column] exactly match the specified [value]. From 20744e238f9e6c98eae7b67ba104fe82d69fa745 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Fri, 14 Jul 2023 13:57:05 +0200 Subject: [PATCH 02/14] feat: add defaultToNull --- .../lib/src/postgrest_query_builder.dart | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/postgrest/lib/src/postgrest_query_builder.dart b/packages/postgrest/lib/src/postgrest_query_builder.dart index 8fb57b5ea..c0824281c 100644 --- a/packages/postgrest/lib/src/postgrest_query_builder.dart +++ b/packages/postgrest/lib/src/postgrest_query_builder.dart @@ -94,6 +94,8 @@ class PostgrestQueryBuilder extends PostgrestBuilder { /// /// By default no data is returned. Use a trailing `select` to return data. /// + /// [defaultToNull] Make missing fields default to `null`. Otherwise, use the default value for the column. + /// /// Default (not returning data): /// ```dart /// await supabase.from('messages').insert( @@ -108,9 +110,17 @@ class PostgrestQueryBuilder extends PostgrestBuilder { /// 'channel_id': 1 /// }).select(); /// ``` - PostgrestFilterBuilder insert(dynamic values) { + PostgrestFilterBuilder insert( + dynamic values, { + bool defaultToNull = true, + }) { _method = METHOD_POST; _headers['Prefer'] = ''; + + if (!defaultToNull) { + _headers['Prefer'] = 'missing=default'; + } + _body = values; return PostgrestFilterBuilder(this); } @@ -122,6 +132,8 @@ class PostgrestQueryBuilder extends PostgrestBuilder { /// /// By default no data is returned. Use a trailing `select` to return data. /// + /// [defaultToNull] Make missing fields default to `null`. Otherwise, use the default value for the column. This only applies when inserting new rows, not when merging with existing rows under [ignoreDuplicates] = false. + /// /// Default (not returning data): /// ```dart /// await supabase.from('messages').upsert({ @@ -144,11 +156,17 @@ class PostgrestQueryBuilder extends PostgrestBuilder { dynamic values, { String? onConflict, bool ignoreDuplicates = false, + bool defaultToNull = true, FetchOptions options = const FetchOptions(), }) { _method = METHOD_POST; _headers['Prefer'] = 'resolution=${ignoreDuplicates ? 'ignore' : 'merge'}-duplicates'; + + if (!defaultToNull) { + _headers['Prefer'] = _headers['Prefer']! + ',missing=default'; + } + if (onConflict != null) { _url = _url.replace( queryParameters: { From d56d4a23d7da02026a020c5107296512b23595ed Mon Sep 17 00:00:00 2001 From: Vinzent Date: Sun, 16 Jul 2023 22:45:22 +0200 Subject: [PATCH 03/14] test: add new filter tests --- infra/postgrest/docker-compose.yml | 2 +- packages/postgrest/test/filter_test.dart | 51 ++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/infra/postgrest/docker-compose.yml b/infra/postgrest/docker-compose.yml index fc2a71506..5db0d7fdf 100644 --- a/infra/postgrest/docker-compose.yml +++ b/infra/postgrest/docker-compose.yml @@ -3,7 +3,7 @@ version: '3' services: rest: - image: postgrest/postgrest:v9.0.1.20220802 + image: postgrest/postgrest:v11.0.0 ports: - '3000:3000' environment: diff --git a/packages/postgrest/test/filter_test.dart b/packages/postgrest/test/filter_test.dart index e68b5f344..73073449f 100644 --- a/packages/postgrest/test/filter_test.dart +++ b/packages/postgrest/test/filter_test.dart @@ -183,6 +183,31 @@ void main() { } }); + test('likeAllOf', () async { + PostgrestList res = await postgrest + .from('users') + .select('username') + .likeAllOf('username', ['%supa%', '%bot%']); + expect(res, isNotEmpty); + for (final item in res) { + expect(item['username'], contains('supa')); + expect(item['username'], contains('bot')); + } + }); + + test('likeAnyOf', () async { + PostgrestList res = await postgrest + .from('users') + .select('username') + .likeAnyOf('username', ['%supa%', '%wai%']); + expect(res, isNotEmpty); + for (final item in res) { + expect( + item['username'].contains('supa') || item['username'].contains('wai'), + true); + } + }); + test('ilike', () async { final res = await postgrest .from('users') @@ -195,6 +220,32 @@ void main() { } }); + test('ilikeAllOf', () async { + PostgrestList res = await postgrest + .from('users') + .select('username') + .ilikeAllOf('username', ['%SUPA%', '%bot%']); + expect(res, isNotEmpty); + for (final item in res) { + expect(item['username'].toLowerCase(), contains('supa')); + expect(item['username'].toLowerCase(), contains('bot')); + } + }); + + test('ilikeAnyOf', () async { + PostgrestList res = await postgrest + .from('users') + .select('username') + .ilikeAnyOf('username', ['%SUPA%', '%wai%']); + expect(res, isNotEmpty); + for (final item in res) { + expect( + item['username'].toLowerCase().contains('supa') || + item['username'].toLowerCase().contains('wai'), + true); + } + }); + test('is', () async { final res = await postgrest .from('users') From 1dbede2f04db17b8572c441c518cc9564cba1058 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Mon, 17 Jul 2023 15:52:58 +0200 Subject: [PATCH 04/14] fix: bulk insert with missing column --- .../lib/src/postgrest_query_builder.dart | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/postgrest/lib/src/postgrest_query_builder.dart b/packages/postgrest/lib/src/postgrest_query_builder.dart index c0824281c..886359928 100644 --- a/packages/postgrest/lib/src/postgrest_query_builder.dart +++ b/packages/postgrest/lib/src/postgrest_query_builder.dart @@ -122,6 +122,11 @@ class PostgrestQueryBuilder extends PostgrestBuilder { } _body = values; + + if (values is List) { + _setColumsSearchParam(values); + } + return PostgrestFilterBuilder(this); } @@ -175,6 +180,11 @@ class PostgrestQueryBuilder extends PostgrestBuilder { }, ); } + + if (values is List) { + _setColumsSearchParam(values); + } + _body = values; _options = options.ensureNotHead(); return PostgrestFilterBuilder(this); @@ -241,4 +251,14 @@ class PostgrestQueryBuilder extends PostgrestBuilder { _options = options.ensureNotHead(); return PostgrestFilterBuilder(this); } + + void _setColumsSearchParam(List values) { + final newValues = PostgrestList.from(values); + final columns = newValues.fold>( + [], (value, element) => value..addAll(element.keys)); + if (newValues.isNotEmpty) { + final uniqueColumns = {...columns}.map((e) => '"$e"').join(','); + appendSearchParams("columns", uniqueColumns); + } + } } From 4f58741a5b19d2086ab270c9dcd590ca1d3152bd Mon Sep 17 00:00:00 2001 From: Vinzent Date: Mon, 17 Jul 2023 15:53:14 +0200 Subject: [PATCH 05/14] ci: use postgres 15 --- infra/postgrest/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/postgrest/docker-compose.yml b/infra/postgrest/docker-compose.yml index 5db0d7fdf..68ea7c708 100644 --- a/infra/postgrest/docker-compose.yml +++ b/infra/postgrest/docker-compose.yml @@ -16,7 +16,7 @@ services: depends_on: - db db: - image: supabase/postgres:14.1.0.34 + image: supabase/postgres:15.1.0.37 ports: - '5432:5432' volumes: From 7eacdfc3329c750395d329900ccd6af7e94bf63f Mon Sep 17 00:00:00 2001 From: Vinzent Date: Mon, 17 Jul 2023 15:53:21 +0200 Subject: [PATCH 06/14] test: add defaultToNull test --- packages/postgrest/test/basic_test.dart | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/postgrest/test/basic_test.dart b/packages/postgrest/test/basic_test.dart index e96d2c51a..22ad6acc9 100644 --- a/packages/postgrest/test/basic_test.dart +++ b/packages/postgrest/test/basic_test.dart @@ -133,6 +133,41 @@ void main() { expect(res.length, 2); }); + test('bulk insert without column defaults', () async { + final res = await postgrest.from('users').insert( + [ + { + 'username': "bot", + 'status': 'OFFLINE', + }, + { + 'username': "crazy bot", + }, + ], + ).select(); + expect(res.length, 2); + expect(res.first['status'], 'OFFLINE'); + expect(res.last['status'], null); + }); + + test('bulk insert with column defaults', () async { + final res = await postgrest.from('users').insert( + [ + { + 'username': "bot", + 'status': 'OFFLINE', + }, + { + 'username': "crazy bot", + }, + ], + defaultToNull: false, + ).select(); + expect(res.length, 2); + expect(res.first['status'], 'OFFLINE'); + expect(res.last['status'], 'ONLINE'); + }); + test('basic update', () async { final res = await postgrest .from('messages') From c8240be9aadf2c877292c526a6883c4a68c01cec Mon Sep 17 00:00:00 2001 From: Vinzent Date: Mon, 17 Jul 2023 16:02:22 +0200 Subject: [PATCH 07/14] test: fix basic update --- packages/postgrest/test/basic_test.dart | 29 ++----------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/packages/postgrest/test/basic_test.dart b/packages/postgrest/test/basic_test.dart index 22ad6acc9..e3892e495 100644 --- a/packages/postgrest/test/basic_test.dart +++ b/packages/postgrest/test/basic_test.dart @@ -176,33 +176,8 @@ void main() { ) .is_("data", null) .select(); - expect(res, [ - { - 'id': 1, - 'data': null, - 'message': 'Hello World 👋', - 'username': 'supabot', - 'channel_id': 2, - 'inserted_at': '2021-06-25T04:28:21.598+00:00' - }, - { - 'id': 2, - 'data': null, - 'message': - 'Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.', - 'username': 'supabot', - 'channel_id': 2, - 'inserted_at': '2021-06-29T04:28:21.598+00:00' - }, - { - 'id': 3, - 'data': null, - 'message': 'Supabase Launch Week is on fire', - 'username': 'supabot', - 'channel_id': 2, - 'inserted_at': '2021-06-20T04:28:21.598+00:00' - } - ]); + expect(res, isNotEmpty); + expect(res, everyElement(containsPair("channel_id", 2))); final messages = await postgrest.from('messages').select(); for (final rec in messages) { From 5b4c3d0ba8cf59eaf2d3701b8d2815849a4e13de Mon Sep 17 00:00:00 2001 From: Vinzent Date: Mon, 17 Jul 2023 16:27:41 +0200 Subject: [PATCH 08/14] fix: comment for ilike --- packages/postgrest/lib/src/postgrest_filter_builder.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/postgrest/lib/src/postgrest_filter_builder.dart b/packages/postgrest/lib/src/postgrest_filter_builder.dart index 7d4ba1e0e..0e248d6ab 100644 --- a/packages/postgrest/lib/src/postgrest_filter_builder.dart +++ b/packages/postgrest/lib/src/postgrest_filter_builder.dart @@ -197,7 +197,7 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { /// await supabase /// .from('users') /// .select() - /// .likeAllOf('username', ['%supa%', '%bot%']); + /// .ilikeAllOf('username', ['%supa%', '%bot%']); /// ``` PostgrestFilterBuilder ilikeAllOf(String column, List patterns) { appendSearchParams(column, 'ilike(all).{${patterns.join(',')}}'); @@ -210,7 +210,7 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { /// await supabase /// .from('users') /// .select() - /// .likeAnyOf('username', ['%supa%', '%bot%']); + /// .ilikeAnyOf('username', ['%supa%', '%bot%']); /// ``` PostgrestFilterBuilder ilikeAnyOf(String column, List patterns) { appendSearchParams(column, 'ilike(any).{${patterns.join(',')}}'); From e6008b94db139e5c0d513c1f8c4df243716af3ee Mon Sep 17 00:00:00 2001 From: Vinzent Date: Mon, 17 Jul 2023 21:19:12 +0200 Subject: [PATCH 09/14] docs: prevent loose list in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a9bd604f5..a66bf98b9 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Monorepo containing all [Supabase](https://supabase.com/) libraries for Flutter. - [supabase_flutter](https://github.com/supabase/supabase-flutter/tree/main/packages/supabase_flutter) - [yet_another_json_isolate](https://github.com/supabase/supabase-flutter/tree/main/packages/yet_another_json_isolate) -- Documentation: https://supabase.com/docs/reference/dart/introduction +Documentation: https://supabase.com/docs/reference/dart/introduction --- From 8686868f02f273fd105b66a323e53b9a7edf323a Mon Sep 17 00:00:00 2001 From: Vinzent Date: Thu, 20 Jul 2023 12:06:53 +0200 Subject: [PATCH 10/14] docs: update insert/upsert doc --- packages/postgrest/lib/src/postgrest_query_builder.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/postgrest/lib/src/postgrest_query_builder.dart b/packages/postgrest/lib/src/postgrest_query_builder.dart index 886359928..c2cf56ebf 100644 --- a/packages/postgrest/lib/src/postgrest_query_builder.dart +++ b/packages/postgrest/lib/src/postgrest_query_builder.dart @@ -94,7 +94,7 @@ class PostgrestQueryBuilder extends PostgrestBuilder { /// /// By default no data is returned. Use a trailing `select` to return data. /// - /// [defaultToNull] Make missing fields default to `null`. Otherwise, use the default value for the column. + /// When inserting multiple rows in bulk, [defaultToNull] can be used to set the values of the missing fields to be either `null` or the default value for the column. For single row insertions, missing fields will be set to default values when applicable. /// /// Default (not returning data): /// ```dart @@ -137,7 +137,7 @@ class PostgrestQueryBuilder extends PostgrestBuilder { /// /// By default no data is returned. Use a trailing `select` to return data. /// - /// [defaultToNull] Make missing fields default to `null`. Otherwise, use the default value for the column. This only applies when inserting new rows, not when merging with existing rows under [ignoreDuplicates] = false. + /// When inserting multiple rows in bulk, [defaultToNull] can be used to set the values of the missing fields to be either `null` or the default value for the column. For single row insertions, missing fields will be set to default values when applicable. /// /// Default (not returning data): /// ```dart From 213e1d22bca2e5e60e52de1b2dd25a017523ad2d Mon Sep 17 00:00:00 2001 From: Vinzent Date: Fri, 21 Jul 2023 10:50:19 +0200 Subject: [PATCH 11/14] test: remove failing rls test --- packages/postgrest/test/basic_test.dart | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/postgrest/test/basic_test.dart b/packages/postgrest/test/basic_test.dart index e3892e495..2f283683e 100644 --- a/packages/postgrest/test/basic_test.dart +++ b/packages/postgrest/test/basic_test.dart @@ -360,15 +360,6 @@ void main() { expect(res.count, 1); }); - test('row level security error', () async { - try { - await postgrest.from('sample').update({'id': 2}); - fail('Returned even with row level security'); - } on PostgrestException catch (error) { - expect(error.code, '404'); - } - }); - test('withConverter', () async { final res = await postgrest .from('users') From 68b903b50e7072c9621ac3bb69bd9c74c3f446e9 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Fri, 21 Jul 2023 10:52:31 +0200 Subject: [PATCH 12/14] test: add more insert tests --- packages/postgrest/test/basic_test.dart | 31 +++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/postgrest/test/basic_test.dart b/packages/postgrest/test/basic_test.dart index 2f283683e..8674613cb 100644 --- a/packages/postgrest/test/basic_test.dart +++ b/packages/postgrest/test/basic_test.dart @@ -125,6 +125,37 @@ void main() { expect(res, isEmpty); }); + test('insert', () async { + final res = await postgrest.from('users').insert( + { + 'username': "bot", + 'status': 'OFFLINE', + }, + ).select(); + expect(res.length, 1); + expect(res.first['status'], 'OFFLINE'); + }); + + test('insert uses default value', () async { + final res = await postgrest.from('users').insert( + { + 'username': "bot", + }, + ).select(); + expect(res.length, 1); + expect(res.first['status'], 'ONLINE'); + }); + + test('bulk insert with one row uses default value', () async { + final res = await postgrest.from('users').insert( + { + 'username': "bot", + }, + ).select(); + expect(res.length, 1); + expect(res.first['status'], 'ONLINE'); + }); + test('bulk insert', () async { final res = await postgrest.from('messages').insert([ {'id': 4, 'message': 'foo', 'username': 'supabot', 'channel_id': 2}, From e942306fc76c962a15d44ecf5521599715b460d0 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Fri, 21 Jul 2023 10:53:20 +0200 Subject: [PATCH 13/14] fix: typo --- packages/postgrest/lib/src/postgrest_query_builder.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/postgrest/lib/src/postgrest_query_builder.dart b/packages/postgrest/lib/src/postgrest_query_builder.dart index c2cf56ebf..3e869bf73 100644 --- a/packages/postgrest/lib/src/postgrest_query_builder.dart +++ b/packages/postgrest/lib/src/postgrest_query_builder.dart @@ -124,7 +124,7 @@ class PostgrestQueryBuilder extends PostgrestBuilder { _body = values; if (values is List) { - _setColumsSearchParam(values); + _setColumnsSearchParam(values); } return PostgrestFilterBuilder(this); @@ -182,7 +182,7 @@ class PostgrestQueryBuilder extends PostgrestBuilder { } if (values is List) { - _setColumsSearchParam(values); + _setColumnsSearchParam(values); } _body = values; @@ -252,7 +252,7 @@ class PostgrestQueryBuilder extends PostgrestBuilder { return PostgrestFilterBuilder(this); } - void _setColumsSearchParam(List values) { + void _setColumnsSearchParam(List values) { final newValues = PostgrestList.from(values); final columns = newValues.fold>( [], (value, element) => value..addAll(element.keys)); From 52bbca7b9ef9040ec02b1579e85005a80e2a6276 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Sun, 23 Jul 2023 23:24:57 +0200 Subject: [PATCH 14/14] docs: update insert upsert comment --- .../postgrest/lib/src/postgrest_query_builder.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/postgrest/lib/src/postgrest_query_builder.dart b/packages/postgrest/lib/src/postgrest_query_builder.dart index 3e869bf73..3260824da 100644 --- a/packages/postgrest/lib/src/postgrest_query_builder.dart +++ b/packages/postgrest/lib/src/postgrest_query_builder.dart @@ -94,7 +94,11 @@ class PostgrestQueryBuilder extends PostgrestBuilder { /// /// By default no data is returned. Use a trailing `select` to return data. /// - /// When inserting multiple rows in bulk, [defaultToNull] can be used to set the values of the missing fields to be either `null` or the default value for the column. For single row insertions, missing fields will be set to default values when applicable. + /// When inserting multiple rows in bulk, [defaultToNull] is used to set the values of fields missing in a proper subset of rows + /// to be either `NULL` or the default value of these columns. + /// Fields missing in all rows always use the default value of these columns. + /// + /// For single row insertions, missing fields will be set to default values when applicable. /// /// Default (not returning data): /// ```dart @@ -137,7 +141,11 @@ class PostgrestQueryBuilder extends PostgrestBuilder { /// /// By default no data is returned. Use a trailing `select` to return data. /// - /// When inserting multiple rows in bulk, [defaultToNull] can be used to set the values of the missing fields to be either `null` or the default value for the column. For single row insertions, missing fields will be set to default values when applicable. + /// When inserting multiple rows in bulk, [defaultToNull] is used to set the values of fields missing in a proper subset of rows + /// to be either `NULL` or the default value of these columns. + /// Fields missing in all rows always use the default value of these columns. + /// + /// For single row insertions, missing fields will be set to default values when applicable. /// /// Default (not returning data): /// ```dart