Node.js mysql transaction - mysql

Can anyone provide an example of how I could achieve MySQL transactions in Node.js. I am trying to get my head around using the node-mysql driver and node-mysql-queue.
As far are I can tell, using node-mysql-queue greatly reduces the asynchronous nature of Node.js as new queries have to wait until existing ones have completed. To get around this, has anyone attempted to combine node-mysql-queue with node-mysql's connection-pooling capabilities. i.e starting a new mysql connection for each new http request, and starting transaction queues on individual connections?

Update
See the edit below for async/await syntax
I spent some time writing a generalized version of the transaction example given by node mysql, so I thought I would share it here. I am using Bluebird as my promise library, and used it to 'promisify' the connection object which simplified the asynchronous logic a lot.
const Promise = ('bluebird');
const mysql = ('mysql');
/**
* Run multiple queries on the database using a transaction. A list of SQL queries
* should be provided, along with a list of values to inject into the queries.
* #param {array} queries An array of mysql queries. These can contain `?`s
* which will be replaced with values in `queryValues`.
* #param {array} queryValues An array of arrays that is the same length as `queries`.
* Each array in `queryValues` should contain values to
* replace the `?`s in the corresponding query in `queries`.
* If a query has no `?`s, an empty array should be provided.
* #return {Promise} A Promise that is fulfilled with an array of the
* results of the passed in queries. The results in the
* returned array are at respective positions to the
* provided queries.
*/
function transaction(queries, queryValues) {
if (queries.length !== queryValues.length) {
return Promise.reject(
'Number of provided queries did not match the number of provided query values arrays'
)
}
const connection = mysql.createConnection(databaseConfigs);
Promise.promisifyAll(connection);
return connection.connectAsync()
.then(connection.beginTransactionAsync())
.then(() => {
const queryPromises = [];
queries.forEach((query, index) => {
queryPromises.push(connection.queryAsync(query, queryValues[index]));
});
return Promise.all(queryPromises);
})
.then(results => {
return connection.commitAsync()
.then(connection.endAsync())
.then(() => {
return results;
});
})
.catch(err => {
return connection.rollbackAsync()
.then(connection.endAsync())
.then(() => {
return Promise.reject(err);
});
});
}
If you wanted to use pooling as you suggested in the question, you could easily switch the createConnection line with myPool.getConnection(...), and switch the connection.end lines with connection.release().
Edit
I made another iteration of the code using the mysql2 library (same api as mysql but with promise support) and the new async/await operators. Here is that
const mysql = require('mysql2/promise')
/** See documentation from original answer */
async function transaction(queries, queryValues) {
if (queries.length !== queryValues.length) {
return Promise.reject(
'Number of provided queries did not match the number of provided query values arrays'
)
}
const connection = await mysql.createConnection(databaseConfigs)
try {
await connection.beginTransaction()
const queryPromises = []
queries.forEach((query, index) => {
queryPromises.push(connection.query(query, queryValues[index]))
})
const results = await Promise.all(queryPromises)
await connection.commit()
await connection.end()
return results
} catch (err) {
await connection.rollback()
await connection.end()
return Promise.reject(err)
}
}

The following transaction example was added to the documentation a month ago:
https://github.com/felixge/node-mysql#transactions
connection.beginTransaction(function(err) {
if (err) { throw err; }
connection.query('INSERT INTO posts SET title=?', title, function(err, result) {
if (err) {
connection.rollback(function() {
throw err;
});
}
var log = 'Post ' + result.insertId + ' added';
connection.query('INSERT INTO log SET data=?', log, function(err, result) {
if (err) {
connection.rollback(function() {
throw err;
});
}
connection.commit(function(err) {
if (err) {
connection.rollback(function() {
throw err;
});
}
console.log('success!');
});
});
});
});

