Node.js asynchronous nested mysql queries - mysql

To set the scenario for the code, the database stores Documents, and each document has the potential to have Images associated with them.
I have been trying to write a route that queries the database for each Document that has Images related to them, storing this data in JSON which is returned to the ajax request when completed, so the data can be viewed on the page. The closest I have got so far is the below attempt (see code).
router.post('/advanced_image_search', userAuthenticated, function(req, res, next) {
async.waterfall([
// First function is to get each document which has an image related
function getDocuments(callback){
connection.query(`SELECT DISTINCT(Document.document_id), Document.doc_name, Document.doc_version_no, Document.doc_date_added
FROM Document WHERE doc_type = 'image'`, function(err, results) {
if (err) {
callback(err, null);
return;
}
// The Object containing the array where the data from the db needs to be stored
var documents = {
'docs': []
};
// foreach to iterate through each result found from the first db query (getDocuments)
results.forEach(function(result) {
// New object to store each document
var document = {};
document.entry = result;
// This is the array where each image assciated with a document will be stored
document.entry.images = [];
// Push each document to the array (above)
documents.docs.push(document);
var doc_id = result.document_id;
})
// Returning the results as 'documents' to the next function
callback(null, documents);
})
},
function getImages(documents, callback){
// Variable assignement to the array of documents
var doc_array = documents.docs;
// Foreach of the objects within document array
async.forEachOf(doc_array, function(doc, key, callback){
// Foreach object do the following series of functions
async.waterfall([
function handleImages(callback){
// The id of the document to get the images for
var doc_id = doc.entry.document_id;
connection.query(`SELECT * FROM Image, Document WHERE Image.document_id = '${doc_id}' AND Image.document_id = Document.document_id`, function(err, rows) {
if (err) {
callback(err, null);
return;
}
callback(null, rows);
})
},
// Function below to push each image to the document.entry.images array
//
function pushImages(rows, callback){
// If multiple images are found for that document, the loop iterates through each pushing to the images array
for (var j = 0; j < rows.length; j++) {
// Creating new object for each image found so the data can be stored within this object, then pushed into the images array
var image = {
'image_name': rows[j].image_name
};
doc.entry.images.push(image);
}
callback(null, doc_array);
}
], function(err, doc_array){
if (err) {
console.log('Error in second waterfall callback:')
callback(err);
return;
}
console.log(doc.entry);
// callback(null, doc_array);
})
}, function(err, doc_array){
if (err) {
callback(err);
return;
}
callback(null, doc_array);
});
callback(null, doc_array);
}
], function(err, doc_array) {
if (err){
console.log('Error is: '+err);
return;
}
// The response that should return each document with each related image in the JSON
res.send(doc_array);
})
});
At the moment the results returned are:
1:
{entry: {document_id: 1, doc_name: "DocumentNameHere", doc_version_no: 1,…}}
entry:
{document_id: 1, doc_name: "DocumentNameHere", doc_version_no: 1,…}
doc_date_added:"2016-10-24"
doc_name:"DocumentNameHere"
doc_version_no:1
document_id:1
images:[]
As can be seen above, the images array remains empty even though with testing, the images are being found (console.log).
I hope someone is able to assist with this, as I am struggling to find the problem with this complex one.
Thanks

