Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions lib/src/mock_supabase_http_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,8 @@ class MockSupabaseHttpClient extends BaseClient {
} else if (request.method == 'POST') {
final preferHeader = request.headers['Prefer'];
if (preferHeader != null &&
preferHeader.contains('resolution=merge-duplicates')) {
(preferHeader.contains('resolution=merge-duplicates') ||
preferHeader.contains('resolution=ignore-duplicates'))) {
requestType = RequestType.upsert;
} else {
requestType = RequestType.insert;
Expand Down Expand Up @@ -478,30 +479,41 @@ class MockSupabaseHttpClient extends BaseClient {
.map((e) => e.trim())
.toList();

// Rows that conflict are left untouched when the caller asked for
// `ignoreDuplicates`, and merged into otherwise
final ignoreDuplicates =
request.headers['Prefer']?.contains('resolution=ignore-duplicates') ??
false;

// Upsert each item
final results = items.map((item) {
final results = <Map<String, dynamic>>[];
for (final item in items) {
// Check if all onConflictColumns are set in item
final shouldUpdate =
final hasConflictTarget =
onConflictColumns.every((column) => item[column] != null);

if (shouldUpdate) {
if (hasConflictTarget) {
// Find the index for an item that matches all onConflictColumns
final index = _database[tableKey]!.indexWhere((dbItem) =>
onConflictColumns
.every((column) => dbItem[column] == item[column]));

if (index != -1) {
if (ignoreDuplicates) {
continue;
}
// Update the item in the database
_database[tableKey]![index] = {
..._database[tableKey]![index],
...item
};
return _database[tableKey]![index];
results.add(_database[tableKey]![index]);
continue;
}
}
_database[tableKey]!.add(item);
return item;
}).toList();
results.add(item);
}

return _createResponse(results, request: request);
}
Expand Down
55 changes: 55 additions & 0 deletions test/mock_supabase_http_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,61 @@ void main() {
expect(postsAfterUpdate.first, updatedData);
});

test('Upsert with ignoreDuplicates leaves the conflicting row untouched',
() async {
const table = 'users';
await mockSupabase.from(table).insert({'id': 1, 'name': 'John Doe'});

await mockSupabase.from(table).upsert(
{'id': 1, 'name': 'James Bond'},
ignoreDuplicates: true,
);

final users = await mockSupabase.from(table).select();
expect(users.length, 1);
expect(users.first, {'id': 1, 'name': 'John Doe'});
});

test(
'Upsert with ignoreDuplicates and an onConflict column leaves the '
'conflicting row untouched', () async {
const table = 'users';
await mockSupabase.from(table).insert({'user_id': 1, 'name': 'John Doe'});

await mockSupabase.from(table).upsert(
{'user_id': 1, 'name': 'James Bond'},
onConflict: 'user_id',
ignoreDuplicates: true,
);

final users = await mockSupabase.from(table).select();
expect(users.length, 1);
expect(users.first, {'user_id': 1, 'name': 'John Doe'});
});

test('Upsert with ignoreDuplicates inserts non-conflicting rows', () async {
const table = 'users';
await mockSupabase.from(table).insert({'id': 1, 'name': 'John Doe'});

final inserted = await mockSupabase.from(table).upsert(
[
{'id': 1, 'name': 'James Bond'},
{'id': 2, 'name': 'Jane Doe'},
],
ignoreDuplicates: true,
).select();

expect(inserted, [
{'id': 2, 'name': 'Jane Doe'}
]);
final users =
await mockSupabase.from(table).select().order('id', ascending: true);
expect(users, [
{'id': 1, 'name': 'John Doe'},
{'id': 2, 'name': 'Jane Doe'},
]);
});

test('Upsert then select', () async {
// Test upserting a record
await mockSupabase
Expand Down
Loading