Skip to content
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

[HOLD https://github.com/mrousavy/nitro/issues/422] Migrate to react-native-nitro-sqlite #602

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion jestSetup.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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: () => {}}),
}));

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 All @@ -48,4 +49,5 @@ export type {
UseOnyxResult,
WithOnyxState,
Connection,
OnyxSQLiteKeyValuePair,
};
69 changes: 51 additions & 18 deletions lib/storage/providers/SQLiteProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,39 @@
* 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 {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';

/**
* 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 +56,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 +82,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 @@ -69,12 +99,12 @@ const provider: StorageProvider = {
`;

const nonNullishPairs = pairs.filter((pair) => pair[1] !== undefined);
const queryArguments = nonNullishPairs.map((pair) => {
const params = nonNullishPairs.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 +127,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
Loading