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

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 ]));

Related

Node.js with SQL: ERR_EMPTY_QUERY

I am trying to create a service with MySQL as database. I have my query stored in the database and am calling it in the node.js service. It is a two step process as the first query will give the query to be the run as the result to next connection. However my first part is working fine but when the connection moves to second part it gives ERR_EMPTY_QUERY.
Here is my code. The error is at the second part connection.query(dbresult, function (err, done)).
var async = require('async');
module.exports.getChart = function (chartcode,filter,callback){
var fuelweekquery= "SELECT * FROM t_chart_val where CHART_CODE=?"
if (null == filter || "week"==(filter)){
result= fuelweekquery;
}
async.parallel([
function (callback) {
//if(null==filter || "day"==(filter))
pool.getConnection(function (err, connection) {
connection.query(result,[chartcode], function (err, done) {
if (err) {
connection.release();
}
for (var i in done) {
dbresult = done[i].CHART_ATTR_VAL;
}
callback(err, done);
}
);
});
},
function (callback) {
//if(null==filter || "day"==(filter))
pool.getConnection(function (err, connection) {
connection.query(dbresult, function (err, done) {
if (err) {
connection.release();
}
//connection.release();
console.log(done);
callback(err, done);
}
);
});
},
], function (err, results) {
if (err) console.log(err);
//console.log('queries finished', results);
callback(null, results);
});
}
According to caolan async documentation (Source),
Run the tasks collection of functions in parallel, without waiting until the previous function has completed. If any of the functions pass an error to its callback, the main callback is immediately called with the value of the error. Once the tasks have completed, the results are passed to the final callback as an array.
Therefore it does not wait for the first in the array to complete before executing the second, for this I would recommend you use async waterfall
async.waterfall([
function(callback) {
//Your DB operation
callback(null, dbresult );
},
function(dbresult, callback) {
// use dbresult for your second operation
callback(null, 'done');
}
], function (err, result) {
// result now equals 'done'
});

MySQL user-defined variables in Node.js mysql module

I was wondering whether MySQL user-defined variables will work using Node.js mysql module. The example below highlight exactly what I want to achieve using a transaction:
connection.beginTransaction(err => {
if (err) { throw err; }
connection.query('INSERT INTO user SET = ?', {id: 12, username: 'name'}, (err, results) => {
if (err) {
return connection.rollback(function() {
throw error;
});
}
connection.query('SELECT #user_id:=userID FROM user WHERE username = ?', ['name'], (err, results) => {
if (err) {
return connection.rollback(function() {
throw error;
});
}
connection.query('INSERT INTO authentication SET `userID` = #user_id, ?', {password: 'userpassword'}, (err, results) => {
if (err) {
return connection.rollback(function() {
throw error;
});
}
connection.commit(err => {
if (err) {
return connection.rollback(function() {
throw err;
});
}
console.log('success!');
});
});
});
});
});
You might be wondering, why not use the result of the second query in the third query. The transaction function is wrapped inside a utility function that accepts queries as an argument to be executed using transaction.
If the above code sample doesn't work, please is there a concise way to achieve this. Thank you.
After running the code sample it failed not because of MySQL variable in the second query. This code sample is what works for me:
connection.beginTransaction(err => {
if (err) { throw err; }
connection.query('INSERT INTO user (id, username) VALUES(?, ?)', [12, 'name'], (err, results) => {
if (err) {
return connection.rollback(function() {
throw error;
});
}
connection.query('SELECT #user_id:=userID FROM user WHERE username = ?', ['name'], (err, results) => {
if (err) {
return connection.rollback(function() {
throw error;
});
}
connection.query('INSERT INTO authentication (id, password) VALUES (#user_id, ?), ['userpassword'], (err, results) => {
if (err) {
return connection.rollback(function() {
throw error;
});
}
connection.commit(err => {
if (err) {
return connection.rollback(function() {
throw err;
});
}
console.log('success!');
});
});
});
});
});

nodejs- unable to return result to controller function

From my Model, I fetch some articles from a MySQL database for a user.
Model
var mysql = require('mysql');
var db = mysql.createPool({
host: 'localhost',
user: 'sampleUser',
password: '',
database: 'sampleDB'
});
fetchArticles: function (user, callback) {
var params = [user.userId];
var query = `SELECT * FROM articles WHERE userId = ? LOCK IN SHARE MODE`;
db.getConnection(function (err, connection) {
if (err) {
throw err;
}
connection.beginTransaction(function (err) {
if (err) {
throw err;
}
return connection.query(query, params, function (err, result) {
if (err) {
connection.rollback(function () {
throw err;
});
}
//console.log(result);
});
});
});
}
This is working and the function fetches the result needed. But it's not returning the result to the controller function (I am returning it but I'm not able to fetch it in the controller function. I guess, I did something wrong here).
When I did console.log(result) this is what I got.
[ RowDataPacket {
status: 'New',
article_code: 13362,
created_date: 2017-10-22T00:30:00.000Z,
type: 'ebook'} ]
My controller function looks like this:
var Articles = require('../models/Articles');
exports.getArticle = function (req, res) {
var articleId = req.body.articleId;
var article = {
userId: userId
};
Articles.fetchArticles(article, function (err, rows) {
if (err) {
res.json({ success: false, message: 'no data found' });
}
else {
res.json({ success: true, articles: rows });
}
});
};
Can anyone help me figure out what mistakes I made here?
I'm pretty new to nodejs. Thanks!
The simple answer is that you're not calling the callback function, anywhere.
Here's the adjusted code:
fetchArticles: function (user, callback) {
var params = [user.userId];
var query = `SELECT * FROM articles WHERE userId = ? LOCK IN SHARE MODE`;
db.getConnection(function (err, connection) {
if (err) {
// An error. Ensure `callback` gets called with the error argument.
return callback(err);
}
connection.beginTransaction(function (err) {
if (err) {
// An error. Ensure `callback` gets called with the error argument.
return callback(err);
}
return connection.query(query, params, function (err, result) {
if (err) {
// An error.
// Rollback
connection.rollback(function () {
// Once the rollback finished, ensure `callback` gets called
// with the error argument.
return callback(err);
});
} else {
// Query success. Call `callback` with results and `null` for error.
//console.log(result);
return callback(null, result);
}
});
});
});
}
There's no point in throwing errors inside the callbacks on the connection methods, since these functions are async.
Ensure you pass the error to the callback instead, and stop execution (using the return statement).
One more thing, without knowing the full requirements of this:
I'm not sure you need transactions for just fetching data from the database, without modifying it; so you can just do the query() and skip on using any beginTransaction(), rollback() and commit() calls.

