multiple nested callback functions before parent's callback in javascript - mysql

Im using expressjs and mysql package.
I have a user table, each user could have many skills, projects, certifications stored in different tables. How can I get all of these data into a single user object.
getUserDataById(req.params.id, function (user) {
if (user) {
res.send(user);
}
}
var getUserDataById = function (id, callback) {
connection.query(userSql, id, function (err, rows) {
if (rows[0]) {
user = new User(rows[0]); //parse row data to user obj
// The main problem
//get skills and projects.... and asign to user obj
parseArrayData(skillSql, user.id, Skill, function (skills) {
user.skills = skills;
}
);
parseArrayData(projectSql, user.id, Project, function (project) {
user.projects = projects;
}
);
// is here
callback(user);
}
});
}
var parseArrayData = function (query, id, Obj, callback) {
connection.pool.query(query, id, function (err, rows) {
if (err) {
throw err;
} else {
console.log('rows',rows);
var array = [];
for (var i = 0; i < rows.length; i++) {
array[i] = new Obj(rows[i]); // map obj's attributes with fields
};
callback(rows);
}
});
};

You might have these different tables with names (assume): users, skills, projects, certifications;
function to get result from db for query;
function executeQuery(tablename, userId, callback) {
var sql = 'select * from ' + tablename + ' where id = ' + userId;
connection.query(sql, function (err, rows) {
if (err) { callback(err, null); }
else { callback(null, rows); }
})
}
Function that will fetch all results for user id and return json object;
var getUserDataById = function (userId, callback) {
executeQuery('users', userId, function(users) {
executeQuery('projects', userId, function(projects) {
executeQuery('skills', userId, function(skills) {
executeQuery('certifications', userId, function(certifications) {
var result = { // format you result.
id: userId,
users: users,
projects: projects,
skills: skills,
certifications: certifications
};
callback(result);
});
});
});
});
callback(null);
});
Usage:
getUserDataById(req.params.id, function (user) {
if (user) {
res.send(user);
}
}

Related

Cannot enqueue Query after invoking quit when nesting promises

I am trying to iterate through a MySQL query result and make subsequent queries in order to build out my data model. Each object requires multiple queries, therefore I am chaining promises.
The problem occurs when I nest a second set of promises.
So first I am getting a list of the objects that need to be retrieved using g.getSnapshotIds. Then I iterate through those and use the snapshotId to retrieve a full snapshot.
var gData = {};
g.getSnapshotIds(data.gId, data.userId)
.then(function(value) {
gData = value;
for ( var snapshot in value ) {
var snapshotId = value[snapshot].snapshotId;
var snapshot = {};
g.getSnapshotFull(snapshotId)
.then(function(value) {
console.log(value);
return g.getTs(snapshotId);
})
.then(function(value) {
for ( var te in value ) {
var name = value[te].t;
snapshot[name] = value[te].value;
}
console.log(snapshot);
})
.catch(function(err) {
console.log('Error:', err);
});
}
g.close();
})
.catch(function(err) {
console.log('Error:', err);
});
I am able to call g.getSnapshotFull on each ID, but when I try to move on to the next query (g.getTs(snapshotId)) it gives me the error:
Error: Cannot enqueue Query after invoking quit.
I have no idea why the MySQL connection is closing before all queries are done. Shouldn't everything inside the for loop execute sequentially before moving on?
If I comment out g.close(), I don't get the error, but the process doesn't end.
These are the relevant query methods:
class gDB {
close() {
return new Promise(function(resolve, reject) {
db.end(function(error) {
if ( error ){
reject(new Error(error));
}
// For some reason it is necessary to reestablish this
db = mysql.createConnection({
host: process.env.DBHOST,
user: process.env.DBUSER,
password: process.env.DBPASS,
database: process.env.DBNAME,
ssl: {
ca: fs.readFileSync(__dirname + '/' + process.env.DBCA)
}
});
resolve(true);
});
});
}
getSnapshotIds(gId, uId) {
return new Promise(function(resolve, reject) {
var sql = 'SELECT id AS snapshotId FROM snapshots WHERE gId=' + db.escape(gId) + ' AND uId=' + db.escape(uId) + ' ORDER BY timestamp DESC';
db.query(sql, function (error, results, fields) {
if (error) {
db.destroy();
reject(new Error(error));
} else {
resolve(results);
}
});
});
}
getSnapshotFull(snapshotId) {
return new Promise(function(resolve, reject) {
var sql = 'SELECT s.id AS snapshotId, s.timestamp, s.gId, s.uId, s.clientId FROM snapshots s INNER JOIN controls c ON s.id = c.snapshotId INNER JOIN weathers w ON s.id = w.snapshotId WHERE s.id=' + db.escape(snapshotId);
db.query(sql, function (error, results, fields) {
if (error) {
db.destroy();
reject(new Error(error));
} else {
resolve(results[0]);
}
});
});
}
getTs(snapshotId) {
return new Promise(function(resolve, reject) {
var sql = 'SELECT t.t, st.value FROM snapshots s LEFT JOIN snapshot_t st ON s.id = st.snapshotId INNER JOIN ts t ON st.tId = t.id WHERE s.id=' + db.escape(snapshotId);
db.query(sql, function (error, results, fields) {
if (error) {
db.destroy();
reject(new Error(error));
} else {
resolve(results);
}
});
});
}
The problem you are having is for loops are synchronous while promises are asynchronous. What is going on is you are creating a bunch of promises that are waiting for something to happen (the promise to receive data), then the for loop ends (before any of the promises finish) and you then call close. What you'll want to do is something similar to the below.
var gData = {};
g.getSnapshotIds(data.gId, data.userId)
.then(function (value) {
gData = value;
var promises = [];
for (var snapshot in value) {
var snapshotId = value[snapshot].snapshotId;
var snapshot = {};
var promise = g.getSnapshotFull(snapshotId)
.then(function (value) {
console.log(value);
return g.getTs(snapshotId);
})
.then(function (value) {
for (var te in value) {
var name = value[te].t;
snapshot[name] = value[te].value;
}
console.log(snapshot);
});
promises.push(promise);
}
return Promise.all(promises);
})
.then(function (values) {
g.close();
console.log(values);
})
.catch(function (err) {
console.log('Error:', err);
});
What solves this is saving the promise and then using Promise.all(promises) to wait for all the promises to finish. The last then block will have the results of all of the promises and that is where you can close your database connection.

NodeJs handling the multiple mysql requests

I am new to nodejs and I am trying to use multiple queries to display result in a single page. I have used async parallel to get the result but I am unable to handle the requests.
This is my callback function
gettournamentDetail: function (res, req) {
var connection = mysqlConnectionProvider.getSqlConnection();
//var test=[];
var collection = { collection:[] };
var sqlStatement = "SELECT * FROM tournaments WHERE tournamentId =" + res;
var tgroup1 = { collection:[]};
var tournamentdetails = [];
var sqlStatement2 = "select * from " +
"(SELECT DISTINCT teamId, teamName,ttournament FROM teams, tournamentTeam WHERE ttournament=" + res + ") as main LEFT JOIN (SELECT DISTINCT groupName,groupTournament FROM groupTeam,tournamentGroup WHERE groupt=tgId) as sub ON sub.groupTournament=main.ttournament";
// var return_data = {};
async.parallel([
function () {
if (connection) {
connection.query(sqlStatement, function (err, rows, fields) {
rows.forEach(function (row) {
tournamentdetails=row;
});
req(tournamentdetails);
});
}
},
function () {
var tgroup=[];
if (connection) {
connection.query(sqlStatement2, function (err, rows, fields) {
for (var i in rows) {
tgroup.push("group",{
teamName: rows[i].teamName,
teamId: rows[i].teamId,
groupName: rows[i].groupName
});
}
req(tgroup);
});
}
}
This is my routing page
exports.tournamentDetail = function( req, res) {
var tournamentdetail = require('../database/getTournament.js');
tournamentdetail.tournament.gettournamentDetail(req.params.id, function (collection) {
console.log(collection);
//res.render('tournamentdetail', {title: 'Tournament Detail ', tdetail: collection});
});
};
Any Idea how Can I handle the two requests to display the data.
This is how I am getting the data
RowDataPacket {
tournamentId: 1,
tournamentUser: 1,
tournamentDate: 1472299200,
tournamentLocation: 'Reading',
tournamentDesc: 'An alternative to renaming app.js is to create an elastic beanstalk configuration file. Add a .config file into the .ebextensions folder, for example, .ebextensions/34.config. Change the NodeCommand setting in the namespace aws:elasticbeanstalk:container:nodejs to whatever command you want to run to start the server. For example, this is a minimal .config file to run npm start instead of app.js:\n\n',
tournamentInfo: 'Test',
tournamentCreated: 1471171131,
tournamentName: 'Ping Pong' }
[ 'group',
{ teamName: 'TeamName', teamId: 1, groupName: 'A' },
'group',
{ teamName: 'Team2', teamId: 2, groupName: 'A' } ]
I little simplify you code, but I don't understand exactly what you needs.
// connect on application start
var connection = mysqlConnectionProvider.getSqlConnection();
if (!connection)
throw new Error('Smth wrong');
...
let getTournamentDetail = function (tournament_id, callback) {
// I think both queries are bad, because they used Cartesian product and distinct
// Use placeholder to escaping params; it's more safety
var sql = 'select * from tournaments where tournamentId = ??';
var sql2 = 'select distinct teamId, teamName, ttournament '+
'from teams, tournamentTeam WHERE ttournament = ??) as main ' +
'left join (select distinct groupName, groupTournament ' +
'from groupTeam, tournamentGroup where groupt = tgId) as sub ' +
'on sub.groupTournament = main.ttournament';
async.parallel([
function (callback) { connection.query(sql, [tournament_id], callback) },
function (callback) { connection.query(sql2, [tournament_id], callback) }
],
function (err, results) {
if (err)
return callback(err);
callback(null, {tournamentdetails: results[0], tgrup: results[1]})
}
);
}
...
var youRouteFunc = function (req, res) {
getTournamentDetail(req.params.id, function (err, data) {
if (err)
return res.send(err.message);
res.render('tournamentdetail', {title: 'Tournament Detail ', tdetail: data});
});
}

Node.js - MySQL API, multi GET functions

I'm new in making API. I use Node.js and MySQL.
The fact is I have two GET function to get all users and one to get user by ID.
Both function are working when they are alone implemented. If both of them are implemented the function to get all user try to enter in the function to get user by ID so the API crash.
So here is my model users.js
var connection = require("../connection");
function Users()
{
//GET ALL USERS
this.get = function(res)
{
console.log('Request without id');
connection.acquire(function(err, con)
{
con.query('SELECT * FROM users', function(err, result)
{
con.release();
if (err)
res.send({status: 1, message: 'Failed to get users'})
else
res.send(result);
});
});
}
//GET USER BY ID
this.get = function(id, res)
{
console.log('Request with ID');
connection.acquire(function(err, con)
{
if (id != null)
{
con.query('SELECT * FROM users WHERE id = ?', id, function(err, result)
{
con.release();
if (err)
res.send({status: 1, message: 'Failed to find user: ' + id});
else if (result == "")
res.send({status: 1, message: 'Failed to find user: ' + id});
else
res.send(result);
});
}
});
}
And here is the routes.js
var users = require('./models/users');
module.exports = {
configure: function(app) {
app.get('/users/', function(req, res) {
users.get(res);
});
app.get('/users/:id/', function(req, res) {
users.get(req.params.id, res);
});
Do you have any idea why ?
Thanks for help :)
You can't have two functions with the same name in the same scope.
You have to rename your functions
/**
* Get all users
*/
this.get = function(res) {...}
/**
* Get user by id
*/
this.getById = function(id, res) {...}
Or you can have one function and check if an id is provided
this.get = function(id, res) {
if ( Number.isInteger(id) ) {
// return the user
} else {
res = id;
// return all users
}
}

Assign One to Many Relationships in Bulk using Mongoose

How do I map one to many relationships as a batch operation using Mongoose?
I have controllers for building seed data for accounts and users.
This data is sourced from a spreadsheet. I then map ONE account to MANY users with another controller, this mapping is bi-direction, i.e Account has array of userIds and user has single accountId.
The code listed, below all works correctly, but it took me 8 hours to write and was quite painful to debug with all the Async and Callback issues that I had to work with.
I researched into bulk operations, native mongo drive stuff etc... and this was the best I could come up with.
I feel that this code could use faster and/or better techniques but I'm not sure how to improve it.
I have included some screenshots or code for
test data in Excel and JSON
SPEC that fires account, user and relationship generation
relationship builder controller with debug code left in place.
screenshot of the output
I'm hoping for feedback.
Seed Data SPEC
describe('Relationship', function () {
beforeEach(td.accounts.teardown);
beforeEach(td.users.teardown);
beforeEach(td.users.seedData);
beforeEach(td.users.setAuthenticatedTokenAdmin);
it('should create relationships between the user and accounts table', function (done) {
// Build Account Data
request(app).put('/api/seeds/accountCreateSeed').set('Authorization', 'Bearer ' + td.users.authenticatedToken).send({ logDetail: 0, useSampleJson: true })
.end(function (err, res) {
if (err) {
l.logTheUnknown(err);
return done(err);
}
// Build User Data
request(app).put('/api/seeds/userCreateSeed').set('Authorization', 'Bearer ' + td.users.authenticatedToken).send({ logDetail: 0, useSampleJson: true })
.end(function (err, res) {
if (err) {
l.logTheUnknown(err);
return done(err);
}
// Build User /Account Relationship Data
request(app)
.put('/api/seed-relationships/createUserAccountRelationship')
.set('Authorization', 'Bearer ' + td.users.authenticatedToken)
.send({ logDetail: 0, useSampleJson: true })
.end(function (err, res) {
if (err) {
l.logTheUnknown(err);
return done(err);
}
return done(err);
});
});
});
});
});
Seed Data Relationship Controller
// ----------------------------------------------------------------------
// User / Account - Relationships
// ----------------------------------------------------------------------
exports.createUserAccountRelationship = function (req, res) {
var useSampleJson = req.body.useSampleJson ? req.body.useSampleJson : false;
// userAccounts = the input data to be processed
var userAccounts = useSampleJson ? readFile('accountUserRelationship.json') : req.body.data;
var logDetail = req.body.logDetail ? req.body.logDetail : 0;
if (logDetail >= LOG_DETAIL) {
l.kv('useSampleJson', useSampleJson);
l.kv('logDetail', logDetail);
}
if (logDetail >= LOG_LOW) {
l.block('Relationship Data for User/Account');
}
if (logDetail >= LOG_LOW) {
l.line('User / Account Relationships JSON');
if (userAccounts) {
l.kv('Length', userAccounts.length);
l.inspect(userAccounts);
}
}
async.eachSeries(userAccounts,
function (userAccount, callback) {
l.kv('Search Account', userAccount.accountName);
Account.findOne({ name: userAccount.accountName }, function (err, account) {
if (err) {
l.inspect(err);
} else {
if (account) {
findAccountUsers(userAccount, account, callback);
} else {
l.kv('Could not find Account', userAccount.accountName);
callback();
}
}
});
},
function (err) {
if (err) {
l.inspect(err);
} else {
l.block('User Account Relations Processed ');
}
return res.sendStatus(200);
});
};
function findAccountUsers(userAccount, account, callback) {
// userAccount = A single relationship between account and multipleUsers
// account = the DB account to work with
// Find all the users that are associated with this account
l.inspect(userAccount.userEmail);
User.find({ email: { $in: userAccount.userEmail } }, function (err, users) {
l.inspect(_.map(users, function (user) { return user.id + ' - ' + user.email; }));
assignAccountUserRelationship(account, users, callback);
});
}
function assignAccountUserRelationship(account, users, callback) {
// account = the DB account to work with
// users = the DB users to work with
account.userIds = [];
for (var userIndex = 0; userIndex < users.length; userIndex++) {
var user = users[userIndex];
user.accountId = account._id;
account.userIds.push(user._id);
}
account.save(function (err) {
if (err) {
l.inspect(err);
} else {
l.kv('Account Saved', account.name);
}
async.eachSeries(users,
function (user, userCallback) {
user.save(function (err) {
if (err) {
l.inspect(err);
} else {
l.kv('user saved', user.email);
}
userCallback();
});
},
function (err) {
if (err) {
l.inspect(err);
} else {
l.block('User Relation Processed ');
}
callback();
});
});
}
Test Data
Test Data Relationship as JSON
Sample Log Output
Sample Output from Mongo Shell

better way of selecting 1 to many?

I have an express.js based rest application. Please have a look on following code and suggest me what would be better way.
I want to select user and its associated images (1 user has many images).
function getUser (connection, req, res) {
var userId = req.params.id;
connection.query('SELECT * FROM user p'
+ ' WHERE p.id = ' + connection.escape(userId), function handleSql(err, rows) {
if (err){ logAndRespond(err,res); return; }
if (rows.length === 0){ res.send(204); return; }
var adId = rows[0].adId;
// load images
connection.query('SELECT id, url FROM image WHERE ad_id = ' + connection.escape(adId), function (err, imgRows) {
if (err){ logAndRespond(err,res); return; }
if (rows.length != 0){
rows[0].images = imgRows;
}
res.json({'user': rows});
connection.release();
});
});
}
You don't have to escape parameters by yourself
You don't release the connection if an error occurred
The problem now is I don't know what you want to do with selected rows. You are also checking the rows.length twice but if there weren't any records in the first query then the second one will not be executed.
function getUser(conn, req, res) {
conn.query("SELECT * FROM user p WHERE p.id = ?;", [req.params.id], function(err, rows) {
if (err) {
return logAndRespond(err, res);
}
if (!rows.length) {
return res.send(204);
}
conn.query("SELECT id, url FROM image WHERE ad_id = ?;", [rows[0].adId], function(err, imgRows) {
if (err) {
return logAndRespond(err, res);
}
if (rows.length) { // ???
rows[0].images = imgRows;
}
res.json({"user": rows});
conn.release();
});
});
}