-
Notifications
You must be signed in to change notification settings - Fork 73
/
SQLiteProvider.ts
111 lines (103 loc) · 4.75 KB
/
SQLiteProvider.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
* The SQLiteStorage provider stores everything in a key/value store by
* converting the value to a JSON string
*/
import type {BatchQueryResult, QuickSQLiteConnection} from 'react-native-quick-sqlite';
import {open} from 'react-native-quick-sqlite';
import {getFreeDiskStorage} from 'react-native-device-info';
import type StorageProvider from './types';
import utils from '../../utils';
import type {KeyList, KeyValuePairList} from './types';
const DB_NAME = 'OnyxDB';
let db: QuickSQLiteConnection;
const provider: StorageProvider = {
/**
* The name of the provider that can be printed to the logs
*/
name: 'SQLiteProvider',
/**
* Initializes the storage provider
*/
init() {
db = open({name: DB_NAME});
db.execute('CREATE TABLE IF NOT EXISTS keyvaluepairs (record_key TEXT NOT NULL PRIMARY KEY , valueJSON JSON NOT NULL) WITHOUT ROWID;');
// All of the 3 pragmas below were suggested by SQLite team.
// You can find more info about them here: https://www.sqlite.org/pragma.html
db.execute('PRAGMA CACHE_SIZE=-20000;');
db.execute('PRAGMA synchronous=NORMAL;');
db.execute('PRAGMA journal_mode=WAL;');
},
getItem(key) {
return db.executeAsync('SELECT record_key, valueJSON FROM keyvaluepairs WHERE record_key = ?;', [key]).then(({rows}) => {
if (!rows || rows?.length === 0) {
return null;
}
const result = rows?.item(0);
return JSON.parse(result.valueJSON);
});
},
multiGet(keys) {
const placeholders = keys.map(() => '?').join(',');
const command = `SELECT record_key, valueJSON FROM keyvaluepairs WHERE record_key IN (${placeholders});`;
return db.executeAsync(command, keys).then(({rows}) => {
// eslint-disable-next-line no-underscore-dangle
const result = rows?._array.map((row) => [row.record_key, JSON.parse(row.valueJSON)]);
return (result ?? []) as KeyValuePairList;
});
},
setItem(key, value) {
return db.executeAsync('REPLACE INTO keyvaluepairs (record_key, valueJSON) VALUES (?, ?);', [key, JSON.stringify(value)]);
},
multiSet(pairs) {
const stringifiedPairs = pairs.map((pair) => [pair[0], JSON.stringify(pair[1] === undefined ? null : pair[1])]);
if (utils.isEmptyObject(stringifiedPairs)) {
return Promise.resolve();
}
return db.executeBatchAsync([['REPLACE INTO keyvaluepairs (record_key, valueJSON) VALUES (?, json(?));', stringifiedPairs]]);
},
multiMerge(pairs) {
// Note: We use `ON CONFLICT DO UPDATE` here instead of `INSERT OR REPLACE INTO`
// so the new JSON value is merged into the old one if there's an existing value
const query = `INSERT INTO keyvaluepairs (record_key, valueJSON)
VALUES (:key, JSON(:value))
ON CONFLICT DO UPDATE
SET valueJSON = JSON_PATCH(valueJSON, JSON(:value));
`;
const nonNullishPairs = pairs.filter((pair) => pair[1] !== undefined);
const queryArguments = nonNullishPairs.map((pair) => {
const value = JSON.stringify(pair[1]);
return [pair[0], value];
});
return db.executeBatchAsync([[query, queryArguments]]);
},
mergeItem(key, deltaChanges, preMergedValue, shouldSetValue) {
if (shouldSetValue) {
return this.setItem(key, preMergedValue) as Promise<BatchQueryResult>;
}
return this.multiMerge([[key, deltaChanges]]) as Promise<BatchQueryResult>;
},
getAllKeys: () =>
db.executeAsync('SELECT record_key FROM keyvaluepairs;').then(({rows}) => {
// eslint-disable-next-line no-underscore-dangle
const result = rows?._array.map((row) => row.record_key);
return (result ?? []) as KeyList;
}),
removeItem: (key) => db.executeAsync('DELETE FROM keyvaluepairs WHERE record_key = ?;', [key]),
removeItems: (keys) => {
const placeholders = keys.map(() => '?').join(',');
const query = `DELETE FROM keyvaluepairs WHERE record_key IN (${placeholders});`;
return db.executeAsync(query, keys);
},
clear: () => db.executeAsync('DELETE FROM keyvaluepairs;', []),
getDatabaseSize() {
return Promise.all([db.executeAsync('PRAGMA page_size;'), db.executeAsync('PRAGMA page_count;'), getFreeDiskStorage()]).then(([pageSizeResult, pageCountResult, bytesRemaining]) => {
const pageSize: number = pageSizeResult.rows?.item(0).page_size;
const pageCount: number = pageCountResult.rows?.item(0).page_count;
return {
bytesUsed: pageSize * pageCount,
bytesRemaining,
};
});
},
};
export default provider;