There are several async operations going on here and each operation needs a callback. See revised code:
router.post('/advanced_image_search', userAuthenticated, function(req, res, next) {
var getDocuments = function(next) {
// Function for getting documents from DB
var query = `SELECT DISTINCT(Document.document_id), Document.doc_name, Document.doc_version_no, Document.doc_date_added FROM Document WHERE doc_type = 'image'`; // Set the query
connection.query(query, function(err, results) {
// Run the query async
if(err) {
// If err end execution
next(err, null);
return;
}
var documentList = []; // Array for holding docs
for(var i=0; i<results.length; i++) {
// Loop over results, construct the document and push to an array
var documentEntry = results[i];
var documentObject = {};
documentObject.entry = documentEntry;
documentObject.entry.images = [];
documentObject.id = documentEntry.document_id;
documentList.push(documentObject);
}
next(null, documents); // Pass to next async operation
});
};
var getImages = function(documents, next) {
// Function for getting images from documents
var finalDocs = []; // Blank arry for final documents with images
for (var i=0; i<documents.length; i++) {
// Loop over each document and construct the query
var id = documents[i].id;
var query = `SELECT * FROM Image, Document WHERE Image.document_id = '${doc_id}' AND Image.document_id = Document.document_id`;
connection.query(query, function(err, images) {
// Execute the query async
if(err) {
// Throw error to callback
next(err, null);
return;
}
var processedDoc = processImages(documents[i], images); // Call a helper function to process all images into the document object
finalDocs.push(processedDoc); // Push the processed doc
if(i === documents.length) {
// If there are no more documents move onto next async
next(null, finalDocs);
}
});
}
};
var processImages = function(doc, images) {
for (var i=0; i< images.length; i++) {
// Loop over each document image - construct object
var image = {
'image_name': rows[j].image_name
};
doc.entry.images.push(image); // Push image into document object
}
return doc; // Return processed doc
};
getDocuments(function(err, docs) {
if(err) {
// Your error handler
}
if(docs) {
getImages(docs, function(err, finalDocs) {
if(err) {
// Your error handler
}
if(finalDocs) {
console.log(finalDocs);
res.status(200).json(finalDocs); // Send response
}
});
}
});
});
First we create a function to get documents - This function accepts a callback as its parameter. We run our query and construct our doc list. Then we return the document list by executing our callback
Next we run a function to get our images for each document. This function accepts our document list and a callback as our parameters. It retrieves images for each document and calls a helper function (sync)
Our helper function processes the images into each document and returns the processed document.
We then finish our operations by returning an array of processed documents via the second callback.
Other notes
We could tide this up by structuring this procedural style code into a contained JSON object
The nesting of the function executions at the end of the document could be cleaned up further
I've avoided using the async libray as it helps better understanding of the callback model
Event emitters could be used to flatten the callbacks - see https://nodejs.org/dist/latest-v7.x/docs/api/events.html
Hope this helps
Dylan

Related

How to get total file count of a specific folder including subfolder

I know there is NOT a direct way to get all files + folders of a hierarchy of BOX folder. we have to recursively get items of subfolders.
However, if I simply need a count estimation, is it possible to get the basic info?
The Transferring tool https://www.multcloud.com can estimate the file count firstly, after it is done, it will start the transferring. In my observation, it looks it also recursively iterates the folders, but how it can know the recursive has completed?
Thank you for any hints!
If your tool is executed on the same machine on different dates, you can maintain some "estimation data" (i.e. persist the number of files of certain directories in a run, and use this old number of files contained in a subtree as estimation values for further runs)
Hi finally found it is just a common question on how to determine a recursively iteration has completed. The other post helped a lot. How to detect completion of recursive asynchronous calls in javascript
With the hints, my code works well. The below is the script for reference. It can be converted to Promise. I have not tried, though.
function buildTreeNodes(boxTopFolderId){
function startBuild(){
//get the top folder info
box.folders.getItems(boxTopFolderId ,
function (err, data) {
var results = [];
results.finished = 0;
var len = data.entries.length;
if (err){
console.log(err);
}
else{
//iterate from the top folder
data.entries.forEach(function (item, index) {
// BOX of file or folder
var boxid = item.id;
// file or folder
var boxtype = item.type;
//item name
var boxname = item.name;
if(boxtype === 'folder'){
recursiveFolder(boxid,function(result){
results[index] = result;
if (++results.finished == len) {
//recursion done!
}
});
}else{
results[index] = item;
if (++results.finished == len) {
//recursion done!
}
}
}); //end for each
if(len == 0)
//recursion done!
}
});
}
function recursiveFolder(folderId,callback){
box.folders.getItems(folderId,
function (err, data) {
var results = [];
results.finished = 0;
var len = data.entries.length;
if (err){
console.log(err);
}
else{
//iterate from the top folder
data.entries.forEach(function (item, index) {
// BOX of file or folder
var boxid = item.id;
// file or folder
var boxtype = item.type;
//item name
var boxname = item.name;
if(boxtype === 'folder'){
recursiveFolder(boxid,function(result)
{
results[index] = result;
if (++results.finished == len) {
callback(results);
}
});
}
else{
results[index] = item;
if (++results.finished == len){
callback(results);
}
}
});
if(len == 0)
callback(results);
}
});
}
startBuild();
}

send data to express.js in a for loop

I need help with this, I really can't crack this one up.
I have this web site using Node.js to call mySQL queries and display them as tables, each in it's own page. To do so, I created 4 objects that include a name, a title and the query. when I have this code going four times (from queryPage[0] to queryPage[3]), It works great. But i want to put it into a for loop so I won't need 4 repetitions of the same lines, but then it doesn't work. the pages don't load... Any idea what is going on?
connection.query(queryPage[0].query, function(err, rows) {
var arr = [];
for (var i in rows) {
arr[i] = rows[i];
}
app.get('/' + queryPage[0].name, function(req, res) {
res.render('pages/' + queryPage[0].name, {
title: queryPage[0].title,
data: arr
});
});
});
I think you need something more like this
var queryPage = [ ... ];
// This route catches all request
app.get('/:name', function (req, res, next) {
// :name = req.params.name
var page;
for(var i = 0, l = queryPage.length; i < l; i++)
if(queryPage[i].name === req.params.name) {
page = queryPage[i];
break;
}
// Check the page exists
if(!page) return res.status(404).send('Not found');
// if(!page) return next();
connection.query(page.query, function(err, rows) {
res.render('pages/' + page.name , {
title: page.title,
data: rows
});
});
});