I am using the following approach. There is an add function in my Model where I am performing database operations.
add : function (data, callback) {
//Begin transaction
connection.beginTransaction(function(err) {
if (err) {
throw err;
}
var user_query = "INSERT INTO `calldata`.`users` (`username`, `password`, `enabled`, `accountNonExpired`, `accountNonLocked`, `credentialsNonExpired`) VALUES ('" + data.mobile + "', '" + sha1(data.password) + "', '1', '1', '1', '1')";
connection.query(user_query, function(err, results) {
if (err) {
return connection.rollback(function() {
throw err;
});
}
var accnt_dtls_query = "INSERT INTO `calldata`.`accnt_dtls` (`req_mob_nmbr`, `usr_nme`, `dvce_id`, `mngr_id`, `cmpny_id`, `actve_flg`, `crtd_on`, `usr`) VALUES (" + data.mobile + ", '" + data.name + "', '', " + data.managerId + ", " + data.companyId + ", 1, now(), '" + data.mobile+ "')";
connection.query(accnt_dtls_query, function(err, results) {
if (err) {
return connection.rollback(function() {
throw err;
});
}
var user_role_query = "INSERT INTO `calldata`.`user_roles` (`username`, `ROLE`) VALUES ('" + data.mobile + "', '" + data.role + "')";
connection.query(user_role_query, function(err, result) {
if (err) {
return connection.rollback(function() {
throw err;
});
}
//add an entry to manager table
var mngr_dtls_query = "INSERT INTO `calldata`.`mngr_dtls` (`mngr_nm`, `cmpny_id`, `crtd_on`, `usr_nm`, `eml_id`) VALUES ('" + data.name + "'," + data.companyId + " , now(), '" + data.mobile + "', '" + data.mobile + "')";
connection.query(mngr_dtls_query, function(err, result) {
if (err) {
return connection.rollback(function () {
throw err;
});
}
console.log('Changed ' + result.changedRows + ' results');
connection.commit(function (err) {
console.log('Commiting transaction.....');
if (err) {
return connection.rollback(function () {
throw err;
});
}
console.log('Transaction Complete.');
connection.end();
callback(null, result);
});
});
});
});
});
});
//transaction ends here
}
and calling from controller:
agentAccountModel.add(data, function(err, results) {
if(err)
{
res.status(500);
res.json({
"status": 500,
"message": err
});
}
res.status(200);
res.json({
"status": 200,
"message": "Saved successfully"
});
});

I have come up with a solution using a recursive function.
var sql = 'INSERT INTO logs SET data = ?';
// array of rows to insert
var rows = [[/*first row*/], [/*additional row*/]];
connection.beginTransaction(function (err) {
if (err) {
throw err;
}
var insertEachRow = function () {
var row = rows.shift();
if (! row) {
// Done, now commit
return noMoreRows();
}
connection.query(sql, row, function (err, result) {
if (err) {
connection.rollback(function () {
throw err;
});
}
insertEachRow();
});
};
var noMoreRows = function () {
connection.commit(function (err) {
if (err) {
connection.rollback(function () {
throw err;
});
}
console.log('success!');
});
};
insertEachRow();
});

I found one useful links which uses node js mysql pooling with transaction. Database Connection pooling is always useful. One can check this link
https://github.com/mysqljs/mysql

I created a wrapper ORM type thing for this specific purpose, hope it helps SQl-connecton - pool ORM type helper methods

You can use it with the mysql also, just use this function without any parameters /call back .
Make sure you have async function and use these instead
await con.rollback();
await con.beginTransaction();
await con.commit();
Life set.....

Related

NPM - MYSQL - Passing query results to another variable

