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

Commit 797f4f12 by Victor Pontis

Merge branch 'master' into associating-sections-when-deletions

* master:
  Allow removing has-many associations by id
  Update README.md
  #2338 - Associations remove[AS][plural]
  Use the new beta build env on Travis
  Text fix
  More promises
  Promisify tests
  Remove unnecssarily asynchronous tests
  Update changelog with BC breaking hstore change
  Update json test expectation
  Merge fix
  Update pg-hstore
  Removed no longer supported nested hstore objects from tests. Improved hstore array handling.
  Use pg-hstore for hstore parsing
  Hstore empty string tests
2 parents b17e71d7 de05498b
before_script:
- "mysql -e 'create database sequelize_test;'"
- "psql -c 'create database sequelize_test;' -U postgres"
language: node_js
script:
- "make test"
node_js:
- "0.10"
notifications:
hipchat:
- 40e8850aaba9854ac4c9963bd33f8b@253477
irc:
- "chat.freenode.net#sequelizejs"
sudo: false
cache:
directories:
- node_modules
env:
- DB=mysql DIALECT=mysql
......@@ -18,30 +16,32 @@ env:
- DB=mysql DIALECT=sqlite
- DB=mysql DIALECT=mariadb
language: node_js
addons:
postgresql: "9.3"
before_script:
- "mysql -e 'create database sequelize_test;'"
- "psql -c 'create database sequelize_test;' -U postgres"
node_js:
- "0.10"
script:
- "make test"
branches:
only:
- master
- 1.7.0
cache:
directories:
- node_modules
matrix:
fast_finish: true
include:
- node_js: "0.10"
env: COVERAGE=true
allow_failures:
- node_js: "0.10"
env: COVERAGE=true
addons:
postgresql: "9.3"
\ No newline at end of file
notifications:
hipchat:
- 40e8850aaba9854ac4c9963bd33f8b@253477
irc:
- "chat.freenode.net#sequelizejs"
......@@ -36,7 +36,7 @@ To install 2.x.x branch - which has a unstable API and will break backwards comp
- [Getting Started](http://sequelizejs.com/articles/getting-started)
- [Documentation](http://sequelizejs.com/docs)
- [API Reference](https://github.com/sequelize/sequelize/wiki/API-Reference) *Work in progress*
- [Collaboration and pull requests](https://github.com/sequelize/sequelize/wiki/Collaboration)
- [Collaboration and pull requests](https://github.com/sequelize/sequelize/blob/master/CONTRIBUTING.md)
- [Roadmap](https://github.com/sequelize/sequelize/wiki/Roadmap)
- [Meetups](https://github.com/sequelize/sequelize/wiki/Meetups)
- [Twitter](http://twitter.com/sdepold)
......
# Next
- [FEATURE] Added the possibility of removing multiple associations in 1 call [#2338](https://github.com/sequelize/sequelize/issues/2338)
- [BUG] Add support for `field` named the same as the attribute in `reload`, `bulkCreate` and `save` [#2348](https://github.com/sequelize/sequelize/issues/2348)
- [BUG] Copy the options object in association getters. [#2311](https://github.com/sequelize/sequelize/issues/2311)
- [BUG] `Model#destroy()` now supports `field`, this also fixes an issue with `N:M#removeAssociation` and `field`
......@@ -6,6 +7,7 @@
#### 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 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
- [FEATURE] Added to posibility of using a sequelize object as key in `sequelize.where`. Also added the option of specifying a comparator
......
......@@ -176,6 +176,7 @@ module.exports = (function() {
add: 'add' + singular,
create: 'create' + singular,
remove: 'remove' + singular,
removeMultiple: 'remove' + plural,
hasSingle: 'has' + singular,
hasAll: 'has' + plural
};
......@@ -473,6 +474,14 @@ module.exports = (function() {
return instance[association.accessors.get]({}, options).then(function(currentAssociatedObjects) {
var newAssociations = [];
if (!(oldAssociatedObject instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[primaryKeyAttribute] = oldAssociatedObject;
oldAssociatedObject = association.target.build(tmpInstance, {
isNewRecord: false
});
}
currentAssociatedObjects.forEach(function(association) {
if (!Utils._.isEqual(oldAssociatedObject.identifiers, association.identifiers)) {
newAssociations.push(association);
......@@ -483,6 +492,41 @@ module.exports = (function() {
});
};
obj[this.accessors.removeMultiple] = function(oldAssociatedObjects, options) {
var instance = this;
return instance[association.accessors.get]({}, options).then(function(currentAssociatedObjects) {
var newAssociations = [];
// Ensure the oldAssociatedObjects array is an array of target instances
oldAssociatedObjects = oldAssociatedObjects.map(function(oldAssociatedObject) {
if (!(oldAssociatedObject instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[primaryKeyAttribute] = oldAssociatedObject;
oldAssociatedObject = association.target.build(tmpInstance, {
isNewRecord: false
});
}
return oldAssociatedObject;
});
currentAssociatedObjects.forEach(function(association) {
// Determine is this is an association we want to remove
var obj = Utils._.find(oldAssociatedObjects, function(oldAssociatedObject) {
return Utils._.isEqual(oldAssociatedObject.identifiers, association.identifiers);
});
// This is not an association we want to remove. Add it back
// to the set of associations we will associate our instance with
if (!obj) {
newAssociations.push(association);
}
});
return instance[association.accessors.set](newAssociations, options);
});
};
return this;
};
......
......@@ -187,7 +187,8 @@ Mixin.belongsTo = singleLinked(BelongsTo);
* * add[AS] - for example addPicture(instance, defaultAttributes|options). Add another associated object.
* * add[AS] [plural] - for example addPictures([instance1, instance2], defaultAttributes|options). Add some more associated objects.
* * create[AS] - for example createPicture(values, options). Build and save a new association.
* * remove[AS] - for example removePicture(instance). Remove a single association
* * remove[AS] - for example removePicture(instance). Remove a single association.
* * remove[AS] [plural] - for example removePictures(instance). Remove multiple association.
* * has[AS] - for example hasPicture(instance). Is source associated to this target?
* * has[AS] [plural] - for example hasPictures(instances). Is source associated to all these targets?
*
......
'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);
}
};
......@@ -852,7 +852,11 @@ module.exports = (function() {
}
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)) {
value = JSON.stringify(value);
}
......
......@@ -12,13 +12,16 @@ var Utils = require('../../utils')
// This cannot be done in the 'pg' lib because hstore is a UDT.
var parseHstoreFields = function(model, row) {
Utils._.forEach(row, function(value, key) {
if (model._isHstoreAttribute(key) || (model.attributes[key] && model.attributes[key].type === DataTypes.ARRAY(DataTypes.HSTORE))) {
row[key] = hstore.parse(value);
if(value === null) return row[key] = null;
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 @@
"node-uuid": "~1.4.1",
"bluebird": "~2.3.2",
"sql": "~0.40.0",
"pg-hstore": "^2.2.0",
"toposort-class": "~0.3.0",
"validator": "~3.22.0"
},
......
......@@ -2016,14 +2016,46 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
return this.sequelize.sync().then(function() {
return Sequelize.Promise.all([
Worker.create({}),
Task.bulkCreate([{}, {}]).then(function () {
Task.bulkCreate([{}, {}, {}]).then(function () {
return Task.findAll();
})
]);
}).spread(function (worker, tasks) {
// Set all tasks, then remove one tasks, then return all tasks
// Set all tasks, then remove one task by instance, then remove one task by id, then return all tasks
return worker.setTasks(tasks).then(function () {
return worker.removeTask(tasks[0]);
}).then(function() {
return worker.removeTask(tasks[1].id);
}).then(function () {
return worker.getTasks();
});
}).then(function (tasks) {
expect(tasks.length).to.equal(1);
});
});
it('should remove multiple entries without any attributes (and timestamps off) on the through model', function () {
var Worker = this.sequelize.define('Worker', {}, {timestamps: false})
, Task = this.sequelize.define('Task', {}, {timestamps: false})
, WorkerTasks = this.sequelize.define('WorkerTasks', {}, {timestamps: false});
Worker.hasMany(Task, { through: WorkerTasks });
Task.hasMany(Worker, { through: WorkerTasks });
// Test setup
return this.sequelize.sync().then(function() {
return Sequelize.Promise.all([
Worker.create({}),
Task.bulkCreate([{}, {}, {}, {}, {}]).then(function () {
return Task.findAll();
})
]);
}).spread(function (worker, tasks) {
// Set all tasks, then remove two tasks by instance, then remove two tasks by id, then return all tasks
return worker.setTasks(tasks).then(function () {
return worker.removeTasks([tasks[0], tasks[1]]);
}).then(function () {
return worker.removeTasks([tasks[2].id, tasks[3].id]);
}).then(function () {
return worker.getTasks();
});
......
......@@ -137,7 +137,12 @@ if (Support.dialectIsMySQL()) {
self.user.removeTask(self.tasks[0]).on('success', function() {
self.user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).to.equal(self.tasks.length - 1)
done()
self.user.removeTasks([self.tasks[1], self.tasks[2]]).on('success', function() {
self.user.getTasks().on('success', function(_tasks) {
expect(_tasks).to.have.length(self.tasks.length - 3)
done()
})
})
})
})
})
......
......@@ -151,7 +151,12 @@ if (dialect.match(/^postgres/)) {
self.user.removeTask(self.tasks[0]).on('success', function() {
self.user.getTasks().on('success', function(_tasks) {
expect(_tasks).to.have.length(self.tasks.length - 1)
done()
self.user.removeTasks([self.tasks[1], self.tasks[2]]).on('success', function() {
self.user.getTasks().on('success', function(_tasks) {
expect(_tasks).to.have.length(self.tasks.length - 3)
done()
})
})
})
})
})
......
......@@ -16,7 +16,7 @@ if (dialect.match(/^postgres/)) {
username: DataTypes.STRING,
email: { type: DataTypes.ARRAY(DataTypes.TEXT) },
settings: DataTypes.HSTORE,
document: { type: DataTypes.HSTORE, defaultValue: { default: 'value' } },
document: { type: DataTypes.HSTORE, defaultValue: { default: "'value'" } },
phones: DataTypes.ARRAY(DataTypes.HSTORE),
emergency_contact: DataTypes.JSON
})
......@@ -66,7 +66,7 @@ if (dialect.match(/^postgres/)) {
username: 'bob',
emergency_contact: { name: 'joe', phones: [1337, 42] }
}).on('sql', function (sql) {
var expected = 'INSERT INTO "Users" ("id","username","document","emergency_contact","createdAt","updatedAt") VALUES (DEFAULT,\'bob\',\'"default"=>"value"\',\'{"name":"joe","phones":[1337,42]}\''
var expected = 'INSERT INTO "Users" ("id","username","document","emergency_contact","createdAt","updatedAt") VALUES (DEFAULT,\'bob\',\'"default"=>"\'\'value\'\'"\',\'{"name":"joe","phones":[1337,42]}\''
expect(sql.indexOf(expected)).to.equal(0);
});
});
......@@ -179,25 +179,24 @@ if (dialect.match(/^postgres/)) {
});
describe('hstore', function() {
it('should tell me that a column is hstore and not USER-DEFINED', function(done) {
this.sequelize.queryInterface.describeTable('Users').success(function(table) {
it('should tell me that a column is hstore and not USER-DEFINED', function() {
return this.sequelize.queryInterface.describeTable('Users').then(function(table) {
expect(table.settings.type).to.equal('HSTORE')
expect(table.document.type).to.equal('HSTORE')
done()
})
})
it('should stringify hstore with insert', function(done) {
this.User.create({
it('should stringify hstore with insert', function() {
return this.User.create({
username: 'bob',
email: ['myemail@email.com'],
settings: {mailing: false, push: 'facebook', frequency: 3}
}).on('sql', function(sql) {
var expected = 'INSERT INTO "Users" ("id","username","email","settings","document","createdAt","updatedAt") VALUES (DEFAULT,\'bob\',ARRAY[\'myemail@email.com\']::TEXT[],\'"mailing"=>false,"push"=>"facebook","frequency"=>3\',\'"default"=>"value"\''
var expected = 'INSERT INTO "Users" ("id","username","email","settings","document","createdAt","updatedAt") VALUES (DEFAULT,\'bob\',ARRAY[\'myemail@email.com\']::TEXT[],\'"mailing"=>"false","push"=>"facebook","frequency"=>"3"\',\'"default"=>"\'\'value\'\'"\''
expect(sql.indexOf(expected)).to.equal(0)
done()
})
})
})
describe('enums', function() {
......@@ -403,106 +402,105 @@ if (dialect.match(/^postgres/)) {
})
})
it("should save hstore correctly", function(done) {
this.User
.create({ username: 'user', email: ['foo@bar.com'], settings: { created: { test: '"value"' }}})
.success(function(newUser) {
it("should save hstore correctly", function() {
return this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { created: '"value"' }}).then(function(newUser) {
// Check to see if the default value for an hstore field works
expect(newUser.document).to.deep.equal({ default: 'value' })
expect(newUser.settings).to.deep.equal({ created: { test: '"value"' }})
expect(newUser.document).to.deep.equal({ default: "'value'" })
expect(newUser.settings).to.deep.equal({ created: '"value"' })
// Check to see if updating an hstore field works
newUser.updateAttributes({settings: {should: 'update', to: 'this', first: 'place'}}).success(function(oldUser){
return newUser.updateAttributes({settings: {should: 'update', to: 'this', first: 'place'}}).then(function(oldUser){
// Postgres always returns keys in alphabetical order (ascending)
expect(oldUser.settings).to.deep.equal({first: 'place', should: 'update', to: 'this'})
done()
})
})
.error(console.log)
})
it('should save hstore array correctly', function(done) {
this.User.create({
it('should save hstore array correctly', function() {
var User = this.User;
return this.User.create({
username: 'bob',
email: ['myemail@email.com'],
phones: [{ number: '123456789', type: 'mobile' }, { number: '987654321', type: 'landline' }]
}).on('sql', function(sql) {
var expected = 'INSERT INTO "Users" ("id","username","email","document","phones","createdAt","updatedAt") VALUES (DEFAULT,\'bob\',ARRAY[\'myemail@email.com\']::TEXT[],\'"default"=>"value"\',ARRAY[\'"number"=>"123456789","type"=>"mobile"\',\'"number"=>"987654321","type"=>"landline"\']::HSTORE[]'
expect(sql).to.contain(expected)
done()
phones: [{ number: '123456789', type: 'mobile' }, { number: '987654321', type: 'landline' }, { number : '8675309', type : "Jenny's"}, {number : '5555554321', type : '"home"' }]
}).then(function(){
return User.find(1).then(function(user){
expect(user.phones.length).to.equal(4);
expect(user.phones[1].number).to.equal('987654321');
expect(user.phones[2].type).to.equal("Jenny's");
expect(user.phones[3].type).to.equal('"home"');
})
})
})
it("should update hstore correctly", function(done) {
var self = this
it('should bulkCreate with hstore property', function() {
var User = this.User;
this.User
.create({ username: 'user', email: ['foo@bar.com'], settings: { created: { test: '"value"' }}})
.success(function(newUser) {
return this.User.bulkCreate([{
username: 'bob',
email: ['myemail@email.com'],
settings: {mailing: true, push: 'facebook', frequency: 3}
}]).then(function(){
return User.find(1).then(function(user){
expect(user.settings.mailing).to.equal("true")
})
})
})
it("should update hstore correctly", function() {
var self = this;
return this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' }}).then(function(newUser) {
// Check to see if the default value for an hstore field works
expect(newUser.document).to.deep.equal({default: 'value'})
expect(newUser.settings).to.deep.equal({ created: { test: '"value"' }})
expect(newUser.document).to.deep.equal({default: "'value'"})
expect(newUser.settings).to.deep.equal({ test: '"value"' })
// Check to see if updating an hstore field works
self.User.update({settings: {should: 'update', to: 'this', first: 'place'}}, {where: newUser.identifiers}).success(function() {
newUser.reload().success(function() {
return self.User.update({settings: {should: 'update', to: 'this', first: 'place'}}, {where: newUser.identifiers}).then(function() {
return newUser.reload().success(function() {
// Postgres always returns keys in alphabetical order (ascending)
expect(newUser.settings).to.deep.equal({first: 'place', should: 'update', to: 'this'})
done()
});
})
})
})
.error(console.log)
})
it("should update hstore correctly and return the affected rows", function(done) {
it("should update hstore correctly and return the affected rows", function() {
var self = this
this.User
.create({ username: 'user', email: ['foo@bar.com'], settings: { created: { test: '"value"' }}})
.success(function(oldUser) {
return this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' }}).then(function(oldUser) {
// Update the user and check that the returned object's fields have been parsed by the hstore library
self.User.update({settings: {should: 'update', to: 'this', first: 'place'}}, {where: oldUser.identifiers, returning: true }).spread(function(count, users) {
return self.User.update({settings: {should: 'update', to: 'this', first: 'place'}}, {where: oldUser.identifiers, returning: true }).spread(function(count, users) {
expect(count).to.equal(1);
expect(users[0].settings).to.deep.equal({should: 'update', to: 'this', first: 'place'})
done()
})
})
.error(console.log)
})
it("should read hstore correctly", function(done) {
it("should read hstore correctly", function() {
var self = this
var data = { username: 'user', email: ['foo@bar.com'], settings: { created: { test: '"value"' }}}
var data = { username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' }}
this.User
.create(data)
.success(function() {
return this.User.create(data)
.then(function() {
return self.User.find({ where: { username: 'user' }})
})
.then(function(user){
// Check that the hstore fields are the same when retrieving the user
self.User.find({ where: { username: 'user' }})
.success(function(user) {
expect(user.settings).to.deep.equal(data.settings)
done()
})
expect(user.settings).to.deep.equal(data.settings)
})
.error(console.log)
})
it('should read an hstore array correctly', function(done) {
it('should read an hstore array correctly', function() {
var self = this
var data = { username: 'user', email: ['foo@bar.com'], phones: [{ number: '123456789', type: 'mobile' }, { number: '987654321', type: 'landline' }] }
this.User
.create(data)
.success(function() {
return this.User.create(data)
.then(function() {
// Check that the hstore fields are the same when retrieving the user
self.User.find({ where: { username: 'user' }})
.success(function(user) {
expect(user.phones).to.deep.equal(data.phones)
done()
})
return self.User.find({ where: { username: 'user' }});
}).then(function(user) {
expect(user.phones).to.deep.equal(data.phones)
})
})
......@@ -510,24 +508,24 @@ if (dialect.match(/^postgres/)) {
var self = this
self.User
.create({ username: 'user1', email: ['foo@bar.com'], settings: { created: { test: '"value"' }}})
.create({ username: 'user1', email: ['foo@bar.com'], settings: { test: '"value"' }})
.then(function() {
return self.User.create({ username: 'user2', email: ['foo2@bar.com'], settings: { updated: { another: '"example"' }}})
return self.User.create({ username: 'user2', email: ['foo2@bar.com'], settings: { another : '"example"' }})
})
.then(function() {
// Check that the hstore fields are the same when retrieving the user
return self.User.findAll({ order: 'username' })
})
.then(function(users) {
expect(users[0].settings).to.deep.equal({ created: { test: '"value"' }})
expect(users[1].settings).to.deep.equal({ updated: { another: '"example"' }})
expect(users[0].settings).to.deep.equal({ test: '"value"' })
expect(users[1].settings).to.deep.equal({ another: '"example"' })
done()
})
.error(console.log)
})
})
describe('[POSTGRES] Unquoted identifiers', function() {
it("can insert and select", function(done) {
var self = this
......
......@@ -3,131 +3,83 @@ 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()
describe('stringify', function() {
it('should handle empty objects correctly', function() {
expect(hstore.stringify({ })).to.equal('')
})
it("handles null values correctly", function(done) {
expect(hstore.stringifyPart(null)).to.equal('NULL')
done()
it('should handle null values correctly', function() {
expect(hstore.stringify({ null: null })).to.equal('"null"=>NULL')
})
it("handles boolean values correctly", function(done) {
expect(hstore.stringifyPart(false)).to.equal('false')
expect(hstore.stringifyPart(true)).to.equal('true')
done()
it('should handle null values correctly', function() {
expect(hstore.stringify({ foo: null })).to.equal('"foo"=>NULL')
})
it("handles strings correctly", function(done) {
expect(hstore.stringifyPart('foo')).to.equal('"foo"')
it('should handle empty string correctly', function(done) {
expect(hstore.stringify({foo : ""})).to.equal('"foo"=>\"\"')
done()
})
it("handles strings with backslashes correctly", function(done) {
expect(hstore.stringifyPart("\\'literally\\'")).to.equal('"\\\\\'literally\\\\\'"')
done()
it('should handle a string with backslashes correctly', function() {
expect(hstore.stringify({foo : "\\"})).to.equal('"foo"=>"\\\\"')
})
it("handles arrays correctly", function(done) {
expect(hstore.stringifyPart([1,['2'],'"3"'])).to.equal('"[1,[\\"2\\"],\\"\\\\\\"3\\\\\\"\\"]"')
done()
it('should handle a string with double quotes correctly', function() {
expect(hstore.stringify({foo : '""a"'})).to.equal('"foo"=>"\\"\\"a\\""')
})
it("handles simple objects correctly", function(done) {
expect(hstore.stringifyPart({ test: 'value' })).to.equal('"{\\"test\\":\\"value\\"}"')
done()
it('should handle a string with single quotes correctly', function() {
expect(hstore.stringify({foo : "''a'"})).to.equal('"foo"=>"\'\'\'\'a\'\'"')
})
it("handles nested objects correctly", function(done) {
expect(hstore.stringifyPart({ test: { nested: 'value' } })).to.equal('"{\\"test\\":{\\"nested\\":\\"value\\"}}"')
done()
it('should handle simple objects correctly', function() {
expect(hstore.stringify({ test: 'value' })).to.equal('"test"=>"value"')
})
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('')
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()
describe('parse', function() {
it('should handle a null object correctly', function() {
expect(hstore.parse(null)).to.deep.equal(null)
})
it('should handle arrays correctly', function(done) {
expect(hstore.stringify([{ test: 'value' }, { another: 'val' }])).to.deep.equal(['\"test\"=>\"value\"','\"another\"=>\"val\"'])
done()
});
it('should handle nested objects correctly', function(done) {
expect(hstore.stringify({ test: { nested: 'value' } })).to.equal('"test"=>"{\\"nested\\":\\"value\\"}"')
done()
it('should handle empty string correctly', function() {
expect(hstore.parse('"foo"=>\"\"')).to.deep.equal({foo : ""})
})
it('should handle nested arrays correctly', function(done) {
expect(hstore.stringify({ test: [ 1, '2', [ '"3"' ] ] })).to.equal('"test"=>"[1,\\"2\\",[\\"\\\\\\"3\\\\\\"\\"]]"')
done()
it('should handle a string with double quotes correctly', function() {
expect(hstore.parse('"foo"=>"\\\"\\\"a\\\""')).to.deep.equal({foo : "\"\"a\""})
})
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\\"}"')
done()
it('should handle a string with single quotes correctly', function() {
expect(hstore.parse('"foo"=>"\'\'\'\'a\'\'"')).to.deep.equal({foo : "''a'"})
})
})
describe('parse', function() {
it('should handle null objects correctly', function(done) {
expect(hstore.parse(null)).to.equal(null)
done()
it('should handle a string with backslashes correctly', function() {
expect(hstore.parse('"foo"=>"\\\\"')).to.deep.equal({foo : "\\"})
})
it('should handle empty objects correctly', function(done) {
it('should handle empty objects correctly', function() {
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' })
done()
})
it('should handle arrays correctly', function(done) {
expect(hstore.parse('{"\\"test\\"=>\\"value\\"","\\"another\\"=>\\"val\\""}')).to.deep.equal([{ test: 'value' }, { another: 'val' }])
done()
})
it('should handle nested objects correctly', function(done) {
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()
})
describe('stringify and parse', function() {
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);
expect(hstore.parse(hstore.stringify(hstore.parse(hstore.stringify(testObj))))).to.deep.equal(testObj);
})
})
})
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!