margelo / react-native-nitro-sqlite

A fast react-native SQLite library built using JSI
https://margelo.io
MIT License
354 stars 28 forks source link

General development suggestions #62

Open vhakulinen opened 1 month ago

vhakulinen commented 1 month ago

Responding to https://github.com/margelo/react-native-quick-sqlite/pull/55#issuecomment-2391285662, since bloating that PR with extra hopes and dreams wouldn't be productive.

W.r.t. Concurrency. It would be nice to be able to have a read only connection pool and the one connection (or another connection pool) for writing. This could be a huge performance win for more complex application that has heavy database usage.

Having studied this code base (and having had my own, long abandoned venture to alternative, sqlite-jsi), I have some idea of the effort required to implement these things. While I dont have the time / resources to work on this myself (nor am I expecting anyone to do the work either!), if someone is up for the task I am more than happy to chat about the ideas / design / implementation details.

As the sqlite-jsi repo might suggest, I have interest in having a solid sqlite library for react-native - I just lack the time to work on it.

chrispader commented 1 month ago

Thanks for all the input! I'm going to try improve the codebase and implement most of this in the next weeks! 🚀

We might plan on doing a greater API redesign anyway, so some of these points might already be covered by other changes

mrousavy commented 1 month ago

💯

vhakulinen commented 3 weeks ago

Oh, another quite thing: testing support!

Running tests on another runtime (i.e. not hermes) is PITA, mainly because the current API diverges from the "normal" so much. With better API testing would be easier (because you can implement better mocks / adapters for other runtimes).

At the moment I'm using the following mock, but only does the bare minimum. This mock uses better-sqlite3 node package, but I suppose the same could be adapted to other runtimes too. Because quick-sqlite doesn't support named parameters, and better-sqlite3 doesnt support the ?NNNN syntax, I can't use all our queries in our tests at the moment.

import BetterDB from 'better-sqlite3';
import { QuickSQLiteConnection } from 'react-native-quick-sqlite';

/**
 * Partially adapts better-sqlite3 to the quick-sqlite interface.
 */
export const adaptBetterSqlite = (conn: BetterDB.Database): QuickSQLiteConnection => {

  const execute: QuickSQLiteConnection['execute'] = (query, params) => {
    const stmt = conn.prepare(query);

    if (stmt.reader) {
      const rows = stmt.all(params || []);

      return {
        rowsAffected: rows.length,
        rows: {
          length: rows.length,
          _array: rows,
          item: (i: number) => rows[i],
        }
      };
    } else {
      const rows = stmt.run(params || []);

      return {
        rowsAffected: rows.changes,
        rows: {
          length: 0,
          _array: [],
          item: (i: number) => [][i],
        }
      };
    }
  };

  const executeAsync: QuickSQLiteConnection['executeAsync'] = (query: string, params?: unknown[]) => {
    return Promise.resolve(execute(query, params));
  };

  const executeBatch: QuickSQLiteConnection['executeBatch'] = tuples => {
    let n = 0;

    for (const tuple of tuples) {
      const stmt = conn.prepare(tuple[0]);

      const res = stmt.run(tuple[1] || []);

      n += res.changes;
    }

    return { rowsAffected: n };
  };

  const executeBatchAsync: QuickSQLiteConnection['executeBatchAsync'] = tuples => {
    return Promise.resolve(executeBatch(tuples));
  };

  const transaction: QuickSQLiteConnection['transaction'] = async fn => {
    const commit = () => {
      return execute('COMMIT');
    };

    const rollback = () => {
      return execute('ROLLBACK');
    };

    try {
      execute('BEGIN TRANSACTION');

      await fn({
        commit,
        execute,
        executeAsync,
        rollback,
      });

      commit();
    } catch (err) {
      rollback();
      throw err;
    }
  };

  const notImplemented = () => {
    throw new Error('not implemented');
  };

  return {
    execute,
    executeAsync,
    executeBatch,
    executeBatchAsync,
    transaction,

    close: notImplemented,
    delete: notImplemented,
    detach: notImplemented,
    attach: notImplemented,
    loadFile: notImplemented,
    loadFileAsync: notImplemented,
  };
};
vhakulinen commented 2 weeks ago

Another performance related issue: cancellation.

Say you have a view that loads data for ~0.5s from the database, but the user can press a button to change the query parameters (for example, time frame) faster that the query executes. This will currently cause the queries to queue up and delay the UI quite a bit.