question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

using transaction with multiple prepared statements

See original GitHub issue

I found information about how to create prepared statements with a pool but didn’t find information about how to put multiple prepared statements in a Transaction. I created a database class first. This class manages the Connection and fires the query.

import sql, { ConnectionPool, PreparedStatement, IProcedureResult } from 'mssql';
import { injectable } from 'inversify';

import { IDatabase } from './IDatabase';
import { InternalServerErrorException } from '../../interfaceAdapters/httpExceptions/InternalServerErrorException';
import * as databaseConfig from '../../config/DatabaseConfig';

@injectable()
export class Database implements IDatabase {
    public connectionPool: ConnectionPool;

    constructor() {
        this.connectionPool = new sql.ConnectionPool(databaseConfig);
    }

    public connect = async (): Promise<void> => {
        try {
            await this.connectionPool.connect();
        } catch (error) {
            throw error;
        }
    }

    public query = async (builder: Function) : Promise<IProcedureResult<any>> => {
        try {
            const rawStatement: PreparedStatement = new sql.PreparedStatement(this.connectionPool);

            const queryInfo: any = builder(rawStatement);
            const { preparedStatement, queryString, queryParams = {} }: { preparedStatement: PreparedStatement, queryString: string, queryParams: object } = queryInfo;

            await preparedStatement.prepare(queryString);
            const queryResult: IProcedureResult<any> = await preparedStatement.execute(queryParams);
            await preparedStatement.unprepare();

            return queryResult;
        } catch (error) {
            throw new InternalServerErrorException(error.message);
        }
    }
}

As you can see I created a query function with a builder parameter. I have multiple queries from multiple classes and don’t want to write the same code over and over again so I pass in only the things that differ from query to query. A basic example would be my fetchUserById example

    public fetchUserById = async (params: any[]): Promise<QueryResult> => {
        try {
            const queryResult: IProcedureResult<any> = await this.database.query((preparedStatement: PreparedStatement) => {
                preparedStatement.input('userId', sql.Numeric);
            
                const queryString: string = `
                    SELECT *
                    FROM users
                    WHERE id = @userId
                `;
    
                const queryParams: object = {
                    userId: params[0]
                };
                
                return {
                    preparedStatement, 
                    queryString,
                    queryParams,
                };
            });
    
            return new QueryResult(queryResult.recordset, queryResult.rowsAffected.length);
        } catch (error) {
            throw error;
        }
    }

The database provides the preparedStatement object and the query passes back the updated statement, the query string and the parameters.

This code works fine but doesn’t provide the possibility for transactional queries.

I found some information about prepared statements here

https://www.npmjs.com/package/mssql#prepared-statement

and about Transactions

https://www.npmjs.com/package/mssql#transaction

Would someone mind explaining how to use the mssql module to provide the possibility using prepared statements within transactions? My database query function should provide a functionality giving access to write something like this in my query file

  • begin transaction
  • run insertQuery one
  • run insertQuery two
  • end transaction

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:7

github_iconTop GitHub Comments

1reaction
byteCrunshercommented, Sep 5, 2019

So I finally got it working, this is my updated run function but would someone mind telling me if I can shorten this? I am not sure if this is the best way to do it

    public run = async (): Promise<void> => {
        try {
            await this.connectionPool.connect();

            const transaction: Transaction = new sql.Transaction(this.connectionPool);
            
            const workingStatement: PreparedStatement = new sql.PreparedStatement(transaction);
            workingStatement.input('number', sql.Numeric(18, 0));

            const invalidStatement: PreparedStatement = new sql.PreparedStatement(transaction);
            invalidStatement.input('number', sql.Numeric(18, 0));

            try {
                await transaction.begin();

                await workingStatement.prepare('INSERT INTO integer (value) VALUES (@number)');

                try {
                    await workingStatement.execute({ number: 1 });
                } finally {
                    await workingStatement.unprepare();
                }

                await invalidStatement.prepare('INSERT INTO integer (value) VALUES (@number)');
                
                try {
                    await invalidStatement.execute({ number: false });
                } finally {
                    await invalidStatement.unprepare();
                }

                await transaction.commit();
            } catch (error) {
                await transaction.rollback();
                console.log('execution failed...');
            }

            console.log('done...');
        } catch (error) {
            throw error;
        }
    }
0reactions
byteCrunshercommented, Sep 4, 2019

@willmorgan I tried to reproduce my own question. First I created a database with a table called “integer” and a numeric column “value”. Then I created an empty Node/Typescript project with this code

import sql, { ConnectionPool, PreparedStatement } from 'mssql';

class App {
    private connectionPool: ConnectionPool;
    
    constructor() {
        this.connectionPool = new sql.ConnectionPool(databaseConfig);
    }
    
    public run = async (): Promise<void> => {
        try {
            await this.connectionPool.connect();

            const preparedStatement: PreparedStatement = new sql.PreparedStatement(this.connectionPool);
            
            preparedStatement.input('number', sql.Numeric(18, 0));

            await preparedStatement.prepare('INSERT INTO integer (value) VALUES (@number)');
            await preparedStatement.execute({ number: 1 });
            await preparedStatement.unprepare();

            console.log('done...');
        } catch (error) {
            throw error;
        }
    }
}

(async () => {
    const app: App = new App();
    await app.run();
})().catch(error => {
    throw error;
});

So far so good. Now I want to run two INSERTs within a transaction but the second one passes in a string so I would expect an error and the first one gets rolled back. My updated run function:

    public run = async (): Promise<void> => {
        try {
            await this.connectionPool.connect();

            const transaction: Transaction = new sql.Transaction(this.connectionPool);
            
            const workingStatement: PreparedStatement = new sql.PreparedStatement(transaction);
            workingStatement.input('number', sql.Numeric(18, 0));

            const invalidStatement: PreparedStatement = new sql.PreparedStatement(transaction);
            invalidStatement.input('number', sql.Numeric(18, 0));

            try {
                await transaction.begin();

                await workingStatement.prepare('INSERT INTO integer (value) VALUES (@number)');
                await workingStatement.execute({ number: 1 });
                await workingStatement.unprepare();

                await invalidStatement.prepare('INSERT INTO integer (value) VALUES (@number)');
                await invalidStatement.execute({ number: false });
                await invalidStatement.unprepare();

                await transaction.commit();
            } catch (error) {
                await transaction.rollback();
                console.log('execution failed...');
            }

            console.log('done...');
        } catch (error) {
            throw error;
        }
    }

Now I get this error

(node:18416) UnhandledPromiseRejectionWarning: TransactionError: Can’t rollback transaction. There is a request in progress.

and I’m not sure if my syntax is wrong.

I hope this clearifies my Problems 😃

Read more comments on GitHub >

github_iconTop Results From Across the Web

Multiple mysqli prepared statements with transactions
I'm trying to figure out how to use sql transactions with mysqli prepared statements. I haven't been able to find any examples that...
Read more >
Using Prepared Statements - JDBC Basics - Oracle Help Center
The advantage of using SQL statements that take parameters is that you can use the same statement and supply it with different values...
Read more >
Prepared Statements - Manual - PHP
The prepared statement execution consists of two stages: prepare and execute. At the prepare stage a statement template is sent to the database...
Read more >
How to execute 1000s INSERT/UPDATE queries with PDO?
The key to have your multiple inserts fast and reliable is to use **transactions** and a **prepared statement**.
Read more >
Using Prepared Statements - Go database/sql tutorial
To use a prepared statement prepared outside the transaction in a Tx , you can use Tx.Stmt() , which will create a new...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found