不要怂,就是干,撸起袖子干!

Json dot notation and postgres syntax in json helper

1 parent a8cfa7e0
...@@ -16,7 +16,7 @@ var Sequelize = require(__dirname + "/../../index") ...@@ -16,7 +16,7 @@ var Sequelize = require(__dirname + "/../../index")
var Content = sequelize.define('Content', { var Content = sequelize.define('Content', {
title: { type: Sequelize.STRING }, title: { type: Sequelize.STRING },
type: { type: Sequelize.STRING }, // ENUM('Movie', 'Episode', 'Music Video') }, type: { type: Sequelize.STRING },
metadata: { type: Sequelize.JSON } metadata: { type: Sequelize.JSON }
}) })
, movie = Content.build({ , movie = Content.build({
...@@ -30,6 +30,7 @@ var Content = sequelize.define('Content', { ...@@ -30,6 +30,7 @@ var Content = sequelize.define('Content', {
}) })
, episode = Content.build({ , episode = Content.build({
title: 'Chapter 3', title: 'Chapter 3',
type: 'Episode',
metadata: { metadata: {
season: 1, season: 1,
episode: 3, episode: 3,
...@@ -50,7 +51,8 @@ sequelize.sync({ force: true }) ...@@ -50,7 +51,8 @@ sequelize.sync({ force: true })
console.log('====================================='); console.log('=====================================');
console.log('Searching for any content in Japanese'); console.log('Searching for any content in Japanese');
console.log('-------------------------------------'); console.log('-------------------------------------');
// Using nested object query syntax
return Content.find({ where: Sequelize.json({ metadata: { language: 'Japanese' } }) }) return Content.find({ where: Sequelize.json({ metadata: { language: 'Japanese' } }) })
.then(function(content) { .then(function(content) {
console.log('Result:', content.dataValues); console.log('Result:', content.dataValues);
...@@ -62,12 +64,21 @@ sequelize.sync({ force: true }) ...@@ -62,12 +64,21 @@ sequelize.sync({ force: true })
console.log('Searching for any content in English'); console.log('Searching for any content in English');
console.log('-------------------------------------'); console.log('-------------------------------------');
return Content.find({ where: "metadata->>'language' = 'English'" }) // Using the postgres json syntax
return Content.find({ where: Sequelize.json("metadata->>'language'", 'English') })
.then(function(content) { .then(function(content) {
console.log('Result:', content.dataValues); console.log('Result:', content.dataValues);
console.log('====================================='); console.log('=====================================');
}) })
}) })
.error(function(error) { .then(function() {
console.log(error) console.log('===========================================');
console.log('Searching for series named "House of Cards"');
console.log('-------------------------------------------');
return Content.find({ where: Sequelize.json('metadata.seriesTitle', 'House of Cards') })
.then(function(content) {
console.log('Result:', content.dataValues);
console.log('===========================================');
})
}); });
\ No newline at end of file
...@@ -836,11 +836,12 @@ module.exports = (function() { ...@@ -836,11 +836,12 @@ module.exports = (function() {
* @see {Model#find} * @see {Model#find}
* *
* @method json * @method json
* @param {Object} conditions A hash containing strings/numbers or other nested hashes * @param {String|Object} conditions A hash containing strings/numbers or other nested hash, a string using dot notation or a string using postgres json syntax.
* @param {String|Number|Boolean} [value] An optional value to compare against. Produces a string of the form "<json path> = '<value>'".
* @return {Sequelize.json} * @return {Sequelize.json}
*/ */
Sequelize.json = Sequelize.prototype.json = function (conditions) { Sequelize.json = Sequelize.prototype.json = function (conditionsOrPath, value) {
return new Utils.json(conditions); return new Utils.json(conditionsOrPath, value);
}; };
/* /*
......
...@@ -547,8 +547,15 @@ var Utils = module.exports = { ...@@ -547,8 +547,15 @@ var Utils = module.exports = {
this.args = args; this.args = args;
}, },
json: function(conditions) { json: function(conditionsOrPath, value) {
this.conditions = conditions; if (Utils._.isObject(conditionsOrPath)) {
this.conditions = conditionsOrPath;
} else {
this.path = conditionsOrPath;
if (value) {
this.value = value;
}
}
}, },
where: function(attribute, logic) { where: function(attribute, logic) {
...@@ -622,11 +629,11 @@ Utils.json.prototype.toString = function () { ...@@ -622,11 +629,11 @@ Utils.json.prototype.toString = function () {
var _ = Utils._; var _ = Utils._;
// A recursive parser for nested where conditions // A recursive parser for nested where conditions
function parse(_conditions, path) { function parseConditionObject(_conditions, path) {
path = path || []; path = path || [];
return _.reduce(_conditions, function (r, v, k) { // result, key, value return _.reduce(_conditions, function (r, v, k) { // result, key, value
if (_.isObject(v)) { if (_.isObject(v)) {
r = r.concat(parse(v, path.concat(k))); // Recursively parse objects r = r.concat(parseConditionObject(v, path.concat(k))); // Recursively parse objects
} else { } else {
r.push({ path: path.concat(k), value: v }); r.push({ path: path.concat(k), value: v });
} }
...@@ -634,16 +641,36 @@ Utils.json.prototype.toString = function () { ...@@ -634,16 +641,36 @@ Utils.json.prototype.toString = function () {
}, []); }, []);
} }
// TODO: Move this postgres specific logic to a more appropriate place // Parse nested object
function generateSql(condition) { if (this.conditions) {
return util.format("%s#>>'{%s}' = '%s'", var conditions = _.map(parseConditionObject(this.conditions), function generateSql(condition) {
_.first(condition.path), // TODO: Move this postgres specific logic to a more appropriate place
_.rest(condition.path).join(','), return util.format("%s#>>'{%s}' = '%s'",
condition.value); _.first(condition.path),
} _.rest(condition.path).join(','),
condition.value);
});
return conditions.join(' and ');
} else if (this.path) {
var str;
var conditions = _.map(parse(this.conditions), generateSql); // Allow specifying conditions using the postgres json syntax
return conditions.join(' and '); if (_.any(['->', '->>', '#>'], _.partial(_.contains, this.path))) { // TODO: Move postgres stuff somewhere else
str = this.path;
} else {
// Also support json dot notation
var path = this.path.split('.');
str = util.format("%s#>>'{%s}'",
_.first(path),
_.rest(path).join(','));
}
if (this.value) {
str += util.format(" = '%s'", this.value);
}
return str;
}
}; };
Utils.CustomEventEmitter = require(__dirname + '/emitters/custom-event-emitter'); Utils.CustomEventEmitter = require(__dirname + '/emitters/custom-event-emitter');
......
...@@ -87,15 +87,16 @@ if (dialect.match(/^postgres/)) { ...@@ -87,15 +87,16 @@ if (dialect.match(/^postgres/)) {
it('should be able to retrieve element of array by index', function () { it('should be able to retrieve element of array by index', function () {
var self = this; var self = this;
var emergencyContact = { name: 'kate', phones: [1337,42] }; var emergencyContact = { name: 'kate', phones: [1337, 42] };
return this.User.create({ username: 'swen', emergency_contact: emergencyContact }) return this.User.create({ username: 'swen', emergency_contact: emergencyContact })
.then(function (user) { .then(function (user) {
expect(user.emergency_contact).to.eql(emergencyContact); expect(user.emergency_contact).to.eql(emergencyContact);
return self.User.find({ where: { username: 'swen' }, attributes: [['emergency_contact->\'phones\'->1', 'emergency_contact']] }); //return self.User.find({ where: { username: 'swen' }, attributes: [['emergency_contact->\'phones\'->1', 'emergency_contact']] });
return self.User.find({ where: { username: 'swen' }, attributes: [[sequelize.json('emergency_contact.phones.1'), 'emergency_contact']] });
}) })
.then(function (user) { .then(function (user) {
expect(user.emergency_contact).to.equal(42); expect(parseInt(user.emergency_contact)).to.equal(42);
}); });
}); });
...@@ -106,10 +107,10 @@ if (dialect.match(/^postgres/)) { ...@@ -106,10 +107,10 @@ if (dialect.match(/^postgres/)) {
return this.User.create({ username: 'swen', emergency_contact: emergencyContact }) return this.User.create({ username: 'swen', emergency_contact: emergencyContact })
.then(function (user) { .then(function (user) {
expect(user.emergency_contact).to.eql(emergencyContact); expect(user.emergency_contact).to.eql(emergencyContact);
return self.User.find({ where: { username: 'swen' }, attributes: [['emergency_contact->\'kate\'', 'emergency_contact']] }); return self.User.find({ where: { username: 'swen' }, attributes: [[sequelize.json('emergency_contact.kate'), 'emergency_contact']] });
}) })
.then(function (user) { .then(function (user) {
expect(user.emergency_contact).to.equal(1337); expect(parseInt(user.emergency_contact)).to.equal(1337);
}); });
}); });
...@@ -120,16 +121,16 @@ if (dialect.match(/^postgres/)) { ...@@ -120,16 +121,16 @@ if (dialect.match(/^postgres/)) {
return this.User.create({ username: 'swen', emergency_contact: emergencyContact }) return this.User.create({ username: 'swen', emergency_contact: emergencyContact })
.then(function (user) { .then(function (user) {
expect(user.emergency_contact).to.eql(emergencyContact); expect(user.emergency_contact).to.eql(emergencyContact);
return self.User.find({ where: { username: 'swen' }, attributes: [['emergency_contact#>\'{kate,email}\'', 'emergency_contact']] }); return self.User.find({ where: { username: 'swen' }, attributes: [[sequelize.json('emergency_contact.kate.email'), 'emergency_contact']] });
}) })
.then(function (user) { .then(function (user) {
expect(user.emergency_contact).to.equal('kate@kate.com'); expect(user.emergency_contact).to.equal('kate@kate.com');
}) })
.then(function () { .then(function () {
return self.User.find({ where: { username: 'swen' }, attributes: [['emergency_contact#>\'{kate,phones,1}\'', 'emergency_contact']] }); return self.User.find({ where: { username: 'swen' }, attributes: [[sequelize.json('emergency_contact.kate.phones.1'), 'emergency_contact']] });
}) })
.then(function (user) { .then(function (user) {
expect(user.emergency_contact).to.equal(42); expect(parseInt(user.emergency_contact)).to.equal(42);
}); });
}); });
......
...@@ -157,7 +157,23 @@ describe(Support.getTestDialectTeaser("Utils"), function() { ...@@ -157,7 +157,23 @@ describe(Support.getTestDialectTeaser("Utils"), function() {
another_json_field: { x: 1 } another_json_field: { x: 1 }
}; };
expect((new Utils.json(conditions)).toString()).to.deep.equal("metadata#>>'{language}' = 'icelandic' and metadata#>>'{pg_rating,dk}' = 'G' and another_json_field#>>'{x}' = '1'"); expect((new Utils.json(conditions)).toString()).to.deep.equal("metadata#>>'{language}' = 'icelandic' and metadata#>>'{pg_rating,dk}' = 'G' and another_json_field#>>'{x}' = '1'");
}) });
it('successfully parses a string using dot notation', function () {
var path = 'metadata.pg_rating.dk';
expect((new Utils.json(path)).toString()).to.equal("metadata#>>'{pg_rating,dk}'");
});
it('successfully parses a string and value using dot notation', function () {
var path = 'metadata.pg_rating.dk';
var value = 'G';
expect((new Utils.json(path, value)).toString()).to.equal("metadata#>>'{pg_rating,dk}' = 'G'");
});
it('allows postgres json syntax', function () {
var path = 'metadata->pg_rating->>dk';
expect((new Utils.json(path)).toString()).to.equal(path);
});
}); });
describe('inflection', function () { describe('inflection', function () {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!