Skip to content

Commit c62d758

Browse files
authored
add executeMultiple for multi-statement strings (#123)
1 parent b8b9a65 commit c62d758

File tree

7 files changed

+119
-0
lines changed

7 files changed

+119
-0
lines changed

packages/sqlite_async/lib/src/common/connection/sync_sqlite_connection.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,11 @@ final class _UnsafeSyncContext extends UnscopedContext {
164164
}
165165
}, sql: sql);
166166
}
167+
168+
@override
169+
Future<void> executeMultiple(String sql) async {
170+
task.timeSync('executeMultiple', () {
171+
db.execute(sql);
172+
}, sql: sql);
173+
}
167174
}

packages/sqlite_async/lib/src/impl/context.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import '../sqlite_connection.dart';
1212
abstract base class UnscopedContext implements SqliteReadContext {
1313
Future<ResultSet> execute(String sql, List<Object?> parameters);
1414
Future<void> executeBatch(String sql, List<List<Object?>> parameterSets);
15+
Future<void> executeMultiple(String sql);
1516

1617
/// Returns an [UnscopedContext] useful as the outermost transaction.
1718
///
@@ -143,6 +144,12 @@ final class ScopedWriteContext extends ScopedReadContext
143144
return await _context.executeBatch(sql, parameterSets);
144145
}
145146

147+
@override
148+
Future<void> executeMultiple(String sql) async {
149+
_checkNotLocked();
150+
return await _context.executeMultiple(sql);
151+
}
152+
146153
@override
147154
Future<T> writeTransaction<T>(
148155
Future<T> Function(SqliteWriteContext tx) callback) async {

packages/sqlite_async/lib/src/native/database/native_sqlite_connection_impl.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,14 @@ final class _UnsafeContext extends UnscopedContext {
275275
}
276276
});
277277
}
278+
279+
@override
280+
Future<void> executeMultiple(String sql) async {
281+
return computeWithDatabase((db) async {
282+
// execute allows multiple statements, but does not return results.
283+
db.execute(sql);
284+
});
285+
}
278286
}
279287

280288
void _sqliteConnectionIsolate(_SqliteConnectionParams params) async {

packages/sqlite_async/lib/src/sqlite_connection.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ abstract interface class SqliteWriteContext extends SqliteReadContext {
7676
/// parameter set.
7777
Future<void> executeBatch(String sql, List<List<Object?>> parameterSets);
7878

79+
// Execute a query that potentially contains multiple statements.
80+
Future<void> executeMultiple(String sql);
81+
7982
/// Open a read-write transaction on this write context.
8083
///
8184
/// When called on a [SqliteConnection], this takes a global lock - only one

packages/sqlite_async/lib/src/sqlite_queries.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,13 @@ mixin SqliteQueries implements SqliteWriteContext, SqliteConnection {
136136
});
137137
}
138138

139+
@override
140+
Future<void> executeMultiple(String sql) {
141+
return writeTransaction((tx) async {
142+
return tx.executeMultiple(sql);
143+
});
144+
}
145+
139146
@override
140147
Future<void> refreshSchema() {
141148
return getAll("PRAGMA table_info('sqlite_master')");

packages/sqlite_async/lib/src/web/database.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,19 @@ final class _UnscopedContext extends UnscopedContext {
293293
});
294294
}
295295

296+
@override
297+
Future<void> executeMultiple(String sql) {
298+
return _task.timeAsync('executeMultiple', sql: sql, () {
299+
return wrapSqliteException(() async {
300+
await _database._database.execute(
301+
sql,
302+
token: _lock,
303+
checkInTransaction: _checkInTransaction,
304+
);
305+
});
306+
});
307+
}
308+
296309
@override
297310
UnscopedContext interceptOutermostTransaction() {
298311
// All execute calls done in the callback will be checked for the

packages/sqlite_async/test/basic_test.dart

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,80 @@ void main() {
303303
)
304304
});
305305

306+
test('execute single statement with RETURNING populates ResultSet',
307+
() async {
308+
final db = await testUtils.setupDatabase(path: path);
309+
await createTables(db);
310+
final result = await db.execute(
311+
'INSERT INTO test_data(description) VALUES(?) RETURNING id, description',
312+
['test returning with params']);
313+
314+
expect(result.columnNames, equals(['id', 'description']));
315+
expect(result.rows.length, equals(1));
316+
expect(result.rows[0][0], isA<int>());
317+
expect(result.rows[0][1], equals('test returning with params'));
318+
});
319+
320+
test(
321+
'execute single statment with RETURNING populates ResultSet without params',
322+
() async {
323+
final db = await testUtils.setupDatabase(path: path);
324+
await createTables(db);
325+
final result = await db.execute(
326+
"INSERT INTO test_data(description) VALUES('test returning without params') RETURNING id, description");
327+
328+
expect(result.columnNames, equals(['id', 'description']));
329+
expect(result.rows.length, equals(1));
330+
expect(result.rows[0][0], isA<int>());
331+
expect(result.rows[0][1], equals('test returning without params'));
332+
});
333+
334+
test('executeMultiple handles multiple statements', () async {
335+
final db = await testUtils.setupDatabase(path: path);
336+
await createTables(db);
337+
338+
await db.executeMultiple('''
339+
INSERT INTO test_data(description) VALUES('row1');
340+
INSERT INTO test_data(description) VALUES('row2');
341+
''');
342+
343+
final results =
344+
await db.getAll('SELECT description FROM test_data ORDER BY id');
345+
expect(results.length, equals(2));
346+
expect(results.rows[0], equals(['row1']));
347+
expect(results.rows[1], equals(['row2']));
348+
349+
await db.close();
350+
});
351+
352+
test('executeMultiple rolls back on failure', () async {
353+
final db = await testUtils.setupDatabase(path: path);
354+
await createTables(db);
355+
356+
// Insert an initial row with id=1
357+
await db.execute('INSERT INTO test_data(id, description) VALUES(?, ?)',
358+
[1, 'initial']);
359+
360+
// Attempt executeMultiple where second statement fails due to duplicate primary key
361+
await expectLater(
362+
db.executeMultiple('''
363+
INSERT INTO test_data(id, description) VALUES(2, 'should_rollback');
364+
INSERT INTO test_data(id, description) VALUES(1, 'duplicate_key');
365+
'''),
366+
throwsA(isA<SqliteException>()),
367+
);
368+
369+
// Verify only the initial row exists - the first insert in
370+
// executeMultiple should have been rolled back.
371+
final results =
372+
await db.getAll('SELECT id, description FROM test_data ORDER BY id');
373+
expect(results, [
374+
{'id': 1, 'description': 'initial'}
375+
]);
376+
377+
await db.close();
378+
});
379+
306380
test('with all connections', () async {
307381
final maxReaders = _isWeb ? 0 : 3;
308382

0 commit comments

Comments
 (0)