diesel-wasm-sqlite 0.0.1

SQLite WebAssembly backend for Diesel
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";

const log = console.log;
const err_log = console.error;

export class SQLiteError extends Error {
  constructor(message, code) {
    super(message);
    this.code = code;
  }
}

export class SQLite {
  #module;
  #sqlite3;
  constructor(sqlite3) {
    if (typeof sqlite3 === "undefined") {
      throw new Error(
        "`sqliteObject` must be defined before calling constructor",
      );
    }
    this.sqlite3 = sqlite3;
  }

  static async init_module(opts) {
    return await sqlite3InitModule({
      print: log,
      printErr: err_log,
      ...opts,
    });
  }

  version() {
    return this.sqlite3.version;
  }

  filename(db, name) {
    return this.sqlite3.capi.sqlite3_db_filename(db, name);
  }

  extended_errcode(connection) {
    return this.sqlite3.capi.sqlite3_extended_errcode(connection);
  }

  errstr(code) {
    return this.sqlite3.capi.sqlite3_errstr(code);
  }

  errmsg(connection) {
    return this.sqlite3.capi.sqlite3_errmsg(connection);
  }

  result_js(context, value) {
    return this.sqlite3.capi.sqlite3_result_js(context, value);
  }

  result_text(context, value) {
    return this.sqlite3.capi.sqlite3_result_text(context, value);
  }

  result_int(context, value) {
    return this.sqlite3.capi.sqlite3_result_int(context, value);
  }

  result_int64(context, value) {
    return this.sqlite3.capi.sqlite3_result_int64(context, value);
  }

  result_double(context, value) {
    return this.sqlite3.capi.sqlite3_result_double(context, value);
  }

  result_blob(context, value) {
    return this.sqlite3.capi.sqlite3_result_blob(context, value);
  }

  result_null(context) {
    return this.sqlite3.capi.sqlite3_result_null(context);
  }

  bind_blob(stmt, i, value, len, flags) {
    return this.sqlite3.capi.sqlite_bind_blob(stmt, i, value);
  }

  bind_text(stmt, i, value, len, flags) {
    return this.sqlite3.capi.sqlite3_bind_text(stmt, i, value, len, flags);
  }

  bind_double(stmt, i, value) {
    return this.sqlite3.capi.sqlite3_bind_double(stmt, i, value);
  }

  bind_int(stmt, i, value) {
    return this.sqlite3.capi.sqlite3_bind_int(stmt, i, value);
  }

  bind_int64(stmt, i, value) {
    return this.sqlite3.capi.sqlite3_bind_int64(stmt, i, value);
  }

  bind_null(stmt, i) {
    this.sqlite3.capi.sqlite3_bind_null(stmt, i);
    /// There's no way bind_null can fail.
    return this.sqlite3.capi.SQLITE_OK;
  }

  bind_parameter_count(stmt) {
    return this.sqlite3.capi.sqlite3_bind_parameter_count(stmt);
  }

  bind_parameter_name(stmt, i) {
    return this.sqlite3.capi.sqlite3_bind_paramater_name(stmt, it);
  }

  value_dup(pValue) {
    return this.sqlite3.capi.sqlite3_value_dup(pValue);
  }

  value_blob(pValue) {
    return this.sqlite3.capi.sqlite3_value_blob(pValue);
  }

  value_bytes(pValue) {
    return this.sqlite3.capi.sqlite3_value_bytes(pValue);
  }

  value_double(pValue) {
    return this.sqlite3.capi.sqlite3_value_double(pValue);
  }

  value_int(pValue) {
    return this.sqlite3.capi.sqlite3_value_int(pValue);
  }

  value_int64(pValue) {
    return this.sqlite3.capi.sqlite3_value_int64(pValue);
  }

  value_text(pValue) {
    return this.sqlite3.capi.sqlite3_value_text(pValue);
  }

  value_type(pValue) {
    return this.sqlite3.capi.sqlite3_value_type(pValue);
  }

