Skip to content

Commit

Permalink
Use pg-hstore for hstore parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
seth-admittedly committed Nov 4, 2014
1 parent c327355 commit 718de51
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 166 deletions.
92 changes: 6 additions & 86 deletions lib/dialects/postgres/hstore.js
Original file line number Diff line number Diff line change
@@ -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);
}
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
109 changes: 29 additions & 80 deletions test/postgres/hstore.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('')
Expand All @@ -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()
Expand Down

0 comments on commit 718de51

Please sign in to comment.