Skip to content

Commit fae3594

Browse files
committed
Report position of syntax errors
1 parent 5ccc018 commit fae3594

File tree

11 files changed

+113
-12
lines changed

11 files changed

+113
-12
lines changed

sqlite3/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
- Web: Support `localtime` datetime modifier in SQLite.
44
- Introduce topics to dartdoc documentation.
5+
- Report locations of syntax errors in `SqliteException`.
56

67
## 2.7.2
78

sqlite3/assets/sqlite3.h

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ int sqlite3_extended_result_codes(sqlite3 *db, int onoff);
2424
int sqlite3_extended_errcode(sqlite3 *db);
2525
sqlite3_char *sqlite3_errmsg(sqlite3 *db);
2626
sqlite3_char *sqlite3_errstr(int code);
27+
int sqlite3_error_offset(sqlite3 *db);
2728
void sqlite3_free(void *ptr);
2829

2930
// Versions

sqlite3/lib/src/exception.dart

+7-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ final class SqliteException implements Exception {
2525
/// code, providing some idea of the cause of the failure.
2626
int get resultCode => extendedResultCode & 0xFF;
2727

28+
/// If this error is related to a syntex error in SQL, contains the byte
29+
/// offset of the token associated with the error.
30+
final int? offset;
31+
2832
/// An informal description of what the `sqlite3` package was attempting to do
2933
/// when the exception occured, e.g. "preparing a statement",
3034
/// "opening the database".
@@ -48,6 +52,7 @@ final class SqliteException implements Exception {
4852
this.causingStatement,
4953
this.parametersToStatement,
5054
this.operation,
55+
this.offset,
5156
]);
5257

5358
@override
@@ -67,7 +72,8 @@ final class SqliteException implements Exception {
6772
if (causingStatement != null) {
6873
buffer
6974
..writeln()
70-
..write(' Causing statement: ')
75+
..write(' Causing statement')
76+
..write(offset != null ? ' (at position $offset): ' : ': ')
7177
..write(causingStatement);
7278

7379
if (parametersToStatement != null) {

sqlite3/lib/src/ffi/bindings.dart

+17-6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class BindingsWithLibrary {
2424
final DynamicLibrary library;
2525

2626
final bool supportsPrepareV3;
27+
final bool supportsErrorOffset;
2728
final bool supportsColumnTableName;
2829

2930
factory BindingsWithLibrary(DynamicLibrary library) {
@@ -50,17 +51,18 @@ class BindingsWithLibrary {
5051
i++;
5152
} while (lastOption != null);
5253
}
54+
final supportsErrorOffset = library.providesSymbol('sqlite3_error_offset');
5355

5456
return BindingsWithLibrary._(
55-
bindings,
56-
library,
57-
bindings.sqlite3_libversion_number() >= _firstVersionForV3,
58-
hasColumnMetadata,
59-
);
57+
bindings,
58+
library,
59+
bindings.sqlite3_libversion_number() >= _firstVersionForV3,
60+
hasColumnMetadata,
61+
supportsErrorOffset);
6062
}
6163

6264
BindingsWithLibrary._(this.bindings, this.library, this.supportsPrepareV3,
63-
this.supportsColumnTableName);
65+
this.supportsColumnTableName, this.supportsErrorOffset);
6466
}
6567

