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) {
|
AbstractClass.exists = function exists(id, cb) {
|
||||||
if (id) {
|
if (id) {
|
||||||
this.schema.adapter.exists(this.modelName, id, cb);
|
this.schema.adapter.exists(this.modelName, id, cb);
|
||||||
|
@ -250,9 +274,7 @@ function substractDirtyAttributes(object, data) {
|
||||||
|
|
||||||
AbstractClass.destroyAll = function destroyAll(cb) {
|
AbstractClass.destroyAll = function destroyAll(cb) {
|
||||||
this.schema.adapter.destroyAll(this.modelName, function (err) {
|
this.schema.adapter.destroyAll(this.modelName, function (err) {
|
||||||
if (!err) {
|
|
||||||
clearCache(this);
|
clearCache(this);
|
||||||
}
|
|
||||||
cb(err);
|
cb(err);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
};
|
};
|
||||||
|
@ -424,7 +446,7 @@ AbstractClass.prototype.updateAttributes = function updateAttributes(data, cb) {
|
||||||
}
|
}
|
||||||
done.call(inst, function () {
|
done.call(inst, function () {
|
||||||
saveDone.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) {
|
Memory.prototype.create = function create(model, data, callback) {
|
||||||
var id = this.ids[model]++;
|
var id = data.id || this.ids[model]++;
|
||||||
data.id = id;
|
data.id = id;
|
||||||
this.cache[model][id] = data;
|
this.cache[model][id] = data;
|
||||||
callback(null, id);
|
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) {
|
Memory.prototype.save = function save(model, data, callback) {
|
||||||
this.cache[model][data.id] = data;
|
this.cache[model][data.id] = data;
|
||||||
callback();
|
callback(null, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
Memory.prototype.exists = function exists(model, id, callback) {
|
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) {
|
function merge(base, update) {
|
||||||
|
if (!base) return update;
|
||||||
Object.keys(update).forEach(function (key) {
|
Object.keys(update).forEach(function (key) {
|
||||||
base[key] = update[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) {
|
MongoDB.prototype.destroy = function destroy(model, id, callback) {
|
||||||
this.collection(model).remove({_id: new ObjectID(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,
|
port: s.port || 3306,
|
||||||
user: s.username,
|
user: s.username,
|
||||||
password: s.password,
|
password: s.password,
|
||||||
database: s.database,
|
|
||||||
debug: s.debug
|
debug: s.debug
|
||||||
});
|
});
|
||||||
|
|
||||||
schema.adapter = new MySQL(schema.client);
|
schema.adapter = new MySQL(schema.client);
|
||||||
|
schema.adapter.schema = schema;
|
||||||
// schema.client.query('SET TIME_ZONE = "+04:00"', callback);
|
// 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) {
|
function MySQL(client) {
|
||||||
|
@ -32,10 +43,27 @@ function MySQL(client) {
|
||||||
require('util').inherits(MySQL, BaseSQL);
|
require('util').inherits(MySQL, BaseSQL);
|
||||||
|
|
||||||
MySQL.prototype.query = function (sql, callback) {
|
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 time = Date.now();
|
||||||
var log = this.log;
|
var log = this.log;
|
||||||
if (typeof callback !== 'function') throw new Error('callback should be a function');
|
if (typeof callback !== 'function') throw new Error('callback should be a function');
|
||||||
this.client.query(sql, function (err, data) {
|
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);
|
if (log) log(sql, time);
|
||||||
callback(err, data);
|
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) {
|
MySQL.prototype.toFields = function (model, data) {
|
||||||
var fields = [];
|
var fields = [];
|
||||||
var props = this._models[model].properties;
|
var props = this._models[model].properties;
|
||||||
|
|
|
@ -207,7 +207,7 @@ Neo4j.prototype.save = function save(model, data, callback) {
|
||||||
if (err) return callback(err);
|
if (err) return callback(err);
|
||||||
self.updateIndexes(model, node, function (err) {
|
self.updateIndexes(model, node, function (err) {
|
||||||
if (err) return console.log(err);
|
if (err) return console.log(err);
|
||||||
callback(null);
|
callback(null, node.data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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) {
|
PG.prototype.toFields = function (model, data, forCreate) {
|
||||||
var fields = [];
|
var fields = [];
|
||||||
var props = this._models[model].properties;
|
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) {
|
SQLite3.prototype.toFields = function (model, data) {
|
||||||
var fields = [];
|
var fields = [];
|
||||||
var props = this._models[model].properties;
|
var props = this._models[model].properties;
|
||||||
|
|
|
@ -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) {
|
it('all tests done', function (test) {
|
||||||
test.done();
|
test.done();
|
||||||
process.nextTick(allTestsDone);
|
process.nextTick(allTestsDone);
|
||||||
|
|
Loading…
Reference in New Issue