@@ -587,6 +587,38 @@ void testPreparedStatements(
587587 throwsSqlError (19 , 2067 ),
588588 );
589589 });
590+
591+ test ('recovers from SQLITE_BUSY' , () {
592+ final vfs = _ErrorInjectingVfs (
593+ InMemoryFileSystem (),
594+ name: 'test-recover-sqlite-busy' ,
595+ );
596+ sqlite3.registerVirtualFileSystem (vfs);
597+ addTearDown (() => sqlite3.unregisterVirtualFileSystem (vfs));
598+
599+ var db = sqlite3.open ('/db' , vfs: vfs.name);
600+ addTearDown (() => db.dispose ());
601+
602+ db
603+ ..execute ('CREATE TABLE foo (bar TEXT) STRICT' )
604+ ..execute ('INSERT INTO foo (bar) VALUES (?)' , ['testing' ])
605+ ..dispose ();
606+
607+ db = db = sqlite3.open ('/db' , vfs: vfs.name);
608+ final stmt = db.prepare ('SELECT * FROM foo' );
609+ final cursor = stmt.selectCursor ();
610+ vfs.maybeError = () => throw VfsException (SqlError .SQLITE_BUSY );
611+
612+ expect (
613+ () => cursor.moveNext (),
614+ throwsSqlError (SqlError .SQLITE_BUSY , SqlError .SQLITE_BUSY ),
615+ );
616+ vfs.maybeError = null ;
617+ expect (cursor.moveNext (), isTrue);
618+ expect (cursor.current, {'bar' : 'testing' });
619+
620+ stmt.dispose ();
621+ });
590622 });
591623}
592624
@@ -604,3 +636,111 @@ class _CustomValue implements CustomStatementParameter {
604636 stmt.statement.sqlite3_bind_int64 (index, 42 );
605637 }
606638}
639+
640+ final class _ErrorInjectingVfs extends BaseVirtualFileSystem {
641+ final VirtualFileSystem _base;
642+ void Function ()? maybeError;
643+
644+ _ErrorInjectingVfs (this ._base, {required super .name});
645+
646+ void _op () {
647+ maybeError? .call ();
648+ }
649+
650+ @override
651+ int xAccess (String path, int flags) {
652+ _op ();
653+ return _base.xAccess (path, flags);
654+ }
655+
656+ @override
657+ void xDelete (String path, int syncDir) {
658+ _op ();
659+ return _base.xDelete (path, syncDir);
660+ }
661+
662+ @override
663+ String xFullPathName (String path) {
664+ _op ();
665+ return _base.xFullPathName (path);
666+ }
667+
668+ @override
669+ XOpenResult xOpen (Sqlite3Filename path, int flags) {
670+ _op ();
671+ final inner = _base.xOpen (path, flags);
672+ return (
673+ outFlags: inner.outFlags,
674+ file: _ErrorInjectingFile (this , inner.file),
675+ );
676+ }
677+
678+ @override
679+ void xSleep (Duration duration) {
680+ return _base.xSleep (duration);
681+ }
682+ }
683+
684+ final class _ErrorInjectingFile implements VirtualFileSystemFile {
685+ final _ErrorInjectingVfs _vfs;
686+ final VirtualFileSystemFile _base;
687+
688+ _ErrorInjectingFile (this ._vfs, this ._base);
689+
690+ @override
691+ void xRead (Uint8List target, int fileOffset) {
692+ _vfs._op ();
693+ return _base.xRead (target, fileOffset);
694+ }
695+
696+ @override
697+ int xCheckReservedLock () {
698+ _vfs._op ();
699+ return _base.xCheckReservedLock ();
700+ }
701+
702+ @override
703+ int get xDeviceCharacteristics => _base.xDeviceCharacteristics;
704+
705+ @override
706+ void xClose () {
707+ _vfs._op ();
708+ _base.xClose ();
709+ }
710+
711+ @override
712+ int xFileSize () {
713+ _vfs._op ();
714+ return _base.xFileSize ();
715+ }
716+
717+ @override
718+ void xLock (int mode) {
719+ _vfs._op ();
720+ _base.xLock (mode);
721+ }
722+
723+ @override
724+ void xSync (int flags) {
725+ _vfs._op ();
726+ _base.xSync (flags);
727+ }
728+
729+ @override
730+ void xTruncate (int size) {
731+ _vfs._op ();
732+ _base.xTruncate (size);
733+ }
734+
735+ @override
736+ void xUnlock (int mode) {
737+ _vfs._op ();
738+ _base.xUnlock (mode);
739+ }
740+
741+ @override
742+ void xWrite (Uint8List buffer, int fileOffset) {
743+ _vfs._op ();
744+ _base.xWrite (buffer, fileOffset);
745+ }
746+ }
0 commit comments