1+ import 'dart:async' ;
12import 'dart:math' ;
23
4+ import 'package:sqlite3/sqlite3.dart' as sqlite;
5+ import 'package:sqlite_async/mutex.dart' ;
36import 'package:sqlite_async/sqlite_async.dart' ;
47import 'package:test/test.dart' ;
5- import 'package:sqlite3/sqlite3.dart' as sqlite;
8+
69import 'util.dart' ;
710
811void main () {
@@ -108,18 +111,22 @@ void main() {
108111 await expectLater (() async {
109112 await db.execute (
110113 'INSERT INTO test_data(description) VALUES(?)' , ['test' ]);
111- }, throwsA ((e) => e is AssertionError ));
114+ }, throwsA ((e) => e is LockError && e.message. contains ( 'tx.execute' ) ));
112115 });
113116 });
114117
115- test ('does allow read-only db calls within transaction callback' , () async {
118+ test ('should not allow read-only db calls within transaction callback' ,
119+ () async {
116120 final db = await setupDatabase (path: path);
117121 await createTables (db);
118122
119123 await db.writeTransaction ((tx) async {
120- // This uses a different connection, so it's fine.
121- // Perhaps we should warn on this, since it's likely unintentional?
122- await db.getAll ('SELECT * FROM test_data' );
124+ // This uses a different connection, so it _could_ work.
125+ // But it's likely unintentional and could cause weird bugs, so we don't
126+ // allow it by default.
127+ await expectLater (() async {
128+ await db.getAll ('SELECT * FROM test_data' );
129+ }, throwsA ((e) => e is LockError && e.message.contains ('tx.getAll' )));
123130 });
124131
125132 await db.readTransaction ((tx) async {
@@ -129,24 +136,68 @@ void main() {
129136 // opens another connection, but doesn't use it.
130137 await expectLater (() async {
131138 await db.getAll ('SELECT * FROM test_data' );
132- }, throwsA ((e) => e is AssertionError ));
139+ }, throwsA ((e) => e is LockError && e.message. contains ( 'tx.getAll' ) ));
133140 });
134141 });
135142
136- test ('does allow read-only db calls within lock callback' , () async {
143+ test ('should not allow read-only db calls within lock callback' , () async {
137144 final db = await setupDatabase (path: path);
138145 await createTables (db);
139146 // Locks - should behave the same as transactions above
140147
141148 await db.writeLock ((tx) async {
142- await db.getAll ('SELECT * FROM test_data' );
149+ await expectLater (() async {
150+ await db.getOptional ('SELECT * FROM test_data' );
151+ },
152+ throwsA (
153+ (e) => e is LockError && e.message.contains ('tx.getOptional' )));
143154 });
144155
145156 await db.readLock ((tx) async {
146157 await expectLater (() async {
158+ await db.getOptional ('SELECT * FROM test_data' );
159+ },
160+ throwsA (
161+ (e) => e is LockError && e.message.contains ('tx.getOptional' )));
162+ });
163+ });
164+
165+ test (
166+ 'should allow read-only db calls within transaction callback in separate zone' ,
167+ () async {
168+ final db = await setupDatabase (path: path);
169+ await createTables (db);
170+
171+ // Get a reference to the parent zone (outside the transaction).
172+ final zone = Zone .current;
173+
174+ // Each of these are fine, since it could use a separate connection.
175+ // Note: In highly concurrent cases, it could exhaust the connection pool and cause a deadlock.
176+
177+ await db.writeTransaction ((tx) async {
178+ await zone.fork ().run (() async {
179+ await db.getAll ('SELECT * FROM test_data' );
180+ });
181+ });
182+
183+ await db.readTransaction ((tx) async {
184+ await zone.fork ().run (() async {
147185 await db.getAll ('SELECT * FROM test_data' );
148- }, throwsA ((e) => e is AssertionError ));
186+ });
187+ });
188+
189+ await db.readTransaction ((tx) async {
190+ await zone.fork ().run (() async {
191+ await db.execute ('SELECT * FROM test_data' );
192+ });
149193 });
194+
195+ // This would deadlock, since it shares a global write lock.
196+ // await db.writeTransaction((tx) async {
197+ // await zone.fork().run(() async {
198+ // await db.execute('SELECT * FROM test_data');
199+ // });
200+ // });
150201 });
151202
152203 test ('should allow PRAMGAs' , () async {
0 commit comments