diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ea7517..8451464 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## [0.1.0] + +- deprecated: `cs()` in filter. Use `contains()` instead. +- deprecated: `cd()` in filter. Use `containedBy()` instead. +- deprecated: `sl()` in filter. Use `rangeLt()` instead. +- deprecated: `sr()` in filter. Use `rangeGt()` instead. +- deprecated: `nxl()` in filter. Use `rangeGte()` instead. +- deprecated: `nxr()` in filter. Use `rangeLte()` instead. +- deprecated: `adj()` in filter. Use `rangeAdjacent()` instead. +- deprecated: `ov()` in filter. Use `overlaps()` instead. +- deprecated: `fts()` in filter. Use `textSearch()` instead. +- deprecated: `plfts()` in filter. Use `textSearch()` instead. +- deprecated: `phfts()` in filter. Use `textSearch()` instead. +- deprecated: `wfts()` in filter. Use `textSearch()` instead. + ## [0.0.8] - feat: Migrate to null-safe dart diff --git a/lib/src/postgrest_filter_builder.dart b/lib/src/postgrest_filter_builder.dart index 573e584..244480d 100644 --- a/lib/src/postgrest_filter_builder.dart +++ b/lib/src/postgrest_filter_builder.dart @@ -1,5 +1,7 @@ import 'dart:convert'; +import 'package:postgrest/src/text_search_type.dart'; + import 'postgrest_builder.dart'; import 'postgrest_transform_builder.dart'; @@ -137,9 +139,9 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { /// Finds all rows whose json, array, or range value on the stated [column] contains the values specified in [value]. /// /// ```dart - /// postgrest.from('users').select().cs('age_range', '[1,2)') + /// postgrest.from('users').select().contains('age_range', '[1,2)') /// ``` - PostgrestFilterBuilder cs(String column, dynamic value) { + PostgrestFilterBuilder contains(String column, dynamic value) { if (value is String) { // range types can be inclusive '[', ']' or exclusive '(', ')' so just // keep it simple and accept a string @@ -154,12 +156,15 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return this; } + @Deprecated('Use `contains()` instead.') + PostgrestFilterBuilder Function(String, dynamic) get cs => contains; + /// Finds all rows whose json, array, or range value on the stated [column] is contained by the specified [value]. /// /// ```dart - /// postgrest.from('users').select().cd('age_range', '[1,2)') + /// postgrest.from('users').select().containedBy('age_range', '[1,2)') /// ``` - PostgrestFilterBuilder cd(String column, dynamic value) { + PostgrestFilterBuilder containedBy(String column, dynamic value) { if (value is String) { // range types can be inclusive '[', ']' or exclusive '(', ')' so just // keep it simple and accept a string @@ -174,62 +179,80 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return this; } + @Deprecated('Use `containedBy()` instead.') + PostgrestFilterBuilder Function(String, dynamic) get cd => containedBy; + /// Finds all rows whose range value on the stated [column] is strictly to the left of the specified [range]. /// /// ```dart /// postgrest.from('users').select().sl('age_range', '[2,25)') /// ``` - PostgrestFilterBuilder sl(String column, String range) { + PostgrestFilterBuilder rangeLt(String column, String range) { appendSearchParams(column, 'sl.$range'); return this; } + @Deprecated('Use `rangeLt()` instead.') + PostgrestFilterBuilder Function(String, String) get sl => rangeLt; + /// Finds all rows whose range value on the stated [column] is strictly to the right of the specified [range]. /// /// ```dart - /// postgrest.from('users').select().sr('age_range', '[2,25)') + /// postgrest.from('users').select().rangeGt('age_range', '[2,25)') /// ``` - PostgrestFilterBuilder sr(String column, String range) { + PostgrestFilterBuilder rangeGt(String column, String range) { appendSearchParams(column, 'sr.$range'); return this; } + @Deprecated('Use `rangeGt()` instead.') + PostgrestFilterBuilder Function(String, String) get sr => rangeGt; + /// Finds all rows whose range value on the stated [column] does not extend to the left of the specified [range]. /// /// ```dart - /// postgrest.from('users').select().nxl('age_range', '[2,25)') + /// postgrest.from('users').select().rangeGte('age_range', '[2,25)') /// ``` - PostgrestFilterBuilder nxl(String column, String range) { + PostgrestFilterBuilder rangeGte(String column, String range) { appendSearchParams(column, 'nxl.$range'); return this; } + @Deprecated('Use `rangeGte()` instead.') + PostgrestFilterBuilder Function(String, String) get nxl => rangeGte; + /// Finds all rows whose range value on the stated [column] does not extend to the right of the specified [range]. /// /// ```dart - /// postgrest.from('users').select().nxr('age_range', '[2,25)') + /// postgrest.from('users').select().rangeLte('age_range', '[2,25)') /// ``` - PostgrestFilterBuilder nxr(String column, String range) { + PostgrestFilterBuilder rangeLte(String column, String range) { appendSearchParams(column, 'nxr.$range'); return this; } + @Deprecated('Use `rangeLte()` instead.') + PostgrestFilterBuilder Function(String, String) get nxr => rangeLte; + /// Finds all rows whose range value on the stated [column] is adjacent to the specified [range]. /// /// ```dart - /// postgrest.from('users').select().adj('age_range', '[2,25)') + /// postgrest.from('users').select().rangeAdjacent('age_range', '[2,25)') /// ``` - PostgrestFilterBuilder adj(String column, String range) { + PostgrestFilterBuilder rangeAdjacent(String column, String range) { appendSearchParams(column, 'adj.$range'); return this; } - /// Finds all rows whose array or range value on the stated [column] iscontained by the specified [value]. + @Deprecated('Use `rangeAdjacent()` instead.') + PostgrestFilterBuilder Function(String, String) get adj => rangeAdjacent; + + /// Finds all rows whose array or range value on the stated [column] overlaps (has a value in common) with the specified [value]. /// /// ```dart - /// postgrest.from('users').select().ov('age_range', '[2,25)') + /// postgrest.from('users').select().overlaps('age_range', '[2,25)') /// ``` - PostgrestFilterBuilder ov(String column, dynamic value) { + PostgrestFilterBuilder overlaps(String column, dynamic value) { if (value is String) { // range types can be inclusive '[', ']' or exclusive '(', ')' so just // keep it simple and accept a string @@ -241,12 +264,44 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { return this; } + @Deprecated('Use `overlaps()` instead.') + PostgrestFilterBuilder Function(String, String) get ov => overlaps; + + /// Finds all rows whose text or tsvector value on the stated [column] matches the tsquery in [query]. + /// + /// ```dart + /// postgrest.from('users').select().textSearch('catchphrase', "'fat' & 'cat'", config: 'english') + /// ``` + PostgrestFilterBuilder textSearch( + String column, + String query, { + + /// The text search configuration to use. + String? config, + + /// The type of tsquery conversion to use on [query]. + TextSearchType? type, + }) { + var typePart = ''; + if (type == TextSearchType.plain) { + typePart = 'pl'; + } else if (type == TextSearchType.phrase) { + typePart = 'ph'; + } else if (type == TextSearchType.websearch) { + typePart = 'w'; + } + final configPart = config == null ? '' : '($config)'; + appendSearchParams(column, '${typePart}fts$configPart.$query'); + return this; + } + /// Finds all rows whose tsvector value on the stated [column] matches to_tsquery([query]). /// - /// [options] can contains `config` key which is text search configuration to use. + /// [options] can contain `config` key which is text search configuration to use. /// ```dart /// postgrest.from('users').select().fts('catchphrase', "'fat' & 'cat'", { config: 'english' }) /// ``` + @Deprecated('Use `textSearch()` instead.') PostgrestFilterBuilder fts(String column, String query, {String? config}) { final configPart = config == null ? '' : '($config)'; appendSearchParams(column, 'fts$configPart.$query'); @@ -255,10 +310,11 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { /// Finds all rows whose tsvector value on the stated [column] matches plainto_tsquery([query]). /// - /// [options] can contains `config` key which is text search configuration to use. + /// [options] can contain `config` key which is text search configuration to use. /// ```dart /// postgrest.from('users').select().plfts('catchphrase', "'fat' & 'cat'", { config: 'english' }) /// ``` + @Deprecated('Use `textSearch()` instead.') PostgrestFilterBuilder plfts(String column, String query, {String? config}) { final configPart = config == null ? '' : '($config)'; appendSearchParams(column, 'plfts$configPart.$query'); @@ -267,10 +323,11 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { /// Finds all rows whose tsvector value on the stated [column] matches phraseto_tsquery([query]). /// - /// [options] can contains `config` key which is text search configuration to use. + /// [options] can contain `config` key which is text search configuration to use. /// ```dart /// postgrest.from('users').select().phfts('catchphrase', 'cat', { config: 'english' }) /// ``` + @Deprecated('Use `textSearch()` instead.') PostgrestFilterBuilder phfts(String column, String query, {String? config}) { final configPart = config == null ? '' : '($config)'; appendSearchParams(column, 'phfts$configPart.$query'); @@ -279,10 +336,11 @@ class PostgrestFilterBuilder extends PostgrestTransformBuilder { /// Finds all rows whose tsvector value on the stated [column] matches websearch_to_tsquery([query]). /// - /// [options] can contains `config` key which is text search configuration to use. + /// [options] can contain `config` key which is text search configuration to use. /// ```dart /// postgrest.from('users').select().wfts('catchphrase', "'fat' & 'cat'", { config: 'english' }) /// ``` + @Deprecated('Use `textSearch()` instead.') PostgrestFilterBuilder wfts(String column, String query, {String? config}) { final configPart = config == null ? '' : '($config)'; appendSearchParams(column, 'wfts$configPart.$query'); diff --git a/lib/src/text_search_type.dart b/lib/src/text_search_type.dart new file mode 100644 index 0000000..67556b9 --- /dev/null +++ b/lib/src/text_search_type.dart @@ -0,0 +1,18 @@ +/// The type of tsquery conversion to use on [query]. +enum TextSearchType { + /// Uses PostgreSQL's plainto_tsquery function. + plain, + + /// Uses PostgreSQL's phraseto_tsquery function. + phrase, + + /// Uses PostgreSQL's websearch_to_tsquery function. + /// This function will never raise syntax errors, which makes it possible to use raw user-supplied input for search, and can be used with advanced operators. + websearch, +} + +extension TextSearchTypeName on TextSearchType { + String name() { + return toString().split('.').last; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 01eefa9..2c1ad89 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: postgrest description: PostgREST client for Dart. This library provides an ORM interface to PostgREST. -version: 0.0.8 +version: 0.1.0 homepage: "https://supabase.io" repository: "https://github.com/supabase/postgrest-dart" diff --git a/test/filter_test.dart b/test/filter_test.dart index 6ac2190..d64095f 100644 --- a/test/filter_test.dart +++ b/test/filter_test.dart @@ -1,3 +1,4 @@ +import 'package:postgrest/src/text_search_type.dart'; import 'package:test/test.dart'; import 'package:postgrest/postgrest.dart'; @@ -10,7 +11,8 @@ void main() { }); test('not', () async { - final res = await postgrest.from('users').select().not('status', 'eq', 'OFFLINE').execute(); + final res = + await postgrest.from('users').select('status').not('status', 'eq', 'OFFLINE').execute(); res.data.forEach((item) { expect(item['status'] != ('OFFLINE'), true); }); @@ -19,7 +21,7 @@ void main() { test('or', () async { final res = await postgrest .from('users') - .select() + .select('status, username') .or('status.eq.OFFLINE,username.eq.supabot') .execute(); res.data.forEach((item) { @@ -28,7 +30,8 @@ void main() { }); test('eq', () async { - final res = await postgrest.from('users').select().eq('username', 'supabot').execute(); + final res = + await postgrest.from('users').select('username').eq('username', 'supabot').execute(); res.data.forEach((item) { expect(item['username'] == ('supabot'), true); @@ -36,49 +39,52 @@ void main() { }); test('neq', () async { - final res = await postgrest.from('users').select().neq('username', 'supabot').execute(); + final res = + await postgrest.from('users').select('username').neq('username', 'supabot').execute(); res.data.forEach((item) { expect(item['username'] == ('supabot'), false); }); }); test('gt', () async { - final res = await postgrest.from('messages').select().gt('id', 1).execute(); + final res = await postgrest.from('messages').select('id').gt('id', 1).execute(); res.data.forEach((item) { expect(item['id'] > 1, true); }); }); test('gte', () async { - final res = await postgrest.from('messages').select().gte('id', 1).execute(); + final res = await postgrest.from('messages').select('id').gte('id', 1).execute(); res.data.forEach((item) { expect(item['id'] < 1, false); }); }); test('lt', () async { - final res = await postgrest.from('messages').select().lt('id', 2).execute(); + final res = await postgrest.from('messages').select('id').lt('id', 2).execute(); res.data.forEach((item) { expect(item['id'] < 2, true); }); }); test('lte', () async { - final res = await postgrest.from('messages').select().lte('id', 2).execute(); + final res = await postgrest.from('messages').select('id').lte('id', 2).execute(); res.data.forEach((item) { expect(item['id'] > 2, false); }); }); test('like', () async { - final res = await postgrest.from('users').select().like('username', '%supa%').execute(); + final res = + await postgrest.from('users').select('username').like('username', '%supa%').execute(); res.data.forEach((item) { expect(item['username'].contains('supa'), true); }); }); test('ilike', () async { - final res = await postgrest.from('users').select().ilike('username', '%SUPA%').execute(); + final res = + await postgrest.from('users').select('username').ilike('username', '%SUPA%').execute(); res.data.forEach((item) { final user = item['username'].toLowerCase(); expect(user.contains('supa'), true); @@ -86,98 +92,115 @@ void main() { }); test('is', () async { - final res = await postgrest.from('users').select().is_('data', null).execute(); + final res = await postgrest.from('users').select('data').is_('data', null).execute(); res.data.forEach((item) { expect(item['data'], null); }); }); test('in', () async { - final res = - await postgrest.from('users').select().in_('status', ['ONLINE', 'OFFLINE']).execute(); + final res = await postgrest + .from('users') + .select('status') + .in_('status', ['ONLINE', 'OFFLINE']).execute(); res.data.forEach((item) { expect(item['status'] == 'ONLINE' || item['status'] == 'OFFLINE', true); }); }); - test('cs', () async { - final res = await postgrest.from('users').select().cs('age_range', '[1,2)').execute(); + test('contains', () async { + final res = + await postgrest.from('users').select('username').contains('age_range', '[1,2)').execute(); expect(res.data[0]['username'], 'supabot'); }); - test('cd', () async { - final res = await postgrest.from('users').select().cd('age_range', '[1,2)').execute(); + test('containedBy', () async { + final res = await postgrest + .from('users') + .select('username') + .containedBy('age_range', '[1,2)') + .execute(); expect(res.data[0]['username'], 'supabot'); }); - test('sl', () async { - final res = await postgrest.from('users').select().sl('age_range', '[2,25)').execute(); + test('rangeLt', () async { + final res = + await postgrest.from('users').select('username').rangeLt('age_range', '[2,25)').execute(); expect(res.data[0]['username'], 'supabot'); }); - test('sr', () async { - final res = await postgrest.from('users').select().sr('age_range', '[2,25)').execute(); + test('rangeGt', () async { + final res = + await postgrest.from('users').select('age_range').rangeGt('age_range', '[2,25)').execute(); res.data.forEach((item) { expect(item['username'] != 'supabot', true); }); }); - test('nxl', () async { - final res = await postgrest.from('users').select().nxl('age_range', '[2,25)').execute(); + test('rangeGte', () async { + final res = + await postgrest.from('users').select('age_range').rangeGte('age_range', '[2,25)').execute(); res.data.forEach((item) { expect(item['username'] != 'supabot', true); }); }); - test('nxr', () async { - final res = await postgrest.from('users').select().nxr('age_range', '[2,25)').execute(); + test('rangeLte', () async { + final res = + await postgrest.from('users').select('username').rangeLte('age_range', '[2,25)').execute(); res.data.forEach((item) { expect(item['username'] == 'supabot', true); }); }); - test('adj', () async { - final res = await postgrest.from('users').select().adj('age_range', '[2,25)').execute(); + test('rangeAdjacent', () async { + final res = await postgrest + .from('users') + .select('age_range') + .rangeAdjacent('age_range', '[2,25)') + .execute(); expect(res.data.length, 3); }); - test('ov', () async { - final res = await postgrest.from('users').select().ov('age_range', '[2,25)').execute(); + test('overlaps', () async { + final res = + await postgrest.from('users').select('username').overlaps('age_range', '[2,25)').execute(); expect(res.data[0]['username'], 'dragarcia'); }); - test('fts', () async { + test('textSearch', () async { final res = await postgrest .from('users') - .select() - .fts('catchphrase', "'fat' & 'cat'", config: 'english') + .select('username') + .textSearch('catchphrase', "'fat' & 'cat'", config: 'english') .execute(); expect(res.data[0]['username'], 'supabot'); }); - test('plfts', () async { + test('textSearch with plainto_tsquery', () async { final res = await postgrest .from('users') - .select() - .plfts('catchphrase', "'fat' & 'cat'", config: 'english') + .select('username') + .textSearch('catchphrase', "'fat' & 'cat'", config: 'english', type: TextSearchType.plain) .execute(); expect(res.data[0]['username'], 'supabot'); }); - test('phfts', () async { + test('textSearch with phraseto_tsquery', () async { final res = await postgrest .from('users') - .select() - .phfts('catchphrase', 'cat', config: 'english') + .select('username') + .textSearch('catchphrase', 'cat', config: 'english', type: TextSearchType.phrase) .execute(); expect(res.data.length, 2); }); - test('wfts', () async { + test('textSearch with websearch_to_tsquery', () async { final res = await postgrest .from('users') - .select() - .wfts('catchphrase', "'fat' & 'cat'", config: 'english') + .select('username') + .textSearch('catchphrase', "'fat' & 'cat'", + config: 'english', type: TextSearchType.websearch) .execute(); expect(res.data[0]['username'], 'supabot'); }); @@ -188,9 +211,9 @@ void main() { .select() .eq('username', 'supabot') .is_('data', null) - .ov('age_range', '[1,2)') + .overlaps('age_range', '[1,2)') .eq('status', 'ONLINE') - .fts('catchphrase', 'cat') + .textSearch('catchphrase', 'cat') .execute(); expect(res.data[0]['username'], 'supabot'); });