Giter VIP home page Giter VIP logo

Comments (13)

nolanlawson avatar nolanlawson commented on July 22, 2024 3

I think I might understand after looking at your repro. It sounds like basically you're saying that the code works when you use callbacks, but not async await. In particular, if you try to break up a single transaction into multiple operations across multiple promises (each handled using async/await), then the transaction automatically closes.

This makes sense to me, because the WebSQL API was designed before Promises ever arrived in browsers. It wasn't really designed to work well with Promises.

I guess the closest analog is how IndexedDB eventually allowed microtasks to execute without closing the transaction: w3c/IndexedDB#87. Based on this analog, I guess you could say that node-websql should match what happened with IndexedDB.

Here is the minimal repro of your issue, as I understand it:

const openDatabase = require('websql')
const db = openDatabase(':memory:', '1.0', 'yolo', 100000);

async function main() {
  const txn = await new Promise(resolve => {
    db.transaction(resolve)
  })
  await new Promise(resolve => {
    txn.executeSql('CREATE TABLE foo (bar text);', [], resolve)
  })
  await new Promise(resolve => {
    txn.executeSql('INSERT INTO foo VALUES ("baz")', [], resolve)
  })
  const res = await new Promise(resolve => {
    txn.executeSql('SELECT * FROM foo', [], (txn, res) => resolve(res))
  })
  console.log('res', res)
}

main()

As I understand it, you would expect to be able to chain all these operations in the same transaction, but instead what actually happens is that the transaction closes after the CREATE call.

from node-websql.

nolanlawson avatar nolanlawson commented on July 22, 2024 2

I'm glad you found a solution. I may never get around to solving this bug, especially since it's somewhat against the spirit of the library, as I said years ago that my goal was just to emulate the WebSQL API as designed (circa 2010!) and not "carry the flame" of WebSQL.

I'll leave this issue open, though, as it seems potentially solvable without breaking backwards compatibility semantics.

from node-websql.

nolanlawson avatar nolanlawson commented on July 22, 2024 1

Hmm, based on your repro, expo-sqlite is implemented on top of iOS using React Native, and you have a custom implementation of node-websql's SQLite adapter running on top of that. Are you able to repro using only node-websql? If not, could this be a problem in expo-sqlite or in your adapter?

from node-websql.

nolanlawson avatar nolanlawson commented on July 22, 2024 1

Added a failing test for this issue: aaf5c0f

from node-websql.

DerGuteMoritz avatar DerGuteMoritz commented on July 22, 2024 1

FWIW, we were able to make this work by patching in a way to disable the auto-commit mechanism and adding explicit commit and rollback methods to WebSQLTransaction. We also extended the test suite accordingly. @nolanlawson As far as I understand you wouldn't be interested in a PR to that effect, right?

from node-websql.

nolanlawson avatar nolanlawson commented on July 22, 2024 1

@DerGuteMoritz

As far as I understand you wouldn't be interested in a PR to that effect, right?

Correct, I would prefer not to add new extensions to the WebSQL API. The goal of this project is to pretty narrowly just match the original WebSQL semantics and not invent new ones. Microtasks are kind of fuzzy, though, as the original WebSQL API predated promises.

from node-websql.

trajano avatar trajano commented on July 22, 2024

Looking further into it when using promises alone things are okay. So I narrowed it down even further in that it is due to the async / await. Which "yields" and may be triggering the immediate to run. Just promise chaining will not do that.

from node-websql.

trajano avatar trajano commented on July 22, 2024

I am suspecting it could be in expo itself too. But Expo's implementation is pretty light in terms of code. However, it is running on top of React Native iOS and Android

from node-websql.

trajano avatar trajano commented on July 22, 2024

I have an mvce https://github.com/trajano/websql-mvce that proves that it does not work when doing the async/await style. I tweaked it around since the @types/websql does not work and I don't have Expo's typings.

When the asyncAwait is used. It runs the first SQL in the transaction but does not execute the other two. Just like in Expo.

