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

Commit cd8cc16b by Daniel Durante

Merge pull request #695 from twg/master

Fix HSTORE encoding by using pg-hstore library.
2 parents 2a25f661 4a4d27b6
...@@ -2,6 +2,7 @@ var Utils = require("./utils") ...@@ -2,6 +2,7 @@ var Utils = require("./utils")
, Mixin = require("./associations/mixin") , Mixin = require("./associations/mixin")
, DaoValidator = require("./dao-validator") , DaoValidator = require("./dao-validator")
, DataTypes = require("./data-types") , DataTypes = require("./data-types")
, hstore = require('./dialects/postgres/hstore')
module.exports = (function() { module.exports = (function() {
var DAO = function(values, options, isNewRecord) { var DAO = function(values, options, isNewRecord) {
...@@ -130,15 +131,7 @@ module.exports = (function() { ...@@ -130,15 +131,7 @@ module.exports = (function() {
if (isHstore) { if (isHstore) {
if (typeof values[attrName] === "object") { if (typeof values[attrName] === "object") {
var text = [] values[attrName] = hstore.stringify(values[attrName])
Utils._.each(values[attrName], function(value, key){
if (typeof value !== "string" && typeof value !== "number") {
throw new Error('Value for HSTORE must be a string or number.')
}
text.push(this.QueryInterface.quoteIdentifier(key) + '=>' + (typeof value === "string" ? this.QueryInterface.quoteIdentifier(value) : value))
}.bind(this))
values[attrName] = text.join(',')
} }
} }
} }
...@@ -362,10 +355,6 @@ module.exports = (function() { ...@@ -362,10 +355,6 @@ module.exports = (function() {
// add all passed values to the dao and store the attribute names in this.attributes // add all passed values to the dao and store the attribute names in this.attributes
for (key in values) { for (key in values) {
if (values.hasOwnProperty(key)) { if (values.hasOwnProperty(key)) {
if (typeof values[key] === "string" && !!this.__factory && !!this.__factory.rawAttributes[key] && !!this.__factory.rawAttributes[key].type && !!this.__factory.rawAttributes[key].type.type && this.__factory.rawAttributes[key].type.type === DataTypes.HSTORE.type) {
values[key] = this.QueryInterface.QueryGenerator.toHstore(values[key])
}
this.addAttribute(key, values[key]) this.addAttribute(key, values[key])
} }
} }
......
module.exports = {
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, '\\"') + '"'
}
},
stringify: function(data) {
var self = this
return Object.keys(data).map(function(key) {
return self.stringifyPart(key) + '=>' + self.stringifyPart(data[key])
}).join(',')
},
parsePart: function(part) {
part = part.replace(/\\\\/g, '\\').replace(/\\"/g, '"')
switch(part[0]) {
case '{':
case '[':
return JSON.parse(part)
default:
return part
}
},
parse: function(string) {
var self = this
const rx = /\"((?:\\\"|[^"])*)\"\s*\=\>\s*((?:true|false|NULL|\d+|\d+\.\d+|\"((?:\\\"|[^"])*)\"))/g
var object = { }
string.replace(rx, function(match, key, value, innerValue) {
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;
}
}
...@@ -675,21 +675,6 @@ module.exports = (function() { ...@@ -675,21 +675,6 @@ module.exports = (function() {
return matches.slice(0, -1) return matches.slice(0, -1)
}, },
toHstore: function(text) {
var obj = {}
, pattern = '("\\\\.|[^"\\\\]*"\s*=|[^=]*)\s*=\s*>\s*("(?:\\.|[^"\\\\])*"|[^,]*)(?:\s*,\s*|$)'
, rex = new RegExp(pattern,'g')
, r = null
while ((r = rex.exec(text)) !== null) {
if (!!r[1] && !!r[2]) {
obj[r[1].replace(/^"/, '').replace(/"$/, '')] = r[2].replace(/^"/, '').replace(/"$/, '')
}
}
return obj
},
padInt: function (i) { padInt: function (i) {
return (i < 10) ? '0' + i.toString() : i.toString() return (i < 10) ? '0' + i.toString() : i.toString()
}, },
......
var Utils = require("../../utils") var Utils = require("../../utils")
, AbstractQuery = require('../abstract/query') , AbstractQuery = require('../abstract/query')
, DataTypes = require('../../data-types') , DataTypes = require('../../data-types')
, hstore = require('./hstore')
module.exports = (function() { module.exports = (function() {
var Query = function(client, sequelize, callee, options) { var Query = function(client, sequelize, callee, options) {
...@@ -132,7 +133,7 @@ module.exports = (function() { ...@@ -132,7 +133,7 @@ module.exports = (function() {
if (rows[0].hasOwnProperty(key)) { if (rows[0].hasOwnProperty(key)) {
var record = rows[0][key] var record = rows[0][key]
if (!!this.callee.daoFactory && !!this.callee.daoFactory.rawAttributes && !!this.callee.daoFactory.rawAttributes[key] && !!this.callee.daoFactory.rawAttributes[key].type && !!this.callee.daoFactory.rawAttributes[key].type.type && this.callee.daoFactory.rawAttributes[key].type.type === DataTypes.HSTORE.type) { if (!!this.callee.daoFactory && !!this.callee.daoFactory.rawAttributes && !!this.callee.daoFactory.rawAttributes[key] && !!this.callee.daoFactory.rawAttributes[key].type && !!this.callee.daoFactory.rawAttributes[key].type.type && this.callee.daoFactory.rawAttributes[key].type.type === DataTypes.HSTORE.type) {
record = this.callee.daoFactory.daoFactoryManager.sequelize.queryInterface.QueryGenerator.toHstore(record) record = hstore.parse(record)
} }
this.callee[key] = record this.callee[key] = record
} }
...@@ -146,7 +147,7 @@ module.exports = (function() { ...@@ -146,7 +147,7 @@ module.exports = (function() {
if (rows[0].hasOwnProperty(key)) { if (rows[0].hasOwnProperty(key)) {
var record = rows[0][key] var record = rows[0][key]
if (!!this.callee.daoFactory && !!this.callee.daoFactory.rawAttributes && !!this.callee.daoFactory.rawAttributes[key] && !!this.callee.daoFactory.rawAttributes[key].type && !!this.callee.daoFactory.rawAttributes[key].type.type && this.callee.daoFactory.rawAttributes[key].type.type === DataTypes.HSTORE.type) { if (!!this.callee.daoFactory && !!this.callee.daoFactory.rawAttributes && !!this.callee.daoFactory.rawAttributes[key] && !!this.callee.daoFactory.rawAttributes[key].type && !!this.callee.daoFactory.rawAttributes[key].type.type && this.callee.daoFactory.rawAttributes[key].type.type === DataTypes.HSTORE.type) {
record = this.callee.daoFactory.daoFactoryManager.sequelize.queryInterface.QueryGenerator.toHstore(record) record = hstore.parse(record)
} }
this.callee[key] = record this.callee[key] = record
} }
......
...@@ -18,7 +18,10 @@ ...@@ -18,7 +18,10 @@
}, },
{ {
"name": "Jan Aagaard Meier", "name": "Jan Aagaard Meier",
"email": ["janzeh@gmail.com", "jmei@itu.dk"] "email": [
"janzeh@gmail.com",
"jmei@itu.dk"
]
} }
], ],
"repository": { "repository": {
......
...@@ -19,7 +19,7 @@ if (dialect.match(/^postgres/)) { ...@@ -19,7 +19,7 @@ if (dialect.match(/^postgres/)) {
self.User = sequelize.define('User', { self.User = sequelize.define('User', {
username: DataTypes.STRING, username: DataTypes.STRING,
email: {type: DataTypes.ARRAY(DataTypes.TEXT)}, email: {type: DataTypes.ARRAY(DataTypes.TEXT)},
document: {type: DataTypes.HSTORE, defaultValue: 'default=>value'} document: {type: DataTypes.HSTORE, defaultValue: '"default"=>"value"'}
}) })
}, },
onComplete: function() { onComplete: function() {
...@@ -47,9 +47,10 @@ if (dialect.match(/^postgres/)) { ...@@ -47,9 +47,10 @@ if (dialect.match(/^postgres/)) {
var self = this var self = this
this.User this.User
.create({ username: 'user', email: ['foo@bar.com'], document: {hello: 'world'}}) .create({ username: 'user', email: ['foo@bar.com'], document: { created: { test: '"value"' }}})
.success(function(newUser) { .success(function(newUser) {
expect(newUser.document).toEqual({hello: 'world'}) expect(newUser.document).toEqual({ created: { test: '"value"' }})
// Check to see if updating an hstore field works // Check to see if updating an hstore field works
newUser.updateAttributes({document: {should: 'update', to: 'this', first: 'place'}}).success(function(oldUser){ newUser.updateAttributes({document: {should: 'update', to: 'this', first: 'place'}}).success(function(oldUser){
// Postgres always returns keys in alphabetical order (ascending) // Postgres always returns keys in alphabetical order (ascending)
......
if(typeof require === 'function') {
const buster = require("buster")
, Helpers = require('../buster-helpers')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
if (dialect.match(/^postgres/)) {
describe('[POSTGRES] hstore', function() {
const hstore = require('../../lib/dialects/postgres/hstore')
describe('stringifyPart', function() {
it("handles undefined values correctly", function() {
expect(hstore.stringifyPart(undefined)).toEqual('NULL')
})
it("handles null values correctly", function() {
expect(hstore.stringifyPart(null)).toEqual('NULL')
})
it("handles boolean values correctly", function() {
expect(hstore.stringifyPart(false)).toEqual('false')
expect(hstore.stringifyPart(true)).toEqual('true')
})
it("handles strings correctly", function() {
expect(hstore.stringifyPart('foo')).toEqual('"foo"')
})
it("handles strings with backslashes correctly", function() {
expect(hstore.stringifyPart("\\'literally\\'")).toEqual('"\\\\\'literally\\\\\'"')
})
it("handles arrays correctly", function() {
expect(hstore.stringifyPart([1,['2'],'"3"'])).toEqual('"[1,[\\"2\\"],\\"\\\\\\"3\\\\\\"\\"]"')
})
it("handles simple objects correctly", function() {
expect(hstore.stringifyPart({ test: 'value' })).toEqual('"{\\"test\\":\\"value\\"}"')
})
it("handles nested objects correctly", function () {
expect(hstore.stringifyPart({ test: { nested: 'value' } })).toEqual('"{\\"test\\":{\\"nested\\":\\"value\\"}}"')
})
it("handles objects correctly", function() {
expect(hstore.stringifyPart({test: {nested: {value: {including: '"string"'}}}})).toEqual('"{\\"test\\":{\\"nested\\":{\\"value\\":{\\"including\\":\\"\\\\\\"string\\\\\\"\\"}}}}"')
})
})
describe('stringify', function() {
it('should handle empty objects correctly', function() {
expect(hstore.stringify({ })).toEqual('')
})
it('should handle null values correctly', function () {
expect(hstore.stringify({ null: null })).toEqual('"null"=>NULL')
})
it('should handle simple objects correctly', function() {
expect(hstore.stringify({ test: 'value' })).toEqual('"test"=>"value"')
})
it('should handle nested objects correctly', function() {
expect(hstore.stringify({ test: { nested: 'value' } })).toEqual('"test"=>"{\\"nested\\":\\"value\\"}"')
})
it('should handle nested arrays correctly', function() {
expect(hstore.stringify({ test: [ 1, '2', [ '"3"' ] ] })).toEqual('"test"=>"[1,\\"2\\",[\\"\\\\\\"3\\\\\\"\\"]]"')
})
it('should handle multiple keys with different types of values', function() {
expect(hstore.stringify({ true: true, false: false, null: null, undefined: undefined, integer: 1, array: [1,'2'], object: { object: 'value' }})).toEqual('"true"=>true,"false"=>false,"null"=>NULL,"undefined"=>NULL,"integer"=>1,"array"=>"[1,\\"2\\"]","object"=>"{\\"object\\":\\"value\\"}"')
})
})
describe('parse', function() {
it('should handle empty objects correctly', function() {
expect(hstore.parse('')).toEqual({ })
})
it('should handle simple objects correctly', function() {
expect(hstore.parse('"test"=>"value"')).toEqual({ test: 'value' })
})
it('should handle nested objects correctly', function() {
expect(hstore.parse('"test"=>"{\\"nested\\":\\"value\\"}"')).toEqual({ test: { nested: 'value' } })
})
it('should handle nested arrays correctly', function() {
expect(hstore.parse('"test"=>"[1,\\"2\\",[\\"\\\\\\"3\\\\\\"\\"]]"')).toEqual({ test: [ 1, '2', [ '"3"' ] ] })
})
it('should handle multiple keys with different types of values', function() {
expect(hstore.parse('"true"=>true,"false"=>false,"null"=>NULL,"undefined"=>NULL,"integer"=>1,"array"=>"[1,\\"2\\"]","object"=>"{\\"object\\":\\"value\\"}"')).toEqual({ true: true, false: false, null: null, undefined: null, integer: 1, array: [1,'2'], object: { object: 'value' }})
})
})
})
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!