xref: /expo/packages/expo-sqlite/build/SQLite.js (revision c87ab946)
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}
13class SQLiteDatabase {
14    _name;
15    _closed = false;
16    constructor(name) {
17        this._name = name;
18    }
19    exec(queries, readOnly, callback) {
20        if (this._closed) {
21            throw new Error(`The SQLite database is closed`);
22        }
23        ExpoSQLite.exec(this._name, queries.map(_serializeQuery), readOnly).then((nativeResultSets) => {
24            callback(null, nativeResultSets.map(_deserializeResultSet));
25        }, (error) => {
26            // TODO: make the native API consistently reject with an error, not a string or other type
27            callback(error instanceof Error ? error : new Error(error));
28        });
29    }
30    close() {
31        this._closed = true;
32        return ExpoSQLite.close(this._name);
33    }
34    deleteAsync() {
35        if (!this._closed) {
36            throw new Error(`Unable to delete '${this._name}' database that is currently open. Close it prior to deletion.`);
37        }
38        return ExpoSQLite.deleteAsync(this._name);
39    }
40}
41function _serializeQuery(query) {
42    return Platform.OS === 'android'
43        ? {
44            sql: query.sql,
45            args: query.args.map(_escapeBlob),
46        }
47        : [query.sql, query.args];
48}
49function _deserializeResultSet(nativeResult) {
50    const [errorMessage, insertId, rowsAffected, columns, rows] = nativeResult;
51    // TODO: send more structured error information from the native module so we can better construct
52    // a SQLException object
53    if (errorMessage !== null) {
54        return { error: new Error(errorMessage) };
55    }
56    return {
57        insertId,
58        rowsAffected,
59        rows: rows.map((row) => zipObject(columns, row)),
60    };
61}
62function _escapeBlob(data) {
63    if (typeof data === 'string') {
64        /* eslint-disable no-control-regex */
65        return data
66            .replace(/\u0002/g, '\u0002\u0002')
67            .replace(/\u0001/g, '\u0001\u0002')
68            .replace(/\u0000/g, '\u0001\u0001');
69        /* eslint-enable no-control-regex */
70    }
71    else {
72        return data;
73    }
74}
75const _openExpoSQLiteDatabase = customOpenDatabase(SQLiteDatabase);
76// @needsAudit @docsMissing
77/**
78 * Open a database, creating it if it doesn't exist, and return a `Database` object. On disk,
79 * the database will be created under the app's [documents directory](./filesystem), i.e.
80 * `${FileSystem.documentDirectory}/SQLite/${name}`.
81 * > The `version`, `description` and `size` arguments are ignored, but are accepted by the function
82 * for compatibility with the WebSQL specification.
83 * @param name Name of the database file to open.
84 * @param version
85 * @param description
86 * @param size
87 * @param callback
88 * @return
89 */
90export function openDatabase(name, version = '1.0', description = name, size = 1, callback) {
91    if (name === undefined) {
92        throw new TypeError(`The database name must not be undefined`);
93    }
94    const db = _openExpoSQLiteDatabase(name, version, description, size, callback);
95    db.exec = db._db.exec.bind(db._db);
96    db.closeAsync = db._db.close.bind(db._db);
97    db.deleteAsync = db._db.deleteAsync.bind(db._db);
98    return db;
99}
100//# sourceMappingURL=SQLite.js.map