Skip to content

Migrate to react-native-nitro-sqlite #602

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1a5973e
chore(deps): use `react-native-nitro-sqlite`
chrispader Nov 26, 2024
5805c1f
refactor: update usages from QuickSQLite to NitroSQLite
chrispader Nov 26, 2024
cd59903
chore: add prepare script (build library if it is a git dependency)
chrispader Nov 26, 2024
3b624bb
fix: export SQLiteProvider type
chrispader Nov 26, 2024
32bb6f5
fix: add nitro-modules dependency
chrispader Nov 26, 2024
54fdc81
chore(deps): downgrade nitro to 0.16.2
chrispader Nov 26, 2024
742ce23
Merge branch 'main' into @chrispader/migrate-to-react-native-nitro-sq…
chrispader Dec 10, 2024
b41c879
fix: return null instead of undefined
chrispader Dec 11, 2024
26d555e
Merge branch 'main' into @chrispader/migrate-to-react-native-nitro-sq…
chrispader Dec 17, 2024
ddeeb49
Merge branch 'main' into @chrispader/migrate-to-react-native-nitro-sq…
chrispader Feb 27, 2025
a16c7d2
chore(deps): update Nitro + NitroSQLite + RN to v76
chrispader Feb 27, 2025
c89bd6d
chore(deps): update NitroSQLite
chrispader Mar 5, 2025
c2058ab
Merge branch 'main' into @chrispader/migrate-to-react-native-nitro-sq…
chrispader Apr 7, 2025
b5f22db
chore: update NitroSQLite
chrispader Apr 14, 2025
024f6f0
fix: enable simple null handling in SQLiteProvider
chrispader Apr 14, 2025
183f3cf
fix: update NitroSQLite mock
chrispader Apr 15, 2025
b9d5fe0
fix: prettier
chrispader Apr 15, 2025
93b428c
Merge branch 'main' into @chrispader/migrate-to-react-native-nitro-sq…
chrispader Apr 22, 2025
3e6915f
Merge branch 'main' into @chrispader/migrate-to-react-native-nitro-sq…
chrispader May 4, 2025
8a596b1
docs: add comment for enableSimpleNullHandling
chrispader May 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion jestSetup.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ jest.mock('./lib/storage/platforms/index', () => require('./lib/storage/__mocks_
jest.mock('./lib/storage/providers/IDBKeyValProvider', () => require('./lib/storage/__mocks__'));

jest.mock('react-native-device-info', () => ({getFreeDiskStorage: () => {}}));
jest.mock('react-native-quick-sqlite', () => ({
jest.mock('react-native-nitro-sqlite', () => ({
open: () => ({execute: () => {}}),
enableSimpleNullHandling: () => undefined,
}));

jest.useRealTimers();
Expand Down
2 changes: 2 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type {Connection} from './OnyxConnectionManager';
import useOnyx from './useOnyx';
import withOnyx from './withOnyx';
import type {WithOnyxState} from './withOnyx/types';
import type {OnyxSQLiteKeyValuePair} from './storage/providers/SQLiteProvider';

export default Onyx;
export {useOnyx, withOnyx};
Expand Down Expand Up @@ -49,4 +50,5 @@ export type {
WithOnyxState,
Connection,
UseOnyxOptions,
OnyxSQLiteKeyValuePair,
};
78 changes: 59 additions & 19 deletions lib/storage/providers/SQLiteProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,46 @@
* 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 type {BatchQueryResult, NitroSQLiteConnection} from 'react-native-nitro-sqlite';
import {enableSimpleNullHandling, open} from 'react-native-nitro-sqlite';
import {getFreeDiskStorage} from 'react-native-device-info';
import type StorageProvider from './types';
import utils from '../../utils';
import type {KeyList, KeyValuePairList} from './types';

// By default, NitroSQLite does not accept nullish values due to current limitations in Nitro Modules.
// This flag enables a feature in NitroSQLite that allows for nullish values to be passed to operations, such as "execute" or "executeBatch".
// Simple null handling can potentially add a minor performance overhead,
// since parameters and results from SQLite queries need to be parsed from and to JavaScript nullish values.
// https://github.com/margelo/react-native-nitro-sqlite#sending-and-receiving-nullish-values
enableSimpleNullHandling();

/**
* The type of the key-value pair stored in the SQLite database
* @property record_key - the key of the record
* @property valueJSON - the value of the record in JSON string format
*/
type OnyxSQLiteKeyValuePair = {
record_key: string;
valueJSON: string;
};

/**
* The result of the `PRAGMA page_size`, which gets the page size of the SQLite database
*/
type PageSizeResult = {
page_size: number;
};

/**
* The result of the `PRAGMA page_count`, which gets the page count of the SQLite database
*/
type PageCountResult = {
page_count: number;
};

const DB_NAME = 'OnyxDB';
let db: QuickSQLiteConnection;
let db: NitroSQLiteConnection;

const provider: StorageProvider = {
/**
Expand All @@ -32,18 +63,23 @@ const provider: StorageProvider = {
db.execute('PRAGMA journal_mode=WAL;');
},
getItem(key) {
return db.executeAsync('SELECT record_key, valueJSON FROM keyvaluepairs WHERE record_key = ?;', [key]).then(({rows}) => {
return db.executeAsync<OnyxSQLiteKeyValuePair>('SELECT record_key, valueJSON FROM keyvaluepairs WHERE record_key = ?;', [key]).then(({rows}) => {
if (!rows || rows?.length === 0) {
return null;
}
const result = rows?.item(0);

if (result == null) {
return null;
}

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}) => {
return db.executeAsync<OnyxSQLiteKeyValuePair>(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;
Expand All @@ -53,11 +89,12 @@ const provider: StorageProvider = {
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)) {
const query = 'REPLACE INTO keyvaluepairs (record_key, valueJSON) VALUES (?, json(?));';
const params = pairs.map((pair) => [pair[0], JSON.stringify(pair[1] === undefined ? null : pair[1])]);
if (utils.isEmptyObject(params)) {
return Promise.resolve();
}
return db.executeBatchAsync([['REPLACE INTO keyvaluepairs (record_key, valueJSON) VALUES (?, json(?));', stringifiedPairs]]);
return db.executeBatchAsync([{query, params}]);
},
multiMerge(pairs) {
// Note: We use `ON CONFLICT DO UPDATE` here instead of `INSERT OR REPLACE INTO`
Expand All @@ -68,13 +105,13 @@ const provider: StorageProvider = {
SET valueJSON = JSON_PATCH(valueJSON, JSON(:value));
`;

const nonNullishPairs = pairs.filter((pair) => pair[1] !== undefined);
const queryArguments = nonNullishPairs.map((pair) => {
const nonUndefinedPairs = pairs.filter((pair) => pair[1] !== undefined);
const params = nonUndefinedPairs.map((pair) => {
const value = JSON.stringify(pair[1]);
return [pair[0], value];
});

return db.executeBatchAsync([[query, queryArguments]]);
return db.executeBatchAsync([{query, params}]);
},
mergeItem(key, deltaChanges, preMergedValue, shouldSetValue) {
if (shouldSetValue) {
Expand All @@ -97,15 +134,18 @@ const provider: StorageProvider = {
},
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,
};
});
return Promise.all([db.executeAsync<PageSizeResult>('PRAGMA page_size;'), db.executeAsync<PageCountResult>('PRAGMA page_count;'), getFreeDiskStorage()]).then(
([pageSizeResult, pageCountResult, bytesRemaining]) => {
const pageSize = pageSizeResult.rows?.item(0)?.page_size ?? 0;
const pageCount = pageCountResult.rows?.item(0)?.page_count ?? 0;
return {
bytesUsed: pageSize * pageCount,
bytesRemaining,
};
},
);
},
};

export default provider;
export type {OnyxSQLiteKeyValuePair};
2 changes: 1 addition & 1 deletion lib/storage/providers/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {BatchQueryResult, QueryResult} from 'react-native-quick-sqlite';
import type {BatchQueryResult, QueryResult} from 'react-native-nitro-sqlite';
import type {OnyxKey, OnyxValue} from '../../types';

type KeyValuePair = [OnyxKey, OnyxValue<OnyxKey>];
Expand Down
Loading