Can't insert element from array in mysql database with nodejs

I actually have a problem saving some data from an array in a mysql database with nodejs.
This is my code
for (var i = 0; i < data.data.length; i++) {
var imageObject = data.data[i];
var url = imageObject.images.standard_resolution.url;
var id = imageObject.id;
var sql = 'SELECT COUNT(*) AS imageIDCount FROM images WHERE id = ?'
var ids
connection.query(sql, [id], function(err, rows, fields) {
console.log(rows[0].imageIDCount);
if (err) throw err;
if (rows[0].imageIDCount == 0) {
console.log(id + " doesn't exist"); // ### the ID at this point is always the last from that array
//insertImage(id, url);
} else {
// console.log("ID exists");
}
});
}
This code run's when I get a response from an rest-api with the request-framework.
So my problem is that at the point I get the result from the count-query and there is no element with the specific id I get always the same id. I think that's because I use the same variable "id" there but how can I fix it ? I hope somebody can help me.
In this case, you're a victim of Node's asynchronous event loop. You're executing a synchronous for-loop and defining id:
for (var i = 0; i < data.data.length; i++) {
var id = imageObject.id;
}
This works in normal Javascript if you try and do something with id, but because the database module you're using runs asynchronously, that entire loop will have already completed before your first db query completes, effectively clobbering the value of id.
You'll need to re-write your function to behave asynchronously instead, or use something like node-async to help.
Here's a quick example of how that might look. Note that I didn't write your insertImage function for you; you'll need to rewrite that to support a callback as well.
async.each(data.data, function(imageObject, callback) {
var sql = 'SELECT COUNT(*) AS imageIDCount FROM images WHERE id = ?'
connection.query(sql, [imageObject.id], function(err, rows, fields) {
if (err) callback(err);
if (rows[0].imageIDCount == 0) {
console.log(id + " doesn't exist");
insertImage(imageObject.id, imageObject.images.standard_resolution.url, function(err) {
callback(err); // fires the callback to async
})
} else {
console.log(id + " already exists");
callback(); // maybe you want an error here too?
}
});
}, function(err, results) {
// all of your db queries are completed
});

NODEJS node-mysql cannot pass query results in nested queries

