nodejs express knex master detail record update - node.js

I have master(Workbook) & Child(WorkBookDataset). I am trying to update master and at the same time (insert or update or delete) child records. Everything works fine except it is not returning the updated child records. I know I am doing something wrong while specifying the 'then', and as it is always asyc operation, the result is already returned before child updates complete.
var Promise = require('bluebird');
return knex.transaction(function(trx) {
return knex('workbook').where('workbookid',workbook.workbookid).andWhere('userid', workbook.userid)
.update(workbook)
.then(function(updatedrecords) {
return Promise.map(datasets, function(dataset) {
if(dataset.workbookdatasetid && dataset.workbookdatasetid == -1){
//remove
return knex('workbookdataset').where('workbookid',workbook.workbookid).andWhere('datasetid', dataset.datasetid)
.delete();
} else {
dataset.workbookid = workbook.workbookid;
knex('workbookdataset').where('workbookid',workbook.workbookid).andWhere('datasetid', dataset.datasetid)
.then(function(alreadyds) {
if(alreadyds.length == 1){
//update
return knex('workbookdataset').where('workbookid',workbook.workbookid).andWhere('datasetid', dataset.datasetid)
.update(dataset)
}else{
//insert
if(dataset.workbookdatasetid){
delete dataset.workbookdatasetid;
}
return knex('workbookdataset')
.insert(dataset)
}
})
}
});
})
})
.then(function(updatedrecords) {
return getWorkBook(workbook.userid, workbook.workbookid); //this returns updated workbook information, but not updated workbookdataset information
});
I tried putting then() to Promise, but still same. Any help/pointer would be a great help! Thanks in advance!

