Skip to content

Commit b145e64

Browse files
authored
Merge pull request #602 from margelo/@chrispader/migrate-to-react-native-nitro-sqlite
Migrate to `react-native-nitro-sqlite`
2 parents 7e0d434 + 8a596b1 commit b145e64

6 files changed

Lines changed: 9992 additions & 25877 deletions

File tree

jestSetup.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ jest.mock('./lib/storage/platforms/index', () => require('./lib/storage/__mocks_
44
jest.mock('./lib/storage/providers/IDBKeyValProvider', () => require('./lib/storage/__mocks__'));
55

66
jest.mock('react-native-device-info', () => ({getFreeDiskStorage: () => {}}));
7-
jest.mock('react-native-quick-sqlite', () => ({
7+
jest.mock('react-native-nitro-sqlite', () => ({
88
open: () => ({execute: () => {}}),
9+
enableSimpleNullHandling: () => undefined,
910
}));
1011

1112
jest.useRealTimers();

lib/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type {Connection} from './OnyxConnectionManager';
2222
import useOnyx from './useOnyx';
2323
import withOnyx from './withOnyx';
2424
import type {WithOnyxState} from './withOnyx/types';
25+
import type {OnyxSQLiteKeyValuePair} from './storage/providers/SQLiteProvider';
2526

2627
export default Onyx;
2728
export {useOnyx, withOnyx};
@@ -49,4 +50,5 @@ export type {
4950
WithOnyxState,
5051
Connection,
5152
UseOnyxOptions,
53+
OnyxSQLiteKeyValuePair,
5254
};

lib/storage/providers/SQLiteProvider.ts

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,46 @@
22
* The SQLiteStorage provider stores everything in a key/value store by
33
* converting the value to a JSON string
44
*/
5-
import type {BatchQueryResult, QuickSQLiteConnection} from 'react-native-quick-sqlite';
6-
import {open} from 'react-native-quick-sqlite';
5+
import type {BatchQueryResult, NitroSQLiteConnection} from 'react-native-nitro-sqlite';
6+
import {enableSimpleNullHandling, open} from 'react-native-nitro-sqlite';
77
import {getFreeDiskStorage} from 'react-native-device-info';
88
import type StorageProvider from './types';
99
import utils from '../../utils';
1010
import type {KeyList, KeyValuePairList} from './types';
1111

12+
// By default, NitroSQLite does not accept nullish values due to current limitations in Nitro Modules.
13+
// This flag enables a feature in NitroSQLite that allows for nullish values to be passed to operations, such as "execute" or "executeBatch".
14+
// Simple null handling can potentially add a minor performance overhead,
15+
// since parameters and results from SQLite queries need to be parsed from and to JavaScript nullish values.
16+
// https://github.com/margelo/react-native-nitro-sqlite#sending-and-receiving-nullish-values
17+
enableSimpleNullHandling();
18+
19+
/**
20+
* The type of the key-value pair stored in the SQLite database
21+
* @property record_key - the key of the record
22+
* @property valueJSON - the value of the record in JSON string format
23+
*/
24+
type OnyxSQLiteKeyValuePair = {
25+
record_key: string;
26+
valueJSON: string;
27+
};
28+
29+
/**
30+
* The result of the `PRAGMA page_size`, which gets the page size of the SQLite database
31+
*/
32+
type PageSizeResult = {
33+
page_size: number;
34+
};
35+
36+
/**
37+
* The result of the `PRAGMA page_count`, which gets the page count of the SQLite database
38+
*/
39+
type PageCountResult = {
40+
page_count: number;
41+
};
42+
1243
const DB_NAME = 'OnyxDB';
13-
let db: QuickSQLiteConnection;
44+
let db: NitroSQLiteConnection;
1445

1546
const provider: StorageProvider = {
1647
/**
@@ -32,18 +63,23 @@ const provider: StorageProvider = {
3263
db.execute('PRAGMA journal_mode=WAL;');
3364
},
3465
getItem(key) {
35-
return db.executeAsync('SELECT record_key, valueJSON FROM keyvaluepairs WHERE record_key = ?;', [key]).then(({rows}) => {
66+
return db.executeAsync<OnyxSQLiteKeyValuePair>('SELECT record_key, valueJSON FROM keyvaluepairs WHERE record_key = ?;', [key]).then(({rows}) => {
3667
if (!rows || rows?.length === 0) {
3768
return null;
3869
}
3970
const result = rows?.item(0);
71+
72+
if (result == null) {
73+
return null;
74+
}
75+
4076
return JSON.parse(result.valueJSON);
4177
});
4278
},
4379
multiGet(keys) {
4480
const placeholders = keys.map(() => '?').join(',');
4581
const command = `SELECT record_key, valueJSON FROM keyvaluepairs WHERE record_key IN (${placeholders});`;
46-
return db.executeAsync(command, keys).then(({rows}) => {
82+
return db.executeAsync<OnyxSQLiteKeyValuePair>(command, keys).then(({rows}) => {
4783
// eslint-disable-next-line no-underscore-dangle
4884
const result = rows?._array.map((row) => [row.record_key, JSON.parse(row.valueJSON)]);
4985
return (result ?? []) as KeyValuePairList;
@@ -53,11 +89,12 @@ const provider: StorageProvider = {
5389
return db.executeAsync('REPLACE INTO keyvaluepairs (record_key, valueJSON) VALUES (?, ?);', [key, JSON.stringify(value)]);
5490
},
5591
multiSet(pairs) {
56-
const stringifiedPairs = pairs.map((pair) => [pair[0], JSON.stringify(pair[1] === undefined ? null : pair[1])]);
57-
if (utils.isEmptyObject(stringifiedPairs)) {
92+
const query = 'REPLACE INTO keyvaluepairs (record_key, valueJSON) VALUES (?, json(?));';
93+
const params = pairs.map((pair) => [pair[0], JSON.stringify(pair[1] === undefined ? null : pair[1])]);
94+
if (utils.isEmptyObject(params)) {
5895
return Promise.resolve();
5996
}
60-
return db.executeBatchAsync([['REPLACE INTO keyvaluepairs (record_key, valueJSON) VALUES (?, json(?));', stringifiedPairs]]);
97+
return db.executeBatchAsync([{query, params}]);
6198
},
6299
multiMerge(pairs) {
63100
// Note: We use `ON CONFLICT DO UPDATE` here instead of `INSERT OR REPLACE INTO`
@@ -68,13 +105,13 @@ const provider: StorageProvider = {
68105
SET valueJSON = JSON_PATCH(valueJSON, JSON(:value));
69106
`;
70107

71-
const nonNullishPairs = pairs.filter((pair) => pair[1] !== undefined);
72-
const queryArguments = nonNullishPairs.map((pair) => {
108+
const nonUndefinedPairs = pairs.filter((pair) => pair[1] !== undefined);
109+
const params = nonUndefinedPairs.map((pair) => {
73110
const value = JSON.stringify(pair[1]);
74111
return [pair[0], value];
75112
});
76113

77-
return db.executeBatchAsync([[query, queryArguments]]);
114+
return db.executeBatchAsync([{query, params}]);
78115
},
79116
mergeItem(key, deltaChanges, preMergedValue, shouldSetValue) {
80117
if (shouldSetValue) {
@@ -97,15 +134,18 @@ const provider: StorageProvider = {
97134
},
98135
clear: () => db.executeAsync('DELETE FROM keyvaluepairs;', []),
99136
getDatabaseSize() {
100-
return Promise.all([db.executeAsync('PRAGMA page_size;'), db.executeAsync('PRAGMA page_count;'), getFreeDiskStorage()]).then(([pageSizeResult, pageCountResult, bytesRemaining]) => {
101-
const pageSize: number = pageSizeResult.rows?.item(0).page_size;
102-
const pageCount: number = pageCountResult.rows?.item(0).page_count;
103-
return {
104-
bytesUsed: pageSize * pageCount,
105-
bytesRemaining,
106-
};
107-
});
137+
return Promise.all([db.executeAsync<PageSizeResult>('PRAGMA page_size;'), db.executeAsync<PageCountResult>('PRAGMA page_count;'), getFreeDiskStorage()]).then(
138+
([pageSizeResult, pageCountResult, bytesRemaining]) => {
139+
const pageSize = pageSizeResult.rows?.item(0)?.page_size ?? 0;
140+
const pageCount = pageCountResult.rows?.item(0)?.page_count ?? 0;
141+
return {
142+
bytesUsed: pageSize * pageCount,
143+
bytesRemaining,
144+
};
145+
},
146+
);
108147
},
109148
};
110149

111150
export default provider;
151+
export type {OnyxSQLiteKeyValuePair};

lib/storage/providers/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {BatchQueryResult, QueryResult} from 'react-native-quick-sqlite';
1+
import type {BatchQueryResult, QueryResult} from 'react-native-nitro-sqlite';
22
import type {OnyxKey, OnyxValue} from '../../types';
33

44
type KeyValuePair = [OnyxKey, OnyxValue<OnyxKey>];

0 commit comments

Comments
 (0)