I am using node, angular and mysql, the node routes would return a json that would be processed by angular, the json is returned by first querying the mysql DB using the node-mysql module,
In the below code I am unable to set the value of CreatedID, but the value gets logged properly in terminal. I was facing the same issue in the 1st query but then sorted it in the below code, now unable to access the nested query results.
var mysql = require('node-mysql/node_modules/mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : "root",
password: "",
database:'designtaskmanager'
});
connection.connect();
var allDbCalls = function() {
var sendData = {};
var rowData = {};
var temp={};
var _this = this;
this.sendTask = function(callback) {
module.exports.taskData = rowData;
callback['success']();
};
this.getTask = function(callback) {
var strQuery = "select * from task";
connection.query( strQuery, function(err, rows){
if(err)
{
callback['failure']();
throw err;
}
else
{
//rowData = rows;
var tasks=[];
for (var i in rows)
{
var Title = rows[i].task_title;
var TaskDescription=rows[i].task_description;
var TaskCategory=rows[i].task_category;
var TaskID=rows[i].task_id;
var TaskStatus=rows[i].task_status;
var TaskStatusMessage
var CreatedBy;
var TaskCreationDate=rows[i].task_creation_date;
var _MS_PER_DAY = 1000 * 60 * 60 * 24;
var currentdate = new Date();
var ddd=dateDiffInDays(TaskCreationDate,currentdate);
function dateDiffInDays(a, b) {
// Discard the time and time-zone information.
var utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
var utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
return Math.floor((utc2 - utc1) / _MS_PER_DAY);
}
if(TaskStatus==0)
{
TaskStatus="label-info";
TaskStatusMessage="Ongoing since";
}
else if(TaskStatus==1)
{
TaskStatus="label-default";
TaskStatusMessage="Paused since"
}
else if(TaskStatus==2)
{
TaskStatus="label-success";
TaskStatusMessage="Completed in"
}
//USER DETAILS QUERY
var crid=rows[i].task_created_by;
var creatorQuery = "select user_email from users where user_id like ?";
connection.query( creatorQuery,[crid], function(err, createdbyrows){
if(err)
{
callback['failure']();
throw err;
}
else
{
for(var j=0; j< createdbyrows.length;j++)
{
CreatedBy=createdbyrows[0].user_email;
console.log(j);
}
console.log(CreatedBy);
}
});
var taskItem={"TaskID":TaskID,"TaskTitle":Title,"TaskDescription":TaskDescription,"TaskCategory":TaskCategory,"CreatedBy":CreatedBy,"TaskStatus":TaskStatus,"TaskStatusMessage":TaskStatusMessage,"DifferenceInDays":ddd};
tasks.push(taskItem);
}
rowData=tasks;
_this.sendTask(callback);
}
});
}
}
module.exports = function () {
var instance = new allDbCalls();
return instance;
};
The reason that you're seeing it on the console but not in the callback is due to a misunderstanding of asynchronous programming. When you:
for(var i in rows) {}
You are actually queuing up all of those queries at the same time, then, immediately after you try to set rowData to an empty array:
rowData=tasks; // remember, none of the queries have finished yet
_this.sendTask(callback);
So you pretty much call your callback when tasks is still an empty array. Remember, you can't call your final callback until ALL of your nested queries have finished!
To accomplish this, you may want to look at the async library: https://github.com/caolan/async#eachSeries
This will help you accomplish what you really want.
var async = require("async");
async.eachSeries(rows, function(row, cb) {
// Do each query here
// then call cb() when done, which tells the async library
// to "go to the next item in the array"
}, function(err) {
// This will get called when all of the single queries are finished
// Check err, then call your callback
_this.sendTask(callback);
});

NodeJS MySQL Dump

