From 718de511caf66a9a8c03cda48cc62d502dcc08da Mon Sep 17 00:00:00 2001 From: Seth Samuel Date: Thu, 18 Sep 2014 11:49:57 -0400 Subject: [PATCH] Use pg-hstore for hstore parsing --- lib/dialects/postgres/hstore.js | 92 ++------------------------- package.json | 1 + test/postgres/hstore.test.js | 109 +++++++++----------------------- 3 files changed, 36 insertions(+), 166 deletions(-) diff --git a/lib/dialects/postgres/hstore.js b/lib/dialects/postgres/hstore.js index 1c531e41cba3..4a2177eb4706 100644 --- a/lib/dialects/postgres/hstore.js +++ b/lib/dialects/postgres/hstore.js @@ -1,96 +1,16 @@ 'use strict'; -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, '\\"') + '"'; - } - }, - stringifyObject: function(data) { - var self = this; +var hstore = require("pg-hstore")({sanitize : true}); - return Object.keys(data).map(function(key) { - return self.stringifyPart(key) + '=>' + self.stringifyPart(data[key]); - }).join(','); - }, - stringifyArray: function(data) { - return data.map(this.stringifyObject, this); - }, +module.exports = { stringify: function(data) { - if (Array.isArray(data)) { - 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 || ''; + if(data === null) return null; - 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; - }, - parseArray: function(string) { - var matches = string.match(/{(.*)}/); - var array = JSON.parse('['+ matches[1] +']'); - - return array.map(this.parseObject, this); + return hstore.stringify(data); }, parse: function(value) { - if ('string' !== typeof value) { - return value; - } - - if ('{' === value[0] && '}' === value[value.length - 1]) { - return this.parseArray(value); - } + if(value === null) return null; - return this.parseObject(value); + return hstore.parse(value); } }; diff --git a/package.json b/package.json index d68a7a5e0656..a99170d496f5 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "node-uuid": "~1.4.1", "bluebird": "~2.3.2", "sql": "~0.40.0", + "pg-hstore": "^2.1.2", "toposort-class": "~0.3.0", "validator": "~3.22.0" }, diff --git a/test/postgres/hstore.test.js b/test/postgres/hstore.test.js index a894ceea2752..25b6391ccf1a 100644 --- a/test/postgres/hstore.test.js +++ b/test/postgres/hstore.test.js @@ -3,60 +3,12 @@ var chai = require('chai') , expect = chai.expect , Support = require(__dirname + '/../support') , dialect = Support.getTestDialect() - , hstore = require(__dirname + '/../../lib/dialects/postgres/hstore') + , hstore = require("../../lib/dialects/postgres/hstore") chai.config.includeStack = true if (dialect.match(/^postgres/)) { describe('[POSTGRES Specific] hstore', function() { - describe('stringifyPart', function() { - it("handles undefined values correctly", function(done) { - expect(hstore.stringifyPart(undefined)).to.equal('NULL') - done() - }) - - it("handles null values correctly", function(done) { - expect(hstore.stringifyPart(null)).to.equal('NULL') - done() - }) - - it("handles boolean values correctly", function(done) { - expect(hstore.stringifyPart(false)).to.equal('false') - expect(hstore.stringifyPart(true)).to.equal('true') - done() - }) - - it("handles strings correctly", function(done) { - expect(hstore.stringifyPart('foo')).to.equal('"foo"') - done() - }) - - it("handles strings with backslashes correctly", function(done) { - expect(hstore.stringifyPart("\\'literally\\'")).to.equal('"\\\\\'literally\\\\\'"') - done() - }) - - it("handles arrays correctly", function(done) { - expect(hstore.stringifyPart([1,['2'],'"3"'])).to.equal('"[1,[\\"2\\"],\\"\\\\\\"3\\\\\\"\\"]"') - done() - }) - - it("handles simple objects correctly", function(done) { - expect(hstore.stringifyPart({ test: 'value' })).to.equal('"{\\"test\\":\\"value\\"}"') - done() - }) - - it("handles nested objects correctly", function(done) { - expect(hstore.stringifyPart({ test: { nested: 'value' } })).to.equal('"{\\"test\\":{\\"nested\\":\\"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() { it('should handle empty objects correctly', function(done) { expect(hstore.stringify({ })).to.equal('') @@ -68,81 +20,78 @@ if (dialect.match(/^postgres/)) { done() }) - it('should handle empty string correctly', function(done) { - expect(hstore.stringify({foo : ""})).to.equal('"foo"=>\"\"') + it('should handle null values correctly', function(done) { + expect(hstore.stringify({ foo: null })).to.equal('"foo"=>NULL') done() }) - it('should handle simple objects correctly', function(done) { - expect(hstore.stringify({ test: 'value' })).to.equal('"test"=>"value"') + it('should handle empty string correctly', function(done) { + expect(hstore.stringify({foo : ""})).to.equal('"foo"=>\"\"') done() }) - it('should handle arrays correctly', function(done) { - expect(hstore.stringify([{ test: 'value' }, { another: 'val' }])).to.deep.equal(['\"test\"=>\"value\"','\"another\"=>\"val\"']) + it('should handle a string with backslashes correctly', function(done) { + expect(hstore.stringify({foo : "\\"})).to.equal('"foo"=>"\\\\"') done() - }); + }) - it('should handle nested objects correctly', function(done) { - expect(hstore.stringify({ test: { nested: 'value' } })).to.equal('"test"=>"{\\"nested\\":\\"value\\"}"') + it('should handle a string with double quotes correctly', function(done) { + expect(hstore.stringify({foo : '""a"'})).to.equal('"foo"=>"\\"\\"a\\""') done() }) - it('should handle nested arrays correctly', function(done) { - expect(hstore.stringify({ test: [ 1, '2', [ '"3"' ] ] })).to.equal('"test"=>"[1,\\"2\\",[\\"\\\\\\"3\\\\\\"\\"]]"') + it('should handle a string with single quotes correctly', function(done) { + expect(hstore.stringify({foo : "''a'"})).to.equal('"foo"=>"\'\'\'\'a\'\'"') done() }) - it('should handle multiple keys with different types of values', function(done) { - 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\\"}"') + it('should handle simple objects correctly', function(done) { + expect(hstore.stringify({ test: 'value' })).to.equal('"test"=>"value"') done() }) + }) describe('parse', function() { - it('should handle null objects correctly', function(done) { - expect(hstore.parse(null)).to.equal(null) + it('should handle a null object correctly', function(done) { + expect(hstore.parse(null)).to.deep.equal(null) done() }) it('should handle empty string correctly', function(done) { - expect(hstore.parse('"foo"=>\"\"')).to.equal({foo : ""}) + expect(hstore.parse('"foo"=>\"\"')).to.deep.equal({foo : ""}) done() }) - it('should handle empty objects correctly', function(done) { - expect(hstore.parse('')).to.deep.equal({ }) + it('should handle a string with double quotes correctly', function(done) { + expect(hstore.parse('"foo"=>"\\\"\\\"a\\\""')).to.deep.equal({foo : "\"\"a\""}) done() }) - it('should handle simple objects correctly', function(done) { - expect(hstore.parse('"test"=>"value"')).to.deep.equal({ test: 'value' }) + it('should handle a string with single quotes correctly', function(done) { + expect(hstore.parse('"foo"=>"\'\'\'\'a\'\'"')).to.deep.equal({foo : "''a'"}) done() }) - it('should handle arrays correctly', function(done) { - expect(hstore.parse('{"\\"test\\"=>\\"value\\"","\\"another\\"=>\\"val\\""}')).to.deep.equal([{ test: 'value' }, { another: 'val' }]) + it('should handle a string with backslashes correctly', function(done) { + expect(hstore.parse('"foo"=>"\\\\"')).to.deep.equal({foo : "\\"}) done() }) - it('should handle nested objects correctly', function(done) { - expect(hstore.parse('"test"=>"{\\"nested\\":\\"value\\"}"')).to.deep.equal({ test: { nested: 'value' } }) + it('should handle empty objects correctly', function(done) { + expect(hstore.parse('')).to.deep.equal({ }) done() }) - it('should handle nested arrays correctly', function(done) { - expect(hstore.parse('"test"=>"[1,\\"2\\",[\\"\\\\\\"3\\\\\\"\\"]]"')).to.deep.equal({ test: [ 1, '2', [ '"3"' ] ] }) + it('should handle simple objects correctly', function(done) { + expect(hstore.parse('"test"=>"value"')).to.deep.equal({ test: 'value' }) 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() - }) }) describe('stringify and parse', function() { it('should stringify then parse back the same structure', function(done){ - var testObj = {foo : "bar", count : "1", emptyString : "", quotyString : "\"\"", nully : null}; + 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); expect(hstore.parse(hstore.stringify(hstore.parse(hstore.stringify(testObj))))).to.deep.equal(testObj); done()