You need a return on the line where you have knex('workbookdataset').where('workbookid'

Have a look at a .returning method from knex documentation. It allows you to return inserted/updated/deleted records from a database.
Also, I'd recommend you to check your promise chain to be sure you are returning correct values in correct places.
P.S. This is not related to question but if you are using transactions add to your queries .transacting(trx) to actually run them in one transaction instead of separately.
knex('workbook')
.where('workbookid',workbook.workbookid)
.andWhere('userid', workbook.userid)
.update(workbook)
.returning('*')
.transacting(trx)
.then(data => {
console.log(data) // => [{id: 1, workbookid: 1, userid: 981,...}, ...]
})

Related

result prints undefined after invoking a new method

I am trying to loop though all users data in my mongo database and check and see if an email is in the database. The loop currently works and correctly identifies if an email is in a database but the problem is once I verify the email exist I get the id of the same object and use findById() to find the specific object the email was found in then update it. Once I find the object when I try and print the result I got from the first find() it logs undefined but when I log it before the findById() method it logs the result without no problem. Why is this happening and how can I log the previous result after invoking findById(). Take a look at the comments on my code to understand the question better. Thanks in advance.
const Users = require('pathToSchema')
const email = 'test#gmail.com'
Users.find()
.then(async(result) => {
for (var i = 0; i < result.length; i++) {
if (result[i].email == email) {
//this prints result successfully
console.log(result[i])
Users.findById(result[i].id)
.then((result2) => {
//this prints undefiend
console.log(result[i])
})
.catch((err) => {
console.log(err)
})
} else {
if (i === result.length - 1) {
console.log('email not found')
}
}
}
})
.catch((err) => {
console.log(err)
})
From the code snippet it looks like you are trying to print a value from result and not result2. result is not available inside the findById() method callback handler.
Continuing the discussion from the comments, you can use the findOneAndUpdate method in mongodb to find a user with a given email and update. With this, you will not have to find the user before you update. You can do that in a single DB command.
Users.findOneAndUpdate({email: 'test#gmail.com'},{updates},{options});
This will return the original document before update. If you need the updated document in the response, pass returnNewDocument: true in the options.
Link to the documentation for this function
https://www.mongodb.com/docs/manual/reference/method/db.collection.findOneAndUpdate/

Model.findOne() function not working when passed a variable as the value

I am new to node.js.
I am trying to create function, where a randomly generated String is queried to check if it exists or not. If it already exists, the String is randomly generated till it is unique.
let validID = false;
console.log(temp); //temp is the randomly generated String.
while(!validID){
Website.findOne({shortcut: temp},function(err,docs){
if(docs==null){
validID = true;
console.log("The shortcut for the url is" + temp);
}else{
console.log("FOUND");
temp = generateId();
}
});
}
When run, the code is stuck in an infinite while loop.
I tried to see whether the code works with a String value ( not a variable ) passed in as the query inside findOne(). It worked. I am assuming that the fact that temp is a variable is causing the problem. Can variables be passed in as a value in a query? If so, what is the correct way?
Website.findOne operates asynchronously, i.e. the callback-function you passed to it, will be run once the results from the mongodb are fetched. However, node will not be able to actually process this callback, since your callstack never gets emptied due to your while-loop. If you're interested, you can find out more about this here.
One way to solve this is to wrap your Mongo-DB call in a promise, wait for it to resolve, then return if the ID is unique and continue by calling it recursively otherwise (note that this can be highly simplified by using async/await but for understanding how this works using promised are beneficial imo):
function findIdPromise(temp) {
return new Promise((resolve, reject) => {
Website.findOne({
shortcut: temp
}, function (err, docs) {
if (err) {
return reject(err);
}
resolve(docs);
});
});
}
function getNextIsUniqueIdPromise(shortcut) {
return findIdPromise()
.then(docs => {
if (docs == null) {
return shortcut;
}
return getNextIsUniqueIdPromise(generateId());
});
}
// call it initially with
getNextIsUniqueIdPromise(firstShortcutToCheck)
.then(shortcut => {
console.log("The shortcut for the url is" + shortcut):
})
.catch(err => {
console.log("an error occured", err):
});

Multple SQL queries in Node with oracledb

I'm new to Node and am having problems reading from Oracle.
I have the basic examples all set up and can issue basic queries, and process the results etc..
The problem I'm having is that I need to;
Execute one query (Q1)
For each item in the results of Q1 I need to execute a second query (Q2)
I need to combine the results of Q1 and Q2s into an array to return as a promise
I am struggling to find an example where I can perform #2 - call the same query multiple times for each item returned from Q1, using the same connection which was used for Q1.
My code is below - I first perform a read, then iterate through the results storing connection.execute objects which I then run via the Promise.all line - the result of which I just output as I want to get this working before I code the logic to combine the results of Q1 and Q2.
When I run this via mocha, the results of don't contain any data - I see the column headings but no data.
So what am I missing here?
// placeholder for the connection
let conn;
// return case list array
var caseList = [];
var queryList = [];
return new Promise((resolve, reject) => {
// retrieve connection
oracledb.getConnection({
user: dbconfig.user,
password: dbconfig.password,
connectString: dbconfig.connectString
}) // the connection is returned as a promise
.then(connection => {
console.log('Connected to the DB!');
// assign connection
conn = connection;
// execute statement
return connection.execute(
`select caseid, casereference, startdate from caseheader inner join orgobjectlink on caseheader.ownerorgobjectlinkid = orgobjectlink.orgobjectlinkid where orgobjectlink.username = :username`,
[params.username], {
outFormat: oracledb.OBJECT // set the output format to be object
}
);
})
.then(result => {
// iterate around rows
result.rows.forEach(row => {
var caseObj = {
caseID: row.CASEID,
reference: row.CASEREFERENCE,
dateAssigned: moment(row.STARTDATE).format('YYYY-MM-DD'),
username: params.username,
}
caseList.push(caseObj);
console.log(caseObj.caseID)
queryList.push(conn.execute(`select concernroleid, concernrolename from concernrole inner join caseparticipantrole on concernrole.concernroleid = caseparticipantrole.participantroleid where caseparticipantrole.caseid = :caseID and (caseparticipantrole.typecode = 'PRI' or caseparticipantrole.typecode = 'MEM')`,
[caseObj.caseID], {
outFormat: oracledb.OBJECT
}));
});
// build up queries
return Promise.all(queryList).then(results => {
console.log(results);
Promise.resolve(results);
}, err => {
console.log(err);
});
}).then({
if(conn){
console.log("Closing DB connection");
conn.close();
}
}).catch(err => {
console.log('Error', err);
});
});
Promise.all will not work for you as you want to use a single connection and as mentioned previously a connection will only do one thing at a time anyway. To solve this problem using promises, you'd have to build up and unwind a promise chain. I can show you an example, but it's nasty - probably better to just forget I mentioned it.
A better option would be to go into a simple for loop using async/await. I can show you can example of that too but again, I think this is the wrong move. We call this row by row fetching (a.k.a slow by slow).
It's likely the best solution for you will be to take the results from the first query and build up an array. Then execute the second query using one of these options to process the array. https://oracle.github.io/node-oracledb/doc/api.html#sqlwherein
You'll need to include the caseid column in the select clause and perhaps even order by that column so that post-processing of the result set is simplified in Node.js.
This solution has the potential to greatly improve performance and resource utilization, but that has to be balanced against the amount of data you have, the resources, etc. I could probably show you an example of this too, but it will take a bit longer and I'd want to get some more info from you to ensure we're on the right path.
One problem is the Promise.all().then... function doesn't return anything (and doesn't need the additional resolve()). The way to get this sorted is build small, testable, promise returning functions, and test them individually.
Starting simply, write a mocha test to connect to the database...
function connect() {
return oracledb.getConnection({
user: dbconfig.user,
password: dbconfig.password,
connectString: dbconfig.connectString
});
}
Here's one that can run a command on the db. Test this with a simple query that you know will return some results.
function executeCmd(connection, cmd, params) {
return connection.execute(cmd, params, { outFormat: oracledb.OBJECT });
}
With just these two (and one more) we can outline a simple function that does the job: connect to the database, run a select, process each result asynchronously, then disconnect.
function connectAndQuery(username) {
let connection;
return connect().then(result => {
connection = result;
let cmd = `select caseid, casereference, startdate from caseheader inner join orgobjectlink on caseheader.ownerorgobjectlinkid = orgobjectlink.orgobjectlinkid where orgobjectlink.username = :username`;
return executeCmd(connection, cmd, [username]);
}).then(result => {
let promises = result.rows.map(row => processCaseRow(connection, row, username));
return Promise.all(promises);
}).then(result => {
// result should be an array of caseObj's
return connection.close().then(() => result);
});
}
The last thing to build and test is a promise-returning function which processes a row from the main function above.
I had to take some liberty with this, but I think the objective is -- given a row representing a "case" -- build a case object, including a collection of "concernedRoles" that can be queried with the caseID. (that last bit was my idea, but you can build a separate collection if you like)
// return a promise that resolves to an object with the following properties...
// caseID, reference, dateAssigned, username, concernedRoles
// get concernedRoles by querying the db
function processCaseRow(connection, row, username) {
var caseObj = {
caseID: row.CASEID,
reference: row.CASEREFERENCE,
dateAssigned: moment(row.STARTDATE).format('YYYY-MM-DD'),
username: username
}
let cmd = `select concernroleid, concernrolename from concernrole inner join caseparticipantrole on concernrole.concernroleid = caseparticipantrole.participantroleid where caseparticipantrole.caseid = :caseID and (caseparticipantrole.typecode = 'PRI' or caseparticipantrole.typecode = 'MEM')`;
return executeCmd(connection, cmd, row.CASEID).then(result => {
caseObj.concernedRole = result
return caseObj
})
}

