How to implement nested query in sqlite3 - node.js

So i have this 2-layer query in node.js, each query could return multiple results. My code actually just ignores that for now. This is the best i can get, it seems working.
How to correct it please, i don't know how to callback for the 2nd one.
Also the db.close() is always called before the 2nd query finishes, even i have serialize().
var getInfo1Db = function(callback) {
var db = new sqlite3.Database("DB.sqlite3");
var cnt = 0;
var info1JsonObj = [];
db.all("select * from Info1DB",
function(err, rows) {
db.serialize(function() {
for(var ii=0, len=rows.length; ii<len; ii++) {
var t2 = rows[ii].info1;
var doorId = ...
db.all("select * from DoorDB where ObjectID=" + doorId,
function(err, row2) {
if(err) {
} else {
var doorName = row2[0]...
var info1JsonElem = {
"DoorName" : doorName
};
info1JsonObj.push(info1JsonElem);
cnt++;
if(cnt === rows.length) {
callback(null, info1JsonObj);
}
}
}
); // for the only door info based on door id
} // for each row of info1
db.close(); // why this finishes before the 2nd db.all
} ); // end of serialize
});
};

You can't implement nested query in sqlite3's normal way. ( I mean you even can't do it in the callback hell way, because the sqlite3 need to close the connection before another query called. otherwise you will always got error)
You have to use Promise, async and await to do this.
( it's worth to spend 30 minutes to learn these 3 words )
Step1. define a async function like this:
async query_1() {
new Promise(resolve => {
db = ...
db.serialize( () => {
db.get('select .. from ... where id = 1', [], (error, row) => {
// here is the KEY: put the result into resolve
// this equals to the "return" statement in non-sync method.
resolve(row)
}
})
db.close()
})
}
and also implement your query_2 function like this:
async query_2() {
let query_1_result = await this.query_1()
db = ...
db.serialize( () => {
db.get('select .. from ... where dependency_id = ' + query_1_result, [], (error, row) => {
// other code here...
}
})
db.close()
}
refer to my answer: https://stackoverflow.com/a/67881159/445908

How about using 2 function to do these ?
function db_query1(your_param,...., callback){
  // database operation
db.run( sql , [param,...] , function(err,rows){
if(err) // return
else{
// get rows with callback
callback(null, rows);
}
});
}
function db_query2(your_param,...., callback){
  // database operation
db.run( sql , [param,...] , function(err,rows){
if(err) // return
else{
// get rows with callback
callback(null, rows);
}
});
}
And call these function:
db_query1(....,function(err,result1){
if(err) ...// return
// do the things with result1
// And then call query2
db_query2(....,function(err,result2){
if(err) ...// return
// do the things with result1
});
});
Hope this will help :)

You can use Promises.all, an array and the second callback for node sqlite3 db.each() that is executed when all rows have been fetched. Node Sqlite3 db.each usage to simplify the nested query and
I cannot really get the meaning of the variables you are using thus I assume that each row in Info1DB has a one-to-many relationship with DoorDB on the field doorId.
async function getInfo (callback) {
sql = "select * from Info1DB;";
numVersions = 0;
countVersions = 0;
info1JsonObj = [];
db.serialize(function() {
db.each(sql, [], (err, info1Row) => {
sql = "select * from DoorDB where ObjectID=?;";
info1Row.doors = [];
doorId = ...
db.each(sql, [doorId], (err, doorRow) => {
info1Row.doors.push(new Promise((resolve, reject) => {
if (err) {
reject(err);
} else {
resolve(doorRow);
}
}));
}, (err, num) => {
Promise.all(info1Row.doors)
.then((doors) => {
info1Row.doors = doors;
info1JsonObj.push(info1Row);
countVersions++;
if (countVersions == numVersions) {
callback(null, info1JsonObj);
}
}).catch((err) => {
callback(err, null);
});
});
}, (err, versions) => {
numVersions = versions;
});
});
}

Related

export module return object with added method

I have the following codes :
app.post("/login/auth", (req, res) => {
(async function() {
let NikitaBellucci = (await auth.login(req, db, crypto))[0];
res.send(NikitaBellucci);
})();
});
and
exports.login = (req, db, crypto) => {
pro = new Promise((resolve,reject) => {
let pseudo = req.body.pseudo;
let password = crypto.createHmac('sha256', req.body.password)
.update('jojofags suck')
.digest('hex');
let query = "SELECT * FROM users WHERE users.pseudo = ? AND users.password = ? LIMIT 1";
db.query(query, [pseudo, password], function (err, result) {
if (err) throw err; // GESTION D'ERREURS
result.isAdministrator = function() {
if(this.role <= 90) { return true; } else { return false; }
}
resolve(result);
});
})
return pro.then((val) => {
console.log(val);
return val;
})
}
On console.log(val);, I can see the previously added method to my object. But when returning it to my main file, method "disappear", how to avoid that?
thank you
Your function is attached to the entire result object, but you get the 0 property of it in (await auth.login(req, db, crypto))[0]; which won't have the function. Just remove the [0] and NikitaBellucci.isAdministrator will be the function in question.

Waiting for async function in for loop

I need to wait for an async method (a call to my database) for every object in an array. Right now I have a for loop going through the array calling the async method on each object. The async function is successful but I need to wait for every async call to finish before moving on. Doing some research I have found that Promises combined with await or other methods are the solution to my problem but I haven't been able to figure it out. Here is my code I have right now.
Here is my class with the async function
Vacation : class Vacation {
constructor(id, destination, description, attendee_creator_id) {
this.id = id;
this.destination = destination;
this.description = description;
this.attendee_creator_id = attendee_creator_id;
this.creator = undefined;
this.votes = undefined;
}
find_creator(pool){
return new Promise(function (resolve, reject) {
var self = this;
var query = "SELECT * FROM vacation_attendee WHERE id = " + self.attendee_creator_id;
pool.query(query, function(error, result) {
if (error) {
console.log("Error in query for vacation creator " + error);
return reject(error);
}
var creator = new attendee.VacationAttendee(result.rows[0].full_name, result.rows[0].email, result.rows[0].password_hash);
self.creator = creator;
console.log("creator found ----> " + self.creator.full_name + "in " + self.destination);
resolve(true);
});
})
}
here is how i'm calling the async function
function get_all_vacations(callback) {
var sql_vacations_query = "SELECT * FROM vacation";
pool.query(sql_vacations_query, function(error, vacations_query_result) {
if (error) {
console.log("error in vacations query " + error);
callback(error);
}
var all_complete = loop_through_vacations(vacations_query_result.rows, pool);
callback(null, all_complete);
});
}
async function loop_through_vacations(vacations_incomplete, pool) {
var all_vacations = [];
for (var vacation_data of vacations_incomplete) {
var vacation_at_index = new vac.Vacation(vacation_data.id, vacation_data.destination, vacation_data.description, vacation_data.attendee_creator_id);
vacation_at_index.find_creator(pool)
.then(()=> {
all_vacations.push(vacation_at_index);
})
.catch(error=> {
console.log(error);
});
console.log("passed vacation " + vacation_at_index.destination);
}
return await Promise.all(all_vacations);
}
You do it in a wrong way, you don't wait anything in you for-loop.
return await Promise.all(all_vacations); does not work, because all_vacations is not a Promise array.
In your case, we have many way to do this, my way just is a example: Create a array to store all promises what have been created in your for-loop, then wait until the all promises finished by Promise.all syntax.
async function loop_through_vacations(vacations_incomplete, pool) {
var all_vacations = [];
var promises = []; // store all promise
for (var vacation_data of vacations_incomplete) {
var vacation_at_index = new vac.Vacation(vacation_data.id, vacation_data.destination, vacation_data.description, vacation_data.attendee_creator_id);
promises.push( // push your promise to a store - promises
vacation_at_index.find_creator(pool)
.then(() => {
all_vacations.push(vacation_at_index)
})
.catch((err) => {
console.log(err); // Skip error???
})
);
}
await Promise.all(promises); // wait until all promises finished
return all_vacations;
}
I don't know why you use async/await mix with callback style, I recommend async/await for any case:
Mix style:
function get_all_vacations(callback) {
var sql_vacations_query = "SELECT * FROM vacation";
pool.query(sql_vacations_query, async function(error, vacations_query_result) { // async
if (error) {
console.log("error in vacations query " + error);
return callback(error); // return to break process
}
var all_complete = await loop_through_vacations(vacations_query_result.rows, pool); // await
callback(null, all_complete);
});
}
async/await:
async function get_all_vacations() {
var sql_vacations_query = "SELECT * FROM vacation";
var rows = await new Promise((resolve, reject) => {
pool.query(sql_vacations_query, function(error, vacations_query_result) {
if (error) {
console.log("error in vacations query " + error);
return reject(error);
}
resolve(vacations_query_result.rows);
});
})
var all_complete = await loop_through_vacations(rows, pool); // await
return all_complete;
}
For me, you can try to reformat your SQL queries, the code will be cleaner and it will take less time to make a big query once than multiple queries.
If I understand joins in your database:
SELECT *
FROM vacation_attendee AS attendee
INNER JOIN vacation ON vacation.attendee_creator_id = attendee.id
-- WHERECLAUSEYOUWANT
Then:
async function get_all_vacations(callback){
const allComplete = await makeThatBigQuery(); // + treat data as you want
callback(null, allComplete);
}
In your definition of get_all_vacations you don't wait all_complete, so the callback is called before all_complete is ready
This is how the await/promises part of the code should look like:
function get_all_vacations(callback) {
var sql_vacations_query = "SELECT * FROM vacation";
pool.query(sql_vacations_query, async function(error, vacations_query_result) {
if (error) {
console.log("error in vacations query " + error);
callback(error);
}
var all_complete = await loop_through_vacations(vacations_query_result.rows, pool);
callback(null, all_complete);
});
}
async function loop_through_vacations(vacations_incomplete, pool) {
const promises = [];
for (var vacation_data of vacations_incomplete) {
var vacation_at_index = new vac.Vacation(vacation_data.id, vacation_data.destination, vacation_data.description, vacation_data.attendee_creator_id);
promises.push(vacation_at_index.find_creator(pool));
}
return await Promise.all(promises);
}
Ideally you should be removing all the callbacks(not sure if asyn/await is supported for pool.query methods) and change the whole code to async/await style. Also you should be resolving the promise from 'find_creator' with resolve(creator), so that the value is obtained when the promise is resolved.

How to call a postgres 9.6 function with json param in node pg

im stuck with this, thanks for your time, im trying to call a postgres fuction receiving a json array as param, im getting the following error qhen the exec the query:
error: bind message supplies 3 parameters, but prepared statement "" requires 1
im using pg for the connection an query, this is my code
create() {
var datajson=[];
var data=this.params.body;
if (Object.keys(data).length>1)
{
for (var i=0; i<Object.keys(data).length; i++)
{
datajson.push(data[i]);
console.log(JSON.stringify(data[i]));
}
}
var _this=this;
pg.connect(Nodal.my.Config.db.main, function(err, client, done) {
console.log('2');
if (err) {
console.log('3');
}
console.log("llamoexec"+JSON.stringify(datajson));
var query = {
// give the query a unique name
text: 'SELECT setProduct($1)',
values: datajson
}
client.query(query, function(err, result) {
console.log('4');
if (err) {
console.log('5'+err);
}
done();
});
});
}
Thank you so much for your help
I think the problem is in how you are calling client.query. I'm not familiar with the method you are using but this is how I normally use it:
let params = [ "param1", ["param2"] ]; /* array of params */
let query = "select someFunction($1::text, $2::text[])";
client.query( query, params, function (error, result) {
if (error) { /* handle */ }
else { /* do work */ }
});
In your case, assuming datajson is intended to be array of strings:
let params = [ datajson ];
let query = "select setProduct($1::text[])";
/* note in your case if you had intended to send in a json string as a string then instead:
let query = select setProduct($1::text);
or if you had intended to send in as json:
let query = select setProduct($1::json);
*/
client.query (query, params, function(error, result){
if (error) { /* handle it */ }
else {
console.log(result);
}
});
As you can see, params is intended to be an array and referenced in the select string by it's ordinal: $1, $2, etc...
The final working function is:
create() {
var datajson=[];
var data=this.params.body;
if (Object.keys(data).length>=1)
{
for (var i=0; i<Object.keys(data).length; i++)
{
datajson.push(data[i]);
console.log(JSON.stringify(data[i]));
}
}
var _this=this;
pg.connect(Nodal.my.Config.db.main, function(err, client, done) {
console.log('2');
if (err) {
console.log('3');
}
console.log("llamoexec"+JSON.stringify(datajson));
var query = {
// give the query a unique name
text: 'SELECT setProduct($1)',
values: datajson
}
client.query('SELECT setProduct($1)',[JSON.stringify(datajson)], function(err, result) {
console.log('4');
if (err) {
console.log('5'+err.message);
}
done();
_this.respond(result);
});
});

Node.js - object loses values after function

I have a REST API in Node.js using Mongoose. I have the following function that does something specific for my application. The problem is that I set test.questions value and after a particular loop, I find it is losing scope of those variables. What is the problem here? Here is my code:
randomizeTest = (req, res) => {
const test = new Test;
let questions: String[] = [];
let num = 5;
while (num >= 1) {
var self = this;
this.quesmodel.count().exec(function (err, count) {
var random = Math.floor(Math.random() * count)
self.quesmodel.findOne().skip(random).exec(
function (err, result) {
questions.push(result._id);
test.questions = questions;
console.log(test.questions); // prints data
});
});
num--;
}
console.log(test.questions); // prints nothing
test.save(function (err, test) {
if (err) {
res.sendStatus(400);
console.error(err);
} else {
res.status(200).json({ test });
}
});
}
After Navid's answer, I tried this now:
let questions: String[] = [];
let num = 5;
var self = this;
asyncLoop(questions, function (item, next) {
while (num >= 1) {
self.quesmodel.count().exec(function (err, count) {
var random = Math.floor(Math.random() * count)
self.quesmodel.findOne().skip(random).exec(
function (err, result) {
questions.push(result._id);
test.questions = questions;
next();
});
});
num--;
}
}, function () {
console.log(test.questions);
console.log('Finished!');
});
In nodejs functions with some I/O job run asynchronously so when one thread is looping inside while loop and executing database I/O jobs, another one is running the rest of your code and executing console.log(test.questions); with its previous values which is empty.
PS. one good way to handle these type of problems is using some async libraries to run your loops sequentially like node-async-loop.
var asyncLoop = require('node-async-loop');
var array = ['item0', 'item1', 'item2'];
asyncLoop(array, function (item, next)
{
do.some.action(item, function (err) //database operations come here
{
if (err)
{
next(err);
return;
}
next();
});
}, function (err)
{
if (err)
{
console.error('Error: ' + err.message);
return;
}
// the rest of your code like console.log(test.questions); goes here
console.log('Finished!');
});
this might help you but its not the best way to do it:
let questions: String[] = [];
let num = Array.from(Array(5).keys()); //this makes num = [0, 1, 2, 3, 4]
var self = this;
asyncLoop(num, function (item, next) {
self.quesmodel.count().exec(function (err, count) {
var random = Math.floor(Math.random() * count)
self.quesmodel.findOne().skip(random).exec(function (err, result) {
questions.push(result._id);
test.questions = questions;
next();
});
});
}, function (err) {
if(err)
throw err;
else {
console.log(test.questions);
console.log('Finished!');
}
});
Here's a solution using the promises built into mongodb (which are a much better way to manage multiple asynchronous operations) and then simplifying things a bit with async/await so your loop actually runs serially:
randomizeTest = async (req, res) => {
const test = new Test();
let questions: String[] = [];
test.questions = questions;
try {
for (let num = 5; num >= 1; --num) {
let count = await this.quesmodel.count();
let random = Math.floor(Math.random() * count);
let result = await this.quesmodel.findOne().skip(random).exec();
questions.push(result._id);
}
console.log(test.questions); // prints final results
await test.save();
res.status(200).json({ test });
} catch(e) {
res.sendStatus(500);
console.error(e);
}
}
FYI, your scheme for picking a random record from a collection is subject to a race condition if there are other processes modifying that collection while you are selecting the random record. This is because there's a time period between when you do .count() and .skip(random) and the collection could be changed in that time window. There are multiple other techniques for picking a random item that each have their own situations where they are best.
I am definitely sure this is problem with your loop. Please use async in place of while.

Execute a loop on a POST request on Node.js server

im trying to count how many people of every gender are there in a json list passed by the client with a POST request (on Node.js server). I have problems understanding javascript asynchronization, callbacks and closures.
What i want is:
getting a list from the client,
for every entry ask my collection if that is a m, a f or a u,
count how many fs, ms and us there are,
send an array to the client with the three values.
I always get "Cant set headers after they are sent" or similar errors due to async execution. I tried different callback orders and many different options.
This is how the functions on the server looks like:
app.post('/genderize', function(req, res){
createCounter("conto", req, function(req,contat ){
count(req, contat);
}).then(res.send( result ));
});
function createCounter( nome, req, callback ) {
result = [0,0,0];
var contatore = function(){
var m = 0;
var f = 0;
var u = 0;
addM = function(){ console.log( "m++ "+result[1]);result[1]++; };
addF = function(){ f++; };
addU = function(){ u++; };
getM = function(){ return this.m;};
getResult = function(){
console.log( result+ " * "+ getM() + " * " + this.u + " * "+ this.f );
return result;
};
return {
addM: addM,
addF: addF,
addU: addU,
getResult: getResult
};
}
callback( req, contatore() );
}
function count( req, counter ){
var collection = db.get('nomi');
var data = req.body.data;
data.forEach(function(value, i){
collection.find({ nome : req.body.data[i].name.split(" ")[0].toUpperCase() }, { fields: {_id:0, nome:0}}, function (err, docs) {
if (!isEmptyObject(docs)) {
docs = JSON.parse(JSON.stringify(docs));;
if(docs[0].sesso == "M"){
counter.addM();
} else {
counter.addF();
}
} else {
counter.addU();
}
});
});
}
There are several issues with this example, but the main thing that you missed is that when you perform your database query, the collection.find call will return immediately, but will only execute its callback (function(err, docs)) at some later time after the database has replied.
Here's a working rewrite:
app.post('/genderize', function(req, res) {
if (!req.body.data || req.body.data.length === undefined) {
return res.status(400).send('Invalid request body.');
}
countGenders(db.get('nomi'), req.body.data, function (err, genders) {
if (err) return res.status(500).send('Unable to process request.');
res.send([genders.M, genders.F, genders.U]);
});
});
function getGenderFromName(collection, name, next) {
collection.find({nome : name.split(" ")[0].toUpperCase()}, {fields: {_id:0, nome:0}}, function (err, docs) {
if (err) return next(err);
var gender = 'U';
if (docs && docs.length > 0) {
gender = (docs[0].sesso == "M") ? 'M' : 'F';
}
next(null, gender);
});
}
function countGenders(collection, data, next) {
var result = { M: 0, F: 0, U: 0 };
var series = function(i) {
if (i == data.length) return next(null, result);
getGenderFromName(collection, data[i].name, function(err, gender) {
if (err) return next(err);
result[gender]++;
series(i+1);
});
};
series(0);
}
Lets review the changes:
Removed the createCounter structure. No need for a heavy, get/set pattern for this simple example.
Checked for error values in every asynchronous callback
if (err) return next(err);
Within a route handler, typically you will want to end the request with a res.status(500).send(). In most other cases, return next(err) will 'bubble' the error up.
Moved the database query into a new function, getGenderFromName. It mostly retains your original code. This was optional, but substantially improves the readability of the count function.
Finally, rewrote the count function using an appropriate asynchronous iteration pattern, courtesy of http://book.mixu.net/node/ch7.html. Mixu gives a very easy to understand explanation of asynchronous node, give it a read.
An even better option would be use the excellent async module. You could rewrite the count method as
function countGenders(collection, data, next) {
var result = { M: 0, F: 0, U: 0 };
async.eachSeries(
data,
function (value, next) {
getGenderFromName(collection, value.name, function(err, gender) {
if (err) return next(err);
result[gender]++;
next();
});
},
function (err) { next(err, results); }
);
}
Async includes lots of different control flow methods to use, not just simple iterations.
Here is a better way to do this. This really cleans up the asynchronous nature of javascript. Checkout the async library that I am using here.
var collection = db.get('nomi');
var async = require('async');
app.post('/genderize', function(req, res){
let countingObject = {
females: 0,
males: 0,
unknown: 0
};
async.each(req.body.data, function(name, callback) {
collection.findOne({ nome : name.split(" ")[0].toUpperCase() }, { fields: {_id:0, nome:0}}, function (err, nameObject) {
//instead, maybe check if it is male, female, or otherwise mark as unknown?
if (!isEmptyObject(nameObject)) {
//this object probably has getters that you could use instead
nameObject = JSON.parse(JSON.stringify(nameObject));
if(nameObject.sesso == "M"){
countingObject.males++;
} else {
countingObject.females++;
}
} else {
countingObject.unknown++;
}
callback();
});
}, function() {
res.setHeader('Content-Header', 'application/json');
res.send(JSON.stringify(countingCallback));
});
});

Resources