I've attempted to write a basic cron script to run and 'dump' a mysql database. For some reason, when it 'successfully saves the file', it does create the file, but it is empty. If instead of saving the file, I perform a console.log, it prints an empty string. Any thoughts on what I may be doing wrong?
Thanks in advance.
var mysql_backup = function(){
this.backup = '';
this.mysql = require('mysql'),
this.init = function(){
this.connection = this.mysql.createConnection({
user: 'root',
password: 'root',
database: 'test'
});
}
this.query = function(sql, callback) {
this.connection.query(sql, function (error, results, fields) {
if (error) {
throw error;
}
if (results.length > 0) {
callback(results);
}
});
}
this.get_tables = function(callback){
var me = this;
me.query('SHOW TABLES',
function(tables) {
for (var table in tables){
me.query(
'SHOW CREATE TABLE ' + tables[table].Tables_in_test,
function(r){
for (var t in r) {
me.backup += "DROP TABLE " + r[t].Table + "\n\n";
me.backup += r[t]["Create Table"] + "\n\n";
}
}
)
}
me.save_backup();
});
}
this.save_backup = function(){
var fs = require('fs');
fs.writeFile("./backup_test.txt", this.backup, function(err) {
if(err) {
console.log(err);
} else {
console.log("The file was saved!");
}
});
}
};
var db = new mysql_backup;
db.init();
db.get_tables();
db.connection.destroy();
The code as written didn't even get to a file saving for me. There seem like a few issues. Not sure if this is the actual code or some things got lost in the copy paste. However, based on what you've got:
A big one is that you never connect to the database in your code with connection.connect().
The code you want to run once connected should be inside the connection.connect() callback. e.g.
connection.connect(function (err, empty) {
if (err)
throw new Error ('Panic');
// if no error, we are off to the races...
}
However, even if you quickly refactor your code to wrap your last lines inside of that get connection callback, you'll still have problems, because you are destroying the connection before the various SQL calls are getting made, so you will want to move the code into some sort of final callback.
Even after you do that, you'll still have an empty file, because you're calling save_backup from your 'SHOW TABLES' callback rather than after you have actually populated it via the inner callback where you get the CREATE TABLE statement and populate the backup property.
This is the minimal rewriting of your code which will do what you are intending. An important thing to note is the "counter" which manages when to write the file and close the connection. I would make other changes if it were mine, including:
Using 'self' instead of 'me'
Using a numeric for loop rather than the for (... in ...) syntax
Having my own callbacks fall the node convention of (err, stuff)
A more substantial changes is that I would rewrite this to use promises, as doing so can spare you some grief with the confusion inherent with deeply nested callbacks. I personally like the Q library, but there are several options here.
Hope this helped.
var mysql_backup = function(){
this.backup = '';
this.mysql = require('mysql');
this.init = function(){
this.connection = this.mysql.createConnection({
user : 'root',
password : 'root',
database : 'test'
});
};
this.query = function(sql, callback) {
this.connection.query(sql, function (error, results, fields) {
if (error) {
throw error;
}
if (results.length > 0) {
callback(results);
}
});
};
this.get_tables = function(callback){
var counter = 0;
var me = this;
this.query('SHOW TABLES',
function(tables) {
for (table in tables){
counter++;
me.query(
'SHOW CREATE TABLE ' + tables[table].Tables_in_mvc,
function(r){
for (t in r) {
me.backup += "DROP TABLE " + r[t].Table + "\n\n";
me.backup += r[t]["Create Table"] + "\n\n";
}
counter--;
if (counter === 0){
me.save_backup();
me.connection.destroy();
}
}
)
}
});
};
this.save_backup = function(){
var fs = require('fs');
fs.writeFile("./backup_test.txt", this.backup, function(err) {
if(err) {
console.log(err);
} else {
console.log("The file was saved!");
}
});
}
};
var db = new mysql_backup;
db.init();
db.connection.connect(function (err){
if (err) console.log(err);
db.get_tables(function(x){;});
});
Update: If you are curious, here is a heavily-commented implementation using promises. Note that without the comments explaining the Q promise library functions, it is somewhat shorter than the original version and also offers more comprehensive error handling.
var MysqlBackup = function(connectionInfo, filename){
var Q = require('q');
var self = this;
this.backup = '';
// my personal preference is to simply require() inline if I am only
// going to use something a single time. I am certain some will find
// this a terrible practice
this.connection = require('mysql').createConnection(connectionInfo);
function getTables(){
// return a promise from invoking the node-style 'query' method
// of self.connection with parameter 'SHOW TABLES'.
return Q.ninvoke(self.connection,'query', 'SHOW TABLES');
};
function doTableEntries(theResults){
// note that because promises only pass a single parameter around,
// if the 'denodeify-ed' callback has more than two parameters (the
// first being the err param), the parameters will be stuffed into
// an array. In this case, the content of the 'fields' param of the
// mysql callback is in theResults[1]
var tables = theResults[0];
// create an array of promises resulting from another Q.ninvoke()
// query call, chained to .then(). Note that then() expects a function,
// so recordEntry() in fact builds and returns a new one-off function
// for actually recording the entry (see recordEntry() impl. below)
var tableDefinitionGetters = [];
for (var i = 0; i < tables.length ; i++){
// I noticed in your original code that your Tables_in_[] did not
// match your connection details ('mvc' vs 'test'), but the below
// should work and is a more generalized solution
var tableName = tables[i]['Tables_in_'+connectionInfo.database];
tableDefinitionGetters.push(Q.ninvoke(self.connection, 'query', 'SHOW CREATE TABLE ' + tableName)
.then(recordEntry(tableName)) );
}
// now that you have an array of promises, you can use Q.allSettled
// to return a promise which will be settled (resolved or rejected)
// when all of the promises in the array are settled. Q.all is similar,
// but its promise will be rejected (immediately) if any promise in the
// array is rejected. I tend to use allSettled() in most cases.
return Q.allSettled(tableDefinitionGetters);
};
function recordEntry (tableName){
return function(createTableQryResult){
self.backup += "DROP TABLE " + tableName + "\n\n";
self.backup += createTableQryResult[0][0]["Create Table"] + "\n\n";
};
};
function saveFile(){
// Q.denodeify return a promise-enabled version of a node-style function
// the below is probably excessively terse with its immediate invocation
return (Q.denodeify(require('fs').writeFile))(filename, self.backup);
}
// with the above all done, now you can actually make the magic happen,
// starting with the promise-return Q.ninvoke to connect to the DB
// note that the successive .then()s will be executed iff (if and only
// if) the preceding item resolves successfully, .catch() will get
// executed in the event of any upstream error, and finally() will
// get executed no matter what.
Q.ninvoke(this.connection, 'connect')
.then(getTables)
.then(doTableEntries)
.then(saveFile)
.then( function() {console.log('Success'); } )
.catch( function(err) {console.log('Something went awry', err); } )
.finally( function() {self.connection.destroy(); } );
};
var myConnection = {
host : '127.0.0.1',
user : 'root',
password : 'root',
database : 'test'
};
// I have left this as constructor-based calling approach, but the
// constructor just does it all so I just ignore the return value
new MysqlBackup(myConnection,'./backup_test.txt');