xref: /expo/apps/test-suite/tests/SQLite.ts (revision 36864eca)
1c52e83e3SKudo Chienimport { Asset } from 'expo-asset';
2c52e83e3SKudo Chienimport * as FS from 'expo-file-system';
3c52e83e3SKudo Chienimport { Platform } from 'expo-modules-core';
4c52e83e3SKudo Chienimport * as SQLite from 'expo-sqlite';
5c52e83e3SKudo Chien
6c52e83e3SKudo Chienexport const name = 'SQLite';
7c52e83e3SKudo Chien
84e419217SAlan Hughes// The version here needs to be the same as both the podspec and build.gradle for expo-sqlite
94b361cb0SAlan Hughesconst VERSION = '3.42.0';
104e419217SAlan Hughes
11c52e83e3SKudo Chien// TODO: Only tests successful cases, needs to test error cases like bad database name etc.
12c52e83e3SKudo Chienexport function test(t) {
13c52e83e3SKudo Chien  t.describe('SQLite', () => {
14c52e83e3SKudo Chien    t.it('should be able to drop + create a table, insert, query', async () => {
15c52e83e3SKudo Chien      const db = SQLite.openDatabase('test.db');
16c52e83e3SKudo Chien      await new Promise((resolve, reject) => {
17c52e83e3SKudo Chien        db.transaction(
18c52e83e3SKudo Chien          (tx) => {
19c52e83e3SKudo Chien            const nop = () => {};
20c52e83e3SKudo Chien            const onError = (tx, error) => {
21c52e83e3SKudo Chien              reject(error);
22c52e83e3SKudo Chien              return false;
23c52e83e3SKudo Chien            };
24c52e83e3SKudo Chien
25c52e83e3SKudo Chien            tx.executeSql('DROP TABLE IF EXISTS Users;', [], nop, onError);
26c52e83e3SKudo Chien            tx.executeSql(
27c52e83e3SKudo Chien              'CREATE TABLE IF NOT EXISTS Users (user_id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64), k INT, j REAL);',
28c52e83e3SKudo Chien              [],
29c52e83e3SKudo Chien              nop,
30c52e83e3SKudo Chien              onError
31c52e83e3SKudo Chien            );
32c52e83e3SKudo Chien            tx.executeSql(
33c52e83e3SKudo Chien              'INSERT INTO Users (name, k, j) VALUES (?, ?, ?)',
34c52e83e3SKudo Chien              ['Tim Duncan', 1, 23.4],
35c52e83e3SKudo Chien              nop,
36c52e83e3SKudo Chien              onError
37c52e83e3SKudo Chien            );
38c52e83e3SKudo Chien            tx.executeSql(
39c52e83e3SKudo Chien              'INSERT INTO Users (name, k, j) VALUES ("Manu Ginobili", 5, 72.8)',
40c52e83e3SKudo Chien              [],
41c52e83e3SKudo Chien              nop,
42c52e83e3SKudo Chien              onError
43c52e83e3SKudo Chien            );
44c52e83e3SKudo Chien            tx.executeSql(
45c52e83e3SKudo Chien              'INSERT INTO Users (name, k, j) VALUES ("Nikhilesh Sigatapu", 7, 42.14)',
46c52e83e3SKudo Chien              [],
47c52e83e3SKudo Chien              nop,
48c52e83e3SKudo Chien              onError
49c52e83e3SKudo Chien            );
50c52e83e3SKudo Chien
51c52e83e3SKudo Chien            tx.executeSql(
52c52e83e3SKudo Chien              'SELECT * FROM Users',
53c52e83e3SKudo Chien              [],
54c52e83e3SKudo Chien              (tx, results) => {
55c52e83e3SKudo Chien                t.expect(results.rows.length).toEqual(3);
56c52e83e3SKudo Chien                t.expect(results.rows.item(0).j).toBeCloseTo(23.4);
57c52e83e3SKudo Chien              },
58c52e83e3SKudo Chien              onError
59c52e83e3SKudo Chien            );
60c52e83e3SKudo Chien          },
61c52e83e3SKudo Chien          reject,
62c52e83e3SKudo Chien          () => {
63c52e83e3SKudo Chien            resolve(null);
64c52e83e3SKudo Chien          }
65c52e83e3SKudo Chien        );
66c52e83e3SKudo Chien      });
67c52e83e3SKudo Chien
68c52e83e3SKudo Chien      if (Platform.OS !== 'web') {
69c52e83e3SKudo Chien        const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
70c52e83e3SKudo Chien        t.expect(exists).toBeTruthy();
71c52e83e3SKudo Chien      }
72c52e83e3SKudo Chien    });
73c52e83e3SKudo Chien
744e419217SAlan Hughes    t.it(`should use specified SQLite version: ${VERSION}`, () => {
754e419217SAlan Hughes      const db = SQLite.openDatabase('test.db');
764e419217SAlan Hughes
774e419217SAlan Hughes      db.transaction((tx) => {
784e419217SAlan Hughes        tx.executeSql('SELECT sqlite_version()', [], (_, results) => {
794e419217SAlan Hughes          const queryVersion = results.rows._array[0]['sqlite_version()'];
804e419217SAlan Hughes          t.expect(queryVersion).toEqual(VERSION);
814e419217SAlan Hughes        });
824e419217SAlan Hughes      });
834e419217SAlan Hughes    });
844e419217SAlan Hughes
854e419217SAlan Hughes    t.it(`unixepoch() is supported`, () => {
864e419217SAlan Hughes      const db = SQLite.openDatabase('test.db');
874e419217SAlan Hughes
884e419217SAlan Hughes      db.transaction((tx) => {
894e419217SAlan Hughes        tx.executeSql('SELECT unixepoch()', [], (_, results) => {
904e419217SAlan Hughes          const epoch = results.rows._array[0]['unixepoch()'];
914e419217SAlan Hughes          t.expect(epoch).toBeTruthy();
924e419217SAlan Hughes        });
934e419217SAlan Hughes      });
944e419217SAlan Hughes    });
954e419217SAlan Hughes
96c52e83e3SKudo Chien    if (Platform.OS !== 'web') {
97c52e83e3SKudo Chien      t.it(
98c52e83e3SKudo Chien        'should work with a downloaded .db file',
99c52e83e3SKudo Chien        async () => {
100c52e83e3SKudo Chien          await FS.downloadAsync(
101c52e83e3SKudo Chien            Asset.fromModule(require('../assets/asset-db.db')).uri,
102c52e83e3SKudo Chien            `${FS.documentDirectory}SQLite/downloaded.db`
103c52e83e3SKudo Chien          );
104c52e83e3SKudo Chien
105c52e83e3SKudo Chien          const db = SQLite.openDatabase('downloaded.db');
106c52e83e3SKudo Chien          await new Promise((resolve, reject) => {
107c52e83e3SKudo Chien            db.transaction(
108c52e83e3SKudo Chien              (tx) => {
109c52e83e3SKudo Chien                const onError = (tx, error) => {
110c52e83e3SKudo Chien                  reject(error);
111c52e83e3SKudo Chien                  return false;
112c52e83e3SKudo Chien                };
113c52e83e3SKudo Chien                tx.executeSql(
114c52e83e3SKudo Chien                  'SELECT * FROM Users',
115c52e83e3SKudo Chien                  [],
116c52e83e3SKudo Chien                  (tx, results) => {
117c52e83e3SKudo Chien                    t.expect(results.rows.length).toEqual(3);
118c52e83e3SKudo Chien                    t.expect(results.rows._array[0].j).toBeCloseTo(23.4);
119c52e83e3SKudo Chien                  },
120c52e83e3SKudo Chien                  onError
121c52e83e3SKudo Chien                );
122c52e83e3SKudo Chien              },
123c52e83e3SKudo Chien              reject,
124c52e83e3SKudo Chien              () => {
125c52e83e3SKudo Chien                resolve(null);
126c52e83e3SKudo Chien              }
127c52e83e3SKudo Chien            );
128c52e83e3SKudo Chien          });
129c52e83e3SKudo Chien          db.closeAsync();
130c52e83e3SKudo Chien        },
131c52e83e3SKudo Chien        30000
132c52e83e3SKudo Chien      );
133c52e83e3SKudo Chien    }
134c52e83e3SKudo Chien
135c52e83e3SKudo Chien    t.it('should be able to recreate db from scratch by deleting file', async () => {
136c52e83e3SKudo Chien      {
137c52e83e3SKudo Chien        const db = SQLite.openDatabase('test.db');
138c52e83e3SKudo Chien        await new Promise((resolve, reject) => {
139c52e83e3SKudo Chien          db.transaction(
140c52e83e3SKudo Chien            (tx) => {
141c52e83e3SKudo Chien              const nop = () => {};
142c52e83e3SKudo Chien              const onError = (tx, error) => {
143c52e83e3SKudo Chien                reject(error);
144c52e83e3SKudo Chien                return false;
145c52e83e3SKudo Chien              };
146c52e83e3SKudo Chien
147c52e83e3SKudo Chien              tx.executeSql('DROP TABLE IF EXISTS Users;', [], nop, onError);
148c52e83e3SKudo Chien              tx.executeSql(
149c52e83e3SKudo Chien                'CREATE TABLE IF NOT EXISTS Users (user_id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64), k INT, j REAL);',
150c52e83e3SKudo Chien                [],
151c52e83e3SKudo Chien                nop,
152c52e83e3SKudo Chien                onError
153c52e83e3SKudo Chien              );
154c52e83e3SKudo Chien              tx.executeSql(
155c52e83e3SKudo Chien                'INSERT INTO Users (name, k, j) VALUES (?, ?, ?)',
156c52e83e3SKudo Chien                ['Tim Duncan', 1, 23.4],
157c52e83e3SKudo Chien                nop,
158c52e83e3SKudo Chien                onError
159c52e83e3SKudo Chien              );
160c52e83e3SKudo Chien
161c52e83e3SKudo Chien              tx.executeSql(
162c52e83e3SKudo Chien                'SELECT * FROM Users',
163c52e83e3SKudo Chien                [],
164c52e83e3SKudo Chien                (tx, results) => {
165c52e83e3SKudo Chien                  t.expect(results.rows.length).toEqual(1);
166c52e83e3SKudo Chien                },
167c52e83e3SKudo Chien                onError
168c52e83e3SKudo Chien              );
169c52e83e3SKudo Chien            },
170c52e83e3SKudo Chien            reject,
171c52e83e3SKudo Chien            () => {
172c52e83e3SKudo Chien              resolve(null);
173c52e83e3SKudo Chien            }
174c52e83e3SKudo Chien          );
175c52e83e3SKudo Chien        });
176c52e83e3SKudo Chien      }
177c52e83e3SKudo Chien
178c52e83e3SKudo Chien      if (Platform.OS !== 'web') {
179c52e83e3SKudo Chien        const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
180c52e83e3SKudo Chien        t.expect(exists).toBeTruthy();
181c52e83e3SKudo Chien      }
182c52e83e3SKudo Chien
183c52e83e3SKudo Chien      if (Platform.OS !== 'web') {
184c52e83e3SKudo Chien        await FS.deleteAsync(`${FS.documentDirectory}SQLite/test.db`);
185c52e83e3SKudo Chien        const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
186c52e83e3SKudo Chien        t.expect(exists).toBeFalsy();
187c52e83e3SKudo Chien      }
188c52e83e3SKudo Chien
189c52e83e3SKudo Chien      {
190c52e83e3SKudo Chien        const db = SQLite.openDatabase('test.db');
191c52e83e3SKudo Chien        await new Promise((resolve, reject) => {
192c52e83e3SKudo Chien          db.transaction(
193c52e83e3SKudo Chien            (tx) => {
194c52e83e3SKudo Chien              const nop = () => {};
195c52e83e3SKudo Chien              const onError = (tx, error) => {
196c52e83e3SKudo Chien                reject(error);
197c52e83e3SKudo Chien                return false;
198c52e83e3SKudo Chien              };
199c52e83e3SKudo Chien
200c52e83e3SKudo Chien              tx.executeSql('DROP TABLE IF EXISTS Users;', [], nop, onError);
201c52e83e3SKudo Chien              tx.executeSql(
202c52e83e3SKudo Chien                'CREATE TABLE IF NOT EXISTS Users (user_id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64), k INT, j REAL);',
203c52e83e3SKudo Chien                [],
204c52e83e3SKudo Chien                nop,
205c52e83e3SKudo Chien                onError
206c52e83e3SKudo Chien              );
207c52e83e3SKudo Chien              tx.executeSql(
208c52e83e3SKudo Chien                'SELECT * FROM Users',
209c52e83e3SKudo Chien                [],
210c52e83e3SKudo Chien                (tx, results) => {
211c52e83e3SKudo Chien                  t.expect(results.rows.length).toEqual(0);
212c52e83e3SKudo Chien                },
213c52e83e3SKudo Chien                onError
214c52e83e3SKudo Chien              );
215c52e83e3SKudo Chien
216c52e83e3SKudo Chien              tx.executeSql(
217c52e83e3SKudo Chien                'INSERT INTO Users (name, k, j) VALUES (?, ?, ?)',
218c52e83e3SKudo Chien                ['Tim Duncan', 1, 23.4],
219c52e83e3SKudo Chien                nop,
220c52e83e3SKudo Chien                onError
221c52e83e3SKudo Chien              );
222c52e83e3SKudo Chien              tx.executeSql(
223c52e83e3SKudo Chien                'SELECT * FROM Users',
224c52e83e3SKudo Chien                [],
225c52e83e3SKudo Chien                (tx, results) => {
226c52e83e3SKudo Chien                  t.expect(results.rows.length).toEqual(1);
227c52e83e3SKudo Chien                },
228c52e83e3SKudo Chien                onError
229c52e83e3SKudo Chien              );
230c52e83e3SKudo Chien            },
231c52e83e3SKudo Chien            reject,
232c52e83e3SKudo Chien            () => {
233c52e83e3SKudo Chien              resolve(null);
234c52e83e3SKudo Chien            }
235c52e83e3SKudo Chien          );
236c52e83e3SKudo Chien        });
237c52e83e3SKudo Chien      }
238c52e83e3SKudo Chien    });
239c52e83e3SKudo Chien
240c52e83e3SKudo Chien    t.it('should maintain correct type of potentialy null bind parameters', async () => {
241c52e83e3SKudo Chien      const db = SQLite.openDatabase('test.db');
242c52e83e3SKudo Chien      await new Promise((resolve, reject) => {
243c52e83e3SKudo Chien        db.transaction(
244c52e83e3SKudo Chien          (tx) => {
245c52e83e3SKudo Chien            const nop = () => {};
246c52e83e3SKudo Chien            const onError = (tx, error) => {
247c52e83e3SKudo Chien              reject(error);
248c52e83e3SKudo Chien              return false;
249c52e83e3SKudo Chien            };
250c52e83e3SKudo Chien
251c52e83e3SKudo Chien            tx.executeSql('DROP TABLE IF EXISTS Nulling;', [], nop, onError);
252c52e83e3SKudo Chien            tx.executeSql(
253c52e83e3SKudo Chien              'CREATE TABLE IF NOT EXISTS Nulling (id INTEGER PRIMARY KEY NOT NULL, x NUMERIC, y NUMERIC)',
254c52e83e3SKudo Chien              [],
255c52e83e3SKudo Chien              nop,
256c52e83e3SKudo Chien              onError
257c52e83e3SKudo Chien            );
258c52e83e3SKudo Chien            tx.executeSql('INSERT INTO Nulling (x, y) VALUES (?, ?)', [null, null], nop, onError);
259c52e83e3SKudo Chien            tx.executeSql('INSERT INTO Nulling (x, y) VALUES (null, null)', [], nop, onError);
260c52e83e3SKudo Chien
261c52e83e3SKudo Chien            tx.executeSql(
262c52e83e3SKudo Chien              'SELECT * FROM Nulling',
263c52e83e3SKudo Chien              [],
264c52e83e3SKudo Chien              (tx, results) => {
265c52e83e3SKudo Chien                t.expect(results.rows.item(0).x).toBeNull();
266c52e83e3SKudo Chien                t.expect(results.rows.item(0).y).toBeNull();
267c52e83e3SKudo Chien                t.expect(results.rows.item(1).x).toBeNull();
268c52e83e3SKudo Chien                t.expect(results.rows.item(1).y).toBeNull();
269c52e83e3SKudo Chien              },
270c52e83e3SKudo Chien              onError
271c52e83e3SKudo Chien            );
272c52e83e3SKudo Chien          },
273c52e83e3SKudo Chien          reject,
274c52e83e3SKudo Chien          () => {
275c52e83e3SKudo Chien            resolve(null);
276c52e83e3SKudo Chien          }
277c52e83e3SKudo Chien        );
278c52e83e3SKudo Chien      });
279c52e83e3SKudo Chien
280c52e83e3SKudo Chien      if (Platform.OS !== 'web') {
281c52e83e3SKudo Chien        const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
282c52e83e3SKudo Chien        t.expect(exists).toBeTruthy();
283c52e83e3SKudo Chien      }
284c52e83e3SKudo Chien    });
285c52e83e3SKudo Chien
286c52e83e3SKudo Chien    // Do not try to test PRAGMA statements support in web
287c52e83e3SKudo Chien    // as it is expected to not be working.
288c52e83e3SKudo Chien    // See https://stackoverflow.com/a/10298712
289c52e83e3SKudo Chien    if (Platform.OS !== 'web') {
290c52e83e3SKudo Chien      t.it('should support PRAGMA statements', async () => {
291c52e83e3SKudo Chien        const db = SQLite.openDatabase('test.db');
292c52e83e3SKudo Chien        await new Promise((resolve, reject) => {
293c52e83e3SKudo Chien          db.transaction(
294c52e83e3SKudo Chien            (tx) => {
295c52e83e3SKudo Chien              const nop = () => {};
296c52e83e3SKudo Chien              const onError = (tx, error) => {
297c52e83e3SKudo Chien                reject(error);
298c52e83e3SKudo Chien                return false;
299c52e83e3SKudo Chien              };
300c52e83e3SKudo Chien
301c52e83e3SKudo Chien              tx.executeSql('DROP TABLE IF EXISTS SomeTable;', [], nop, onError);
302c52e83e3SKudo Chien              tx.executeSql(
303c52e83e3SKudo Chien                'CREATE TABLE IF NOT EXISTS SomeTable (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));',
304c52e83e3SKudo Chien                [],
305c52e83e3SKudo Chien                nop,
306c52e83e3SKudo Chien                onError
307c52e83e3SKudo Chien              );
308c52e83e3SKudo Chien              // a result-returning pragma
309c52e83e3SKudo Chien              tx.executeSql(
310c52e83e3SKudo Chien                'PRAGMA table_info(SomeTable);',
311c52e83e3SKudo Chien                [],
312c52e83e3SKudo Chien                (tx, results) => {
313c52e83e3SKudo Chien                  t.expect(results.rows.length).toEqual(2);
314c52e83e3SKudo Chien                  t.expect(results.rows.item(0).name).toEqual('id');
315c52e83e3SKudo Chien                  t.expect(results.rows.item(1).name).toEqual('name');
316c52e83e3SKudo Chien                },
317c52e83e3SKudo Chien                onError
318c52e83e3SKudo Chien              );
319c52e83e3SKudo Chien              // a no-result pragma
320c52e83e3SKudo Chien              tx.executeSql('PRAGMA case_sensitive_like = true;', [], nop, onError);
321c52e83e3SKudo Chien              // a setter/getter pragma
322c52e83e3SKudo Chien              tx.executeSql('PRAGMA user_version = 123;', [], nop, onError);
323c52e83e3SKudo Chien              tx.executeSql(
324c52e83e3SKudo Chien                'PRAGMA user_version;',
325c52e83e3SKudo Chien                [],
326c52e83e3SKudo Chien                (tx, results) => {
327c52e83e3SKudo Chien                  t.expect(results.rows.length).toEqual(1);
328c52e83e3SKudo Chien                  t.expect(results.rows.item(0).user_version).toEqual(123);
329c52e83e3SKudo Chien                },
330c52e83e3SKudo Chien                onError
331c52e83e3SKudo Chien              );
332c52e83e3SKudo Chien            },
333c52e83e3SKudo Chien            reject,
334c52e83e3SKudo Chien            () => {
335c52e83e3SKudo Chien              resolve(null);
336c52e83e3SKudo Chien            }
337c52e83e3SKudo Chien          );
338c52e83e3SKudo Chien        });
339c52e83e3SKudo Chien      });
340c52e83e3SKudo Chien    }
341c52e83e3SKudo Chien
34250e16f8cSAlan Hughes    t.it('should support the `RETURNING` clause using raw queries', async () => {
34350e16f8cSAlan Hughes      const db = SQLite.openDatabase('test.db');
34450e16f8cSAlan Hughes      await new Promise((resolve, reject) => {
34550e16f8cSAlan Hughes        db.transaction(
34650e16f8cSAlan Hughes          (tx) => {
34750e16f8cSAlan Hughes            const nop = () => {};
34850e16f8cSAlan Hughes            const onError = (_, error) => {
34950e16f8cSAlan Hughes              reject(error);
35050e16f8cSAlan Hughes              return false;
35150e16f8cSAlan Hughes            };
35250e16f8cSAlan Hughes
35350e16f8cSAlan Hughes            tx.executeSql('DROP TABLE IF EXISTS customers;', [], nop, onError);
35450e16f8cSAlan Hughes            tx.executeSql(
35550e16f8cSAlan Hughes              'CREATE TABLE customers (id PRIMARY KEY NOT NULL, name VARCHAR(255),email VARCHAR(255));',
35650e16f8cSAlan Hughes              [],
35750e16f8cSAlan Hughes              nop,
35850e16f8cSAlan Hughes              onError
35950e16f8cSAlan Hughes            );
36050e16f8cSAlan Hughes          },
36150e16f8cSAlan Hughes          reject,
36250e16f8cSAlan Hughes          () => {
36350e16f8cSAlan Hughes            resolve(null);
36450e16f8cSAlan Hughes          }
36550e16f8cSAlan Hughes        );
36650e16f8cSAlan Hughes      });
36750e16f8cSAlan Hughes
36850e16f8cSAlan Hughes      db.execRawQuery(
36950e16f8cSAlan Hughes        [
37050e16f8cSAlan Hughes          {
37150e16f8cSAlan Hughes            // Unsupprted on Android using the `exec` function
37250e16f8cSAlan Hughes            sql: "INSERT INTO customers (id, name, email) VALUES (1, 'John Doe', '[email protected]') RETURNING name, email;",
37350e16f8cSAlan Hughes            args: [],
37450e16f8cSAlan Hughes          },
37550e16f8cSAlan Hughes        ],
37650e16f8cSAlan Hughes        false,
37750e16f8cSAlan Hughes        (tx, results) => {
37850e16f8cSAlan Hughes          // @ts-expect-error
37950e16f8cSAlan Hughes          t.expect(results.rows[0].email).toBe('[email protected]');
38050e16f8cSAlan Hughes          // @ts-expect-error
38150e16f8cSAlan Hughes          t.expect(results.rows[0].name).toBe('John Doe');
38250e16f8cSAlan Hughes        }
38350e16f8cSAlan Hughes      );
38450e16f8cSAlan Hughes
38550e16f8cSAlan Hughes      db.execRawQuery(
38650e16f8cSAlan Hughes        [
38750e16f8cSAlan Hughes          {
38850e16f8cSAlan Hughes            sql: "UPDATE customers SET name='Jane Doe', email='[email protected]' WHERE id=1 RETURNING name, email;",
38950e16f8cSAlan Hughes            args: [],
39050e16f8cSAlan Hughes          },
39150e16f8cSAlan Hughes        ],
39250e16f8cSAlan Hughes        false,
39350e16f8cSAlan Hughes        (tx, results) => {
39450e16f8cSAlan Hughes          // @ts-expect-error
39550e16f8cSAlan Hughes          t.expect(results.rows[0].email).toBe('[email protected]');
39650e16f8cSAlan Hughes          // @ts-expect-error
39750e16f8cSAlan Hughes          t.expect(results.rows[0].name).toBe('Jane Doe');
39850e16f8cSAlan Hughes        }
39950e16f8cSAlan Hughes      );
40050e16f8cSAlan Hughes
40150e16f8cSAlan Hughes      db.execRawQuery(
40250e16f8cSAlan Hughes        [
40350e16f8cSAlan Hughes          {
40450e16f8cSAlan Hughes            // Unsupprted on Android using the `exec` function
40550e16f8cSAlan Hughes            sql: 'DELETE from customers WHERE id=1 RETURNING name, email;',
40650e16f8cSAlan Hughes            args: [],
40750e16f8cSAlan Hughes          },
40850e16f8cSAlan Hughes        ],
40950e16f8cSAlan Hughes        false,
41050e16f8cSAlan Hughes        (tx, results) => {
41150e16f8cSAlan Hughes          // @ts-expect-error
41250e16f8cSAlan Hughes          t.expect(results.rows[0].email).toBe('[email protected]');
41350e16f8cSAlan Hughes          // @ts-expect-error
41450e16f8cSAlan Hughes          t.expect(results.rows[0].name).toBe('Jane Doe');
41550e16f8cSAlan Hughes        }
41650e16f8cSAlan Hughes      );
41750e16f8cSAlan Hughes    });
41850e16f8cSAlan Hughes
419c52e83e3SKudo Chien    t.it('should return correct rowsAffected value', async () => {
420c52e83e3SKudo Chien      const db = SQLite.openDatabase('test.db');
421c52e83e3SKudo Chien      await new Promise((resolve, reject) => {
422c52e83e3SKudo Chien        db.transaction(
423c52e83e3SKudo Chien          (tx) => {
424c52e83e3SKudo Chien            const nop = () => {};
425c52e83e3SKudo Chien            const onError = (tx, error) => {
426c52e83e3SKudo Chien              reject(error);
427c52e83e3SKudo Chien              return false;
428c52e83e3SKudo Chien            };
429c52e83e3SKudo Chien
430c52e83e3SKudo Chien            tx.executeSql('DROP TABLE IF EXISTS Users;', [], nop, onError);
431c52e83e3SKudo Chien            tx.executeSql(
432c52e83e3SKudo Chien              'CREATE TABLE IF NOT EXISTS Users (user_id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));',
433c52e83e3SKudo Chien              [],
434c52e83e3SKudo Chien              nop,
435c52e83e3SKudo Chien              onError
436c52e83e3SKudo Chien            );
437c52e83e3SKudo Chien            tx.executeSql(
438c52e83e3SKudo Chien              'INSERT INTO Users (name) VALUES (?), (?), (?)',
439c52e83e3SKudo Chien              ['name1', 'name2', 'name3'],
440c52e83e3SKudo Chien              nop,
441c52e83e3SKudo Chien              onError
442c52e83e3SKudo Chien            );
443c52e83e3SKudo Chien          },
444c52e83e3SKudo Chien          reject,
445c52e83e3SKudo Chien          () => {
446c52e83e3SKudo Chien            resolve(null);
447c52e83e3SKudo Chien          }
448c52e83e3SKudo Chien        );
449c52e83e3SKudo Chien      });
450c52e83e3SKudo Chien      await new Promise((resolve, reject) => {
451c52e83e3SKudo Chien        db.transaction(
452c52e83e3SKudo Chien          (tx) => {
453c52e83e3SKudo Chien            const onError = (tx, error) => {
454c52e83e3SKudo Chien              reject(error);
455c52e83e3SKudo Chien              return false;
456c52e83e3SKudo Chien            };
457c52e83e3SKudo Chien            tx.executeSql(
458c52e83e3SKudo Chien              'DELETE FROM Users WHERE name=?',
459c52e83e3SKudo Chien              ['name1'],
460c52e83e3SKudo Chien              (tx, results) => {
461c52e83e3SKudo Chien                t.expect(results.rowsAffected).toEqual(1);
462c52e83e3SKudo Chien              },
463c52e83e3SKudo Chien              onError
464c52e83e3SKudo Chien            );
465c52e83e3SKudo Chien            tx.executeSql(
466c52e83e3SKudo Chien              'DELETE FROM Users WHERE name=? OR name=?',
467c52e83e3SKudo Chien              ['name2', 'name3'],
468c52e83e3SKudo Chien              (tx, results) => {
469c52e83e3SKudo Chien                t.expect(results.rowsAffected).toEqual(2);
470c52e83e3SKudo Chien              },
471c52e83e3SKudo Chien              onError
472c52e83e3SKudo Chien            );
473c52e83e3SKudo Chien            tx.executeSql(
474c52e83e3SKudo Chien              // ensure deletion succeedeed
475c52e83e3SKudo Chien              'SELECT * from Users',
476c52e83e3SKudo Chien              [],
477c52e83e3SKudo Chien              (tx, results) => {
478c52e83e3SKudo Chien                t.expect(results.rows.length).toEqual(0);
479c52e83e3SKudo Chien              },
480c52e83e3SKudo Chien              onError
481c52e83e3SKudo Chien            );
482c52e83e3SKudo Chien          },
483c52e83e3SKudo Chien          reject,
484c52e83e3SKudo Chien          () => {
485c52e83e3SKudo Chien            resolve(null);
486c52e83e3SKudo Chien          }
487c52e83e3SKudo Chien        );
488c52e83e3SKudo Chien      });
489c52e83e3SKudo Chien    });
490c52e83e3SKudo Chien
491c52e83e3SKudo Chien    if (Platform.OS !== 'web') {
492c52e83e3SKudo Chien      // It is not expected to work on web, since we cannot execute PRAGMA to enable foreign keys support
493c52e83e3SKudo Chien      t.it('should return correct rowsAffected value when deleting cascade', async () => {
494c52e83e3SKudo Chien        const db = SQLite.openDatabase('test.db');
495c52e83e3SKudo Chien        db.exec([{ sql: 'PRAGMA foreign_keys = ON;', args: [] }], false, () => {});
496c52e83e3SKudo Chien        await new Promise((resolve, reject) => {
497c52e83e3SKudo Chien          db.transaction(
498c52e83e3SKudo Chien            (tx) => {
499c52e83e3SKudo Chien              const nop = () => {};
500c52e83e3SKudo Chien              const onError = (tx, error) => {
501c52e83e3SKudo Chien                reject(error);
502c52e83e3SKudo Chien                return false;
503c52e83e3SKudo Chien              };
504c52e83e3SKudo Chien
505c52e83e3SKudo Chien              tx.executeSql('DROP TABLE IF EXISTS Users;', [], nop, onError);
506c52e83e3SKudo Chien              tx.executeSql('DROP TABLE IF EXISTS Posts;', [], nop, onError);
507c52e83e3SKudo Chien              tx.executeSql(
508c52e83e3SKudo Chien                'CREATE TABLE IF NOT EXISTS Users (user_id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));',
509c52e83e3SKudo Chien                [],
510c52e83e3SKudo Chien                nop,
511c52e83e3SKudo Chien                onError
512c52e83e3SKudo Chien              );
513c52e83e3SKudo Chien              tx.executeSql(
514c52e83e3SKudo Chien                '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);',
515c52e83e3SKudo Chien                [],
516c52e83e3SKudo Chien                nop,
517c52e83e3SKudo Chien                onError
518c52e83e3SKudo Chien              );
519c52e83e3SKudo Chien              tx.executeSql(
520c52e83e3SKudo Chien                'INSERT INTO Users (name) VALUES (?), (?), (?)',
521c52e83e3SKudo Chien                ['name1', 'name2', 'name3'],
522c52e83e3SKudo Chien                nop,
523c52e83e3SKudo Chien                onError
524c52e83e3SKudo Chien              );
525c52e83e3SKudo Chien
526c52e83e3SKudo Chien              tx.executeSql(
527c52e83e3SKudo Chien                'INSERT INTO Posts (content, userposted) VALUES (?, ?), (?, ?), (?, ?)',
528c52e83e3SKudo Chien                ['post1', 1, 'post2', 1, 'post3', 2],
529c52e83e3SKudo Chien                nop,
530c52e83e3SKudo Chien                onError
531c52e83e3SKudo Chien              );
532c52e83e3SKudo Chien              tx.executeSql('PRAGMA foreign_keys=off;', [], nop, onError);
533c52e83e3SKudo Chien            },
534c52e83e3SKudo Chien            reject,
535c52e83e3SKudo Chien            () => {
536c52e83e3SKudo Chien              resolve(null);
537c52e83e3SKudo Chien            }
538c52e83e3SKudo Chien          );
539c52e83e3SKudo Chien        });
540c52e83e3SKudo Chien        await new Promise((resolve, reject) => {
541c52e83e3SKudo Chien          db.transaction(
542c52e83e3SKudo Chien            (tx) => {
543c52e83e3SKudo Chien              const nop = () => {};
544c52e83e3SKudo Chien              const onError = (tx, error) => {
545c52e83e3SKudo Chien                reject(error);
546c52e83e3SKudo Chien                return false;
547c52e83e3SKudo Chien              };
548c52e83e3SKudo Chien              tx.executeSql('PRAGMA foreign_keys=on;', [], nop, onError);
549c52e83e3SKudo Chien              tx.executeSql(
550c52e83e3SKudo Chien                'DELETE FROM Users WHERE name=?',
551c52e83e3SKudo Chien                ['name1'],
552c52e83e3SKudo Chien                (tx, results) => {
553c52e83e3SKudo Chien                  t.expect(results.rowsAffected).toEqual(1);
554c52e83e3SKudo Chien                },
555c52e83e3SKudo Chien                onError
556c52e83e3SKudo Chien              );
557c52e83e3SKudo Chien              tx.executeSql(
558c52e83e3SKudo Chien                'DELETE FROM Users WHERE name=? OR name=?',
559c52e83e3SKudo Chien                ['name2', 'name3'],
560c52e83e3SKudo Chien                (tx, results) => {
561c52e83e3SKudo Chien                  t.expect(results.rowsAffected).toEqual(2);
562c52e83e3SKudo Chien                },
563c52e83e3SKudo Chien                onError
564c52e83e3SKudo Chien              );
565c52e83e3SKudo Chien
566c52e83e3SKudo Chien              tx.executeSql(
567c52e83e3SKudo Chien                // ensure deletion succeeded
568c52e83e3SKudo Chien                'SELECT * from Users',
569c52e83e3SKudo Chien                [],
570c52e83e3SKudo Chien                (tx, results) => {
571c52e83e3SKudo Chien                  t.expect(results.rows.length).toEqual(0);
572c52e83e3SKudo Chien                },
573c52e83e3SKudo Chien                onError
574c52e83e3SKudo Chien              );
575c52e83e3SKudo Chien
576c52e83e3SKudo Chien              tx.executeSql(
577c52e83e3SKudo Chien                'SELECT * from Posts',
578c52e83e3SKudo Chien                [],
579c52e83e3SKudo Chien                (tx, results) => {
580c52e83e3SKudo Chien                  t.expect(results.rows.length).toEqual(0);
581c52e83e3SKudo Chien                },
582c52e83e3SKudo Chien                onError
583c52e83e3SKudo Chien              );
584c52e83e3SKudo Chien              tx.executeSql('PRAGMA foreign_keys=off;', [], nop, onError);
585c52e83e3SKudo Chien            },
586c52e83e3SKudo Chien            reject,
587c52e83e3SKudo Chien            () => {
588c52e83e3SKudo Chien              resolve(null);
589c52e83e3SKudo Chien            }
590c52e83e3SKudo Chien          );
591c52e83e3SKudo Chien        });
592c52e83e3SKudo Chien      });
593c52e83e3SKudo Chien    }
594c52e83e3SKudo Chien
595c52e83e3SKudo Chien    if (Platform.OS !== 'web') {
596c52e83e3SKudo Chien      t.it('should delete db on filesystem from the `deleteAsync()` call', async () => {
597c52e83e3SKudo Chien        const db = SQLite.openDatabase('test.db');
598c52e83e3SKudo Chien        let fileInfo = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
599c52e83e3SKudo Chien        t.expect(fileInfo.exists).toBeTruthy();
600c52e83e3SKudo Chien
601c52e83e3SKudo Chien        await db.closeAsync();
602c52e83e3SKudo Chien        await db.deleteAsync();
603c52e83e3SKudo Chien        fileInfo = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
604c52e83e3SKudo Chien        t.expect(fileInfo.exists).toBeFalsy();
605c52e83e3SKudo Chien      });
606c52e83e3SKudo Chien    }
607c52e83e3SKudo Chien  });
608c52e83e3SKudo Chien
609c52e83e3SKudo Chien  if (Platform.OS !== 'web') {
610c52e83e3SKudo Chien    t.describe('SQLiteAsync', () => {
611c52e83e3SKudo Chien      const throws = async (run) => {
612c52e83e3SKudo Chien        let error = null;
613c52e83e3SKudo Chien        try {
614c52e83e3SKudo Chien          await run();
615c52e83e3SKudo Chien        } catch (e) {
616c52e83e3SKudo Chien          error = e;
617c52e83e3SKudo Chien        }
618c52e83e3SKudo Chien        t.expect(error).toBeTruthy();
619c52e83e3SKudo Chien      };
620c52e83e3SKudo Chien
621c52e83e3SKudo Chien      t.it('should support async transaction', async () => {
622c52e83e3SKudo Chien        const db = SQLite.openDatabase('test.db');
623c52e83e3SKudo Chien
624c52e83e3SKudo Chien        // create table
625c52e83e3SKudo Chien        await db.transactionAsync(async (tx) => {
626c52e83e3SKudo Chien          await tx.executeSqlAsync('DROP TABLE IF EXISTS Users;', []);
627c52e83e3SKudo Chien          await tx.executeSqlAsync(
628c52e83e3SKudo Chien            'CREATE TABLE IF NOT EXISTS Users (user_id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));',
629c52e83e3SKudo Chien            []
630c52e83e3SKudo Chien          );
631c52e83e3SKudo Chien        });
632c52e83e3SKudo Chien
633c52e83e3SKudo Chien        // fetch data from network
634c52e83e3SKudo Chien        async function fakeUserFetcher(userID) {
635c52e83e3SKudo Chien          switch (userID) {
636c52e83e3SKudo Chien            case 1: {
637c52e83e3SKudo Chien              return Promise.resolve('Tim Duncan');
638c52e83e3SKudo Chien            }
639c52e83e3SKudo Chien            case 2: {
640c52e83e3SKudo Chien              return Promise.resolve('Manu Ginobili');
641c52e83e3SKudo Chien            }
642c52e83e3SKudo Chien            case 3: {
643c52e83e3SKudo Chien              return Promise.resolve('Nikhilesh Sigatapu');
644c52e83e3SKudo Chien            }
645c52e83e3SKudo Chien            default: {
646c52e83e3SKudo Chien              return null;
647c52e83e3SKudo Chien            }
648c52e83e3SKudo Chien          }
649c52e83e3SKudo Chien        }
650c52e83e3SKudo Chien
651c52e83e3SKudo Chien        const userName = await fakeUserFetcher(1);
652c52e83e3SKudo Chien        await db.transactionAsync(async (tx) => {
653c52e83e3SKudo Chien          await tx.executeSqlAsync('INSERT INTO Users (name) VALUES (?)', [userName]);
654c52e83e3SKudo Chien          const result = await tx.executeSqlAsync('SELECT * FROM Users LIMIT 1');
655c52e83e3SKudo Chien          const currentUser = result.rows[0].name;
656c52e83e3SKudo Chien          t.expect(currentUser).toEqual('Tim Duncan');
657c52e83e3SKudo Chien        });
658c52e83e3SKudo Chien      });
659c52e83e3SKudo Chien
66074e0b8dfSAlan Hughes      t.it('should load crsqlite extension correctly', async () => {
66174e0b8dfSAlan Hughes        const db = SQLite.openDatabase('test.db');
6623273f84bSAlan Hughes        await db.transactionAsync(async (tx) => {
6633273f84bSAlan Hughes          await tx.executeSqlAsync('DROP TABLE IF EXISTS foo;', []);
664*36864ecaSAlan Hughes          await tx.executeSqlAsync('create table foo (a primary key, b INTEGER);', []);
6653273f84bSAlan Hughes          await tx.executeSqlAsync('select crsql_as_crr("foo");', []);
666c4573fffSKudo Chien          await tx.executeSqlAsync('insert into foo (a,b) values (?, ?);', [1, 2]);
667c4573fffSKudo Chien          await tx.executeSqlAsync('insert into foo (a,b) values (?, ?);', [3, 4]);
6683273f84bSAlan Hughes          const result = await tx.executeSqlAsync('select * from crsql_changes;', []);
6693273f84bSAlan Hughes          const table = result.rows[0].table;
6703273f84bSAlan Hughes          const value = result.rows[0].val;
6713273f84bSAlan Hughes          t.expect(table).toEqual('foo');
6723273f84bSAlan Hughes          t.expect(value).toEqual(2);
6733273f84bSAlan Hughes        });
67474e0b8dfSAlan Hughes      });
67574e0b8dfSAlan Hughes
676c52e83e3SKudo Chien      t.it('should support Promise.all', async () => {
677c52e83e3SKudo Chien        const db = SQLite.openDatabase('test.db');
678c52e83e3SKudo Chien
679c52e83e3SKudo Chien        // create table
680c52e83e3SKudo Chien        await db.transactionAsync(async (tx) => {
681c52e83e3SKudo Chien          await tx.executeSqlAsync('DROP TABLE IF EXISTS Users;', []);
682c52e83e3SKudo Chien          await tx.executeSqlAsync(
683c52e83e3SKudo Chien            'CREATE TABLE IF NOT EXISTS Users (user_id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));',
684c52e83e3SKudo Chien            []
685c52e83e3SKudo Chien          );
686c52e83e3SKudo Chien        });
687c52e83e3SKudo Chien
688c52e83e3SKudo Chien        await db.transactionAsync(async (tx) => {
689c52e83e3SKudo Chien          await Promise.all([
690c52e83e3SKudo Chien            tx.executeSqlAsync('INSERT INTO Users (name) VALUES (?)', ['aaa']),
691c52e83e3SKudo Chien            tx.executeSqlAsync('INSERT INTO Users (name) VALUES (?)', ['bbb']),
692c52e83e3SKudo Chien            tx.executeSqlAsync('INSERT INTO Users (name) VALUES (?)', ['ccc']),
693c52e83e3SKudo Chien          ]);
694c52e83e3SKudo Chien
695c52e83e3SKudo Chien          const result = await tx.executeSqlAsync('SELECT COUNT(*) FROM Users');
696c52e83e3SKudo Chien          const recordCount = result.rows[0]['COUNT(*)'];
697c52e83e3SKudo Chien          t.expect(recordCount).toEqual(3);
698c52e83e3SKudo Chien        });
699c52e83e3SKudo Chien      });
700c52e83e3SKudo Chien
701c52e83e3SKudo Chien      t.it(
702c52e83e3SKudo Chien        'should return `could not prepare ...` error when having write statements in readOnly transaction',
703c52e83e3SKudo Chien        async () => {
704c52e83e3SKudo Chien          const db = SQLite.openDatabase('test.db');
705c52e83e3SKudo Chien
706c52e83e3SKudo Chien          // create table in readOnly transaction
707c52e83e3SKudo Chien          await db.transactionAsync(async (tx) => {
708c4573fffSKudo Chien            let error: Error | null = null;
709c4573fffSKudo Chien            try {
710c4573fffSKudo Chien              await tx.executeSqlAsync('DROP TABLE IF EXISTS Users;', []);
711c4573fffSKudo Chien            } catch (e: unknown) {
712c4573fffSKudo Chien              if (e instanceof Error) {
713c4573fffSKudo Chien                error = e;
714c4573fffSKudo Chien              }
715c4573fffSKudo Chien            }
716c4573fffSKudo Chien            t.expect(error).toBeDefined();
717c4573fffSKudo Chien            t.expect(error.message).toContain('could not prepare ');
718c52e83e3SKudo Chien          }, true);
719c52e83e3SKudo Chien        }
720c52e83e3SKudo Chien      );
721c52e83e3SKudo Chien
722c52e83e3SKudo Chien      t.it('should rollback transaction when exception happens inside a transaction', async () => {
723c52e83e3SKudo Chien        const db = SQLite.openDatabase('test.db');
724c52e83e3SKudo Chien
725c52e83e3SKudo Chien        // create table
726c52e83e3SKudo Chien        await db.transactionAsync(async (tx) => {
727c52e83e3SKudo Chien          await tx.executeSqlAsync('DROP TABLE IF EXISTS Users;', []);
728c52e83e3SKudo Chien          await tx.executeSqlAsync(
729c52e83e3SKudo Chien            'CREATE TABLE IF NOT EXISTS Users (user_id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));',
730c52e83e3SKudo Chien            []
731c52e83e3SKudo Chien          );
732c52e83e3SKudo Chien        });
733c52e83e3SKudo Chien        await db.transactionAsync(async (tx) => {
734c52e83e3SKudo Chien          await tx.executeSqlAsync('INSERT INTO Users (name) VALUES (?)', ['aaa']);
735c52e83e3SKudo Chien        });
736c52e83e3SKudo Chien        await db.transactionAsync(async (tx) => {
737c52e83e3SKudo Chien          const result = await tx.executeSqlAsync('SELECT COUNT(*) FROM Users');
738c52e83e3SKudo Chien          const recordCount = result.rows[0]['COUNT(*)'];
739c52e83e3SKudo Chien          t.expect(recordCount).toEqual(1);
740c52e83e3SKudo Chien        }, true);
741c52e83e3SKudo Chien
742c52e83e3SKudo Chien        await throws(() =>
743c52e83e3SKudo Chien          db.transactionAsync(async (tx) => {
744c52e83e3SKudo Chien            await tx.executeSqlAsync('INSERT INTO Users (name) VALUES (?)', ['bbb']);
745c52e83e3SKudo Chien            await tx.executeSqlAsync('INSERT INTO Users (name) VALUES (?)', ['ccc']);
746c52e83e3SKudo Chien            // exeuting invalid sql statement will throw an exception
7474e419217SAlan Hughes            await tx.executeSqlAsync(null);
748c52e83e3SKudo Chien          })
749c52e83e3SKudo Chien        );
750c52e83e3SKudo Chien
751c52e83e3SKudo Chien        await db.transactionAsync(async (tx) => {
752c52e83e3SKudo Chien          const result = await tx.executeSqlAsync('SELECT COUNT(*) FROM Users');
753c52e83e3SKudo Chien          const recordCount = result.rows[0]['COUNT(*)'];
754c52e83e3SKudo Chien          t.expect(recordCount).toEqual(1);
755c52e83e3SKudo Chien        }, true);
756c52e83e3SKudo Chien      });
757c52e83e3SKudo Chien
758c52e83e3SKudo Chien      t.it('should support async PRAGMA statements', async () => {
759c52e83e3SKudo Chien        const db = SQLite.openDatabase('test.db');
760c52e83e3SKudo Chien        await db.transactionAsync(async (tx) => {
761c52e83e3SKudo Chien          await tx.executeSqlAsync('DROP TABLE IF EXISTS SomeTable;', []);
762c52e83e3SKudo Chien          await tx.executeSqlAsync(
763c52e83e3SKudo Chien            'CREATE TABLE IF NOT EXISTS SomeTable (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));',
764c52e83e3SKudo Chien            []
765c52e83e3SKudo Chien          );
766c52e83e3SKudo Chien          // a result-returning pragma
767c52e83e3SKudo Chien          let result = await tx.executeSqlAsync('PRAGMA table_info(SomeTable);', []);
768c52e83e3SKudo Chien          t.expect(result.rows.length).toEqual(2);
769c52e83e3SKudo Chien          t.expect(result.rows[0].name).toEqual('id');
770c52e83e3SKudo Chien          t.expect(result.rows[1].name).toEqual('name');
771c52e83e3SKudo Chien          // a no-result pragma
772c52e83e3SKudo Chien          await tx.executeSqlAsync('PRAGMA case_sensitive_like = true;', []);
773c52e83e3SKudo Chien          // a setter/getter pragma
774c52e83e3SKudo Chien          await tx.executeSqlAsync('PRAGMA user_version = 123;', []);
775c52e83e3SKudo Chien          result = await tx.executeSqlAsync('PRAGMA user_version;', []);
776c52e83e3SKudo Chien          t.expect(result.rows.length).toEqual(1);
777c52e83e3SKudo Chien          t.expect(result.rows[0].user_version).toEqual(123);
778c52e83e3SKudo Chien        });
779c52e83e3SKudo Chien      });
780c52e83e3SKudo Chien    }); // t.describe('SQLiteAsync')
781c52e83e3SKudo Chien  }
782c52e83e3SKudo Chien}
783