xref: /expo/packages/expo-sqlite/build/SQLite.js (revision 4e5f28ee)
1import './polyfillNextTick';
2import customOpenDatabase from '@expo/websql/custom';
3import { requireNativeModule } from 'expo-modules-core';
4import { Platform } from 'react-native';
5const ExpoSQLite = requireNativeModule('ExpoSQLite');
6function zipObject(keys, values) {
7    const result = {};
8    for (let i = 0; i < keys.length; i++) {
9        result[keys[i]] = values[i];
10    }
11    return result;
12}
13/** The database returned by `openDatabase()` */
14export class SQLiteDatabase {
15    _name;
16    _closed = false;
17    constructor(name) {
18        this._name = name;
19    }
20    /**
21     * Executes the SQL statement and returns a callback resolving with the result.
22     */
23    exec(queries, readOnly, callback) {
24        if (this._closed) {
25            throw new Error(`The SQLite database is closed`);
26        }
27        ExpoSQLite.exec(this._name, queries.map(_serializeQuery), readOnly).then((nativeResultSets) => {
28            callback(null, nativeResultSets.map(_deserializeResultSet));
29        }, (error) => {
30            // TODO: make the native API consistently reject with an error, not a string or other type
31            callback(error instanceof Error ? error : new Error(error));
32        });
33    }
34    /**
35     * Executes the SQL statement and returns a Promise resolving with the result.
36     */
37    async execAsync(queries, readOnly) {
38        if (this._closed) {
39            throw new Error(`The SQLite database is closed`);
40        }
41        const nativeResultSets = await ExpoSQLite.exec(this._name, queries.map(_serializeQuery), readOnly);
42        return nativeResultSets.map(_deserializeResultSet);
43    }
44    /**
45     * @deprecated Use `closeAsync()` instead.
46     */
47    close = this.closeAsync;
48    /**
49     * Close the database.
50     */
51    closeAsync() {
52        this._closed = true;
53        return ExpoSQLite.close(this._name);
54    }
55    /**
56     * Synchronously closes the database.
57     */
58    closeSync() {
59        this._closed = true;
60        return ExpoSQLite.closeSync(this._name);
61    }
62    /**
63     * Delete the database file.
64     * > The database has to be closed prior to deletion.
65     */
66    deleteAsync() {
67        if (!this._closed) {
68            throw new Error(`Unable to delete '${this._name}' database that is currently open. Close it prior to deletion.`);
69        }
70        return ExpoSQLite.deleteAsync(this._name);
71    }
72    /**
73     * Creates a new transaction with Promise support.
74     * @param asyncCallback A `SQLTransactionAsyncCallback` function that can perform SQL statements in a transaction.
75     * @param readOnly true if all the SQL statements in the callback are read only.
76     */
77    async transactionAsync(asyncCallback, readOnly = false) {
78        await this.execAsync([{ sql: 'BEGIN;', args: [] }], false);
79        try {
80            const transaction = new ExpoSQLTransactionAsync(this, readOnly);
81            await asyncCallback(transaction);
82            await this.execAsync([{ sql: 'END;', args: [] }], false);
83        }
84        catch (e) {
85            await this.execAsync([{ sql: 'ROLLBACK;', args: [] }], false);
86            throw e;
87        }
88    }
89    // @ts-expect-error: properties that are added from websql
90    version;
91}
92function _serializeQuery(query) {
93    return Platform.OS === 'android'
94        ? {
95            sql: query.sql,
96            args: query.args.map(_escapeBlob),
97        }
98        : [query.sql, query.args];
99}
100function _deserializeResultSet(nativeResult) {
101    const [errorMessage, insertId, rowsAffected, columns, rows] = nativeResult;
102    // TODO: send more structured error information from the native module so we can better construct
103    // a SQLException object
104    if (errorMessage !== null) {
105        return { error: new Error(errorMessage) };
106    }
107    return {
108        insertId,
109        rowsAffected,
110        rows: rows.map((row) => zipObject(columns, row)),
111    };
112}
113function _escapeBlob(data) {
114    if (typeof data === 'string') {
115        /* eslint-disable no-control-regex */
116        return data
117            .replace(/\u0002/g, '\u0002\u0002')
118            .replace(/\u0001/g, '\u0001\u0002')
119            .replace(/\u0000/g, '\u0001\u0001');
120        /* eslint-enable no-control-regex */
121    }
122    else {
123        return data;
124    }
125}
126const _openExpoSQLiteDatabase = customOpenDatabase(SQLiteDatabase);
127// @needsAudit @docsMissing
128/**
129 * Open a database, creating it if it doesn't exist, and return a `Database` object. On disk,
130 * the database will be created under the app's [documents directory](./filesystem), i.e.
131 * `${FileSystem.documentDirectory}/SQLite/${name}`.
132 * > The `version`, `description` and `size` arguments are ignored, but are accepted by the function
133 * for compatibility with the WebSQL specification.
134 * @param name Name of the database file to open.
135 * @param version
136 * @param description
137 * @param size
138 * @param callback
139 * @return
140 */
141export function openDatabase(name, version = '1.0', description = name, size = 1, callback) {
142    if (name === undefined) {
143        throw new TypeError(`The database name must not be undefined`);
144    }
145    const db = _openExpoSQLiteDatabase(name, version, description, size, callback);
146    db.exec = db._db.exec.bind(db._db);
147    db.execAsync = db._db.execAsync.bind(db._db);
148    db.closeAsync = db._db.closeAsync.bind(db._db);
149    db.closeSync = db._db.closeSync.bind(db._db);
150    db.deleteAsync = db._db.deleteAsync.bind(db._db);
151    db.transactionAsync = db._db.transactionAsync.bind(db._db);
152    return db;
153}
154/**
155 * Internal data structure for the async transaction API.
156 * @internal
157 */
158export class ExpoSQLTransactionAsync {
159    db;
160    readOnly;
161    constructor(db, readOnly) {
162        this.db = db;
163        this.readOnly = readOnly;
164    }
165    async executeSqlAsync(sqlStatement, args) {
166        const resultSets = await this.db.execAsync([{ sql: sqlStatement, args: args ?? [] }], this.readOnly);
167        return resultSets[0];
168    }
169}
170//# sourceMappingURL=SQLite.js.map