I was looking for some help in regards to a database package that I'm building for a larger application. The application will essentially keep certain information on a database through the use of the mysql package in npm. The problem I'm facing is that when I try to pass the results variable to a variable that I've created I always get undefined. Though if I place a log statement underneath the variable transfer statement displaying the results variable it will be properly populated. I've done a fair amount of research and believe that this has something to do with promises and/or the callback function that's part of the query. I was wondering if I could have someone examine my code and let me know the best course of action. I've spent a couple of hours on this researching online and trying various solutions but nothing has worked.
const mysql = require("mysql");
var sqlResults;
var pool = mysql.createPool({
host: "example.com",
user: "exampleUser",
password: "123456Password",
database: "TestDB"
});
module.exports = {
...
databaseSelect: function(table, fields, conditionalStmt) {
pool.getConnection(function(err, connection) {
if (err)
throw err;
console.log("Connected to the example DB!");
var sql = "SELECT " + fields + " FROM " + table + " " + conditionalStmt;
connection.query(sql, function(error, results, fields) {
console.log("Successfully retrieved records from " + table + "\n\t" + sql);
sqlResults = results;
connection.release();
sqlResults = results;
console.log(results);
if (error)
throw error;
});
});
console.log(sqlResults);
return sqlResults;
}
}
Here's a sample of the output that I'm receiving:
I am ready!
undefined
undefined
Connected to the example DB!
Successfully retrieved records from User_Level_Info
SELECT HashID, Level, Experience FROM User_Level_Info WHERE HashID = 'e578059cabc6f937f0219127384126143e272acbac52c331345d573e0f085d21'
[ RowDataPacket {
HashID: 'e578059cabc6f937f0219127384126143e272acbac52c331345d573e0f085d21',
Level: 1,
Experience: 0 } ]
Here convert it into a Promise.
So this will wait until it gets resolved or reject
databaseSelect: function(table, fields, conditionalStmt) {
return new Promise(function(resolve, reject) {
pool.getConnection(function(err, connection) {
if (err)
return reject(err);
console.log("Connected to the example DB!");
var sql = "SELECT " + fields + " FROM " + table + " " + conditionalStmt;
connection.query(sql, function(error, results, fields) {
console.log("Successfully retrieved records from " + table + "\n\t" + sql);
sqlResults = results;
connection.release();
sqlResults = results;
console.log(results);
if (error)
return reject(error);
});
});
resolve(sqlResults);
});
}
//call your function
databaseSelect(params)
.then(function(rows) {
console.log(rows)
})
.catch((err) {
console.log(err)
}); // Throw async to escape the promise chain

Array push doesn't work in promise node.js