Using node.js and promise to fetch paginated data

Please keep in mind that I am new to node.js and I am used with android development.
My scenario is like this:
Run a query against the database that returns either null or a value
Call a web service with that database value, that offers info paginated, meaning that on a call I get a parameter to pass for the next call if there is more info to fetch.
After all the items are retrieved, store them in a database table
If everything is well, for each item received previously, I need to make another web call and store the retrieved info in another table
if fetching any of the data set fails, all data must be reverted from the database
So far, I've tried this:
getAllData: function(){
self.getMainWebData(null)
.then(function(result){
//get secondary data for each result row and insert it into database
}
}
getMainWebData: function(nextPage){
return new Promise(function(resolve, reject) {
module.getWebData(nextPage, function(errorReturned, response, values) {
if (errorReturned) {
reject(errorReturned);
}
nextPage = response.nextPageValue;
resolve(values);
})
}).then(function(result) {
//here I need to insert the returned values in database
//there's a new page, so fetch the next set of data
if (nextPage) {
//call again getMainWebData?
self.getMainWebData(nextPage)
}
})
There are a few things missing, from what I've tested, getAllData.then fires only one for the first set of items and not for others, so clearly handling the returned data in not right.
LATER EDIT: I've edited the scenario. Given some more research my feeling is that I could use a chain or .then() to perform the operations in a sequence.
Yes it is happening as you are resolving the promise on the first call itself. You should put resolve(value) inside an if statement which checks if more data is needed to be fetched. You will also need to restructure the logic as node is asynchronous. And the above code will not work unless you do change the logic.
Solution 1:
You can either append the paginated response to another variable outside the context of the calls you are making. And later use that value after you are done with the response.
getAllData: function(){
self.getMainWebData(null)
.then(function(result){
// make your database transaction if result is not an error
}
}
function getList(nextpage, result, callback){
module.getWebData(nextPage, function(errorReturned, response, values) {
if(errorReturned)
callback(errorReturned);
result.push(values);
nextPage = response.nextPageValue;
if(nextPage)
getList(nextPage, result, callback);
else
callback(null, result);
})
}
getMainWebData: function(nextPage){
return new Promise(function(resolve, reject) {
var result = [];
getList(nextpage, result, function(err, results){
if(err)
reject(err);
else{
// Here all the items are retrieved, you can store them in a database table
// for each item received make your web call and store it into another variable or result set
// suggestion is to make the database transaction only after you have retrieved all your data
// other wise it will include database rollback which will depend on the database which you are using
// after all this is done resolve the promise with the returning value
resolve(results);
}
});
})
}
I have not tested it but something like this should work. If problem persists let me know in comments.
Solution 2:
You can remove promises and try the same thing with callback as they are easier to follow and will make sense to the programmers who are familiar with structural languages.
Looking at your problem, I have created a code that would loop through promises.
and would only procede if there is more data to be fetched, the stored data would still be available in an array.
I hope this help. Dont forget to mark if it helps.
let fetchData = (offset = 0, limit= 10) => {
let addresses = [...Array(100).keys()];
return Promise.resolve(addresses.slice(offset, offset + limit))
}
// o => offset & l => limit
let o = 0, l = 10;
let results = [];
let process = p => {
if (!p) return p;
return p.then(data => {
// Process with data here;
console.log(data);
// increment the pagination
o += l;
results = results.concat(data);
// while there is data equal to limit set then fetch next page
// otherwise return the collected result
return (data.length == l)? process(fetchAddress(o, l)).then(data => data) : results;
})
}
process(fetchAddress(o, l))
.then(data => {
// All the fetched data will be here
}).catch(err => {
// Handle Error here.
// All the retrieved data from database will be available in "results" array
});
if You want to do it more often I have also created a gist for reference.
If You dont want to use any global variable, and want to do it in very functional way. You can check this example. However it requires little more complication.

Returning response from Mongoose promise

Followup from this question > Stopping response if document isn't found since it was recommended I use Promise.
So basic premise, I want node to return "Can't find ID" message if we can't find the id in our database.
v1.post("/", function(req, res) {
// If the project_id isn't provided, return with an error.
if ( !("project_id" in req.body) ) {
return res.send("You need to provide Project ID");
}
// Check if the Project ID is in the file.
helper.documentExists( ProjectsData, {project_id: req.body.project_id} )
.then(function(c) {
if ( c == 0 ) {
return res.send("The provided Project Id does not exist in our database.");
} else {
var gameDataObj = req.body;
GameData.addGameId(gameDataObj, function (err, doc) {
if (err) {
if (err.name == "ValidationError") {
return res.send("Please send all the required details.");
}
throw err;
};
res.json(doc);
})
};
});
});
And helper.documentExists
module.exports = {
documentExists: function(collection, query) {
return collection.count( query ).exec();
},
};
But the script continues to run after this and prints the "required data not found".
Output:
required data not found
1
I am using native ES6 Promises.
var mongoose = require("mongoose");
mongoose.Promise = global.Promise;
EDIT: Included the entire get route. (will fix those throw err later)
#######POINT 1#########
ProjectsData.count( {project_id: req.body.project_id} )
.then(function(c) {
#######POINT 3#########
if ( c == 0 ) {
console.log("1");
return res.send("The provided Project Id does not exist in our database.");
console.log("2");
}
});
#######POINT 2#########
//some other logic
console.log("required data not found");
Following async workflow: after POINT 1, the promise is created and your handler is attached. Now POINT 2 will continue, while (at some future clock the promise is resolved and you reach POINT 3.
With my limited understanding of your workflow/purpose I'd say simply put POINT 2 code in the else{} of the if at POINT 3 (as you rightly guessed in the comments).
EDIT: thanks to #jfriend00 for pointing out a serious mistake in the previous version of my answer.
Your code essentially results in this:
ProjectsData.count().then(...);
console.log("required data not found");
So, of course the second console.log() is going to run and print. Nothing that happens in the .then() handler runs until long after the console.log() has already run. And, even then, it can't stop other code from running. Promises don't make the interpreter "wait". They just provide structure for you to coordinate your asynchronous operations.
If you want to branch with promises, then you have to branch inside the .then() handler, not after it.
You don't show enough of the rest of what you're doing to know how to recommend a complete solution. We need to see the rest of your request in order to help you with the proper branching based on asynchronous results.
You probably need something like this:
ProjectsData.count( {project_id: req.body.project_id} ).then(function(c) {
if ( c == 0 ) {
return res.send("The provided Project Id does not exist in our database.");
} else {
// put other logic here
}
}).catch(function(err) {
// handle error here
});

Resources