node-firebird icon indicating copy to clipboard operation
node-firebird copied to clipboard

Please, consider async/await interface

Open MaximS opened this issue 7 years ago • 3 comments

Please, consider async/await interface for the library. Some frameworks require Promises (async) to get data from instead of callback(err, data). As a starting point you can use this code fragment I've created working on an grphql apollo application:

import * as Firebird from "node-firebird";
import { ConnectionPool, Database } from "node-firebird";

const
  fbOptions: Firebird.Options = {
    host:     "127.0.0.1",
    port:     3050,
    database: "database.fdb",
    user:     "SYSDBA",
    password: "masterkey",
    lowercase_keys: false, // set to true to lowercase keys
    role:      null,            // default
    pageSize:  4096,        // default when creating database
    cacheQuery: true,
  }

const
  dbPool = Firebird.pool(5, fbOptions, () => {});

const
  LOG_SQL = true;

export class FirebirdPromise {
    static attachPool(): Promise<Database> {
        return new Promise<Database>(
            (resolve, reject) => {
                dbPool.get((err, db) => {
                    if (err) return reject(err);
                    resolve(db);
                });
          });
    }

    static async aquery(querysql: string, params?: any[]): Promise<any> {
        const db = await FirebirdPromise.attachPool();
        if (db) {
          return new Promise(
            (resolve, reject) => {
                if (LOG_SQL) {console.log(querysql)};
                db.query(querysql, params, (err, data) => {
                    if (err)  {
                      db.detach();
                      return reject(err);
                    }
                    db.detach();
                    resolve(data);
                });
          });
        } else {
          throw "Not enough connections: Cannot get db from the pool!";
        }
    }
}

And in code requiring promises it can be used like

let dataPromise: Promise<any[]>
dataPromise = FirebirdPromise.aquery(your_sql_query);

or if you need the data in-place then data = await FirebirdPromise.aquery(your_sql_query);

MaximS avatar Jan 03 '18 08:01 MaximS

Any update about this ?

alissonmarqui avatar Oct 07 '20 18:10 alissonmarqui

Any update about this ?

Have some:

// firebird-async.ts

import * as Firebird from "node-firebird";
import promisify = require("../node_modules/util.promisify")

const
  fbOptions: Firebird.Options = {
    host:     process.env.DB_HOST,
    port:     process.env.DB_PORT,
    database: process.env.DB_PATH,
    user:     process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    lowercase_keys: false, // set to true to lowercase keys
    role:      null,            // default
    pageSize:  4096,        // default when creating database
    // cacheQuery: true,
  }


export interface ConnectionPoolAsync {
    get(callback: Firebird.DatabaseCallback): void;
    destroy(): void;
    getAsync: () => Promise<DatabaseAsync>
}

export interface DatabaseAsync {
    detach(callback?: Firebird.SimpleCallback): Firebird.Database;
    transaction(isolation: Firebird.Isolation, callback: Firebird.TransactionCallback);
    escape(value: any): string;
    detachAsync: () => Promise<void>;
    transactionAsync: (/*isolation: Firebird.Isolation*/) => Promise<TransactionAsync>;
}

export interface TransactionAsync {
    query(query: string, params: any[], callback: Firebird.QueryCallback): void;
    execute(query: string, params: any[], callback: Firebird.QueryCallback): void;
    commit(callback?: Firebird.SimpleCallback): void;
    rollback(callback?: Firebird.SimpleCallback): void;

    queryAsync: (query: string, params: any[]) => Promise<any>;
    executeAsync: (query: string, params: any[]) => Promise<any[]>;
    commitAsync: () => Promise<void>;
    rollbackAsync: () => Promise<void>;
    database: DatabaseAsync;
    commitAndDetach: () => Promise<void>;
    rollbackAndDetach: () => Promise<void>;
}


export function pool(max: number, options: Firebird.Options): ConnectionPoolAsync {
    const ap: ConnectionPoolAsync = Firebird.pool(max, options) as ConnectionPoolAsync;
    const aget = promisify<Firebird.Database>(ap.get).bind(ap);
    ap.getAsync = async () => {
        const db: DatabaseAsync = await aget() as unknown as DatabaseAsync;
        db.detachAsync = promisify<void>(db.detach).bind(db);
        db.transactionAsync = async () => {
            const atran = promisify<Firebird.Isolation, Firebird.Transaction>(db.transaction).bind(db);
            const transaction = await atran(Firebird.ISOLATION_READ_COMMITED) as unknown as TransactionAsync;
            transaction.commitAsync = promisify(transaction.commit).bind(transaction);
            transaction.executeAsync = promisify(transaction.execute).bind(transaction);
            transaction.queryAsync = promisify(transaction.query).bind(transaction);
            transaction.rollbackAsync = promisify(transaction.rollback).bind(transaction);

            transaction.database = db;
            transaction.commitAndDetach = (async () => {
                await transaction.commitAsync();
                await transaction.database.detachAsync()
            }).bind(transaction);
            transaction.rollbackAndDetach = (async () => {
              await transaction.rollbackAsync();
              await transaction.database.detachAsync()
            }).bind(transaction);


            return transaction;
        }
        return db;
    }
    return ap;
}

export async function transaction(): Promise<TransactionAsync> {
  const db = await thePool.getAsync();
  const tr = await db.transactionAsync();
  return tr;
}

export const thePool = pool(5, fbOptions);

const SQL_SET_USER_CONTEXT = `
  execute block(user_id integer = ?)
  as
  begin
    RDB$SET_CONTEXT ('USER_TRANSACTION', 'USER_ID', user_id);
  end
`;

export async function userTransaction(userId: string): Promise<TransactionAsync> {
  const db = await thePool.getAsync();
  const tr = await db.transactionAsync();
  await tr.queryAsync(SQL_SET_USER_CONTEXT, [userId]);
  return tr;
}

and then

const tr = await transaction();
try {
   const r: any[] = await transaction.queryAsync(....
   await tr.commitAndDetach();
} catch (e) {
   await tr.rollbackAndDetach();
   throw(e);
}

MaximS avatar May 21 '21 16:05 MaximS

Exactly what I was looking for, thanks! Any updates would be welcome.

tcdewit avatar Jun 29 '23 09:06 tcdewit