Skip to content

Commit 2acbd4c

Browse files
committed
Allow iterating after BUSY errors
1 parent d732685 commit 2acbd4c

File tree

4 files changed

+146
-2
lines changed

4 files changed

+146
-2
lines changed

sqlite3/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.9.3
2+
3+
- Allow iterating over statements after `SQLITE_BUSY` errors.
4+
15
## 2.9.2
26

37
- Fix a bug introduced in version `2.9.1` where the SQLite framework provided by

sqlite3/lib/src/implementation/statement.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,11 @@ class _ActiveCursorIterator extends IteratingCursor {
386386
}
387387

388388
// We're at the end of the result set or encountered an exception here.
389-
statement._currentCursor = null;
389+
if (result != SqlError.SQLITE_BUSY) {
390+
// Statements failing with SQLITE_BUSY can be retried in some instances,
391+
// so we don't want to detach the cursor.
392+
statement._currentCursor = null;
393+
}
390394

391395
if (result != SqlError.SQLITE_OK && result != SqlError.SQLITE_DONE) {
392396
throwException(

sqlite3/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: sqlite3
22
description: Provides lightweight yet convenient bindings to SQLite by using dart:ffi
3-
version: 2.9.2
3+
version: 2.9.3
44
homepage: https://github.com/simolus3/sqlite3.dart/tree/main/sqlite3
55
issue_tracker: https://github.com/simolus3/sqlite3.dart/issues
66
resolution: workspace

sqlite3/test/common/prepared_statement.dart

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,34 @@ void testPreparedStatements(
564564
throwsSqlError(19, 2067),
565565
);
566566
});
567+
568+
test('recovers from SQLITE_BUSY', () {
569+
final vfs = _ErrorInjectingVfs(InMemoryFileSystem(),
570+
name: 'test-recover-sqlite-busy');
571+
sqlite3.registerVirtualFileSystem(vfs);
572+
addTearDown(() => sqlite3.unregisterVirtualFileSystem(vfs));
573+
574+
var db = sqlite3.open('/db', vfs: vfs.name);
575+
addTearDown(() => db.dispose());
576+
577+
db
578+
..execute('CREATE TABLE foo (bar TEXT) STRICT')
579+
..execute('INSERT INTO foo (bar) VALUES (?)', ['testing'])
580+
..dispose();
581+
582+
db = db = sqlite3.open('/db', vfs: vfs.name);
583+
final stmt = db.prepare('SELECT * FROM foo');
584+
final cursor = stmt.selectCursor();
585+
vfs.maybeError = () => throw VfsException(SqlError.SQLITE_BUSY);
586+
587+
expect(() => cursor.moveNext(),
588+
throwsSqlError(SqlError.SQLITE_BUSY, SqlError.SQLITE_BUSY));
589+
vfs.maybeError = null;
590+
expect(cursor.moveNext(), isTrue);
591+
expect(cursor.current, {'bar': 'testing'});
592+
593+
stmt.dispose();
594+
});
567595
});
568596
}
569597

@@ -581,3 +609,111 @@ class _CustomValue implements CustomStatementParameter {
581609
stmt.statement.sqlite3_bind_int64(index, 42);
582610
}
583611
}
612+
613+
final class _ErrorInjectingVfs extends BaseVirtualFileSystem {
614+
final VirtualFileSystem _base;
615+
void Function()? maybeError;
616+
617+
_ErrorInjectingVfs(this._base, {required super.name});
618+
619+
void _op() {
620+
maybeError?.call();
621+
}
622+
623+
@override
624+
int xAccess(String path, int flags) {
625+
_op();
626+
return _base.xAccess(path, flags);
627+
}
628+
629+
@override
630+
void xDelete(String path, int syncDir) {
631+
_op();
632+
return _base.xDelete(path, syncDir);
633+
}
634+
635+
@override
636+
String xFullPathName(String path) {
637+
_op();
638+
return _base.xFullPathName(path);
639+
}
640+
641+
@override
642+
XOpenResult xOpen(Sqlite3Filename path, int flags) {
643+
_op();
644+
final inner = _base.xOpen(path, flags);
645+
return (
646+
outFlags: inner.outFlags,
647+
file: _ErrorInjectingFile(this, inner.file)
648+
);
649+
}
650+
651+
@override
652+
void xSleep(Duration duration) {
653+
return _base.xSleep(duration);
654+
}
655+
}
656+
657+
final class _ErrorInjectingFile implements VirtualFileSystemFile {
658+
final _ErrorInjectingVfs _vfs;
659+
final VirtualFileSystemFile _base;
660+
661+
_ErrorInjectingFile(this._vfs, this._base);
662+
663+
@override
664+
void xRead(Uint8List target, int fileOffset) {
665+
_vfs._op();
666+
return _base.xRead(target, fileOffset);
667+
}
668+
669+
@override
670+
int xCheckReservedLock() {
671+
_vfs._op();
672+
return _base.xCheckReservedLock();
673+
}
674+
675+
@override
676+
int get xDeviceCharacteristics => _base.xDeviceCharacteristics;
677+
678+
@override
679+
void xClose() {
680+
_vfs._op();
681+
_base.xClose();
682+
}
683+
684+
@override
685+
int xFileSize() {
686+
_vfs._op();
687+
return _base.xFileSize();
688+
}
689+
690+
@override
691+
void xLock(int mode) {
692+
_vfs._op();
693+
_base.xLock(mode);
694+
}
695+
696+
@override
697+
void xSync(int flags) {
698+
_vfs._op();
699+
_base.xSync(flags);
700+
}
701+
702+
@override
703+
void xTruncate(int size) {
704+
_vfs._op();
705+
_base.xTruncate(size);
706+
}
707+
708+
@override
709+
void xUnlock(int mode) {
710+
_vfs._op();
711+
_base.xUnlock(mode);
712+
}
713+
714+
@override
715+
void xWrite(Uint8List buffer, int fileOffset) {
716+
_vfs._op();
717+
_base.xWrite(buffer, fileOffset);
718+
}
719+
}

0 commit comments

Comments
 (0)