Merge pull request #432 from jaggernod/throw-meaningful-error-on-prepared-statement

Handling exceptional case when executing Prepared Statement
This commit is contained in:
Simon Binder 2020-03-07 13:35:50 +01:00 committed by GitHub
commit 9b2b73cd20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 79 additions and 2 deletions

View File

@ -67,6 +67,7 @@ class _SQLiteBindings {
int Function(Pointer<Database> db) sqlite3_changes;
int Function(Pointer<Database> db) sqlite3_last_insert_rowid;
int Function(Pointer<Database> db) sqlite3_extended_errcode;
Pointer<CBlob> Function(int code) sqlite3_errstr;
Pointer<CBlob> Function(Pointer<Database> database) sqlite3_errmsg;
@ -165,6 +166,10 @@ class _SQLiteBindings {
sqlite3_finalize = sqlite
.lookup<NativeFunction<sqlite3_finalize_native_t>>('sqlite3_finalize')
.asFunction();
sqlite3_extended_errcode = sqlite
.lookup<NativeFunction<sqlite3_extended_errcode_native_t>>(
'sqlite3_extended_errcode')
.asFunction();
sqlite3_errstr = sqlite
.lookup<NativeFunction<sqlite3_errstr_native_t>>('sqlite3_errstr')
.asFunction();

View File

@ -36,6 +36,9 @@ typedef sqlite3_reset_native_t = Int32 Function(Pointer<Statement> statement);
typedef sqlite3_finalize_native_t = Int32 Function(
Pointer<Statement> statement);
typedef sqlite3_extended_errcode_native_t = Int32 Function(
Pointer<Database> database);
typedef sqlite3_errstr_native_t = Pointer<CBlob> Function(Int32 error);
typedef sqlite3_errmsg_native_t = Pointer<CBlob> Function(

View File

@ -16,7 +16,12 @@ class SqliteException implements Exception {
String explanation;
if (code != null) {
explanation = bindings.sqlite3_errstr(code).readString();
// Getting hold of more explanatory error code as SQLITE_IOERR error group
// has an extensive list of extended error codes
final extendedCode = bindings.sqlite3_extended_errcode(db);
final errStr = bindings.sqlite3_errstr(extendedCode).readString();
explanation = '$errStr (code $extendedCode)';
}
return SqliteException(dbMessage, explanation);

View File

@ -45,10 +45,15 @@ class PreparedStatement {
names[i] = bindings.sqlite3_column_name(_stmt, i).readString();
}
while (_step() == Errors.SQLITE_ROW) {
int resultCode;
while ((resultCode = _step()) == Errors.SQLITE_ROW) {
rows.add([for (var i = 0; i < columnCount; i++) _readValue(i)]);
}
if (resultCode != Errors.SQLITE_OK && resultCode != Errors.SQLITE_DONE) {
throw SqliteException._fromErrorCode(_db._db, resultCode);
}
return Result(names, rows);
}

View File

@ -1,3 +1,4 @@
import 'dart:ffi';
import 'dart:typed_data';
import 'package:moor_ffi/database.dart';
@ -85,4 +86,38 @@ void main() {
db.close();
});
test('throws an exception when iterating over result rows', () {
final db = Database.memory()
..createFunction(
'raise_if_two',
1,
Pointer.fromFunction(_raiseIfTwo),
);
db.execute(
'CREATE TABLE tbl (a INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT)');
// insert with a = 1..3
for (var i = 0; i < 3; i++) {
db.execute('INSERT INTO tbl DEFAULT VALUES');
}
final statement = db.prepare('SELECT raise_if_two(a) FROM tbl ORDER BY a');
expect(
statement.select,
throwsA(isA<SqliteException>()
.having((e) => e.message, 'message', contains('was two'))),
);
});
}
void _raiseIfTwo(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
final value = args[0].value;
if (value == 2) {
ctx.resultError('parameter was two');
} else {
ctx.resultNull();
}
}

View File

@ -0,0 +1,24 @@
import 'package:moor_ffi/database.dart';
import 'package:test/test.dart';
void main() {
Database database;
setUp(() => database = Database.memory());
tearDown(() => database.close());
test('violating constraint throws exception with extended error code', () {
database.execute('CREATE TABLE tbl(a INTEGER NOT NULL)');
final statement = database.prepare('INSERT INTO tbl DEFAULT VALUES');
expect(
statement.execute,
throwsA(
isA<SqliteException>().having(
(e) => e.explanation, 'explanation', endsWith(' (code 1299)')),
),
);
});
}