  open(database_url, iflags) {
    try {
      let db;
      if (database_url === ":memory:") {
        db = new this.sqlite3.oo1.DB("transient_in_memory_db:");
        console.log(`Created in-memory database`);
      } else {
        db = new this.sqlite3.oo1.OpfsDb(database_url);
        console.log(`Created persistent database at ${db.filename}`);
      }
      return db;
    } catch (error) {
      console.log("OPFS open error", error);
      throw error;
    }
  }

  exec(db, query) {
    try {
      return db.exec(query, {
        callback: (row) => {
          log(`exec'd ${row}`);
        },
      });
    } catch (error) {
      throw error;
    }
  }

  finalize(stmt) {
    return this.sqlite3.capi.sqlite3_finalize(stmt);
  }

  changes(db) {
    return this.sqlite3.capi.sqlite3_changes(db);
  }

  clear_bindings(stmt) {
    return this.sqlite3.capi.sqlite3_clear_bindings(stmt);
  }

  reset(stmt) {
    return this.sqlite3.capi.sqlite3_reset(stmt);
  }

  close(db) {
    return this.sqlite3.capi.sqlite3_close_v2(db.pointer);
  }

  db_handle(stmt) {
    return this.sqlite3.capi.sqlite3_db_handle(stmt);
  }

  prepare_v3(db, sql, nByte, prepFlags, ppStmt, pzTail) {
    return this.sqlite3.capi.sqlite3_prepare_v3(
      db.pointer,
      sql,
      nByte,
      prepFlags,
      ppStmt,
      pzTail,
    );
  }

  into_statement(pStmt) {
    const BindTypes = {
      null: 1,
      number: 2,
      string: 3,
      boolean: 4,
      blob: 5,
    };
    BindTypes["undefined"] == BindTypes.null;
    if (wasm.bigIntEnabled) {
      BindTypes.bigint = BindTypes.number;
    }

    new Stmt(this, pStmt, BindTypes);
  }

  step(stmt) {
    return this.sqlite3.capi.sqlite3_step(stmt);
  }

  column_value(stmt, i) {
    return this.sqlite3.capi.sqlite3_column_value(stmt, i);
  }

  column_name(stmt, idx) {
    return this.sqlite3.capi.sqlite3_column_name(stmt, idx);
  }

  column_count(stmt) {
    return this.sqlite3.capi.sqlite3_column_count(stmt);
  }

  create_function(
    database,
    functionName,
    nArg,
    textRep,
    pApp,
    xFunc,
    xStep,
    xFinal,
  ) {
    try {
      this.sqlite3.capi.sqlite3_create_function(
        database,
        functionName,
        nArg,
        textRep,
        pApp, // pApp is ignored
        xFunc,
        xStep,
        xFinal,
      );
      console.log("create function");
    } catch (error) {
      console.log("create function err");
      throw error;
    }
  }

  //TODO: At some point need a way to register functions from rust
  //but for just libxmtp this is fine.
  register_diesel_sql_functions(database) {
    try {
      this.sqlite3.capi.sqlite3_create_function(
        database,
        "diesel_manage_updated_at",
        1,
        this.sqlite3.capi.SQLITE_UTF8,
        0,
        async (context, values) => {
          const table_name = this.sqlite3.value_text(values[0]);

          database.exec(
            context,
            `CREATE TRIGGER __diesel_manage_updated_at_${table_name}
            AFTER UPDATE ON ${table_name}
            FOR EACH ROW WHEN
              old.updated_at IS NULL AND
              new.updated_at IS NULL OR
              old.updated_at == new.updated_at
            BEGIN
            UPDATE ${table_name}
            SET updated_at = CURRENT_TIMESTAMP
            WHERE ROWID = new.ROWID;
            END`,
            (row) => {
              log(`------------------------------------`);
              log(`Created trigger for ${table_name}`);
              log(row);
              log(`------------------------------------`);
            },
          );
        },
      );
    } catch (error) {
      console.log("error creating diesel trigger");
      throw error;
    }
  }

  value_free(value) {
    return this.sqlite3.capi.sqlite3_value_free(value);
  }

  /*
  serialize(database, zSchema, size, flags) {
    return this.module._sqlite3_serialize(database, zSchema, size, flags);
  }
  */
}