I am using sequelize with mysql in my project. The current use case is about populating a table with 'roles_master' with some default roles.
The function createDefaultRoles implementation checks:
if there are rows in the table
if yes, and force is true, deletes existing rows and inserts new rows
if table empty, directly inserts new rows
My code is as below...
/**
* Creates a pre-defined set of roles
* If roles table exists and already contains data,
* this function simply returns, without creating any roles.
* The list of roles is defined in the file "ecp_db_defaults.js"
*
* #param force -- boolean flag to indicate whether to drop existng roles and recreate afresh
**/
function createDefaultRoles(force, callback) {
console.log("Executing createDefaultRoles...");
db.Role.count().then(function(result) {
if (result>0) {
if (force) {
console.log("Emptying the roles table...");
db.Role.destroy({force:true, where: {}}).then(function() {
db.Role.bulkCreate(dbConfig.roles).done(function() {
console.log("1. Successfully created user roles.");
if (callback) return callback(null); else return;
}).catch(function(e){
console.log("Error while creating roles...", e);
if (callback) return callback(e);
});
}).catch(function(e) {
console.error("Error while destroying roles table.", e);
return (callback)?callback(e):e;
});
}
//if (callback) return callback(null); else return;
} else {
db.Role.bulkCreate(dbConfig.roles).done(function(){
console.log("2. Successfully created user roles.");
if (callback) return callback(null);
})
}
}).catch(function(e){
console.error("Error while querying roles table.", e);
if (callback) return callback(e);
});
}
The problem is that, it works partially. While, it is able to delete the existing rows and add new ones, it also throws an exception which is caught and printed as below..
Executing createDefaultRoles...
Emptying the roles table... Error while destroying roles table. [TypeError: Cannot read property 'catch'of undefined]
Successfully created user roles.
Finally, the function fails, because of the above error. I have tried matching all .then() and .catch() promises correctly. They seem to be fine.
Any help in solving this is highly appreciated.
I guess the problem is in the block
db.Role.bulkCreate(dbConfig.roles).done(function() {
console.log("1. Successfully created user roles.");
if (callback) return callback(null); else return;
}).catch(function(e){
console.log("Error while creating roles...", e);
if (callback) return callback(e);
});
Since .done() returns undefined you cannot append a 'catch' on this.
If you replace .done with .then it should work as expected
Related
There is a field called counter in a model and whenever if I call a custom remote method like
Request URL
http://loopback:3000/api/models/increment_counter
Request body
EMPTY
Response
{
"counter" : [Value after Increment]
}
Currently to increment First i have to get the counter value from db and increment it one and update the couter value, This involves two queries, Is it possible to do it in a single NodeAPI call like the below mysql query.I am currently using Mysql as DB.
mysql_query("
UPDATE model
SET counter = counter + 1
WHERE model_id = '1'
");
Thank you
Given the MySQL syntax you want, you seem to need an atomic counter.
database transactions
With the MySQL connector, you can use database transactions. It is supported by the MySQL connector. It's a bit overkill for just atomic counters but it will get the job done.
Here is an example, inside a custom remote method
MyModel.someRemoteMethodForIncrementing = function(callback) {
// Start the transaction
MyModel.beginTransaction({
isolationLevel: MyModel.Transaction.READ_COMMITTED
}, function(err, tx) {
// Retrieve the current counter value as part of the transaction
MyModel.findById(id, {
transaction: tx
}, function(err, data) {
if (err) {
console.log(err);
return tx.rollback(function(err) {
callback(err);
});
}
// Increment the counter as part of the transaction
var inc = data.counter + 1;
MyModel.updateAttributes({
counter: inc
}, {
transaction: tx
}, function(err, newData) {
if (err) {
console.log(err);
return tx.rollback(function(err) {
callback(err);
});
}
// Commit the transaction to make it happen
tx.commit(function(err) {
if (err) return callback(err);
// Counter should have been incremented
callback();
});
});
});
});
};
I haven't tested it but it should work, let me know
extented operators
In the future, you will be able to use the $inc (increment) extended operator but so far it's only supported by MongoDB connector, not MySQL.
Just for reference, here is the syntax (works only with MongoDB)
PUT api/Model/:id?filter={"$inc":{"name":propertyToIncrement, "count":incrementAmount}}
There is an ongoing PR that I am trying to get landed to get MySQL support, but there are many things to be done before it can get merged.
Yes you can do it in a single loopback remote method call. Assume you are sending an Id in your request
yourModel.remoteMethod = function(data, cb){
yourModel.findById(data.id, function(err, object){
if(err){
cb(err, null);
}
object.counter += 1;
object.save(function(saveErr, afterSaveObj){
cb(saveErr, afterSaveObj.counter);
})
});
}
here cb is a callback which loopback passes to your remoteMethod.
I'm pretty new to nodejs and I'm having some difficulties to understand how to use the mysql connection object.
My problem is not in the code but in the design pattern.
lets say I have a user module
module.exports = function(){
return{
id: "",
load: function(id){
var sql = 'SELECT * from users where id = '+ DB.escape(id);
console.log(1);
DB.query(sql, function (err, rows) {
this.id = rows[0].id; // not working
console.log(rows[0].id); // prints the id 4
console.log(2);
});
console.log(3);
}
}
}
from outside the module i run the next code
var user = require('../modules/user');
var selected_user = user();
console.log("entering users me route");
selected_user.load(4);
console.log("user id is " + selected_user.id); //This does not print the id 4
when I run the code, the console logs 1, then 3, and then 2.
This is due to the asynchronous flow of node js.
But if I'm building a website, and I need the query to end in order to populate my user object before I send the HTML to the browser???
What's the right way to do it ?
Also when I try to populate the id property of user in the id i receive from the DB it does not work.
Any ideas?
Thanks
There are several ways to do this. I would go with Promises.
Suppose you have an asynchronous function "getUsers".
It looks like this:
function getUsers() {
longQuery(function(err, result){
// What to do with result?
});
You need to rewrite it to be able to use the result.
Let's try:
function getUsers() {
return new Promise(function(resolve, reject) {
longQuery(function(err, result){
if(err) reject(err)
else resolve(result)
});
});
Now this function returns a promise. What do we do with that promise?
function handleRequest(req, res) {
getUsers().then(function(result) {
// Do stuff with result
res.send(myProcessedData);
}).catch(function(err) {console.log(err)};
}
This could also have been done with callbacks, passing the response object as a parameter to the query function, and many other ways, but I think promises are a very elegant way for handling this.
this.id = rows[0].id; // not working
The above line is not working because you are setting it to this.id from inside a callback function. When you are inside a callback function this does not mean the this in the main object.
For more discussion about this: see How to access the correct `this` context inside a callback?
To tackle the asynchronous nature of javascript you can either use promise like the answer from matanso or you can pass a callback function to your load method. So your load: function(id) method will be load: function(id, callbackFunction) and call the callback function when you get all the data that you need.
I have 2 sequelize models (Event and Inventory associated with 2 tables). I created a Event._create method so that I can use it to create an event in event db with multiple products recorded in the inventory db at the same time. Each inventory is associated with the event_id of the newly created event.
Because all these stuff should success or fail altogether, I use sequelize's transaction to achieve this.
Initially I was thinking about doing something like this.
sequelize.transactionPromise = Promise.promisify(sequelize.transaction, sequelize);
return sequelize.transactionPromise({autocommit: 0})
.then(function(t) {
return Event.create(ev, {transaction: t})
.then(function(event){
var event_id = event.id; // ------ (*)
return Promise.resolve([1, ..., event_number])
.then(function(){
Inventory.create({product_id: some_product_id, event_id: event_id},
{transaction: t});
});
.then(function(){
return Promise.cast(t.commit())
.then(function() { // successfully committed
return res.json(d);
}).catch(function(err){ // cannot commit somehow
return res.json(500, err.toString());
});
}).catch(function(err){ // error rollback
return Promise.cast(t.rollback())
.then(function() {
return Promise.reject('rollback: ' + err.toString());
});
});
});
But this doesn't work because before the transaction is commiteed the (*) has no value and gives me event_id of NULL.
Instead I do something like below:
var Event = sequelize.model('Event');
var Inventory = sequelize.model('Inventory');
var _create = function(t, ev){
var ev_id_secret = {secret: 'some random secret'};
return Promise.cast(Event.create(ev_id_secret))
.then(function(d){
ev_id_secret.id = d.id;
return true;
}).then(function(){
return Promise.resolve(_.range(ev.number_of_products))
.map(function(){
var inventory = {
event_id: ev_id_secret.id,
product_id: ev.product_id
};
return Promise.cast(Inventory._create(t, inventory));
});
}).then(function(){ // thennable a transaction
return Promise.cast(Event.update(ev, ev_id_secret, {transaction: t}));
});
};
So I can do something like this.
sequelize.transactionPromise=Promise.promisify(sequelize.transaction, sequelize);
return sequelize.transactionPromise({autocommit: 0})
.then(function(t) {
return Event._create(t, ev)
.then(function(){
return Promise.cast(t.commit())
.then(function() {
return res.json(d);
}).catch(function(err){
return res.json(500, err.toString());
});
}).catch(function(err){
return Promise.cast(t.rollback())
.then(function() {
return Promise.reject('rollback: ' + err.toString());
});
});
}).catch(function(err){
console.log(err.stack);
res.json(500, {error: err.toString()});
});
What I do with _create is that I just insert an empty event (with randomly generated secret) in Event db and some empty products in Inventory db, later get event_id using this secret to query, update the event and the inventory accordingly.
The thing is when the promise is rejected, the transaction rollback() is called and leave empty event and product records in the db. So I have to deal with the empty records later, which is really disturbing.
So my question is how do I do transaction between 2 tables correctly? Am I on the right track?
p.s: as a side question you can see my code is full of return Promise.xxx statements, this ensures the control flow but the promise gets really messy. Is there something I can do to improve my code? Thanks.
Your original implementation was correct. However, after you created the event, the object should come back with the id already populated without having to commit (I'm assuming you're using the standard auto-incremented sequelize id's). All SQL databases function this way. I recommend debugging your application as to determine the root of the problem.
Here's a sample implementation of sequelize transaction from one of my applications. I've just tested and confirmed that calling db.Deposit.create() resolves to an object with valid id. I am using Bluebird, Sequelize 2.0.0-rc2, and Postgresql 9.3
module.exports.createDeposit = function(deposit) {
return db.sequelize.transaction({isolationLevel: 'READ COMMITTED' })
.then(function(t){
var strQuery = 'UPDATE "Accounts" '
+ 'SET "balance"="balance" + :amount, "updatedAt"=NOW() '
+ 'WHERE "id"= :AccountId RETURNING *';
return sql.query(strQuery,
db.Account.build(),
{ raw: true, transaction : t },
{ AccountId: deposit.AccountId, amount: deposit.amount })
.then(function(account){
if (!account) throw new Error('Account does not exist')
return db.Deposit.create(deposit, { transaction: t });
})
.then(function(dbDeposit){
// If successful, dbDeposit object contains a valid id
if (!dbDeposit) throw new Error('Failed to create deposit');
return t.commit()
.then(function(){
return dbDeposit;
});
})
.catch(function(e){
return t.rollback()
.then(function(){
throw e;
});
});
});
};
Your original implementation was correct as far as i can tell but can be improved by levering managed transactions and full promise support in the latest versions of Sequelize:
return sequelize.transaction({autocommit: false}, function(t) {
return Event.create(ev, {transaction: t}).then(function(event) {
return Promise.map(_.range(event_number), function (number) {
return Inventory.create({product_id: some_product_id, event_id: event.get('id')}, {transaction: t});
});
});
}).then(function(){
// Automatically comitted at this point if the promise chain returned to the transaction successfully resolved
return res.status(200).json(d);
}).catch(function(err){
// Automatically rolledback at this point if the promise chain returned to the transaction was rejected
return res.status(403).json();
});
You can read more about managed transactions on the docs: http://sequelize.readthedocs.org/en/latest/docs/transactions/
I'm using Sequelize in my Nodejs project and I found a problem that I'm having a hard time to solve.
Basically I have a cron that gets an array of objects from a server than inserts it on my database as a object ( for this case, cartoons ). But if I already have one of the objects, I have to update it.
Basically I have a array of objects and a could use the BulkCreate() method. But as the Cron starts again, it doesn't solve it so I was needing some sort of update with an upsert true flag. And the main issue: I must have a callback that fires just once after all these creates or updates. Does anyone have an idea of how can I do that? Iterate over an array of object.. creating or updating it and then getting a single callback after?
Thanks for the attention
From the docs, you don't need to query where to perform the update once you have the object. Also, the use of promise should simplify callbacks:
Implementation
function upsert(values, condition) {
return Model
.findOne({ where: condition })
.then(function(obj) {
// update
if(obj)
return obj.update(values);
// insert
return Model.create(values);
})
}
Usage
upsert({ first_name: 'Taku' }, { id: 1234 }).then(function(result){
res.status(200).send({success: true});
});
Note
This operation is not atomic.
Creates 2 network calls.
which means it is advisable to re-think the approach and probably just update values in one network call and either:
Look at the value returned (i.e. rows_affected) and decide what to do.
Return success if update operation succeeds. This is because whether the resource exists is not within this service's responsibility.
You can use upsert
It's way easier.
Implementation details:
MySQL - Implemented as a single query INSERT values ON DUPLICATE KEY UPDATE values
PostgreSQL - Implemented as a temporary function with exception handling: INSERT EXCEPTION WHEN unique_constraint UPDATE
SQLite - Implemented as two queries INSERT; UPDATE. This means that the update is executed regardless of whether the row already
existed or not
MSSQL - Implemented as a single query using MERGE and WHEN (NOT) MATCHED THEN Note that SQLite returns undefined for created, no
matter if the row was created or updated. This is because SQLite
always runs INSERT OR IGNORE + UPDATE, in a single query, so there
is no way to know whether the row was inserted or not.
Update 07/2019 now with async/await
async function updateOrCreate (model, where, newItem) {
// First try to find the record
const foundItem = await model.findOne({where});
if (!foundItem) {
// Item not found, create a new one
const item = await model.create(newItem)
return {item, created: true};
}
// Found an item, update it
const item = await model.update(newItem, {where});
return {item, created: false};
}
I liked the idea of Ataik, but made it a little shorter:
function updateOrCreate (model, where, newItem) {
// First try to find the record
return model
.findOne({where: where})
.then(function (foundItem) {
if (!foundItem) {
// Item not found, create a new one
return model
.create(newItem)
.then(function (item) { return {item: item, created: true}; })
}
// Found an item, update it
return model
.update(newItem, {where: where})
.then(function (item) { return {item: item, created: false} }) ;
}
}
Usage:
updateOrCreate(models.NewsItem, {slug: 'sometitle1'}, {title: 'Hello World'})
.then(function(result) {
result.item; // the model
result.created; // bool, if a new item was created.
});
Optional: add error handling here, but I strongly recommend to chain all promises of one request and have one error handler at the end.
updateOrCreate(models.NewsItem, {slug: 'sometitle1'}, {title: 'Hello World'})
.then(..)
.catch(function(err){});
This might be an old question, but this is what I did:
var updateOrCreate = function (model, where, newItem, onCreate, onUpdate, onError) {
// First try to find the record
model.findOne({where: where}).then(function (foundItem) {
if (!foundItem) {
// Item not found, create a new one
model.create(newItem)
.then(onCreate)
.catch(onError);
} else {
// Found an item, update it
model.update(newItem, {where: where})
.then(onUpdate)
.catch(onError);
;
}
}).catch(onError);
}
updateOrCreate(
models.NewsItem, {title: 'sometitle1'}, {title: 'sometitle'},
function () {
console.log('created');
},
function () {
console.log('updated');
},
console.log);
User.upsert({ a: 'a', b: 'b', username: 'john' })
It will try to find record by hash in 1st param to update it, if it will not find it - then new record will be created
Here is example of usage in sequelize tests
it('works with upsert on id', function() {
return this.User.upsert({ id: 42, username: 'john' }).then(created => {
if (dialect === 'sqlite') {
expect(created).to.be.undefined;
} else {
expect(created).to.be.ok;
}
this.clock.tick(1000);
return this.User.upsert({ id: 42, username: 'doe' });
}).then(created => {
if (dialect === 'sqlite') {
expect(created).to.be.undefined;
} else {
expect(created).not.to.be.ok;
}
return this.User.findByPk(42);
}).then(user => {
expect(user.createdAt).to.be.ok;
expect(user.username).to.equal('doe');
expect(user.updatedAt).to.be.afterTime(user.createdAt);
});
});
Sound likes you want to wrap your Sequelize calls inside of an async.each.
This can be done with the custom event emitter.
Assuming your data is in a variable called data.
new Sequelize.Utils.CustomEventEmitter(function(emitter) {
if(data.id){
Model.update(data, {id: data.id })
.success(function(){
emitter.emit('success', data.id );
}).error(function(error){
emitter.emit('error', error );
});
} else {
Model.build(data).save().success(function(d){
emitter.emit('success', d.id );
}).error(function(error){
emitter.emit('error', error );
});
}
}).success(function(data_id){
// Your callback stuff here
}).error(function(error){
// error stuff here
}).run(); // kick off the queries
you can use findOrCreate and then update methods in sequelize. here is a sample with async.js
async.auto({
getInstance : function(cb) {
Model.findOrCreate({
attribute : value,
...
}).complete(function(err, result) {
if (err) {
cb(null, false);
} else {
cb(null, result);
}
});
},
updateInstance : ['getInstance', function(cb, result) {
if (!result || !result.getInstance) {
cb(null, false);
} else {
result.getInstance.updateAttributes({
attribute : value,
...
}, ['attribute', ...]).complete(function(err, result) {
if (err) {
cb(null, false);
} else {
cb(null, result);
}
});
}
}]
}, function(err, allResults) {
if (err || !allResults || !allResults.updateInstance) {
// job not done
} else {
// job done
});
});
Here is a simple example that either updates deviceID -> pushToken mapping or creates it:
var Promise = require('promise');
var PushToken = require("../models").PushToken;
var createOrUpdatePushToken = function (deviceID, pushToken) {
return new Promise(function (fulfill, reject) {
PushToken
.findOrCreate({
where: {
deviceID: deviceID
}, defaults: {
pushToken: pushToken
}
})
.spread(function (foundOrCreatedPushToken, created) {
if (created) {
fulfill(foundOrCreatedPushToken);
} else {
foundOrCreatedPushToken
.update({
pushToken: pushToken
})
.then(function (updatedPushToken) {
fulfill(updatedPushToken);
})
.catch(function (err) {
reject(err);
});
}
});
});
};
2022 update:
You can use the upsert function:
https://sequelize.org/api/v6/class/src/model.js~model#static-method-upsert
Insert or update a single row. An update will be executed if a row which matches the supplied values on either the primary key or a unique key is found. Note that the unique index must be defined in your sequelize model and not just in the table. Otherwise you may experience a unique constraint violation, because sequelize fails to identify the row that should be updated.
Implementation details:
MySQL - Implemented with ON DUPLICATE KEY UPDATE`
PostgreSQL - Implemented with ON CONFLICT DO UPDATE. If update data contains PK field, then PK is selected as the default conflict key.
Otherwise first unique constraint/index will be selected, which can satisfy conflict key requirements.
SQLite - Implemented with ON CONFLICT DO UPDATE
MSSQL - Implemented as a single query using MERGE and WHEN (NOT) MATCHED THEN
Note that Postgres/SQLite returns null for created, no matter if the row was created or updated
I'm very new to nodejs and have a question.
Trying to create a function that will call the value of any field where I mention its ID from a table:
function getUserInfo (userID, dynamicField) {
var query = connection.query('SELECT '+dynamicField+' from users WHERE userID = '+connection.escape(userID));
query.on('result', function(row) {
return(row.dynamicField);
});
};
console.log(getUserInfo(8, userEmail)) //this should get me the userEmail value of the user with userID=8
However, I get "undefined". If I use console.log rather than return, it logs the value but this has no use as a function to be used inside other functions to get a value.
I will be glad if I can get help for modifying the function.
This is a common mistake amongst async/nodejs beginners. You have essentially wrapped an async function inside a sync function which breaks down the nature of node's event loop. The return expression needs to be replaced with a callback. See below:
// Method
function getUserInfo (userID, dynamicField, callback) {
var query = connection.query('SELECT '+dynamicField+' from users WHERE userID = '+connection.escape(userID));
query.on('result', function(row) {
callback(null, row.dynamicField);
});
};
// Implementation
getUserInfo(8, userEmail, function(err, result){
console.log(err || result);
});
By convention, in Nodejs we always pass an error object first in the callback. In this case since there is no error to capture, we pass null in its place.