xref: /expo/apps/test-suite/tests/SQLite.ts (revision f9dd6913)
1import assert from 'assert';
2import { Asset } from 'expo-asset';
3import * as FS from 'expo-file-system';
4import { Platform } from 'expo-modules-core';
5import * as SQLite from 'expo-sqlite';
6
7export const name = 'SQLite';
8
9// The version here needs to be the same as both the podspec and build.gradle for expo-sqlite
10const VERSION = '3.39.2';
11
12// TODO: Only tests successful cases, needs to test error cases like bad database name etc.
13export function test(t) {
14  t.describe('SQLite', () => {
15    t.it('should be able to drop + create a table, insert, query', async () => {
16      const db = SQLite.openDatabase('test.db');
17      await new Promise((resolve, reject) => {
18        db.transaction(
19          (tx) => {
20            const nop = () => {};
21            const onError = (tx, error) => {
22              reject(error);
23              return false;
24            };
25
26            tx.executeSql('DROP TABLE IF EXISTS Users;', [], nop, onError);
27            tx.executeSql(
28              'CREATE TABLE IF NOT EXISTS Users (user_id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64), k INT, j REAL);',
29              [],
30              nop,
31              onError
32            );
33            tx.executeSql(
34              'INSERT INTO Users (name, k, j) VALUES (?, ?, ?)',
35              ['Tim Duncan', 1, 23.4],
36              nop,
37              onError
38            );
39            tx.executeSql(
40              'INSERT INTO Users (name, k, j) VALUES ("Manu Ginobili", 5, 72.8)',
41              [],
42              nop,
43              onError
44            );
45            tx.executeSql(
46              'INSERT INTO Users (name, k, j) VALUES ("Nikhilesh Sigatapu", 7, 42.14)',
47              [],
48              nop,
49              onError
50            );
51
52            tx.executeSql(
53              'SELECT * FROM Users',
54              [],
55              (tx, results) => {
56                t.expect(results.rows.length).toEqual(3);
57                t.expect(results.rows.item(0).j).toBeCloseTo(23.4);
58              },
59              onError
60            );
61          },
62          reject,
63          () => {
64            resolve(null);
65          }
66        );
67      });
68
69      if (Platform.OS !== 'web') {
70        const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
71        t.expect(exists).toBeTruthy();
72      }
73    });
74
75    t.it(`should use specified SQLite version: ${VERSION}`, () => {
76      const db = SQLite.openDatabase('test.db');
77
78      db.transaction((tx) => {
79        tx.executeSql('SELECT sqlite_version()', [], (_, results) => {
80          const queryVersion = results.rows._array[0]['sqlite_version()'];
81          t.expect(queryVersion).toEqual(VERSION);
82        });
83      });
84    });
85
86    t.it(`unixepoch() is supported`, () => {
87      const db = SQLite.openDatabase('test.db');
88
89      db.transaction((tx) => {
90        tx.executeSql('SELECT unixepoch()', [], (_, results) => {
91          const epoch = results.rows._array[0]['unixepoch()'];
92          t.expect(epoch).toBeTruthy();
93        });
94      });
95    });
96
97    if (Platform.OS !== 'web') {
98      t.it(
99        'should work with a downloaded .db file',
100        async () => {
101          await FS.downloadAsync(
102            Asset.fromModule(require('../assets/asset-db.db')).uri,
103            `${FS.documentDirectory}SQLite/downloaded.db`
104          );
105
106          const db = SQLite.openDatabase('downloaded.db');
107          await new Promise((resolve, reject) => {
108            db.transaction(
109              (tx) => {
110                const onError = (tx, error) => {
111                  reject(error);
112                  return false;
113                };
114                tx.executeSql(
115                  'SELECT * FROM Users',
116                  [],
117                  (tx, results) => {
118                    t.expect(results.rows.length).toEqual(3);
119                    t.expect(results.rows._array[0].j).toBeCloseTo(23.4);
120                  },
121                  onError
122                );
123              },
124              reject,
125              () => {
126                resolve(null);
127              }
128            );
129          });
130          db.closeAsync();
131        },
132        30000
133      );
134    }
135
136    t.it('should be able to recreate db from scratch by deleting file', async () => {
137      {
138        const db = SQLite.openDatabase('test.db');
139        await new Promise((resolve, reject) => {
140          db.transaction(
141            (tx) => {
142              const nop = () => {};
143              const onError = (tx, error) => {
144                reject(error);
145                return false;
146              };
147
148              tx.executeSql('DROP TABLE IF EXISTS Users;', [], nop, onError);
149              tx.executeSql(
150                'CREATE TABLE IF NOT EXISTS Users (user_id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64), k INT, j REAL);',
151                [],
152                nop,
153                onError
154              );
155              tx.executeSql(
156                'INSERT INTO Users (name, k, j) VALUES (?, ?, ?)',
157                ['Tim Duncan', 1, 23.4],
158                nop,
159                onError
160              );
161
162              tx.executeSql(
163                'SELECT * FROM Users',
164                [],
165                (tx, results) => {
166                  t.expect(results.rows.length).toEqual(1);
167                },
168                onError
169              );
170            },
171            reject,
172            () => {
173              resolve(null);
174            }
175          );
176        });
177      }
178
179      if (Platform.OS !== 'web') {
180        const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
181        t.expect(exists).toBeTruthy();
182      }
183
184      if (Platform.OS !== 'web') {
185        await FS.deleteAsync(`${FS.documentDirectory}SQLite/test.db`);
186        const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
187        t.expect(exists).toBeFalsy();
188      }
189
190      {
191        const db = SQLite.openDatabase('test.db');
192        await new Promise((resolve, reject) => {
193          db.transaction(
194            (tx) => {
195              const nop = () => {};
196              const onError = (tx, error) => {
197                reject(error);
198                return false;
199              };
200
201              tx.executeSql('DROP TABLE IF EXISTS Users;', [], nop, onError);
202              tx.executeSql(
203                'CREATE TABLE IF NOT EXISTS Users (user_id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64), k INT, j REAL);',
204                [],
205                nop,
206                onError
207              );
208              tx.executeSql(
209                'SELECT * FROM Users',
210                [],
211                (tx, results) => {
212                  t.expect(results.rows.length).toEqual(0);
213                },
214                onError
215              );
216
217              tx.executeSql(
218                'INSERT INTO Users (name, k, j) VALUES (?, ?, ?)',
219                ['Tim Duncan', 1, 23.4],
220                nop,
221                onError
222              );
223              tx.executeSql(
224                'SELECT * FROM Users',
225                [],
226                (tx, results) => {
227                  t.expect(results.rows.length).toEqual(1);
228                },
229                onError
230              );
231            },
232            reject,
233            () => {
234              resolve(null);
235            }
236          );
237        });
238      }
239    });
240
241    t.it('should maintain correct type of potentialy null bind parameters', async () => {
242      const db = SQLite.openDatabase('test.db');
243      await new Promise((resolve, reject) => {
244        db.transaction(
245          (tx) => {
246            const nop = () => {};
247            const onError = (tx, error) => {
248              reject(error);
249              return false;
250            };
251
252            tx.executeSql('DROP TABLE IF EXISTS Nulling;', [], nop, onError);
253            tx.executeSql(
254              'CREATE TABLE IF NOT EXISTS Nulling (id INTEGER PRIMARY KEY NOT NULL, x NUMERIC, y NUMERIC)',
255              [],
256              nop,
257              onError
258            );
259            tx.executeSql('INSERT INTO Nulling (x, y) VALUES (?, ?)', [null, null], nop, onError);
260            tx.executeSql('INSERT INTO Nulling (x, y) VALUES (null, null)', [], nop, onError);
261
262            tx.executeSql(
263              'SELECT * FROM Nulling',
264              [],
265              (tx, results) => {
266                t.expect(results.rows.item(0).x).toBeNull();
267                t.expect(results.rows.item(0).y).toBeNull();
268                t.expect(results.rows.item(1).x).toBeNull();
269                t.expect(results.rows.item(1).y).toBeNull();
270              },
271              onError
272            );
273          },
274          reject,
275          () => {
276            resolve(null);
277          }
278        );
279      });
280
281      if (Platform.OS !== 'web') {
282        const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
283        t.expect(exists).toBeTruthy();
284      }
285    });
286
287    // Do not try to test PRAGMA statements support in web
288    // as it is expected to not be working.
289    // See https://stackoverflow.com/a/10298712
290    if (Platform.OS !== 'web') {
291      t.it('should support PRAGMA statements', async () => {
292        const db = SQLite.openDatabase('test.db');
293        await new Promise((resolve, reject) => {
294          db.transaction(
295            (tx) => {
296              const nop = () => {};
297              const onError = (tx, error) => {
298                reject(error);
299                return false;
300              };
301
302              tx.executeSql('DROP TABLE IF EXISTS SomeTable;', [], nop, onError);
303              tx.executeSql(
304                'CREATE TABLE IF NOT EXISTS SomeTable (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));',
305                [],
306                nop,
307                onError
308              );
309              // a result-returning pragma
310              tx.executeSql(
311                'PRAGMA table_info(SomeTable);',
312                [],
313                (tx, results) => {
314                  t.expect(results.rows.length).toEqual(2);
315                  t.expect(results.rows.item(0).name).toEqual('id');
316                  t.expect(results.rows.item(1).name).toEqual('name');
317                },
318                onError
319              );
320              // a no-result pragma
321              tx.executeSql('PRAGMA case_sensitive_like = true;', [], nop, onError);
322              // a setter/getter pragma
323              tx.executeSql('PRAGMA user_version = 123;', [], nop, onError);
324              tx.executeSql(
325                'PRAGMA user_version;',
326                [],
327                (tx, results) => {
328                  t.expect(results.rows.length).toEqual(1);
329                  t.expect(results.rows.item(0).user_version).toEqual(123);
330                },
331                onError
332              );
333            },
334            reject,
335            () => {
336              resolve(null);
337            }
338          );
339        });
340      });
341    }
342
343    t.it('should support the `RETURNING` clause using raw queries', async () => {
344      const db = SQLite.openDatabase('test.db');
345      await new Promise((resolve, reject) => {
346        db.transaction(
347          (tx) => {
348            const nop = () => {};
349            const onError = (_, error) => {
350              reject(error);
351              return false;
352            };
353
354            tx.executeSql('DROP TABLE IF EXISTS customers;', [], nop, onError);
355            tx.executeSql(
356              'CREATE TABLE customers (id PRIMARY KEY NOT NULL, name VARCHAR(255),email VARCHAR(255));',
357              [],
358              nop,
359              onError
360            );
361          },
362          reject,
363          () => {
364            resolve(null);
365          }
366        );
367      });
368
369      db.execRawQuery(
370        [
371          {
372            // Unsupprted on Android using the `exec` function
373            sql: "INSERT INTO customers (id, name, email) VALUES (1, 'John Doe', '[email protected]') RETURNING name, email;",
374            args: [],
375          },
376        ],
377        false,
378        (tx, results) => {
379          // @ts-expect-error
380          t.expect(results.rows[0].email).toBe('[email protected]');
381          // @ts-expect-error
382          t.expect(results.rows[0].name).toBe('John Doe');
383        }
384      );
385
386      db.execRawQuery(
387        [
388          {
389            sql: "UPDATE customers SET name='Jane Doe', email='[email protected]' WHERE id=1 RETURNING name, email;",
390            args: [],
391          },
392        ],
393        false,
394        (tx, results) => {
395          // @ts-expect-error
396          t.expect(results.rows[0].email).toBe('[email protected]');
397          // @ts-expect-error
398          t.expect(results.rows[0].name).toBe('Jane Doe');
399        }
400      );
401
402      db.execRawQuery(
403        [
404          {
405            // Unsupprted on Android using the `exec` function
406            sql: 'DELETE from customers WHERE id=1 RETURNING name, email;',
407            args: [],
408          },
409        ],
410        false,
411        (tx, results) => {
412          // @ts-expect-error
413          t.expect(results.rows[0].email).toBe('[email protected]');
414          // @ts-expect-error
415          t.expect(results.rows[0].name).toBe('Jane Doe');
416        }
417      );
418    });
419
420    t.it('should return correct rowsAffected value', async () => {
421      const db = SQLite.openDatabase('test.db');
422      await new Promise((resolve, reject) => {
423        db.transaction(
424          (tx) => {
425            const nop = () => {};
426            const onError = (tx, error) => {
427              reject(error);
428              return false;
429            };
430
431            tx.executeSql('DROP TABLE IF EXISTS Users;', [], nop, onError);
432            tx.executeSql(
433              'CREATE TABLE IF NOT EXISTS Users (user_id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));',
434              [],
435              nop,
436              onError
437            );
438            tx.executeSql(
439              'INSERT INTO Users (name) VALUES (?), (?), (?)',
440              ['name1', 'name2', 'name3'],
441              nop,
442              onError
443            );
444          },
445          reject,
446          () => {
447            resolve(null);
448          }
449        );
450      });
451      await new Promise((resolve, reject) => {
452        db.transaction(
453          (tx) => {
454            const onError = (tx, error) => {
455              reject(error);
456              return false;
457            };
458            tx.executeSql(
459              'DELETE FROM Users WHERE name=?',
460              ['name1'],
461              (tx, results) => {
462                t.expect(results.rowsAffected).toEqual(1);
463              },
464              onError
465            );
466            tx.executeSql(
467              'DELETE FROM Users WHERE name=? OR name=?',
468              ['name2', 'name3'],
469              (tx, results) => {
470                t.expect(results.rowsAffected).toEqual(2);
471              },
472              onError
473            );
474            tx.executeSql(
475              // ensure deletion succeedeed
476              'SELECT * from Users',
477              [],
478              (tx, results) => {
479                t.expect(results.rows.length).toEqual(0);
480              },
481              onError
482            );
483          },
484          reject,
485          () => {
486            resolve(null);
487          }
488        );
489      });
490    });
491
492    if (Platform.OS !== 'web') {
493      // It is not expected to work on web, since we cannot execute PRAGMA to enable foreign keys support
494      t.it('should return correct rowsAffected value when deleting cascade', async () => {
495        const db = SQLite.openDatabase('test.db');
496        db.exec([{ sql: 'PRAGMA foreign_keys = ON;', args: [] }], false, () => {});
497        await new Promise((resolve, reject) => {
498          db.transaction(
499            (tx) => {
500              const nop = () => {};
501              const onError = (tx, error) => {
502                reject(error);
503                return false;
504              };
505
506              tx.executeSql('DROP TABLE IF EXISTS Users;', [], nop, onError);
507              tx.executeSql('DROP TABLE IF EXISTS Posts;', [], nop, onError);
508              tx.executeSql(
509                'CREATE TABLE IF NOT EXISTS Users (user_id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));',
510                [],
511                nop,
512                onError
513              );
514              tx.executeSql(
515                'CREATE TABLE IF NOT EXISTS Posts (post_id INTEGER PRIMARY KEY NOT NULL, content VARCHAR(64), userposted INTEGER, FOREIGN KEY(userposted) REFERENCES Users(user_id) ON DELETE CASCADE);',
516                [],
517                nop,
518                onError
519              );
520              tx.executeSql(
521                'INSERT INTO Users (name) VALUES (?), (?), (?)',
522                ['name1', 'name2', 'name3'],
523                nop,
524                onError
525              );
526
527              tx.executeSql(
528                'INSERT INTO Posts (content, userposted) VALUES (?, ?), (?, ?), (?, ?)',
529                ['post1', 1, 'post2', 1, 'post3', 2],
530                nop,
531                onError
532              );
533              tx.executeSql('PRAGMA foreign_keys=off;', [], nop, onError);
534            },
535            reject,
536            () => {
537              resolve(null);
538            }
539          );
540        });
541        await new Promise((resolve, reject) => {
542          db.transaction(
543            (tx) => {
544              const nop = () => {};
545              const onError = (tx, error) => {
546                reject(error);
547                return false;
548              };
549              tx.executeSql('PRAGMA foreign_keys=on;', [], nop, onError);
550              tx.executeSql(
551                'DELETE FROM Users WHERE name=?',
552                ['name1'],
553                (tx, results) => {
554                  t.expect(results.rowsAffected).toEqual(1);
555                },
556                onError
557              );
558              tx.executeSql(
559                'DELETE FROM Users WHERE name=? OR name=?',
560                ['name2', 'name3'],
561                (tx, results) => {
562                  t.expect(results.rowsAffected).toEqual(2);
563                },
564                onError
565              );
566
567              tx.executeSql(
568                // ensure deletion succeeded
569                'SELECT * from Users',
570                [],
571                (tx, results) => {
572                  t.expect(results.rows.length).toEqual(0);
573                },
574                onError
575              );
576
577              tx.executeSql(
578                'SELECT * from Posts',
579                [],
580                (tx, results) => {
581                  t.expect(results.rows.length).toEqual(0);
582                },
583                onError
584              );
585              tx.executeSql('PRAGMA foreign_keys=off;', [], nop, onError);
586            },
587            reject,
588            () => {
589              resolve(null);
590            }
591          );
592        });
593      });
594    }
595
596    if (Platform.OS !== 'web') {
597      t.it('should delete db on filesystem from the `deleteAsync()` call', async () => {
598        const db = SQLite.openDatabase('test.db');
599        let fileInfo = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
600        t.expect(fileInfo.exists).toBeTruthy();
601
602        await db.closeAsync();
603        await db.deleteAsync();
604        fileInfo = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
605        t.expect(fileInfo.exists).toBeFalsy();
606      });
607    }
608  });
609
610  if (Platform.OS !== 'web') {
611    t.describe('SQLiteAsync', () => {
612      const throws = async (run) => {
613        let error = null;
614        try {
615          await run();
616        } catch (e) {
617          error = e;
618        }
619        t.expect(error).toBeTruthy();
620      };
621
622      t.it('should support async transaction', async () => {
623        const db = SQLite.openDatabase('test.db');
624
625        // create table
626        await db.transactionAsync(async (tx) => {
627          await tx.executeSqlAsync('DROP TABLE IF EXISTS Users;', []);
628          await tx.executeSqlAsync(
629            'CREATE TABLE IF NOT EXISTS Users (user_id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));',
630            []
631          );
632        });
633
634        // fetch data from network
635        async function fakeUserFetcher(userID) {
636          switch (userID) {
637            case 1: {
638              return Promise.resolve('Tim Duncan');
639            }
640            case 2: {
641              return Promise.resolve('Manu Ginobili');
642            }
643            case 3: {
644              return Promise.resolve('Nikhilesh Sigatapu');
645            }
646            default: {
647              return null;
648            }
649          }
650        }
651
652        const userName = await fakeUserFetcher(1);
653        await db.transactionAsync(async (tx) => {
654          await tx.executeSqlAsync('INSERT INTO Users (name) VALUES (?)', [userName]);
655          const result = await tx.executeSqlAsync('SELECT * FROM Users LIMIT 1');
656          assert(!isResultSetError(result));
657          const currentUser = result.rows[0].name;
658          t.expect(currentUser).toEqual('Tim Duncan');
659        });
660      });
661
662      t.it('should load crsqlite extension correctly', async () => {
663        const db = SQLite.openDatabase('test.db');
664        await db.transactionAsync(async (tx) => {
665          await tx.executeSqlAsync('DROP TABLE IF EXISTS foo;', []);
666          await tx.executeSqlAsync('create table foo (a primary key, b);', []);
667          await tx.executeSqlAsync('select crsql_as_crr("foo");', []);
668          await tx.executeSqlAsync('insert into foo (a,b) values (1,2);', []);
669          await tx.executeSqlAsync('insert into foo (a,b) values (1,2);', []);
670          const result = await tx.executeSqlAsync('select * from crsql_changes;', []);
671          assert(!isResultSetError(result));
672          const table = result.rows[0].table;
673          const value = result.rows[0].val;
674          t.expect(table).toEqual('foo');
675          t.expect(value).toEqual(2);
676        });
677      });
678
679      t.it('should support Promise.all', async () => {
680        const db = SQLite.openDatabase('test.db');
681
682        // create table
683        await db.transactionAsync(async (tx) => {
684          await tx.executeSqlAsync('DROP TABLE IF EXISTS Users;', []);
685          await tx.executeSqlAsync(
686            'CREATE TABLE IF NOT EXISTS Users (user_id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));',
687            []
688          );
689        });
690
691        await db.transactionAsync(async (tx) => {
692          await Promise.all([
693            tx.executeSqlAsync('INSERT INTO Users (name) VALUES (?)', ['aaa']),
694            tx.executeSqlAsync('INSERT INTO Users (name) VALUES (?)', ['bbb']),
695            tx.executeSqlAsync('INSERT INTO Users (name) VALUES (?)', ['ccc']),
696          ]);
697
698          const result = await tx.executeSqlAsync('SELECT COUNT(*) FROM Users');
699          assert(!isResultSetError(result));
700          const recordCount = result.rows[0]['COUNT(*)'];
701          t.expect(recordCount).toEqual(3);
702        });
703      });
704
705      t.it(
706        'should return `could not prepare ...` error when having write statements in readOnly transaction',
707        async () => {
708          const db = SQLite.openDatabase('test.db');
709
710          // create table in readOnly transaction
711          await db.transactionAsync(async (tx) => {
712            const result = await tx.executeSqlAsync('DROP TABLE IF EXISTS Users;', []);
713            assert(isResultSetError(result));
714            t.expect(result.error).toBeDefined();
715            t.expect(result.error.message).toContain('could not prepare ');
716          }, true);
717        }
718      );
719
720      t.it('should rollback transaction when exception happens inside a transaction', async () => {
721        const db = SQLite.openDatabase('test.db');
722
723        // create table
724        await db.transactionAsync(async (tx) => {
725          await tx.executeSqlAsync('DROP TABLE IF EXISTS Users;', []);
726          await tx.executeSqlAsync(
727            'CREATE TABLE IF NOT EXISTS Users (user_id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));',
728            []
729          );
730        });
731        await db.transactionAsync(async (tx) => {
732          await tx.executeSqlAsync('INSERT INTO Users (name) VALUES (?)', ['aaa']);
733        });
734        await db.transactionAsync(async (tx) => {
735          const result = await tx.executeSqlAsync('SELECT COUNT(*) FROM Users');
736          assert(!isResultSetError(result));
737          const recordCount = result.rows[0]['COUNT(*)'];
738          t.expect(recordCount).toEqual(1);
739        }, true);
740
741        await throws(() =>
742          db.transactionAsync(async (tx) => {
743            await tx.executeSqlAsync('INSERT INTO Users (name) VALUES (?)', ['bbb']);
744            await tx.executeSqlAsync('INSERT INTO Users (name) VALUES (?)', ['ccc']);
745            // exeuting invalid sql statement will throw an exception
746            await tx.executeSqlAsync(null);
747          })
748        );
749
750        await db.transactionAsync(async (tx) => {
751          const result = await tx.executeSqlAsync('SELECT COUNT(*) FROM Users');
752          assert(!isResultSetError(result));
753          const recordCount = result.rows[0]['COUNT(*)'];
754          t.expect(recordCount).toEqual(1);
755        }, true);
756      });
757
758      t.it('should support async PRAGMA statements', async () => {
759        const db = SQLite.openDatabase('test.db');
760        await db.transactionAsync(async (tx) => {
761          await tx.executeSqlAsync('DROP TABLE IF EXISTS SomeTable;', []);
762          await tx.executeSqlAsync(
763            'CREATE TABLE IF NOT EXISTS SomeTable (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));',
764            []
765          );
766          // a result-returning pragma
767          let result = await tx.executeSqlAsync('PRAGMA table_info(SomeTable);', []);
768          assert(!isResultSetError(result));
769          t.expect(result.rows.length).toEqual(2);
770          t.expect(result.rows[0].name).toEqual('id');
771          t.expect(result.rows[1].name).toEqual('name');
772          // a no-result pragma
773          await tx.executeSqlAsync('PRAGMA case_sensitive_like = true;', []);
774          // a setter/getter pragma
775          await tx.executeSqlAsync('PRAGMA user_version = 123;', []);
776          result = await tx.executeSqlAsync('PRAGMA user_version;', []);
777          assert(!isResultSetError(result));
778          t.expect(result.rows.length).toEqual(1);
779          t.expect(result.rows[0].user_version).toEqual(123);
780        });
781      });
782    }); // t.describe('SQLiteAsync')
783  }
784}
785
786function isResultSetError(
787  result: SQLite.ResultSet | SQLite.ResultSetError
788): result is SQLite.ResultSetError {
789  return 'error' in result;
790}
791