I have previously attempted to wrap this code in callbacks, async what ever the language has to offer. However, I am still getting nowhere.
The problem is, that members remains empty, even though it should be pushed with info.
channels however, works fine.
Weirdly, the
console.log(values);
prints before the
console.log(result);
Interestingly though,
console.log(result)
does have the correct data, but where I do
console.log(members)
it is empty.
I have tested, the query is all correct, it is literally a problem with the pushing and getting the result earlier than it currently is returned (I assumed Promises would mean things would be more in order, but maybe my understanding is wrong).
Here is the full code:
module.exports.controller = (query, helper, cache, Database, mysql, config) => {
return async (req, res) => {
let zone = req.body.zone;
let returning = {};
if(!zone){
return res.json(helper.responseJson(false, 'Missing parameter "zone"'));
}
function teleport_available(channel_name){
if(channel_name.indexOf("Nadaj / UsuĊ„") === -1){
return true;
}else{
return false;
}
}
await mysql.query("SELECT * FROM flamespeak_pbot.zones WHERE zone = '"+ zone +"'", async (err, row) => {
if (err) throw err;
row = row[0];
if (row.length == 0) {
return res.json(helper.responseJson(false, "Zone not found."));
} else {
var channelsPromise = new Promise((resolve, reject) => {
const channels = [];
JSON.parse(row.assignGroupAdditional_ch).forEach(additionalCh => {
cache.channelList.filter(channel => channel.cid == additionalCh).forEach(mainCh => {
mainCh.propcache.teleport_available = teleport_available(mainCh.propcache.channel_name);
mainCh.propcache.subchannels = [];
cache.channelList.filter(channel => channel.pid == additionalCh).forEach(subCh => {
subCh.propcache.teleport_available = teleport_available(mainCh.propcache.channel_name);
mainCh.propcache.subchannels.push(subCh);
});
channels.push(mainCh.propcache);
});
});
resolve(channels);
});
var membersPromise = new Promise((resolve, reject) => {
let members = [];
query.serverGroupClientList(row.serverGroupID)
.then(serverGroupList => {
serverGroupList.forEach(member => {
var sql = "SELECT * FROM teamspeak_clientDbList WHERE client_database_id = '" + member.cldbid + "'";
mysql.query(sql, function (err, result) {
if (err) throw err;
console.log(result);
members.push(result);
})
});
})
.then(() => {
console.log(members);
resolve(members);
});
});
}
Promise.all([channelsPromise, membersPromise]).then(function(values) {
console.log(values);
returning = {
'channels' : values[0],
'members' : values[1],
'pbot' : row,
};
res.send(helper.responseJson(true, returning));
});
});
};
};
With respect, your mixture of callbacks, promises, and async / await is very hard to follow. You'd be wise to simplify it. (Or risk having your name cursed by the person who must maintain this code later.)
The callback in your second mysql.query() method is called whenever MySQL wants (whenever the query finishes or fails). But you don't resolve any promise or return from an async method within that callback's code. So, your console.log(result) shows a correct result, but your other code doesn't get access to that result; your Promise.all resolves before that method is called.
Something like this would, in general, be the right thing to do:
var sql =
"SELECT * FROM teamspeak_clientDbList WHERE client_database_id = '" +
member.cldbid + "'";
mysql.query(sql, function (err, result) {
if (err) throw err;
console.log(result);
members.push(result);
resolve (result); // <<< add this line
})
But in your case it probably won't work: your function query.serverGroupClientList() seems to return a Promise. You didn't show us that function, so it's hard to guess how to weave that into your logic.
query.serverGroupClientList(row.serverGroupID)
.then(serverGroupList => {
let members = [];
serverGroupList.forEach(member => {
var sql = "SELECT * FROM teamspeak_clientDbList WHERE client_database_id = '" + member.cldbid + "'";
mysql.query(sql, function (err, result) {
if (err) throw err;
console.log(result);
members.push(result);
})
});
resolve(members);
})
Try this
SELECT * FROM teamspeak_clientDbList
While iterating serverGroupList, the db Query on each element is asynchronous. Hence empty members.
Moreover foreach expects a synchronous function so you will have to use for-loop or for-of to use async-await feature.
var membersPromise = new Promise((resolve, reject) => {
let members = [];
query.serverGroupClientList(row.serverGroupID)
.then(async (serverGroupList) => {
console.log("serverGroupList: ", serverGroupList);
for (let index = 0, arrLen = serverGroupList.length; index < arrLen; index++) {
let member = serverGroupList[index];
var sql = "SELECT * FROM teamspeak_clientDbList WHERE client_database_id = '" + member.cldbid + "'";
await mysql.query(sql, async function (err, result) {
if (err) throw err;
console.log(result);
members.push(result);
});
}
console.log(members);
resolve(members);
});
});
Nested Code can be improved if you write a wrapper function for db queries which returns a promise.
function fireQuery(mysql, query, params = []) {
return new Promise((resolve, reject) => {
return mysql.query(query, params, (err, data) => {
if (err) return reject(err);
return resolve(data);
});
});
}
// ex -1
let rows = await fireQuery(mysql,query,params);
if(rows.length == 0) {} // your code
// ex-2
let member = await fireQuery(mysql,query2,params2);
members.push(member);

Node JS Inserting array of objects to mysql database when using transactions

