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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- **SQLite / RETURNING clause support (#243)** — support RETURNING clauses for INSERT, UPDATE, and DELETE DML queries in the SQLite database driver, returning the resulting rows to the client.

## [0.4.7-a] - 2026-06-22

### Added
Expand Down
13 changes: 8 additions & 5 deletions lib/core/database/sqlite_connection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,21 @@ class SqliteConnection {
.toLowerCase();

// SQLite can execute PRAGMA, SELECT, EXPLAIN statements, which return data
final isQuery = sqlLower.startsWith('select') ||
final isReadOnlyQuery = sqlLower.startsWith('select') ||
sqlLower.startsWith('pragma') ||
sqlLower.startsWith('explain') ||
sqlLower.startsWith('with') ||
sqlLower.startsWith('values');

if (isQuery) {
final hasReturning = RegExp(r'\breturning\b').hasMatch(sqlLower);

if (readOnly && !isReadOnlyQuery) {
throw StateError('Database connection is read-only');
}

if (isReadOnlyQuery || hasReturning) {
return await _db!.rawQuery(sql, arguments);
} else {
if (readOnly) {
throw StateError('Database connection is read-only');
}
await _db!.execute(sql, arguments);
return [];
}
Expand Down
24 changes: 24 additions & 0 deletions test/core/database/sqlite_connection_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,30 @@ void main() {
expect(columns, containsAll(['id', 'name']));
});

test('executes INSERT, UPDATE, DELETE with RETURNING clause correctly', () async {
await conn.connect();

await conn.execute('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)');

// INSERT with RETURNING
final insertRes = await conn.execute("INSERT INTO users (name) VALUES ('Alice') RETURNING id, name");
expect(insertRes, isNotEmpty);
expect(insertRes.first['id'], 1);
expect(insertRes.first['name'], 'Alice');

// UPDATE with RETURNING
final updateRes = await conn.execute("UPDATE users SET name = 'Bob' WHERE id = 1 RETURNING id, name");
expect(updateRes, isNotEmpty);
expect(updateRes.first['id'], 1);
expect(updateRes.first['name'], 'Bob');

// DELETE with RETURNING
final deleteRes = await conn.execute("DELETE FROM users WHERE id = 1 RETURNING id, name");
expect(deleteRes, isNotEmpty);
expect(deleteRes.first['id'], 1);
expect(deleteRes.first['name'], 'Bob');
});

test('throws StateError for modify operations in read-only mode', () async {
final roConn = SqliteConnection(
id: 2,
Expand Down
Loading