How to do transaction operation for a number of mysql queries in node js using promise?

I have 27 mysql update queries on a single table. All these queries are to be run in a transaction mode, like if one operation fails all other updated queries should rollback.
How I will implement this nodejs with promises?
I'm assuming you use mysql driver found here.
According to documentation, this drivers natively supports transactions like this (copied from documentation):
connection.beginTransaction(function(err) {
if (err) { throw err; }
connection.query('INSERT INTO posts SET title=?', title, function (error, results, fields) {
if (error) {
return connection.rollback(function() {
throw error;
});
}
connection.query('INSERT INTO log SET data=?', log, function (error, results, fields) {
if (error) {
return connection.rollback(function() {
throw error;
});
}
connection.commit(function(err) {
if (err) {
return connection.rollback(function() {
throw err;
});
}
console.log('success!');
});
});
});
});
Since you mentioned promises, you would want to use promise-mysql package which wraps mysql calls in Bluebird promises.
You can use below approach to handle your secnario.
Create an array of queries which needs to be run. In below example I have created queries and their placeholder values and passed them as an array of objects
return a promise to caller which gets resolved when all queries supposed to be run inside a transaction finished
start the transaction
run queries one by one inside transaction block
store the result inside an accumulator as soon as query returns result
if all transactions are complete the commit the transaction and return accumulated result to caller
otherwise rollback the transaction and return the error to caller via promise
Below is the approach which I have mentioned -
function executeTransaction(queries) {
try {
const connection = yield getConnectionObj({/* your db params to get connection */)
let results = []
return new Promise(function(resolve, reject) {
connection.beginTransaction(function (err) {
if (err) throw err
console.log("Starting transaction")
queries
.reduce(function (sequence, queryToRun) {
return sequence.then(function () {
parent.query(queryToRun.query, queryToRun.values)
/* pass your query and connection to a helper function and execute query there */
return queryConnection(
connection,
query,
queryParams,
).then(function (res) {
/* Accumulate resposes of all queries */
results = results.concat(res)
})
}).catch(function (error) {
reject(error)
})
}, Promise.resolve())
.then(function () {
connection.commit(function (err) {
if (err) {
/* rollback in case of any error */
connection.rollback(function () {
throw err
})
}
console.log('Transactions were completed!')
/* release connection */
connection.release()
/* resolve promise with all results */
resolve({ results })
})
})
.catch(function (err) {
console.log('Transaction failed!')
connection.rollback(function () {
console.log('Abort Transaction !!!')
throw err
})
})
})
})
/* End Transaction */
} catch (error) {
return Promise.reject(error)
}
}

NodeJs Mysql returns empty result

I have api's that queries the data and returns the results in json format. if i call the query inside api.get and set the res.send(rows) it is working fine but i need to call that same method in different methods so I thought I could write it outside and call that method whenever it is needed. but the result returns empty when it is outside.
var customerRows[]
app.get('/customers', function(req, res) {
getCustomers();
res.json({
customers : customerRows
});
});
function getCustomersQuery(callback) {
var customersDataQuery = mysqlConnection.query('SELECT * from customer_info', function(err, rows, fields) {
if (!err) {
if (rows) {
callback(null, rows);
}
} else {
callback(err, null);
console.log('Error while performing Query.');
}
});
}
function getCustomers() {
getCustomersQuery(function(err, result) {
if (err) {
console.log(err);
} else {
customerRows.push(result);
console.log(customerRows)//prints values
}
});
console.log("Result : "+customerRows);//prints empty
}
I'm trying to set the result to my global variable customerRows but it returns empty.
use this code
app.get('/customers', function(req, res) {
getCustomersQuery(function(err, result) {
if (err) {
console.log(err);
}
res.json({
customers : result
});
});
});
function getCustomersQuery(callback) {
var customersDataQuery = mysqlConnection.query('SELECT * from customer_info', function(err, rows, fields) {
if (!err) {
if (rows) {
callback(null, rows);
}
} else {
callback(err, null);
console.log('Error while performing Query.');
}
});
}