Support upsert
This commit is contained in:
parent
ff65e3a7ad
commit
c06f28f433
|
@ -167,6 +167,30 @@ AbstractClass.create = function (data, callback) {
|
|||
}
|
||||
};
|
||||
|
||||
AbstractClass.upsert = AbstractClass.updateOrCreate = function upsert(data, callback) {
|
||||
var Model = this;
|
||||
if (!data.id) return this.create(data, callback);
|
||||
if (this.schema.adapter.updateOrCreate) {
|
||||
this.schema.adapter.updateOrCreate(Model.modelName, data, function (err, data) {
|
||||
var obj = data ? new Model(data) : null;
|
||||
if (obj) {
|
||||
addToCache(Model, obj);
|
||||
}
|
||||
callback(err, obj);
|
||||
});
|
||||
} else {
|
||||
this.find(data.id, function (err, inst) {
|
||||
if (err) return callback(err);
|
||||
if (inst) {
|
||||
inst.updateAttributes(data, callback);
|
||||
} else {
|
||||
var obj = new Model(data);
|
||||
obj.save(data, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
AbstractClass.exists = function exists(id, cb) {
|
||||
if (id) {
|
||||
this.schema.adapter.exists(this.modelName, id, cb);
|
||||
|
@ -250,9 +274,7 @@ function substractDirtyAttributes(object, data) {
|
|||
|
||||
AbstractClass.destroyAll = function destroyAll(cb) {
|
||||
this.schema.adapter.destroyAll(this.modelName, function (err) {
|
||||
if (!err) {
|
||||
clearCache(this);
|
||||
}
|
||||
clearCache(this);
|
||||
cb(err);
|
||||
}.bind(this));
|
||||
};
|
||||
|
@ -424,7 +446,7 @@ AbstractClass.prototype.updateAttributes = function updateAttributes(data, cb) {
|
|||
}
|
||||
done.call(inst, function () {
|
||||
saveDone.call(inst, function () {
|
||||
cb(err);
|
||||
cb(err, inst);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,15 +17,29 @@ Memory.prototype.define = function defineModel(descr) {
|
|||
};
|
||||
|
||||
Memory.prototype.create = function create(model, data, callback) {
|
||||
var id = this.ids[model]++;
|
||||
var id = data.id || this.ids[model]++;
|
||||
data.id = id;
|
||||
this.cache[model][id] = data;
|
||||
callback(null, id);
|
||||
};
|
||||
|
||||
Memory.prototype.updateOrCreate = function (model, data, callback) {
|
||||
var mem = this;
|
||||
this.exists(model, data.id, function (err, exists) {
|
||||
if (exists) {
|
||||
mem.save(model, data, callback);
|
||||
} else {
|
||||
mem.create(model, data, function (err, id) {
|
||||
data.id = id;
|
||||
callback(err, data);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Memory.prototype.save = function save(model, data, callback) {
|
||||
this.cache[model][data.id] = data;
|
||||
callback();
|
||||
callback(null, data);
|
||||
};
|
||||
|
||||
Memory.prototype.exists = function exists(model, id, callback) {
|
||||
|
@ -151,6 +165,7 @@ Memory.prototype.updateAttributes = function updateAttributes(model, id, data, c
|
|||
};
|
||||
|
||||
function merge(base, update) {
|
||||
if (!base) return update;
|
||||
Object.keys(update).forEach(function (key) {
|
||||
base[key] = update[key];
|
||||
});
|
||||
|
|
|
@ -81,6 +81,29 @@ MongoDB.prototype.find = function find(model, id, callback) {
|
|||
});
|
||||
};
|
||||
|
||||
MongoDB.prototype.updateOrCreate = function updateOrCreate(model, data, callback) {
|
||||
var adapter = this;
|
||||
if (!data.id) return this.create(data, callback);
|
||||
this.find(model, data.id, function (err, inst) {
|
||||
if (err) return callback(err);
|
||||
if (inst) {
|
||||
adapter.updateAttributes(model, data.id, data, callback);
|
||||
} else {
|
||||
delete data.id;
|
||||
adapter.create(model, data, function (err, id) {
|
||||
if (err) return callback(err);
|
||||
if (id) {
|
||||
data.id = id;
|
||||
delete data._id;
|
||||
callback(null, data);
|
||||
} else{
|
||||
callback(null, null); // wtf?
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
MongoDB.prototype.destroy = function destroy(model, id, callback) {
|
||||
this.collection(model).remove({_id: new ObjectID(id)}, callback);
|
||||
};
|
||||
|
|
|
@ -15,13 +15,24 @@ exports.initialize = function initializeSchema(schema, callback) {
|
|||
port: s.port || 3306,
|
||||
user: s.username,
|
||||
password: s.password,
|
||||
database: s.database,
|
||||
debug: s.debug
|
||||
});
|
||||
|
||||
schema.adapter = new MySQL(schema.client);
|
||||
schema.adapter.schema = schema;
|
||||
// schema.client.query('SET TIME_ZONE = "+04:00"', callback);
|
||||
process.nextTick(callback);
|
||||
schema.client.query('USE ' + s.database, function (err) {
|
||||
if (err && err.message.match(/^unknown database/i)) {
|
||||
var dbName = s.database;
|
||||
schema.client.query('CREATE DATABASE ' + dbName, function (error) {
|
||||
if (!error) {
|
||||
callback();
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
} else callback();
|
||||
});
|
||||
};
|
||||
|
||||
function MySQL(client) {
|
||||
|
@ -32,10 +43,27 @@ function MySQL(client) {
|
|||
require('util').inherits(MySQL, BaseSQL);
|
||||
|
||||
MySQL.prototype.query = function (sql, callback) {
|
||||
if (!this.schema.connected) {
|
||||
return this.schema.on('connected', function () {
|
||||
this.query(sql, callback);
|
||||
}.bind(this));
|
||||
}
|
||||
var client = this.client;
|
||||
var time = Date.now();
|
||||
var log = this.log;
|
||||
if (typeof callback !== 'function') throw new Error('callback should be a function');
|
||||
this.client.query(sql, function (err, data) {
|
||||
if (err && err.message.match(/^unknown database/i)) {
|
||||
var dbName = err.message.match(/^unknown database '(.*?)'/i)[1];
|
||||
client.query('CREATE DATABASE ' + dbName, function (error) {
|
||||
if (!error) {
|
||||
client.query(sql, callback);
|
||||
} else {
|
||||
callback(err);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (log) log(sql, time);
|
||||
callback(err, data);
|
||||
});
|
||||
|
@ -57,6 +85,40 @@ MySQL.prototype.create = function (model, data, callback) {
|
|||
});
|
||||
};
|
||||
|
||||
MySQL.prototype.updateOrCreate = function (model, data, callback) {
|
||||
var mysql = this;
|
||||
var fieldsNames = [];
|
||||
var fieldValues = [];
|
||||
var combined = [];
|
||||
var props = this._models[model].properties;
|
||||
Object.keys(data).forEach(function (key) {
|
||||
if (props[key] || key === 'id') {
|
||||
var k = '`' + key + '`';
|
||||
var v;
|
||||
if (key !== 'id') {
|
||||
v = mysql.toDatabase(props[key], data[key]);
|
||||
} else {
|
||||
v = data[key];
|
||||
}
|
||||
fieldsNames.push(k);
|
||||
fieldValues.push(v);
|
||||
if (key !== 'id') combined.push(k + ' = ' + v);
|
||||
}
|
||||
});
|
||||
|
||||
var sql = 'INSERT INTO ' + this.tableEscaped(model);
|
||||
sql += ' (' + fieldsNames.join(', ') + ')';
|
||||
sql += ' VALUES (' + fieldValues.join(', ') + ')';
|
||||
sql += ' ON DUPLICATE KEY UPDATE ' + combined.join(', ');
|
||||
|
||||
this.query(sql, function (err, info) {
|
||||
if (!err && info && info.insertId) {
|
||||
data.id = info.insertId;
|
||||
}
|
||||
callback(err, data);
|
||||
});
|
||||
};
|
||||
|
||||
MySQL.prototype.toFields = function (model, data) {
|
||||
var fields = [];
|
||||
var props = this._models[model].properties;
|
||||
|
|
|
@ -207,7 +207,7 @@ Neo4j.prototype.save = function save(model, data, callback) {
|
|||
if (err) return callback(err);
|
||||
self.updateIndexes(model, node, function (err) {
|
||||
if (err) return console.log(err);
|
||||
callback(null);
|
||||
callback(null, node.data);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -55,7 +55,7 @@ PG.prototype.query = function (sql, callback) {
|
|||
* Must invoke callback(err, id)
|
||||
*/
|
||||
PG.prototype.create = function (model, data, callback) {
|
||||
var fields = this.toFields(model, data,true);
|
||||
var fields = this.toFields(model, data, true);
|
||||
var sql = 'INSERT INTO ' + this.tableEscaped(model) + '';
|
||||
if (fields) {
|
||||
sql += ' ' + fields;
|
||||
|
@ -69,6 +69,43 @@ PG.prototype.create = function (model, data, callback) {
|
|||
});
|
||||
};
|
||||
|
||||
PG.prototype.updateOrCreate = function (model, data, callback) {
|
||||
var pg = this;
|
||||
var fieldsNames = [];
|
||||
var fieldValues = [];
|
||||
var combined = [];
|
||||
var props = this._models[model].properties;
|
||||
Object.keys(data).forEach(function (key) {
|
||||
if (props[key] || key === 'id') {
|
||||
var k = '"' + key + '"';
|
||||
var v;
|
||||
if (key !== 'id') {
|
||||
v = pg.toDatabase(props[key], data[key]);
|
||||
} else {
|
||||
v = data[key];
|
||||
}
|
||||
fieldsNames.push(k);
|
||||
fieldValues.push(v);
|
||||
if (key !== 'id') combined.push(k + ' = ' + v);
|
||||
}
|
||||
});
|
||||
|
||||
var sql = 'UPDATE ' + this.tableEscaped(model);
|
||||
sql += ' SET ' + combined + ' WHERE id = ' + data.id + ';';
|
||||
sql += ' INSERT INTO ' + this.tableEscaped(model);
|
||||
sql += ' (' + fieldsNames.join(', ') + ')';
|
||||
sql += ' SELECT ' + fieldValues.join(', ')
|
||||
sql += ' WHERE NOT EXISTS (SELECT 1 FROM ' + this.tableEscaped(model);
|
||||
sql += ' WHERE id = ' + data.id + ') RETURNING id';
|
||||
|
||||
this.query(sql, function (err, info) {
|
||||
if (!err && info && info[0] && info[0].id) {
|
||||
data.id = info[0].id;
|
||||
}
|
||||
callback(err, data);
|
||||
});
|
||||
};
|
||||
|
||||
PG.prototype.toFields = function (model, data, forCreate) {
|
||||
var fields = [];
|
||||
var props = this._models[model].properties;
|
||||
|
|
|
@ -90,6 +90,24 @@ SQLite3.prototype.create = function (model, data, callback) {
|
|||
});
|
||||
};
|
||||
|
||||
SQLite3.prototype.updateOrCreate = function (model, data, callback) {
|
||||
data = data || {};
|
||||
var questions = [];
|
||||
var values = Object.keys(data).map(function (key) {
|
||||
questions.push('?');
|
||||
return data[key];
|
||||
});
|
||||
var sql = 'INSERT OR REPLACE INTO ' + this.tableEscaped(model) + ' (' + Object.keys(data).join(',') + ') VALUES ('
|
||||
sql += questions.join(',');
|
||||
sql += ')';
|
||||
this.command(sql, values, function (err) {
|
||||
if (!err && this) {
|
||||
data.id = this.lastID;
|
||||
}
|
||||
callback(err, data);
|
||||
});
|
||||
};
|
||||
|
||||
SQLite3.prototype.toFields = function (model, data) {
|
||||
var fields = [];
|
||||
var props = this._models[model].properties;
|
||||
|
|
|
@ -37,7 +37,7 @@ Object.keys(schemas).forEach(function (schemaName) {
|
|||
});
|
||||
|
||||
schema.log = function (a) {
|
||||
// console.log(a);
|
||||
// console.log(a);
|
||||
};
|
||||
testOrm(schema);
|
||||
if (specificTest[schemaName]) specificTest[schemaName](schema);
|
||||
|
@ -716,6 +716,40 @@ function testOrm(schema) {
|
|||
});
|
||||
});
|
||||
|
||||
if (schema.name !== 'mongoose' && schema.name !== 'neo4j')
|
||||
it('should update or create record', function (test) {
|
||||
var newData = {
|
||||
id: 1,
|
||||
title: 'New title (really new)',
|
||||
content: 'Some example content (updated)'
|
||||
};
|
||||
Post.updateOrCreate(newData, function (err, updatedPost) {
|
||||
if (err) throw err;
|
||||
test.ok(updatedPost);
|
||||
if (!updatedPost) throw Error('No post!');
|
||||
|
||||
test.equal(newData.id, updatedPost.toObject().id);
|
||||
test.equal(newData.title, updatedPost.toObject().title);
|
||||
test.equal(newData.content, updatedPost.toObject().content);
|
||||
|
||||
Post.find(updatedPost.id, function (err, post) {
|
||||
if (err) throw err;
|
||||
if (!post) throw Error('No post!');
|
||||
test.equal(newData.id, post.toObject().id);
|
||||
test.equal(newData.title, post.toObject().title);
|
||||
test.equal(newData.content, post.toObject().content);
|
||||
Post.updateOrCreate({id: 100001, title: 'hey'}, function (err, post) {
|
||||
if (schema.name !== 'mongodb') test.equal(post.id, 100001);
|
||||
test.equal(post.title, 'hey');
|
||||
Post.find(post.id, function (err, post) {
|
||||
if (!post) throw Error('No post!');
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('all tests done', function (test) {
|
||||
test.done();
|
||||
process.nextTick(allTestsDone);
|
||||
|
|
Loading…
Reference in New Issue