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

Commit ffc50e13 by Jan Aagaard Meier

Merge pull request #2295 from seth-admittedly/feature/hstore-test

Use pg-hstore for improved hstore handling
2 parents 4b55fa21 9e776400
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#### Backwards compatability changes #### Backwards compatability changes
- When eager-loading a many-to-many association, the attributes of the through table are now accessible through an attribute named after the through model rather than the through table name singularized. i.e. `Task.find({include: Worker})` where the table name for through model `TaskWorker` is `TableTaskWorkers` used to produce `{ Worker: { ..., TableTaskWorker: {...} } }`. It now produces `{ Worker: { ..., TaskWorker: {...} } }`. Does not affect models where table name is auto-defined by Sequelize, or where table name is model name pluralized. - When eager-loading a many-to-many association, the attributes of the through table are now accessible through an attribute named after the through model rather than the through table name singularized. i.e. `Task.find({include: Worker})` where the table name for through model `TaskWorker` is `TableTaskWorkers` used to produce `{ Worker: { ..., TableTaskWorker: {...} } }`. It now produces `{ Worker: { ..., TaskWorker: {...} } }`. Does not affect models where table name is auto-defined by Sequelize, or where table name is model name pluralized.
- When using `Model#find()` with an `order` clause, the table name is prepended to the `ORDER BY` SQL. e.g. `ORDER BY Task.id` rather than `ORDER BY id`. The change is to avoid ambiguous column names where there are eager-loaded associations with the same column names. A side effect is that code like `Task.findAll( { include: [ User ], order: [ [ 'Users.id', 'ASC' ] ] } )` will now throw an error. This should be achieved with `Task.findAll( { include: [ User ], order: [ [ User, 'id', 'ASC' ] ] } )` instead. - When using `Model#find()` with an `order` clause, the table name is prepended to the `ORDER BY` SQL. e.g. `ORDER BY Task.id` rather than `ORDER BY id`. The change is to avoid ambiguous column names where there are eager-loaded associations with the same column names. A side effect is that code like `Task.findAll( { include: [ User ], order: [ [ 'Users.id', 'ASC' ] ] } )` will now throw an error. This should be achieved with `Task.findAll( { include: [ User ], order: [ [ User, 'id', 'ASC' ] ] } )` instead.
- Nested HSTORE objects are no longer supported. Use DataTypes.JSON instead.
# 2.0.0-rc2 # 2.0.0-rc2
- [FEATURE] Added to posibility of using a sequelize object as key in `sequelize.where`. Also added the option of specifying a comparator - [FEATURE] Added to posibility of using a sequelize object as key in `sequelize.where`. Also added the option of specifying a comparator
......
'use strict'; 'use strict';
module.exports = { var hstore = require("pg-hstore")({sanitize : true});
stringifyPart: function(part) {
switch (typeof part) {
case 'boolean':
case 'number':
return String(part);
case 'string':
return '"' + part.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
case 'undefined':
return 'NULL';
default:
if (part === null)
return 'NULL';
else
return '"' + JSON.stringify(part).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
}
},
stringifyObject: function(data) {
var self = this;
return Object.keys(data).map(function(key) { module.exports = {
return self.stringifyPart(key) + '=>' + self.stringifyPart(data[key]);
}).join(',');
},
stringifyArray: function(data) {
return data.map(this.stringifyObject, this);
},
stringify: function(data) { stringify: function(data) {
if (Array.isArray(data)) { if(data === null) return null;
return this.stringifyArray(data);
}
return this.stringifyObject(data);
},
parsePart: function(part) {
part = part.replace(/\\\\/g, '\\').replace(/\\"/g, '"');
switch (part[0]) {
case '{':
case '[':
return JSON.parse(part);
default:
return part;
}
},
parseObject: function(string) {
var self = this,
object = { };
if (0 === string.length) {
return object;
}
var rx = /\"((?:\\\"|[^"])*)\"\s*\=\>\s*((?:true|false|NULL|\d+|\d+\.\d+|\"((?:\\\"|[^"])*)\"))/g;
string = string || '';
string.replace(rx, function(match, key, value, innerValue) { return hstore.stringify(data);
switch (value) {
case 'true':
object[self.parsePart(key)] = true;
break;
case 'false':
object[self.parsePart(key)] = false;
break;
case 'NULL':
object[self.parsePart(key)] = null;
break;
default:
object[self.parsePart(key)] = self.parsePart(innerValue || value);
break;
}
});
return object;
},
parseArray: function(string) {
var matches = string.match(/{(.*)}/);
var array = JSON.parse('['+ matches[1] +']');
return array.map(this.parseObject, this);
}, },
parse: function(value) { parse: function(value) {
if ('string' !== typeof value) { if(value === null) return null;
return value;
}
if ('{' === value[0] && '}' === value[value.length - 1]) {
return this.parseArray(value);
}
return this.parseObject(value); return hstore.parse(value);
} }
}; };
...@@ -852,7 +852,11 @@ module.exports = (function() { ...@@ -852,7 +852,11 @@ module.exports = (function() {
} }
if (Utils._.isObject(value) && field && (field.type === DataTypes.HSTORE || field.type === DataTypes.ARRAY(DataTypes.HSTORE))) { if (Utils._.isObject(value) && field && (field.type === DataTypes.HSTORE || field.type === DataTypes.ARRAY(DataTypes.HSTORE))) {
value = hstore.stringify(value); if(field.type === DataTypes.HSTORE){
return "'" + hstore.stringify(value) + "'";
}else if (field.type === DataTypes.ARRAY(DataTypes.HSTORE)){
return "ARRAY[" + Utils._.map(value, function(v){return "'" + hstore.stringify(v) + "'::hstore";}).join(",") + "]::HSTORE[]";
}
} else if (Utils._.isObject(value) && field && (field.type === DataTypes.JSON)) { } else if (Utils._.isObject(value) && field && (field.type === DataTypes.JSON)) {
value = JSON.stringify(value); value = JSON.stringify(value);
} }
......
...@@ -12,13 +12,16 @@ var Utils = require('../../utils') ...@@ -12,13 +12,16 @@ var Utils = require('../../utils')
// This cannot be done in the 'pg' lib because hstore is a UDT. // This cannot be done in the 'pg' lib because hstore is a UDT.
var parseHstoreFields = function(model, row) { var parseHstoreFields = function(model, row) {
Utils._.forEach(row, function(value, key) { Utils._.forEach(row, function(value, key) {
if (model._isHstoreAttribute(key) || (model.attributes[key] && model.attributes[key].type === DataTypes.ARRAY(DataTypes.HSTORE))) { if(value === null) return row[key] = null;
row[key] = hstore.parse(value);
return; if (model._isHstoreAttribute(key)) {
row[key] = hstore.parse(value);
}else if(model.attributes[key] && model.attributes[key].type === DataTypes.ARRAY(DataTypes.HSTORE)) {
var array = JSON.parse('[' + value.slice(1).slice(0,-1) + ']');
row[key] = Utils._.map(array, function(v){return hstore.parse(v);});
}else{
row[key] = value;
} }
row[key] = value;
}); });
}; };
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
"node-uuid": "~1.4.1", "node-uuid": "~1.4.1",
"bluebird": "~2.3.2", "bluebird": "~2.3.2",
"sql": "~0.40.0", "sql": "~0.40.0",
"pg-hstore": "^2.2.0",
"toposort-class": "~0.3.0", "toposort-class": "~0.3.0",
"validator": "~3.22.0" "validator": "~3.22.0"
}, },
......
...@@ -3,131 +3,83 @@ var chai = require('chai') ...@@ -3,131 +3,83 @@ var chai = require('chai')
, expect = chai.expect , expect = chai.expect
, Support = require(__dirname + '/../support') , Support = require(__dirname + '/../support')
, dialect = Support.getTestDialect() , dialect = Support.getTestDialect()
, hstore = require(__dirname + '/../../lib/dialects/postgres/hstore') , hstore = require("../../lib/dialects/postgres/hstore")
chai.config.includeStack = true chai.config.includeStack = true
if (dialect.match(/^postgres/)) { if (dialect.match(/^postgres/)) {
describe('[POSTGRES Specific] hstore', function() { describe('[POSTGRES Specific] hstore', function() {
describe('stringifyPart', function() { describe('stringify', function() {
it("handles undefined values correctly", function(done) { it('should handle empty objects correctly', function() {
expect(hstore.stringifyPart(undefined)).to.equal('NULL') expect(hstore.stringify({ })).to.equal('')
done()
}) })
it("handles null values correctly", function(done) { it('should handle null values correctly', function() {
expect(hstore.stringifyPart(null)).to.equal('NULL') expect(hstore.stringify({ null: null })).to.equal('"null"=>NULL')
done()
}) })
it("handles boolean values correctly", function(done) { it('should handle null values correctly', function() {
expect(hstore.stringifyPart(false)).to.equal('false') expect(hstore.stringify({ foo: null })).to.equal('"foo"=>NULL')
expect(hstore.stringifyPart(true)).to.equal('true')
done()
}) })
it("handles strings correctly", function(done) { it('should handle empty string correctly', function(done) {
expect(hstore.stringifyPart('foo')).to.equal('"foo"') expect(hstore.stringify({foo : ""})).to.equal('"foo"=>\"\"')
done() done()
}) })
it("handles strings with backslashes correctly", function(done) { it('should handle a string with backslashes correctly', function() {
expect(hstore.stringifyPart("\\'literally\\'")).to.equal('"\\\\\'literally\\\\\'"') expect(hstore.stringify({foo : "\\"})).to.equal('"foo"=>"\\\\"')
done()
}) })
it("handles arrays correctly", function(done) { it('should handle a string with double quotes correctly', function() {
expect(hstore.stringifyPart([1,['2'],'"3"'])).to.equal('"[1,[\\"2\\"],\\"\\\\\\"3\\\\\\"\\"]"') expect(hstore.stringify({foo : '""a"'})).to.equal('"foo"=>"\\"\\"a\\""')
done()
}) })
it("handles simple objects correctly", function(done) { it('should handle a string with single quotes correctly', function() {
expect(hstore.stringifyPart({ test: 'value' })).to.equal('"{\\"test\\":\\"value\\"}"') expect(hstore.stringify({foo : "''a'"})).to.equal('"foo"=>"\'\'\'\'a\'\'"')
done()
}) })
it("handles nested objects correctly", function(done) { it('should handle simple objects correctly', function() {
expect(hstore.stringifyPart({ test: { nested: 'value' } })).to.equal('"{\\"test\\":{\\"nested\\":\\"value\\"}}"') expect(hstore.stringify({ test: 'value' })).to.equal('"test"=>"value"')
done()
}) })
it("handles objects correctly", function(done) {
expect(hstore.stringifyPart({test: {nested: {value: {including: '"string"'}}}})).to.equal('"{\\"test\\":{\\"nested\\":{\\"value\\":{\\"including\\":\\"\\\\\\"string\\\\\\"\\"}}}}"')
done()
})
}) })
describe('stringify', function() { describe('parse', function() {
it('should handle empty objects correctly', function(done) { it('should handle a null object correctly', function() {
expect(hstore.stringify({ })).to.equal('') expect(hstore.parse(null)).to.deep.equal(null)
done()
})
it('should handle null values correctly', function(done) {
expect(hstore.stringify({ null: null })).to.equal('"null"=>NULL')
done()
})
it('should handle simple objects correctly', function(done) {
expect(hstore.stringify({ test: 'value' })).to.equal('"test"=>"value"')
done()
}) })
it('should handle arrays correctly', function(done) { it('should handle empty string correctly', function() {
expect(hstore.stringify([{ test: 'value' }, { another: 'val' }])).to.deep.equal(['\"test\"=>\"value\"','\"another\"=>\"val\"']) expect(hstore.parse('"foo"=>\"\"')).to.deep.equal({foo : ""})
done()
});
it('should handle nested objects correctly', function(done) {
expect(hstore.stringify({ test: { nested: 'value' } })).to.equal('"test"=>"{\\"nested\\":\\"value\\"}"')
done()
}) })
it('should handle nested arrays correctly', function(done) { it('should handle a string with double quotes correctly', function() {
expect(hstore.stringify({ test: [ 1, '2', [ '"3"' ] ] })).to.equal('"test"=>"[1,\\"2\\",[\\"\\\\\\"3\\\\\\"\\"]]"') expect(hstore.parse('"foo"=>"\\\"\\\"a\\\""')).to.deep.equal({foo : "\"\"a\""})
done()
}) })
it('should handle multiple keys with different types of values', function(done) { it('should handle a string with single quotes correctly', function() {
expect(hstore.stringify({ true: true, false: false, null: null, undefined: undefined, integer: 1, array: [1,'2'], object: { object: 'value' }})).to.equal('"true"=>true,"false"=>false,"null"=>NULL,"undefined"=>NULL,"integer"=>1,"array"=>"[1,\\"2\\"]","object"=>"{\\"object\\":\\"value\\"}"') expect(hstore.parse('"foo"=>"\'\'\'\'a\'\'"')).to.deep.equal({foo : "''a'"})
done()
}) })
})
describe('parse', function() { it('should handle a string with backslashes correctly', function() {
it('should handle null objects correctly', function(done) { expect(hstore.parse('"foo"=>"\\\\"')).to.deep.equal({foo : "\\"})
expect(hstore.parse(null)).to.equal(null)
done()
}) })
it('should handle empty objects correctly', function(done) { it('should handle empty objects correctly', function() {
expect(hstore.parse('')).to.deep.equal({ }) expect(hstore.parse('')).to.deep.equal({ })
done()
}) })
it('should handle simple objects correctly', function(done) { it('should handle simple objects correctly', function() {
expect(hstore.parse('"test"=>"value"')).to.deep.equal({ test: 'value' }) expect(hstore.parse('"test"=>"value"')).to.deep.equal({ test: 'value' })
done()
}) })
it('should handle arrays correctly', function(done) { })
expect(hstore.parse('{"\\"test\\"=>\\"value\\"","\\"another\\"=>\\"val\\""}')).to.deep.equal([{ test: 'value' }, { another: 'val' }]) describe('stringify and parse', function() {
done() it('should stringify then parse back the same structure', function(){
}) var testObj = {foo : "bar", count : "1", emptyString : "", quotyString : '""', extraQuotyString : '"""a"""""', backslashes : '\\f023', moreBackslashes : '\\f\\0\\2\\1', backslashesAndQuotes : '\\"\\"uhoh"\\"', nully : null};
expect(hstore.parse(hstore.stringify(testObj))).to.deep.equal(testObj);
it('should handle nested objects correctly', function(done) { expect(hstore.parse(hstore.stringify(hstore.parse(hstore.stringify(testObj))))).to.deep.equal(testObj);
expect(hstore.parse('"test"=>"{\\"nested\\":\\"value\\"}"')).to.deep.equal({ test: { nested: 'value' } })
done()
})
it('should handle nested arrays correctly', function(done) {
expect(hstore.parse('"test"=>"[1,\\"2\\",[\\"\\\\\\"3\\\\\\"\\"]]"')).to.deep.equal({ test: [ 1, '2', [ '"3"' ] ] })
done()
})
it('should handle multiple keys with different types of values', function(done) {
expect(hstore.parse('"true"=>true,"false"=>false,"null"=>NULL,"undefined"=>NULL,"integer"=>1,"array"=>"[1,\\"2\\"]","object"=>"{\\"object\\":\\"value\\"}"')).to.deep.equal({ true: true, false: false, null: null, undefined: null, integer: "1", array: [1,'2'], object: { object: 'value' }})
done()
}) })
}) })
}) })
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!