6668
final class FfiBindings extends RawSqliteBindings {
@@ -434,6 +436,15 @@ final class FfiDatabase extends RawSqliteDatabase {
434436
return bindings.bindings.sqlite3_extended_errcode(db);
435437
}
436438

439+
@override
440+
int sqlite3_error_offset() {
441+
if (bindings.supportsErrorOffset) {
442+
return bindings.bindings.sqlite3_error_offset(db);
443+
} else {
444+
return -1;
445+
}
446+
}
447+
437448
@override
438449
void sqlite3_extended_result_codes(int onoff) {
439450
bindings.bindings.sqlite3_extended_result_codes(db, onoff);

sqlite3/lib/src/ffi/sqlite3.g.dart

+14
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,20 @@ class Bindings {
167167
late final _sqlite3_errstr =
168168
_sqlite3_errstrPtr.asFunction<ffi.Pointer<sqlite3_char> Function(int)>();
169169

170+
int sqlite3_error_offset(
171+
ffi.Pointer<sqlite3> db,
172+
) {
173+
return _sqlite3_error_offset(
174+
db,
175+
);
176+
}
177+
178+
late final _sqlite3_error_offsetPtr =
179+
_lookup<ffi.NativeFunction<ffi.Int Function(ffi.Pointer<sqlite3>)>>(
180+
'sqlite3_error_offset');
181+
late final _sqlite3_error_offset =
182+
_sqlite3_error_offsetPtr.asFunction<int Function(ffi.Pointer<sqlite3>)>();
183+
170184
void sqlite3_free(
171185
ffi.Pointer<ffi.Void> ptr,
172186
) {

sqlite3/lib/src/implementation/bindings.dart

+2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ abstract base class RawSqliteDatabase {
7070
int sqlite3_exec(String sql);
7171

7272
int sqlite3_extended_errcode();
73+
int sqlite3_error_offset();
74+
7375
void sqlite3_extended_result_codes(int onoff);
7476
int sqlite3_close_v2();
7577
String sqlite3_errmsg();

sqlite3/lib/src/implementation/exception.dart

+16-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ SqliteException createExceptionRaw(
1313
// Getting hold of more explanatory error code as SQLITE_IOERR error group has
1414
// an extensive list of extended error codes
1515
final extendedCode = db.sqlite3_extended_errcode();
16+
final offset = switch (db.sqlite3_error_offset()) {
17+
< 0 => null,
18+
final offset => offset,
19+
};
20+
1621
return createExceptionFromExtendedCode(
1722
bindings,
1823
db,
@@ -21,6 +26,7 @@ SqliteException createExceptionRaw(
2126
operation: operation,
2227
previousStatement: previousStatement,
2328
statementArgs: statementArgs,
29+
offset: offset,
2430
);
2531
}
2632

@@ -32,6 +38,7 @@ SqliteException createExceptionFromExtendedCode(
3238
String? operation,
3339
String? previousStatement,
3440
List<Object?>? statementArgs,
41+
int? offset,
3542
}) {
3643
// We don't need to free the pointer returned by sqlite3_errmsg: "Memory to
3744
// hold the error message string is managed internally. The application does
@@ -48,6 +55,7 @@ SqliteException createExceptionFromExtendedCode(
4855
previousStatement,
4956
statementArgs,
5057
operation,
58+
offset,
5159
);
5260
}
5361

@@ -68,10 +76,14 @@ SqliteException createException(
6876
);
6977
}
7078

71-
Never throwException(DatabaseImplementation db, int returnCode,
72-
{String? operation,
73-
String? previousStatement,
74-
List<Object?>? statementArgs}) {
79+
Never throwException(
80+
DatabaseImplementation db,
81+
int returnCode, {
82+
String? operation,
83+
String? previousStatement,
84+
List<Object?>? statementArgs,
85+
int? offset,
86+
}) {
7587
throw createException(
7688
db,
7789
returnCode,

sqlite3/lib/src/wasm/bindings.dart

+5
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ final class WasmDatabase extends RawSqliteDatabase {
134134
return bindings.sqlite3_extended_errcode(db);
135135
}
136136

137+
@override
138+
int sqlite3_error_offset() {
139+
return bindings.sqlite3_error_offset(db);
140+
}
141+
137142
@override
138143
void sqlite3_extended_result_codes(int onoff) {
139144
bindings.sqlite3_extended_result_codes(db, onoff);

sqlite3/lib/src/wasm/wasm_interop.dart

+7-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ class WasmBindings {
8989
final JSFunction? _sqlite3_db_config,
9090
_sqlite3_initialize,
9191
_commit_hooks,
92-
_rollback_hooks;
92+
_rollback_hooks,
93+
_sqlite3_error_offset;
9394

9495
final Global _sqlite3_temp_directory;
9596

@@ -167,6 +168,7 @@ class WasmBindings {
167168
_sqlite3_stmt_readonly = instance.functions['sqlite3_stmt_readonly']!,
168169
_sqlite3_db_config = instance.functions['dart_sqlite3_db_config_int'],
169170
_sqlite3_initialize = instance.functions['sqlite3_initialize'],
171+
_sqlite3_error_offset = instance.functions['sqlite3_error_offset'],
170172
_commit_hooks = instance.functions['dart_sqlite3_commits'],
171173
_rollback_hooks = instance.functions['dart_sqlite3_rollbacks'],
172174
_sqlite3_temp_directory = instance.globals['sqlite3_temp_directory']!
@@ -285,6 +287,10 @@ class WasmBindings {
285287
Pointer sqlite3_errstr(int resultCode) =>
286288
_sqlite3_errstr.callReturningInt(resultCode.toJS);
287289

290+
int sqlite3_error_offset(Pointer db) {
291+
return _sqlite3_error_offset?.callReturningInt(db.toJS) ?? -1;
292+
}
293+
288294
int sqlite3_extended_result_codes(Pointer db, int onoff) {
289295
return _sqlite3_extended_result_codes.callReturningInt2(
290296
db.toJS, onoff.toJS);

sqlite3/test/ffi/errors_test.dart

+25
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import 'package:test/test.dart';
99
import 'package:test_descriptor/test_descriptor.dart' as d;
1010

1111
void main() {
12+
final version = sqlite3.version;
13+
final hasErrorOffset = version.versionNumber > 3038000;
14+
1215
test('open read-only exception', () async {
1316
final path = d.path('read_only_exception.db');
1417

@@ -153,5 +156,27 @@ SqliteException(1): message
153156
Causing statement: SELECT foo;''',
154157
);
155158
});
159+
160+
test(
161+
'reports position',
162+
() {
163+
final db = sqlite3.openInMemory();
164+
addTearDown(db.dispose);
165+
166+
expect(
167+
() => db.select('SELECT totally invalid syntax;'),
168+
throwsA(isA<SqliteException>()
169+
.having(
170+
(e) => e.causingStatement,
171+
'causingStatement',
172+
'SELECT totally invalid syntax;',
173+
)
174+
.having((e) => e.offset, 'offset', 23)
175+
.having((e) => e.toString(), 'toString()',
176+
contains('Causing statement (at position 23): SELECT'))),
177+
);
178+
},
179+
skip: hasErrorOffset ? null : 'Missing sqlite3_error_offset',
180+
);
156181
});
157182
}

sqlite3/test/wasm/sqlite3_test.dart

+18
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,24 @@ void main() {
9898
});
9999
}
100100
});
101+
102+
test('can report error location', () {
103+
final db = sqlite3.openInMemory();
104+
addTearDown(db.dispose);
105+
106+
expect(
107+
() => db.select('SELECT totally invalid syntax;'),
108+
throwsA(isA<SqliteException>()
109+
.having(
110+
(e) => e.causingStatement,
111+
'causingStatement',
112+
'SELECT totally invalid syntax;',
113+
)
114+
.having((e) => e.offset, 'offset', 23)
115+
.having((e) => e.toString(), 'toString()',
116+
contains('Causing statement (at position 23): SELECT'))),
117+
);
118+
});
101119
});
102120
}
103121

0 commit comments

Comments
 (0)