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

Commit 448c5eba by Mick Hansen

Merge pull request #3211 from v12/feature/postgres-parsing-included-fields

[PostgreSQL] Parsing included fields of HSTORE and RANGE datatype
2 parents 81c8b330 4d6cce82
...@@ -209,21 +209,6 @@ module.exports = (function() { ...@@ -209,21 +209,6 @@ module.exports = (function() {
attributes: this.options.originalAttributes || this.options.attributes, attributes: this.options.originalAttributes || this.options.attributes,
raw: true raw: true
}); });
} else if (this.options.hasJoinTableModel === true) {
result = results.map(function(result) {
result = Dot.transform(result);
var joinTableData = result[this.options.joinTableModel.name]
, joinTableDAO = this.options.joinTableModel.build(joinTableData, { isNewRecord: false, isDirty: false, raw: true })
, mainDao;
delete result[this.options.joinTableModel.name];
mainDao = this.callee.build(result, { isNewRecord: false, isDirty: false, raw: true });
mainDao[this.options.joinTableModel.name] = joinTableDAO;
return mainDao;
}.bind(this));
// Regular queries // Regular queries
} else { } else {
result = this.callee.bulkBuild(results, { result = this.callee.bulkBuild(results, {
......
...@@ -9,37 +9,68 @@ var Utils = require('../../utils') ...@@ -9,37 +9,68 @@ var Utils = require('../../utils')
, Promise = require('../../promise') , Promise = require('../../promise')
, sequelizeErrors = require('../../errors.js'); , sequelizeErrors = require('../../errors.js');
// Parses hstore fields if the model has any hstore fields. var parseDialectSpecificFields,
// This cannot be done in the 'pg' lib because hstore is a UDT. dialectSpecificTypes;
var parseHstoreFields = function(model, row) {
Utils._.forEach(row, function(value, key) { parseDialectSpecificFields = {
if(value === null) return row[key] = null; // Parses hstore fields if the model has any hstore fields.
// This cannot be done in the 'pg' lib because hstore is a UDT.
if (model._isHstoreAttribute(key)) { hstore: function (value, options) {
row[key] = hstore.parse(value); if (value === null) return null;
}else if (model.attributes[key] && DataTypes.ARRAY.is(model.attributes[key].type, DataTypes.HSTORE)) {
var array = JSON.parse('[' + value.slice(1).slice(0,-1) + ']'); return DataTypes.ARRAY.is(options.dataType, DataTypes.HSTORE) ?
row[key] = Utils._.map(array, function(v){return hstore.parse(v);}); Utils._.map(
}else{ JSON.parse('[' + value.substring(1, value.length - 1) + ']'), function (v) {
row[key] = value; return hstore.parse(v);
} }) : hstore.parse(value);
}); },
range: function (value, options) {
if (value === null) return null;
return DataTypes.ARRAY.is(options.dataType, DataTypes.RANGE) ?
Utils._.map(
JSON.parse('[' + value.substring(1, value.length - 1) + ']'),
function (v) {
return range.parse(v, options.dataType.type);
}) :
range.parse(value, options.dataType);
}
}; };
var parseRangeFields = function (model, row) { dialectSpecificTypes = Utils._.keys(parseDialectSpecificFields);
Utils._.forEach(row, function (value, key) {
if (value === null) return row[key] = null; function dialectSpecificFieldDatatypeMap (options, prefix) {
if (!Utils._.isArray(options.types))
if (model._isRangeAttribute(key)) { return [];
row[key] = range.parse(value, model.attributes[key].type);
} else if (model.attributes[key] && DataTypes.ARRAY.is(model.attributes[key].type, DataTypes.RANGE)) { prefix = prefix || '';
var array = JSON.parse('[' + value.slice(1).slice(0, -1) + ']');
row[key] = Utils._.map(array, function (v) { return range.parse(v, model.attributes[key].type.type); }); var fields = {};
} else { if (options.callee) {
row[key] = value; if (options.as)
} prefix += options.as + '.';
Utils._.each(options.types, function (type) {
Utils._.each(options.callee['_' + type + 'Attributes'], function (attrName) {
fields[prefix + attrName] = {
dataType: options.callee.attributes[attrName].type || null,
type: type
};
});
});
}
Utils._.each(options.include, function (include) {
fields = Utils._.merge(fields, dialectSpecificFieldDatatypeMap({
callee: include.model,
as: include.as,
include: include.include,
types: options.types
}, prefix));
}); });
};
return fields;
}
module.exports = (function() { module.exports = (function() {
var Query = function(client, sequelize, callee, options) { var Query = function(client, sequelize, callee, options) {
...@@ -90,7 +121,9 @@ module.exports = (function() { ...@@ -90,7 +121,9 @@ module.exports = (function() {
}).spread(function(rows, sql, result) { }).spread(function(rows, sql, result) {
var results = rows var results = rows
, isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0) , isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
, isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0); , isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0)
, dialectSpecificFields
, isDialectSpecificField = Utils._.memoize(function (key) { return dialectSpecificFields.hasOwnProperty(key); });
if (isTableNameQuery || isRelNameQuery) { if (isTableNameQuery || isRelNameQuery) {
if (isRelNameQuery) { if (isRelNameQuery) {
...@@ -177,16 +210,23 @@ module.exports = (function() { ...@@ -177,16 +210,23 @@ module.exports = (function() {
}); });
} }
if (!!self.callee && !!self.callee._hasHstoreAttributes) { if (!!self.callee && rows.length > 0) {
rows.forEach(function(row) { dialectSpecificFields = dialectSpecificFieldDatatypeMap({
parseHstoreFields(self.callee, row); callee: self.callee,
include: self.options.include,
types: dialectSpecificTypes
}); });
}
if (!!self.callee && !!self.callee._hasRangeAttributes) { // check whether custom fields exist in responses model
rows.forEach(function (row) { if (!Utils._.isEmpty(dialectSpecificFields)) {
parseRangeFields(self.callee, row); rows.forEach(function (row, rowId, rows) {
}); Utils._.each(row, function (value, key) {
if (isDialectSpecificField(key))
rows[rowId][key] =
parseDialectSpecificFields[dialectSpecificFields[key].type](value, { dataType: dialectSpecificFields[key].dataType });
});
});
}
} }
return self.handleSelectQuery(rows); return self.handleSelectQuery(rows);
...@@ -229,16 +269,22 @@ module.exports = (function() { ...@@ -229,16 +269,22 @@ module.exports = (function() {
return parseInt(result.rowCount, 10); return parseInt(result.rowCount, 10);
} }
if (!!self.callee && !!self.callee._hasHstoreAttributes) { if (!!self.callee && rows.length > 0) {
rows.forEach(function(row) { dialectSpecificFields = dialectSpecificFieldDatatypeMap({
parseHstoreFields(self.callee, row); callee: self.callee,
types: dialectSpecificTypes
}); });
}
if (!!self.callee && !!self.callee._hasRangeAttributes) { // check whether custom fields exist in responses model
rows.forEach(function (row) { if (!Utils._.isEmpty(dialectSpecificFields)) {
parseRangeFields(self.callee, row); rows.forEach(function (row, rowId, rows) {
}); Utils._.each(row, function (value, key) {
if (isDialectSpecificField(key))
rows[rowId][key] =
parseDialectSpecificFields[dialectSpecificFields[key].type](value, { dataType: dialectSpecificFields[key].dataType });
});
});
}
} }
return self.handleSelectQuery(rows); return self.handleSelectQuery(rows);
...@@ -248,12 +294,20 @@ module.exports = (function() { ...@@ -248,12 +294,20 @@ module.exports = (function() {
return rows[0].sequelize_upsert; return rows[0].sequelize_upsert;
} else if (self.isInsertQuery() || self.isUpdateQuery()) { } else if (self.isInsertQuery() || self.isUpdateQuery()) {
if (!!self.callee && self.callee.dataValues) { if (!!self.callee && self.callee.dataValues) {
if (!!self.callee.Model && !!self.callee.Model._hasHstoreAttributes) { if (!!self.callee.Model) {
parseHstoreFields(self.callee.Model, rows[0]); dialectSpecificFields = dialectSpecificFieldDatatypeMap({
} callee: self.callee.Model,
types: dialectSpecificTypes
});
if (!!self.callee.Model && !!self.callee.Model._hasRangeAttributes) { // check whether custom fields exist in responses model
parseRangeFields(self.callee.Model, rows[0]); if (!Utils._.isEmpty(dialectSpecificFields)) {
Utils._.each(rows[0], function (value, key) {
if (isDialectSpecificField(key))
rows[0][key] =
parseDialectSpecificFields[dialectSpecificFields[key].type](value, { dataType: dialectSpecificFields[key].dataType });
});
}
} }
for (var key in rows[0]) { for (var key in rows[0]) {
......
...@@ -34,7 +34,7 @@ module.exports = { ...@@ -34,7 +34,7 @@ module.exports = {
AttributeType = AttributeType || ''; // if attribute is not defined, assign empty string in order to prevent AttributeType = AttributeType || ''; // if attribute is not defined, assign empty string in order to prevent
// AttributeType.toString() to fail with uncaught exception later in the code // AttributeType.toString() to fail with uncaught exception later in the code
var result = value var result = value
.slice(1, -1) .substring(1, value.length - 1)
.split(',', 2); .split(',', 2);
if (result.length !== 2) return value; if (result.length !== 2) return value;
......
...@@ -306,9 +306,9 @@ module.exports = (function() { ...@@ -306,9 +306,9 @@ module.exports = (function() {
self._booleanAttributes.push(name); self._booleanAttributes.push(name);
} else if (definition.type instanceof DataTypes.DATE) { } else if (definition.type instanceof DataTypes.DATE) {
self._dateAttributes.push(name); self._dateAttributes.push(name);
} else if (definition.type instanceof DataTypes.HSTORE) { } else if (definition.type instanceof DataTypes.HSTORE || DataTypes.ARRAY.is(definition.type, DataTypes.HSTORE)) {
self._hstoreAttributes.push(name); self._hstoreAttributes.push(name);
} else if (definition.type instanceof DataTypes.RANGE) { } else if (definition.type instanceof DataTypes.RANGE || DataTypes.ARRAY.is(definition.type, DataTypes.RANGE)) {
self._rangeAttributes.push(name); self._rangeAttributes.push(name);
} else if (definition.type instanceof DataTypes.JSON) { } else if (definition.type instanceof DataTypes.JSON) {
self._jsonAttributes.push(name); self._jsonAttributes.push(name);
......
...@@ -643,6 +643,36 @@ if (dialect.match(/^postgres/)) { ...@@ -643,6 +643,36 @@ if (dialect.match(/^postgres/)) {
.error(console.log); .error(console.log);
}); });
it('should read hstore correctly from included models as well', function() {
var self = this,
HstoreSubmodel = self.sequelize.define('hstoreSubmodel', {
someValue: DataTypes.HSTORE
}),
submodelValue = { testing: '"hstore"' };
self.User.hasMany(HstoreSubmodel);
return self.sequelize
.sync({ force: true })
.then(function() {
return self.User.create({ username: 'user1' })
.then(function (user) {
return HstoreSubmodel.create({ someValue: submodelValue})
.then(function (submodel) {
return user.setHstoreSubmodels([submodel]);
});
});
})
.then(function() {
return self.User.find({ where: { username: 'user1' }, include: [HstoreSubmodel]});
})
.then(function(user) {
expect(user.hasOwnProperty('hstoreSubmodels')).to.be.ok;
expect(user.hstoreSubmodels.length).to.equal(1);
expect(user.hstoreSubmodels[0].someValue).to.deep.equal(submodelValue);
});
});
it('should save range correctly', function() { it('should save range correctly', function() {
var period = [new Date(2015, 0, 1), new Date(2015, 11, 31)]; var period = [new Date(2015, 0, 1), new Date(2015, 11, 31)];
return this.User.create({ username: 'user', email: ['foo@bar.com'], course_period: period}).then(function(newUser) { return this.User.create({ username: 'user', email: ['foo@bar.com'], course_period: period}).then(function(newUser) {
...@@ -673,7 +703,7 @@ if (dialect.match(/^postgres/)) { ...@@ -673,7 +703,7 @@ if (dialect.match(/^postgres/)) {
[new Date(2015, 8, 1), new Date(2015, 9, 15)] [new Date(2015, 8, 1), new Date(2015, 9, 15)]
]; ];
return this.User.create({ return User.create({
username: 'bob', username: 'bob',
email: ['myemail@email.com'], email: ['myemail@email.com'],
holidays: holidays holidays: holidays
...@@ -698,7 +728,7 @@ if (dialect.match(/^postgres/)) { ...@@ -698,7 +728,7 @@ if (dialect.match(/^postgres/)) {
var User = this.User, var User = this.User,
period = [new Date(2015, 0, 1), new Date(2015, 11, 31)]; period = [new Date(2015, 0, 1), new Date(2015, 11, 31)];
return this.User.bulkCreate([{ return User.bulkCreate([{
username: 'bob', username: 'bob',
email: ['myemail@email.com'], email: ['myemail@email.com'],
course_period: period course_period: period
...@@ -714,10 +744,10 @@ if (dialect.match(/^postgres/)) { ...@@ -714,10 +744,10 @@ if (dialect.match(/^postgres/)) {
}); });
it('should update range correctly', function() { it('should update range correctly', function() {
var self = this, var User = this.User
period = [new Date(2015, 0, 1), new Date(2015, 11, 31)]; , period = [new Date(2015, 0, 1), new Date(2015, 11, 31)];
return this.User.create({ username: 'user', email: ['foo@bar.com'], course_period: period}).then(function(newUser) { return User.create({ username: 'user', email: ['foo@bar.com'], course_period: period}).then(function(newUser) {
// Check to see if the default value for a range field works // Check to see if the default value for a range field works
expect(newUser.acceptable_marks.length).to.equal(2); expect(newUser.acceptable_marks.length).to.equal(2);
expect(newUser.acceptable_marks[0]).to.equal(0.65); // lower bound expect(newUser.acceptable_marks[0]).to.equal(0.65); // lower bound
...@@ -732,7 +762,7 @@ if (dialect.match(/^postgres/)) { ...@@ -732,7 +762,7 @@ if (dialect.match(/^postgres/)) {
period = [new Date(2015, 1, 1), new Date(2015, 10, 30)]; period = [new Date(2015, 1, 1), new Date(2015, 10, 30)];
// Check to see if updating a range field works // Check to see if updating a range field works
return self.User.update({course_period: period}, {where: newUser.identifiers}).then(function() { return User.update({course_period: period}, {where: newUser.identifiers}).then(function() {
return newUser.reload().success(function() { return newUser.reload().success(function() {
expect(newUser.course_period[0] instanceof Date).to.be.ok; expect(newUser.course_period[0] instanceof Date).to.be.ok;
expect(newUser.course_period[1] instanceof Date).to.be.ok; expect(newUser.course_period[1] instanceof Date).to.be.ok;
...@@ -744,32 +774,39 @@ if (dialect.match(/^postgres/)) { ...@@ -744,32 +774,39 @@ if (dialect.match(/^postgres/)) {
}); });
}); });
it('should update range correctly and return the affected rows', function() { it('should update range correctly and return the affected rows', function () {
var self = this, var User = this.User
period = [new Date(2015, 1, 1), new Date(2015, 10, 30)]; , period = [new Date(2015, 1, 1), new Date(2015, 10, 30)];
return this.User.create({ username: 'user', email: ['foo@bar.com'], course_period: [new Date(2015, 0, 1), new Date(2015, 11, 31)]}).then(function(oldUser) { return User.create({
// Update the user and check that the returned object's fields have been parsed by the range parser username: 'user',
return self.User.update({course_period: period}, {where: oldUser.identifiers, returning: true }).spread(function(count, users) { email: ['foo@bar.com'],
expect(count).to.equal(1); course_period: [new Date(2015, 0, 1), new Date(2015, 11, 31)]
expect(users[0].course_period[0] instanceof Date).to.be.ok; }).then(function (oldUser) {
expect(users[0].course_period[1] instanceof Date).to.be.ok; // Update the user and check that the returned object's fields have been parsed by the range parser
expect(users[0].course_period[0]).to.equalTime(period[0]); // lower bound return User.update({ course_period: period }, { where: oldUser.identifiers, returning: true })
expect(users[0].course_period[1]).to.equalTime(period[1]); // upper bound .spread(function (count, users) {
expect(users[0].course_period.inclusive).to.deep.equal([false, false]); // not inclusive expect(count).to.equal(1);
expect(users[0].course_period[0] instanceof Date).to.be.ok;
expect(users[0].course_period[1] instanceof Date).to.be.ok;
expect(users[0].course_period[0]).to.equalTime(period[0]); // lower bound
expect(users[0].course_period[1]).to.equalTime(period[1]); // upper bound
expect(users[0].course_period.inclusive).to.deep.equal([false, false]); // not inclusive
});
}); });
});
}); });
it('should read range correctly', function() { it('should read range correctly', function() {
var self = this; var User = this.User;
var course_period = [new Date(2015, 1, 1), new Date(2015, 10, 30)]; var course_period = [new Date(2015, 1, 1), new Date(2015, 10, 30)];
course_period.inclusive = [false, false]; course_period.inclusive = [false, false];
var data = { username: 'user', email: ['foo@bar.com'], course_period: course_period}; var data = { username: 'user', email: ['foo@bar.com'], course_period: course_period};
return this.User.create(data) return User.create(data)
.then(function() { .then(function() {
return self.User.find({ where: { username: 'user' }}); return User.find({ where: { username: 'user' }});
}) })
.then(function(user) { .then(function(user) {
// Check that the range fields are the same when retrieving the user // Check that the range fields are the same when retrieving the user
...@@ -778,7 +815,7 @@ if (dialect.match(/^postgres/)) { ...@@ -778,7 +815,7 @@ if (dialect.match(/^postgres/)) {
}); });
it('should read range array correctly', function() { it('should read range array correctly', function() {
var self = this, var User = this.User,
holidays = [ holidays = [
[new Date(2015, 3, 1, 10), new Date(2015, 3, 15)], [new Date(2015, 3, 1, 10), new Date(2015, 3, 15)],
[new Date(2015, 8, 1), new Date(2015, 9, 15)] [new Date(2015, 8, 1), new Date(2015, 9, 15)]
...@@ -789,30 +826,30 @@ if (dialect.match(/^postgres/)) { ...@@ -789,30 +826,30 @@ if (dialect.match(/^postgres/)) {
var data = { username: 'user', email: ['foo@bar.com'], holidays: holidays }; var data = { username: 'user', email: ['foo@bar.com'], holidays: holidays };
return this.User.create(data) return User.create(data)
.then(function() { .then(function() {
// Check that the range fields are the same when retrieving the user // Check that the range fields are the same when retrieving the user
return self.User.find({ where: { username: 'user' }}); return User.find({ where: { username: 'user' }});
}).then(function(user) { }).then(function(user) {
expect(user.holidays).to.deep.equal(data.holidays); expect(user.holidays).to.deep.equal(data.holidays);
}); });
}); });
it('should read range correctly from multiple rows', function() { it('should read range correctly from multiple rows', function() {
var self = this, var User = this.User,
periods = [ periods = [
[new Date(2015, 0, 1), new Date(2015, 11, 31)], [new Date(2015, 0, 1), new Date(2015, 11, 31)],
[new Date(2016, 0, 1), new Date(2016, 11, 31)] [new Date(2016, 0, 1), new Date(2016, 11, 31)]
]; ];
return self.User return User
.create({ username: 'user1', email: ['foo@bar.com'], course_period: periods[0]}) .create({ username: 'user1', email: ['foo@bar.com'], course_period: periods[0]})
.then(function() { .then(function() {
return self.User.create({ username: 'user2', email: ['foo2@bar.com'], course_period: periods[1]}); return User.create({ username: 'user2', email: ['foo2@bar.com'], course_period: periods[1]});
}) })
.then(function() { .then(function() {
// Check that the range fields are the same when retrieving the user // Check that the range fields are the same when retrieving the user
return self.User.findAll({ order: 'username' }); return User.findAll({ order: 'username' });
}) })
.then(function(users) { .then(function(users) {
expect(users[0].course_period[0]).to.equalTime(periods[0][0]); // lower bound expect(users[0].course_period[0]).to.equalTime(periods[0][0]); // lower bound
...@@ -824,6 +861,39 @@ if (dialect.match(/^postgres/)) { ...@@ -824,6 +861,39 @@ if (dialect.match(/^postgres/)) {
}) })
.error(console.log); .error(console.log);
}); });
it('should read range correctly from included models as well', function () {
var self = this
, period = [new Date(2016, 0, 1), new Date(2016, 11, 31)]
, HolidayDate = this.sequelize.define('holidayDate', {
period: DataTypes.RANGE(DataTypes.DATE)
});
self.User.hasMany(HolidayDate);
return self.sequelize
.sync({ force: true })
.then(function () {
return self.User
.create({ username: 'user', email: ['foo@bar.com'] })
.then(function (user) {
return HolidayDate.create({ period: period })
.then(function (holidayDate) {
return user.setHolidayDates([holidayDate]);
});
});
})
.then(function () {
return self.User.find({ where: { username: 'user' }, include: [HolidayDate] });
})
.then(function (user) {
expect(user.hasOwnProperty('holidayDates')).to.be.ok;
expect(user.holidayDates.length).to.equal(1);
expect(user.holidayDates[0].period.length).to.equal(2);
expect(user.holidayDates[0].period[0]).to.equalTime(period[0]);
expect(user.holidayDates[0].period[1]).to.equalTime(period[1]);
});
});
}); });
describe('[POSTGRES] Unquoted identifiers', function() { describe('[POSTGRES] Unquoted identifiers', function() {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!