Transaction for prepared statements using Node mssql - javascript

I want to create an Express REST API and will use MSSQL for the database part. I use the mssql module and use the PreparedStatement class.
Sometimes my queries have to be transactional, so I have to work with the Transaction class too.
I found some information about prepared statements here and about transactions here but don't know how to execute transactional queries using prepared statements.
Would someone mind explaining how to use the mssql module with prepared statements within transactions? My query function should provide a functionality giving access to write something like this in my query file
begin transaction
run prepared statement one
run prepared statement two
end transaction
So to reproduce the problem:
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 believe the error you're hitting happens because you didn't call unprepare as suggested in the docs:
keep in mind you can't execute other requests in the transaction until you call unprepare
Calling unprepare on the statement before rolling back the transaction works for me:
try {
// ...
await invalidStatement.prepare('INSERT INTO integer (value) VALUES (#number)');
try {
await invalidStatement.execute({ number: false });
} finally {
await invalidStatement.unprepare();
}
} catch (error) {
// rollback here...
}

Related

Why can't I return data after await a promise object in async function

I'm a newbie studying coding.
I tried to mock mysql database query, so I followed below code(https://techhelpnotes.com/node-js-mock-database-jest-using-ts-jest-utils/)
//database.js
const getDbConnection = () => {
const pool = mysql.createPool(DB_CONFIG);
return {
query : function (sql) {
return util.promisify(pool.query).call(pool, sql);
};
//controller.js
try {
let result = await db.query(sql);
res.status(200).json(result);
} catch (error) {
console.log(DB error, error);
}
It worked for me, but I thought I could return the query with await by using it in databse.js like the one below
query : async function(sql) {
try {
return await util.promisify(pool.query).call(pool, sql);
} catch (error) {
console.log(DB error, error);
}
}
so I thought I can use query function without error handling
let result = await db.query(sql);
But it doesn't work. What difference is there between the two codes that makes the above code work and the below now??
Many thanks!

Error "Given transaction number * does not match" in mongodb and nodejs

I want to modify two schema while adding data. For that I used ACID transaction of mongodb with nodejs as follow. But, when I run program it displays the error like
(node:171072) UnhandledPromiseRejectionWarning: MongoError: Given transaction number 3 does not match any in-progress transactions. The active transaction number is 2
at MessageStream.messageHandler (/home/user/Projects/project/node_modules/mongodb/lib/cmap/connection.js:272:20)
at MessageStream.emit (events.js:375:28)
at MessageStream.emit (domain.js:470:12)
addData = async(request: Request, response: Response) => {
const session = await stockSchema.startSession()
try {
const userData = request.body
let data = {}
const transaction = await session.withTransaction(async() => {
try {
userData.products.map(async(item: any) => {
await inventorySchema.findOneAndUpdate({ _id: item.materialID }, { $inc: {qty: -item.qty }}, { session });
});
data = new stockSchema(userData);
await data.save({ session });
} catch (error) {
await session.abortTransaction()
throw new Error("Could not create data. Try again.");
}
});
if (transaction) {
session.endSession()
return returnData(response, data, 'Data created successfully.');
} else {
throw new Error("Could not create data. Try again.");
}
} catch (error: any) {
session.endSession();
return Error(response, error.message, {});
}
}
So you might have figured out the answer to this already, but anyway, after months of frustration, and no clear answer on the internet, i finally figured it out.
The problem with your code above is that you are passing session into a database operation (the .findOneAndUpdate function above) that is running within .map . Meaning, your 'transaction session' is being used concurrently, which is what is causing the error. read this: https://www.mongodb.com/community/forums/t/concurrency-in-mongodb-transactions/14945 (it explains why concurrency with transactions creates a bit of a mess.)
Anyway, instead of .map, use a recursive function that fires each DB operation one after another rather than concurrently, and all your problems will be solved.
You could use a function something like this:
const executeInQueue = async ({
dataAry, //the array that you .map through
callback, //the function that you fire inside .map
idx = 0,
results = [],
}) => {
if (idx === dataAry.length) return results;
//else if idx !== dataAry.length
let d = dataAry[idx];
try {
let result = await callback(d, idx);
results.push(result);
return executeInQueue({
dataAry,
callback,
log,
idx: idx + 1,
results,
});
} catch (err) {
console.log({ err });
return results.push("error");
}
};

Unable to use async await with sequelize.authenticate() method

The SQL password expires every 24 hour once (The interval might change anytime without prior notice), hence I want to validate the authentication using Sequelize beforeConnect hook before I run a SQL query.
Without async await the SQL query will fail and then authenticate and the preceding queries will pass. I want to run the code async way so no SQL queries fail. Kindly help me fix this problem.
const sequelize = new Sequelize(database, username, password, params);
sequelize.beforeConnect(async (currConfig) => {
try {
await sequelize.authenticate();
console.log('Connection has been established successfully.');
} catch (error) {
console.error('Unable to connect to the database:', error);
}
});
You can promisify the beforeConnect
let currConfig = await prepare(sequelize);
try {
await sequelize.authenticate();
console.log("Connection has been established successfully.");
} catch (error) {
console.error("Unable to connect to the database:", error);
}
function prepare(sequelize) {
return new Promise(res => {
sequelize.beforeConnect(config => res(config));
});
}
Just make sure your function has async infront of it

How to put AWS DynamoDB docClient.get item into variable?

I'm currently working with AWS SDK and DynamoDB in my code but the main problem which I constantly encounter is finding scope of variables if I create an asynchronous chain of events.
This is for my node.js server which will run a telegram bot and retrieve/store data in my AWS DynamoDB. It works with a lot of nested calls and passing on variables but makes the code very complicated.
What I want to achieve is something similar to ASYNC/AWAIT.
async DB(){
let paramsGet = { TableName:'some_table', Key:{"id":some_id} }
let database_item = await docClient.get(paramsGet, function(err,data){
if(err){
console.log(err)
} else {
console.log("SUCCESSFULL GET");
return data //RETURN IT TO VARIABLE
}
}
let paramsPut = {
TableName: "my_table",
Item: {
//THESE VALUES SHOULD WAIT FOR GET TO FINISH
//THEN SEND IT TO DB
"id":database_item.id,
"name":database_item.name,
"value":database_item.value
}
}
docClient.put(paramsPut, function(err,data){
if(err){
console.log(err)
} else {
console.log("SUCCESSFULL PUT");
}
}
}
DB();
When I do this let db_item = await docClient.get(...) and console.log(db_item) it will show a stream of data which doesn't look like anything being returned like data in if(err){ console.log(err) } else { console.log(data)}?
How should I handle this return value? Is it a promise or something else that needs to be parsed? I cannot seem to find the db_item.type..
Almost all AWS JavaScript SDK methods have .promise() function returning a Promise of the called operation.
As await works with Promises, you can use that for your needs:
try {
let res = await docClient.get(paramsGet).promise();
let database_item = res.Item;
console.log("SUCCESSFULL GET", database_item);
} catch(err) {
console.log(err);
}
You can find the description of the structure of the returned object in the documentation.
Similar for .put.

How to properly implement mongodb async/await inside a promise?

I've read that having an async inside a Promise is anti-pattern for async/await. The code below works, but I am curious how else to achieve the same result without having async in Promise.
If I remove it, the linter would tell how I can't use await in my mongodb query. If I remove the await in the mongodb query, then it wouldn't wait for the result.
export const getEmployees = (companyId) => {
return new Promise(async (resolve, reject) => {
const employees = await Employees.find(
{ companyId },
);
// other logic here...
resolve({
employees,
});
});
Thanks.
async functions automatically return Promises already, which resolve with whatever expression is eventually returned. Simply make getEmployees an async function:
export const getEmployees = async (companyId) => {
const employees = await Employees.find(
{ companyId },
);
// other logic here...
return { employees };
};
(but make sure to catch in the consumer of getEmployees just in case there's an error)
As #CertainPerformance answered, that is perfect way to retrieve data from mongoDB using async/await, I would like to add some more information about how to handle errors in this case for correctness of the system, and better error handle to return better status to the client about his request.
I'd say it , you usually want to catch all exceptions from async/await call.
try {
const employees = await Employees.find({
companyId
});
// You can add more logic here before return the data.
return {
employees
};
} catch (error) {
console.error(error);
}
Now let's check the ways we can handle our errors that might occur.
Handle error inside error scope.
Assign a default value to the variable in the catch block.
Inspect error instance and act accordingly.
This is the most common way to handle errors in those cases and most elegant way in my opinion.
Handle error inside error scope:
export const getEmployees = async (companyId) => {
try {
const employees = await Employees.find({
companyId
});
// You can add more logic here before return the data.
return {
employees
};
} catch (error) {
console.error(error);
}
};
Assign a default value to the variable in the catch block:
export const getEmployees = async (companyId) => {
let employees;
try {
employees = await Employees.find({
companyId
});
// You can add more logic here before return the data.
employees = employees;
} catch (error) {
console.error(error);
}
if (employees) { // We received the data successfully.
console.log(employees)
// Business logic goes here.
}
return employees;
};
Inspect error instance and act accordingly:
export const getEmployees = async (companyId) => {
try {
const employees = await Employees.find({
companyId
});
// You can add more logic here before return the data.
return {
employees
};
} catch (error) {
if (error instanceof ConnectionError) {
console.error(error);
} else {
throw error;
}
}
};
Some more explanations about async await and more useful methods that you can find in those answers.
How run async / await in parallel in Javascript

Categories

Resources