Am using node-mysql to add records to a database but am facing a challenge when the records to be inserted are an array of objects and I need the operation to be a transaction. I have simplified my problem by creating a test project to better explain my problem.
Lets say I have to tables users and orders and the data to be inserted looks like this
var user = {
name: "Dennis Wanyonyi",
email: "example#email.com"
};
var orders = [{
order_date: new Date(),
price: 14.99
}, {
order_date: new Date(),
price: 39.99
}];
I want to first insert a user to the database and use the insertId to add the each of the orders for that user. Am using a transaction since in case of an error, I want to rollback the whole process. Here is how I try to insert all the records using node-mysql transactions.
connection.beginTransaction(function(err) {
if (err) { throw err; }
connection.query('INSERT INTO users SET ?', user, function(err, result) {
if (err) {
return connection.rollback(function() {
throw err;
});
}
for (var i = 0; i < orders.length; i++) {
orders[i].user_id = result.insertId;
connection.query('INSERT INTO orders SET ?', orders[i], function(err, result2) {
if (err) {
return connection.rollback(function() {
throw err;
});
}
connection.commit(function(err) {
if (err) {
return connection.rollback(function() {
throw err;
});
}
console.log('success!');
});
});
}
});
});
However I have a problem iterating over the array of orders without having to call connection.commit multiple times within the for loop
I would suggest to construct a simple string for multiple row insert query for orders table in the for loop first and then execute it outside the for loop. Use the for loop to only construct the string. So you can rollback the query whenever you want or on error. By multiple insert query string i mean as follows:
INSERT INTO your_table_name
(column1,column2,column3)
VALUES
(1,2,3),
(4,5,6),
(7,8,9);
You can use Promise.all functionality of Bluebird for this.
var promiseArray = dataArray.map(function(data){
return new BluebirdPromise(function(resolve, reject){
connection.insertData(function(error, response){
if(error) reject(error);
else resolve(response);
}); //This is obviously a mock
});
});
And after this:
BluebirdPromise.all(promiseArray).then(function(result){
//result will be the array of "response"s from resolve(response);
database.commit();
});
This way, you can work all the inserts asyncronously and then use database.commit() only once.
Some kind of task in Node.js are Asynchronous( like I/O , DB and etc..), and there is a lots of LIBS that help to handle it.
but if you want don't use any lib,for iterating an array in JS and use it in an asynchronous functionality its better to implement it as recursive function.
connection.beginTransaction(function(err) {
if (err) {
throw err;
}
connection.query('INSERT INTO users SET ?', user, function(err, result) {
if (err) {
return connection.rollback(function() {
throw err;
});
}
// console.log(result.insertId) --> do any thing if need with inserted ID
var insertOrder = function(nextId) {
console.log(nextId);
if ((orders.length - 1) < nextId) {
connection.commit(function(err) {
if (err) {
return connection.rollback(function() {
throw err;
})
}
console.log(" ok");
});
} else {
console.log(orders[nextId]);
connection.query('INSERT INTO orders SET ?', orders[nextId], function(err, result2) {
if (err) {
return connection.rollback(function() {
throw err;
});
}
insertOrder(nextId + 1);
});
}
}
insertOrder(0);
});
});
as you can see I rewrite your for loop as a recursive function inside.
I would use the async.each to do the iteration and to fire all the queries in parallel. If some of the queries will fail, the asyncCallback will be called with an error and the program will stop processing the queries. This will indicate that we should stop executing queries and rollback. If there is no error we can call the commit.
I' ve decoupled the code a bit more and split it into functions:
function rollback(connection, err) {
connection.rollback(function () {
throw err;
});
}
function commit(connection) {
connection.commit(function (err) {
if (err) {
rollback(connection, err);
}
console.log('success!');
});
}
function insertUser(user, callback) {
connection.query('INSERT INTO users SET ?', user, function (err, result) {
return callback(err, result);
});
}
function insertOrders(orders, userId, callback) {
async.each(orders, function (order, asyncCallback) {
order.user_id = userId;
connection.query('INSERT INTO orders SET ?', order, function (err, data) {
return asyncCallback(err, data);
});
}, function (err) {
if (err) {
// One of the iterations above produced an error.
// All processing will stop and we have to rollback.
return callback(err);
}
// Return without errors
return callback();
});
}
connection.beginTransaction(function (err) {
if (err) {
throw err;
}
insertUser(user, function (err, result) {
if (err) {
rollback(connection, err);
}
insertOrders(orders, result.insertId, function (err, data) {
if (err) {
rollback(connection, err);
} else {
commit(connection);
}
});
});
});
you need to use async library for these kind of operation.
connection.beginTransaction(function(err) {
if (err) { throw err; }
async.waterfall([
function(cb){
createUser(userDetail, function(err, data){
if(err) return cb(err);
cb(null, data.userId);
});
},
function(userid,cb){
createOrderForUser(userid,orders, function() {
if(err) return cb(err);
cb(null);
});
}
], function(err){
if (err)
retrun connection.rollback(function() {
throw err;
});
connection.commit(function(err) {
if (err) {
return connection.rollback(function() {
throw err;
});
}
console.log('success!');
});
});
});
var createUser = function(userdetail, cb){
//-- Creation of Orders
};
var createOrderForUser = function (userId, orders, cb) {
async.each(orders, function(order, callback){
//-- create orders for users
},function(err){
// doing err checking.
cb();
});
};
See if you can write a Stored Procedure to encapsulate the queries, and have START TRANSACTION ... COMMIT in the SP.
The tricky part comes with needing to pass a list of things into the SP, since there is no "array" mechanism. One way to achieve this is to have a commalist (or use some other delimiter), then use a loop to pick apart the list.
currentLogs = [
{ socket_id: 'Server', message: 'Socketio online', data: 'Port 3333', logged: '2014-05-14 14:41:11' },
{ socket_id: 'Server', message: 'Waiting for Pi to connect...', data: 'Port: 8082', logged: '2014-05-14 14:41:11' }
];
console.warn(currentLogs.map(logs=>[ logs.socket_id , logs.message , logs.data , logs.logged ]));

