Skip to content

Commit

Permalink
fix: maybeSingle no longer logs error on Postgrest API (#564)
Browse files Browse the repository at this point in the history
* fix: maybeSingle no longer throws on Postgrest server

* fix: modify the body at an appropreate place

* add test for multiple inserts

* fix error code

* update error code

* remove unnecessary data processing in then()

* add tests to see if maybe single status is preserved

* edit comments on maybeSingle

* add converter tests

* add maybeSingle parameter
  • Loading branch information
dshukertjr committed Jul 24, 2023
1 parent 64d8eb5 commit f6854e1
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 13 deletions.
42 changes: 32 additions & 10 deletions packages/postgrest/lib/src/postgrest_builder.dart
Expand Up @@ -27,11 +27,12 @@ typedef _Nullable<T> = T?;
class PostgrestBuilder<T, S> implements Future<T> {
dynamic _body;
late final Headers _headers;
bool _maybeEmpty = false;
// ignore: prefer_final_fields
bool _maybeSingle;
String? _method;
late final String? _schema;
late Uri _url;
PostgrestConverter<T, S>? _converter;
final PostgrestConverter<T, S>? _converter;
late final Client? _httpClient;
late final YAJsonIsolate? _isolate;
// ignore: prefer_final_fields
Expand All @@ -46,7 +47,10 @@ class PostgrestBuilder<T, S> implements Future<T> {
Client? httpClient,
YAJsonIsolate? isolate,
FetchOptions? options,
}) {
bool maybeSingle = false,
PostgrestConverter<T, S>? converter,
}) : _maybeSingle = maybeSingle,
_converter = converter {
_url = url;
_headers = headers;
_schema = schema;
Expand Down Expand Up @@ -75,9 +79,9 @@ class PostgrestBuilder<T, S> implements Future<T> {
isolate: _isolate,
httpClient: _httpClient,
options: _options,
)
.._maybeEmpty = _maybeEmpty
.._converter = converter;
maybeSingle: _maybeSingle,
converter: converter,
);
}

void _assertCorrectGeneric(Type R) {
Expand Down Expand Up @@ -223,6 +227,24 @@ class PostgrestBuilder<T, S> implements Future<T> {
}
}

// Workaround for https://github.com/supabase/supabase-flutter/issues/560
if (_maybeSingle && _method?.toUpperCase() == 'GET' && body is List) {
if (body.length > 1) {
throw PostgrestException(
// https://github.com/PostgREST/postgrest/blob/a867d79c42419af16c18c3fb019eba8df992626f/src/PostgREST/Error.hs#L553
code: '406',
details:
'Results contain ${body.length} rows, application/vnd.pgrst.object+json requires 1 row',
hint: null,
message: 'JSON object requested, multiple (or no) rows returned',
);
} else if (body.length == 1) {
body = body.first;
} else {
body = null;
}
}

final contentRange = response.headers['content-range'];
if (contentRange != null && contentRange.length > 1) {
count = contentRange.split('/').last == '*'
Expand Down Expand Up @@ -301,8 +323,8 @@ class PostgrestBuilder<T, S> implements Future<T> {
details: response.reasonPhrase,
);

if (_maybeEmpty) {
return _handleMaybeEmptyError(response, error);
if (_maybeSingle) {
return _handleMaybeSingleError(response, error);
}
} catch (_) {
error = PostgrestException(
Expand All @@ -324,10 +346,10 @@ class PostgrestBuilder<T, S> implements Future<T> {
}
}

/// on maybeEmpty enable, check for error details contains
/// When [_maybeSingle] is true, check whether error details contain
/// 'Results contain 0 rows' then
/// return PostgrestResponse with null data
PostgrestResponse<T> _handleMaybeEmptyError(
PostgrestResponse<T> _handleMaybeSingleError(
http.Response response,
PostgrestException error,
) {
Expand Down
13 changes: 10 additions & 3 deletions packages/postgrest/lib/src/postgrest_transform_builder.dart
Expand Up @@ -175,15 +175,22 @@ class PostgrestTransformBuilder<T> extends PostgrestBuilder<T, T> {
/// Retrieves at most one row from the result.
///
/// Result must be at most one row or nullable
/// (e.g. using `eq` on a UNIQUE column), otherwise this will result in an error.
/// (e.g. using `eq` on a UNIQUE column or `limit(1)`),
/// otherwise this will result in an error.
///
///
/// Data type is `Map<String, dynamic>?`.
///
/// By specifying this type via `.select<Map<String,dynamic>?>()` you get more type safety.
PostgrestTransformBuilder<T> maybeSingle() {
_headers['Accept'] = 'application/vnd.pgrst.object+json';
_maybeEmpty = true;
// Temporary fix for https://github.com/supabase/supabase-flutter/issues/560
// Issue persists e.g. for `.insert([...]).select().maybeSingle()`
if (_method?.toUpperCase() == 'GET') {
_headers['Accept'] = 'application/json';
} else {
_headers['Accept'] = 'application/vnd.pgrst.object+json';
}
_maybeSingle = true;
return this;
}

Expand Down
60 changes: 60 additions & 0 deletions packages/postgrest/test/transforms_test.dart
Expand Up @@ -252,5 +252,65 @@ void main() {
.maybeSingle();
expect(user, isNull);
});

test('maybeSingle with multiple rows throws', () async {
try {
await postgrest.from('users').select().maybeSingle();
fail('maybeSingle with multiple rows did not throw.');
} on PostgrestException catch (error) {
expect(error.code, '406');
} catch (error) {
fail(
'maybeSingle with multiple rows threw ${error.runtimeType} instead of PostgrestException.');
}
});
test('maybeSingle with multiple inserts throws', () async {
try {
await postgrest
.from('channels')
.insert([
{'data': {}, 'slug': 'channel1'},
{'data': {}, 'slug': 'channel2'},
])
.select()
.maybeSingle();
fail('Query did not throw.');
} on PostgrestException catch (error) {
expect(error.code, '406');
} catch (error) {
fail('Query threw ${error.runtimeType} instead of PostgrestException.');
}
});

test(
'maybeSingle followed by another transformer preserves the maybeSingle status',
() async {
try {
// maybeSingle followed by another transformer preserves the maybeSingle status
// and should throw when the returned data is more than 2 rows.
await postgrest.from('channels').select().maybeSingle().limit(2);
fail('Query did not throw.');
} on PostgrestException catch (error) {
expect(error.code, '406');
} catch (error) {
fail('Query threw ${error.runtimeType} instead of PostgrestException.');
}
});

test('maybeSingle with converter throws if more than 1 rows were returned',
() async {
try {
await postgrest
.from('channels')
.select<PostgrestList>()
.maybeSingle()
.withConverter((data) => data.map((e) => e.toString()).toList());
fail('Query did not throw');
} on PostgrestException catch (error) {
expect(error.code, '406');
} catch (error) {
fail('Query threw ${error.runtimeType} instead of PostgrestException.');
}
});
});
}

0 comments on commit f6854e1

Please sign in to comment.