from node-websql.

trajano avatar trajano commented on July 22, 2024

Yup. I worked around it by creating a new Transaction class that does not use websql transaction semantics. Instead I relied on Expo's SQLite itself, but I think it can be expanded to plain sqlite3

The concept was to use db.exec(...) and use begin transaction commit and rollback. I basically wrapped the exec method which allowed for multiple SQLs to only run one single SQL.

  /**
   * Wraps the exec command but only supports ONE statement without notion of a transaction context.
   * @param sqlStatement SQL statement
   * @param args  arguments array
   * @param readOnly if the exec should be readonly
   * @returns result set.
   */
  async executeSqlAsync(
    sqlStatement: string,
    args: any[] = [],
    readOnly = false
  ): Promise<ResultSet> {
    return new Promise<ResultSet>((resolve, reject) => {
      this.db.exec(
        [{ sql: sqlStatement, args }],
        readOnly,
        (error, resultSet) => {
          if (error) {
            reject(error);
          }
          if (resultSet && resultSet[0]) {
            const result = resultSet[0];
            if ((result as ResultSetError).error) {
              reject((result as ResultSetError).error);
            } else {
              resolve(result as unknown as ResultSet);
            }
          }
        }
      );
    });
  }

I still have a AsyncSqlTransaction class that provides an executeSqlAsync method but does not track the transactions. I coded it so it does not need a AsyncDatabase but it meant I had to copy and paste the code for executeSqlAsync

import type { ResultSet, ResultSetError, WebSQLDatabase } from 'expo-sqlite';

export class AsyncSQLTransaction {
  constructor(private db: WebSQLDatabase, private readOnly = false) {}

  /**
   * This is the same logic as in SQLiteAsyncDatabase, but the database that is
   * passed into this transaction is NOT a SQLiteAsyncDatabase but a WebSQLDatabase
   * for interop.
   * @param sqlStatement
   * @param args
   * @returns
   */
  async executeSqlAsync(
    sqlStatement: string,
    ...args: any
  ): Promise<ResultSet> {
    return new Promise<ResultSet>((resolve, reject) => {
      this.db.exec(
        [{ sql: sqlStatement, args }],
        this.readOnly,
        (error, resultSet) => {
          if (error) {
            reject(error);
          }
          if (resultSet && resultSet[0]) {
            const result = resultSet[0];
            if ((result as ResultSetError).error) {
              reject((result as ResultSetError).error);
            } else {
              resolve(result as unknown as ResultSet);
            }
          }
        }
      );
    });
  }
}

The transactions are wrapped using txn and rtxn which are analogues to transaction and readtransaction which I coded as

  /**
   * Creates a transaction and executes a callback passing in the transaction wrapped with an async API
   * @param callback callback function that would get a transaction that provides async capability.  The return value of the callback will be the return value of this method.
   */
  async txn<T>(callback: AsyncTransactionCallback<T>): Promise<T> {
    try {
      await this.executeSqlAsync('begin transaction');
      const tx = new AsyncSQLTransaction(this);
      const rs = await callback(tx);
      await this.executeSqlAsync('commit');
      return rs;
    } catch (error) {
      await this.executeSqlAsync('rollback');
      throw error;
    }
  }

from node-websql.

trajano avatar trajano commented on July 22, 2024

@DerGuteMoritz would it work in Expo?

from node-websql.

DerGuteMoritz avatar DerGuteMoritz commented on July 22, 2024

@trajano Pretty sure, yes. IIRC the patch that Expo originally vendored node-websql for has since been upstreamed. We're using it with https://github.com/craftzdog/react-native-sqlite-2 rather than Expo's sqlite module which definitely works.

from node-websql.

trajano avatar trajano commented on July 22, 2024

@DerGuteMoritz from the way your code looks, it appears that it requires bare workflow is there a specific place I can discuss your SQLite implementation since it's outside the scope of this project.

from node-websql.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.