Connection.query() return the rows undefined

I have a method getconnection() that creates connection for me.
Here's the function.
I get undefined even if I am fetching result of query that is rows to callback function to send the response to controller.
Can someone help me out.Thanks in advance.
function fetchUserInfo(req,res)
{
var getUserInfo = "select First_Name, Last_Name,COUNT(*) FROM twitter.user_information INNER join twitter.tweets ON user_information.Twitter_Handle = tweets.Twitter_handle WHERE user_information.Twitter_Handle = '"+req.session.handle+"' ";
console.log("Fetch User-------------"+getUserInfo);
mysql.fetchUser(function(err,result){
if(err)
{ console.log("cannot be retrieved");
throw err;
}
else
{
console.log("Successfully retrieved");
console.log("---------------" + result);
console.log(result.First_Name);
res.send(result);
}
},getUserInfo);
}
// mysql function
function fetchUser(callback,sqlQuery)
{
console.log("In fetch Data");
console.log("SQLquery is:::"+sqlQuery);
connection.getConnection().query(sqlQuery, function(err,rows,fields){
if(err)
{
console.log("Error :"+err.message);
}
else
{
console.log("Result is :" + rows.First_Name);
callback(err, rows);
}
})
}
Try this:
console.log("Result is :" + rows[0].First_Name);
or try to print the rows variable to the console instead of rows.First_Name:
console.log(rows);

How to get the results from nodejs using mysql package?

first, i connect the db and select DB:
var defaultOptions = {
user: "root",
pwd:'admin',
db:"britcham_dev_local",
server:"local", // Maybe we don't need this variable.
};
var client = new Client();
client.user = defaultOptions.user;
client.password = defaultOptions.pwd;
client.connect(function (error, results) {
//
});
client.query('USE ' + defaultOptions.db, function (error, results) {
//
});
Second, I query with client object:
var self = this;
var this.users;
client.query("SELECT * FROM users", function (error, results, fields) {
if (error) {
//
}
if (results.length > 0) {
self.users = results;
}
});
console.log(this.users);
it's nothing output ??? Why ??
Since node.js is non-blocking and asynchronous, then in this code:
client.query("SELECT * FROM users", function (error, results, fields) {
if (error) {
//
}
if (results.length > 0) {
self.users = results;
}
});
console.log(this.users);
data from DB are not probably loaded yet into users variable when you are trying to log it into console. You can check it out if you do your console.log operation within the query, for example:
client.query("SELECT * FROM users", function (error, results, fields) {
if (error) {
//
}
if (results.length > 0) {
console.log(results);
}
});
To pass the result into a variable when the operation is finished you can wrap your client DB call into a function with callback parameter and set your variable when the callback is invoked, for example:
function query(sql, callback) {
client.query(sql, function (error, results, fields) {
if (error) {
//
}
if (results.length > 0) {
callback(results);
}
});
}
query("SELECT * FROM users", function(results) {
self.users = results;
console.log(self.users);
});
Above code is just a concept.
How is the suggested answer different from this?
var self = this;
var this.users;
client.query("SELECT * FROM users", function (error, results, fields) {
if (error) {
//
}
if (results.length > 0) {
self.users = results;
console.log(this.users);
}
});
I might be wrong this is not different from the suggested answer in that it writes to console no sooner than when we have the data back from the DB.
The suggested answer seems only to add yet another function?