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

Commit 5f805e3e by Sascha Depold

Merge branch 'master' into features/transactions

Conflicts:
	.gitignore
	lib/dao-factory.js
	lib/utils.js
	package.json
	test/dao-factory.test.js
	test/sequelize.test.js
2 parents 03043e2d 78e20e5c
...@@ -8,3 +8,4 @@ npm-debug.log ...@@ -8,3 +8,4 @@ npm-debug.log
test/binary/tmp/* test/binary/tmp/*
test/tmp/* test/tmp/*
test/sqlite/test.sqlite test/sqlite/test.sqlite
coverage-*
...@@ -18,6 +18,10 @@ test: ...@@ -18,6 +18,10 @@ test:
make teaser && ./node_modules/mocha/bin/mocha --check-leaks --colors -t 10000 --reporter $(REPORTER) $(TESTS); \ make teaser && ./node_modules/mocha/bin/mocha --check-leaks --colors -t 10000 --reporter $(REPORTER) $(TESTS); \
fi fi
cover:
rm -rf coverage \
make teaser && ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- -- -u exports --report lcovonly -- -R spec -- $(TESTS); \
mv coverage coverage-$(DIALECT) \
mariadb: mariadb:
@DIALECT=mariadb make test @DIALECT=mariadb make test
...@@ -32,6 +36,33 @@ postgres-native: ...@@ -32,6 +36,33 @@ postgres-native:
binary: binary:
@./test/binary/sequelize.test.bats @./test/binary/sequelize.test.bats
mariadb-cover:
rm -rf coverage
@DIALECT=mariadb make cover
sqlite-cover:
rm -rf coverage
@DIALECT=sqlite make cover
mysql-cover:
rm -rf coverage
@DIALECT=mysql make cover
postgres-cover:
rm -rf coverage
@DIALECT=postgres make cover
postgres-native-cover:
rm -rf coverage
@DIALECT=postgres-native make cover
binary-cover:
rm -rf coverage
@./test/binary/sequelize.test.bats
merge-coverage:
rm -rf coverage
mkdir coverage
./node_modules/.bin/lcov-result-merger 'coverage-*/lcov.info' 'coverage/lcov.info'
coveralls-send:
cat ./coverage/lcov.info | ./node_modules/.bin/coveralls && rm -rf ./coverage
# test aliases # test aliases
pgsql: postgres pgsql: postgres
...@@ -41,4 +72,7 @@ postgresn: postgres-native ...@@ -41,4 +72,7 @@ postgresn: postgres-native
all: sqlite mysql postgres postgres-native mariadb all: sqlite mysql postgres postgres-native mariadb
all-cover: sqlite-cover mysql-cover postgres-cover postgres-native-cover mariadb-cover merge-coverage
coveralls: sqlite-cover mysql-cover postgres-cover postgres-native-cover mariadb-cover merge-coverage coveralls-send
.PHONY: sqlite mysql postgres pgsql postgres-native postgresn all test .PHONY: sqlite mysql postgres pgsql postgres-native postgresn all test
...@@ -622,13 +622,18 @@ module.exports = (function() { ...@@ -622,13 +622,18 @@ module.exports = (function() {
}).run() }).run()
} }
DAOFactory.prototype.findOrCreate = function (params, defaults, options) { DAOFactory.prototype.findOrCreate = function (where, defaults, options) {
var self = this var self = this
, params = {}
options = Utils._.extend({ options = Utils._.extend({
transaction: null transaction: null
}, options || {}) }, options || {})
for (var attrname in where) {
params[attrname] = where[attrname]
}
return new Utils.CustomEventEmitter(function (emitter) { return new Utils.CustomEventEmitter(function (emitter) {
self.find({ self.find({
where: params where: params
......
...@@ -188,6 +188,9 @@ module.exports = { ...@@ -188,6 +188,9 @@ module.exports = {
FLOAT: FLOAT, FLOAT: FLOAT,
NOW: 'NOW', NOW: 'NOW',
BLOB: BLOB, BLOB: BLOB,
UUID: 'CHAR(36)',
UUIDV1: 'UUIDV1',
UUIDV4: 'UUIDV4',
get ENUM() { get ENUM() {
var result = function() { var result = function() {
......
...@@ -694,7 +694,7 @@ module.exports = (function() { ...@@ -694,7 +694,7 @@ module.exports = (function() {
result.push([_key, _value].join("=")) result.push([_key, _value].join("="))
} else { } else {
for (var logic in value) { for (var logic in value) {
var logicResult = Utils.getWhereLogic(logic) var logicResult = Utils.getWhereLogic(logic, hash[key][logic]);
if (logic === "IN" || logic === "NOT IN") { if (logic === "IN" || logic === "NOT IN") {
var values = Array.isArray(where[i][ii]) ? where[i][ii] : [where[i][ii]] var values = Array.isArray(where[i][ii]) ? where[i][ii] : [where[i][ii]]
_where[_where.length] = i + ' ' + logic + ' (' + values.map(function(){ return '?' }).join(',') + ')' _where[_where.length] = i + ' ' + logic + ' (' + values.map(function(){ return '?' }).join(',') + ')'
......
...@@ -427,9 +427,14 @@ module.exports = (function() { ...@@ -427,9 +427,14 @@ module.exports = (function() {
for (var attrName in row) { for (var attrName in row) {
if (row.hasOwnProperty(attrName) && (attrName !== calleeTableName)) { if (row.hasOwnProperty(attrName) && (attrName !== calleeTableName)) {
existingEntry[attrName] = existingEntry[attrName] || [] existingEntry[attrName] = existingEntry[attrName] || []
var attrRowExists = existingEntry[attrName].some(function(attrRow) {
return Utils._.isEqual(attrRow, row[attrName])
})
if (!attrRowExists) {
existingEntry[attrName].push(row[attrName]) existingEntry[attrName].push(row[attrName])
} }
} }
}
}) })
return result return result
......
...@@ -57,7 +57,7 @@ module.exports = (function() { ...@@ -57,7 +57,7 @@ module.exports = (function() {
} }
var onSuccess = function(rows, sql) { var onSuccess = function(rows, sql) {
var results = [] var results = rows
, self = this , self = this
, 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)
......
var util = require("util") var util = require("util")
, EventEmitter = require("events").EventEmitter , EventEmitter = require("events").EventEmitter
, Promise = require("promise") , Promise = require("bluebird")
, proxyEventKeys = ['success', 'error', 'sql'] , proxyEventKeys = ['success', 'error', 'sql']
, Utils = require('../utils') , Utils = require('../utils')
......
...@@ -233,7 +233,7 @@ module.exports = (function() { ...@@ -233,7 +233,7 @@ module.exports = (function() {
// private // private
var getLastMigrationFromDatabase = function() { var getLastMigrationFromDatabase = Migrator.prototype.getLastMigrationFromDatabase = function() {
var self = this var self = this
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
...@@ -255,7 +255,7 @@ module.exports = (function() { ...@@ -255,7 +255,7 @@ module.exports = (function() {
}).run() }).run()
} }
var getLastMigrationIdFromDatabase = function() { var getLastMigrationIdFromDatabase = Migrator.prototype.getLastMigrationIdFromDatabase = function() {
var self = this var self = this
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
...@@ -270,7 +270,7 @@ module.exports = (function() { ...@@ -270,7 +270,7 @@ module.exports = (function() {
}).run() }).run()
} }
var getFormattedDateString = function(s) { var getFormattedDateString = Migrator.prototype.getFormattedDateString = function(s) {
var result = null var result = null
try { try {
...@@ -282,11 +282,11 @@ module.exports = (function() { ...@@ -282,11 +282,11 @@ module.exports = (function() {
return result return result
} }
var stringToDate = function(s) { var stringToDate = Migrator.prototype.stringToDate = function(s) {
return moment(getFormattedDateString(s), "YYYYMMDDHHmmss") return moment(getFormattedDateString(s), "YYYYMMDDHHmmss")
} }
var saveSuccessfulMigration = function(from, to, callback) { var saveSuccessfulMigration = Migrator.prototype.saveSuccessfulMigration = function(from, to, callback) {
var self = this var self = this
self.findOrCreateSequelizeMetaDAO().success(function(SequelizeMeta) { self.findOrCreateSequelizeMetaDAO().success(function(SequelizeMeta) {
...@@ -296,7 +296,7 @@ module.exports = (function() { ...@@ -296,7 +296,7 @@ module.exports = (function() {
}) })
} }
var deleteUndoneMigration = function(from, to, callback) { var deleteUndoneMigration = Migrator.prototype.deleteUndoneMigration = function(from, to, callback) {
var self = this var self = this
self.findOrCreateSequelizeMetaDAO().success(function(SequelizeMeta) { self.findOrCreateSequelizeMetaDAO().success(function(SequelizeMeta) {
......
...@@ -143,7 +143,7 @@ SqlString.format = function(sql, values, timeZone, dialect) { ...@@ -143,7 +143,7 @@ SqlString.format = function(sql, values, timeZone, dialect) {
} }
SqlString.formatNamedParameters = function(sql, values, timeZone, dialect) { SqlString.formatNamedParameters = function(sql, values, timeZone, dialect) {
return sql.replace(/\:(\w+)/g, function (value, key) { return sql.replace(/\:(?!\d)(\w+)/g, function (value, key) {
if (values.hasOwnProperty(key)) { if (values.hasOwnProperty(key)) {
return SqlString.escape(values[key], false, timeZone, dialect) return SqlString.escape(values[key], false, timeZone, dialect)
} else { } else {
......
...@@ -4,6 +4,7 @@ var util = require("util") ...@@ -4,6 +4,7 @@ var util = require("util")
, lodash = require("lodash") , lodash = require("lodash")
, _string = require('underscore.string') , _string = require('underscore.string')
, ParameterValidator = require('./utils/parameter-validator') , ParameterValidator = require('./utils/parameter-validator')
, uuid = require('node-uuid')
var Utils = module.exports = { var Utils = module.exports = {
_: (function() { _: (function() {
...@@ -181,7 +182,7 @@ var Utils = module.exports = { ...@@ -181,7 +182,7 @@ var Utils = module.exports = {
} }
else if (type === "object") { else if (type === "object") {
Object.keys(where[i]).forEach(function(ii) { Object.keys(where[i]).forEach(function(ii) {
logic = self.getWhereLogic(ii) logic = self.getWhereLogic(ii, where[i][ii]);
switch(logic) { switch(logic) {
case 'IN': case 'IN':
...@@ -283,7 +284,7 @@ var Utils = module.exports = { ...@@ -283,7 +284,7 @@ var Utils = module.exports = {
return Utils._.compactLite([text.join(' AND ')].concat(whereArgs)) return Utils._.compactLite([text.join(' AND ')].concat(whereArgs))
}, },
getWhereLogic: function(logic) { getWhereLogic: function(logic, val) {
switch (logic) { switch (logic) {
case 'join': case 'join':
return 'JOIN' return 'JOIN'
...@@ -298,7 +299,7 @@ var Utils = module.exports = { ...@@ -298,7 +299,7 @@ var Utils = module.exports = {
case 'eq': case 'eq':
return '=' return '='
case 'ne': case 'ne':
return '!=' return val ? '!=' : 'IS NOT'
case 'between': case 'between':
case '..': case '..':
return 'BETWEEN' return 'BETWEEN'
...@@ -376,8 +377,14 @@ var Utils = module.exports = { ...@@ -376,8 +377,14 @@ var Utils = module.exports = {
toDefaultValue: function(value) { toDefaultValue: function(value) {
if (lodash.isFunction(value)) { if (lodash.isFunction(value)) {
return value() return value()
} else if (value === DataTypes.UUIDV1) {
return uuid.v1()
} else if (value === DataTypes.UUIDV4) {
return uuid.v4()
} else if (value === DataTypes.NOW) {
return Utils.now()
} else { } else {
return (value === DataTypes.NOW) ? Utils.now() : value return value
} }
}, },
...@@ -395,6 +402,8 @@ var Utils = module.exports = { ...@@ -395,6 +402,8 @@ var Utils = module.exports = {
// have been normalized for this case // have been normalized for this case
if (value === DataTypes.NOW) {return false} if (value === DataTypes.NOW) {return false}
if (value === DataTypes.UUIDV1 || value === DataTypes.UUIDV4) {return false}
if (lodash.isFunction(value)) { if (lodash.isFunction(value)) {
return false return false
} }
......
...@@ -45,9 +45,10 @@ ...@@ -45,9 +45,10 @@
"dottie": "0.0.8-0", "dottie": "0.0.8-0",
"toposort-class": "~0.2.0", "toposort-class": "~0.2.0",
"generic-pool": "2.0.4", "generic-pool": "2.0.4",
"promise": "~3.2.0",
"sql": "~0.31.0", "sql": "~0.31.0",
"circular-json": "~0.1.5" "circular-json": "~0.1.5",
"bluebird": "~0.11.5",
"node-uuid": "~1.4.1"
}, },
"devDependencies": { "devDependencies": {
"sqlite3": "~2.1.12", "sqlite3": "~2.1.12",
...@@ -60,7 +61,10 @@ ...@@ -60,7 +61,10 @@
"chai-datetime": "~1.1.1", "chai-datetime": "~1.1.1",
"sinon": "~1.7.3", "sinon": "~1.7.3",
"mariasql": "git://github.com/mscdex/node-mariasql.git", "mariasql": "git://github.com/mscdex/node-mariasql.git",
"chai-spies": "~0.5.1" "chai-spies": "~0.5.1",
"lcov-result-merger": "0.0.2",
"istanbul": "~0.1.45",
"coveralls": "~2.5.0"
}, },
"keywords": [ "keywords": [
"mysql", "mysql",
......
...@@ -535,7 +535,9 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -535,7 +535,9 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
Task.hasMany(User) Task.hasMany(User)
expect(Task.attributes.UserId).not.to.exist expect(Task.attributes.UserId).not.to.exist
setTimeout(function () {
done() done()
}, 50)
}) })
describe('setAssociations', function() { describe('setAssociations', function() {
......
...@@ -2241,6 +2241,45 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -2241,6 +2241,45 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
it('including two has many relations should not result in duplicate values', function(done) {
var self = this
self.Contact = self.sequelize.define('Contact', { name: DataTypes.TEXT })
self.Photo = self.sequelize.define('Photo', { img: DataTypes.TEXT })
self.PhoneNumber = self.sequelize.define('PhoneNumber', { phone: DataTypes.TEXT })
self.Contact.hasMany(self.Photo, { as: 'Photos' })
self.Contact.hasMany(self.PhoneNumber)
self.sequelize.sync({ force: true }).success(function() {
self.Contact.create({ name: 'Boris' }).success(function(someContact) {
self.Photo.create({ img: 'img.jpg' }).success(function(somePhoto) {
self.PhoneNumber.create({ phone: '000000' }).success(function(somePhone1) {
self.PhoneNumber.create({ phone: '111111' }).success(function(somePhone2) {
someContact.setPhotos([somePhoto]).complete(function (err, data) {
expect(err).to.be.null
someContact.setPhoneNumbers([somePhone1, somePhone2]).complete(function (err, data) {
self.Contact.find({
where: {
name: 'Boris'
},
include: [self.PhoneNumber, { daoFactory: self.Photo, as: 'Photos' }]
}).complete(function (err, fetchedContact) {
expect(err).to.be.null
expect(fetchedContact).to.exist
expect(fetchedContact.photos.length).to.equal(1)
expect(fetchedContact.phoneNumbers.length).to.equal(2)
done()
})
})
})
})
})
})
})
})
})
it('eager loads with non-id primary keys', function(done) { it('eager loads with non-id primary keys', function(done) {
var self = this var self = this
self.User = self.sequelize.define('UserPKeagerone', { self.User = self.sequelize.define('UserPKeagerone', {
......
...@@ -7,6 +7,7 @@ var chai = require('chai') ...@@ -7,6 +7,7 @@ var chai = require('chai')
, config = require(__dirname + "/config/config") , config = require(__dirname + "/config/config")
, sinon = require('sinon') , sinon = require('sinon')
, datetime = require('chai-datetime') , datetime = require('chai-datetime')
, uuid = require('node-uuid')
, _ = require('lodash') , _ = require('lodash')
chai.use(datetime) chai.use(datetime)
...@@ -16,6 +17,8 @@ describe(Support.getTestDialectTeaser("DAO"), function () { ...@@ -16,6 +17,8 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
beforeEach(function(done) { beforeEach(function(done) {
this.User = this.sequelize.define('User', { this.User = this.sequelize.define('User', {
username: { type: DataTypes.STRING }, username: { type: DataTypes.STRING },
uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 },
uuidv4: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4 },
touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
aNumber: { type: DataTypes.INTEGER }, aNumber: { type: DataTypes.INTEGER },
bNumber: { type: DataTypes.INTEGER }, bNumber: { type: DataTypes.INTEGER },
...@@ -634,6 +637,26 @@ describe(Support.getTestDialectTeaser("DAO"), function () { ...@@ -634,6 +637,26 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
}) })
describe('default values', function() { describe('default values', function() {
describe('uuid', function() {
it('should store a string in uuidv1 and uuidv4', function(done) {
var user = this.User.build({ username: 'a user'})
expect(user.uuidv1).to.be.a('string')
expect(user.uuidv4).to.be.a('string')
done()
})
it('should store a string of length 36 in uuidv1 and uuidv4', function(done) {
var user = this.User.build({ username: 'a user'})
expect(user.uuidv1).to.have.length(36)
expect(user.uuidv4).to.have.length(36)
done()
})
it('should store a valid uuid in uuidv1 and uuidv4 that can be parsed to something of length 16', function(done) {
var user = this.User.build({ username: 'a user'})
expect(uuid.parse(user.uuidv1)).to.have.length(16)
expect(uuid.parse(user.uuidv4)).to.have.length(16)
done()
})
})
describe('current date', function() { describe('current date', function() {
it('should store a date in touchedAt', function(done) { it('should store a date in touchedAt', function(done) {
var user = this.User.build({ username: 'a user'}) var user = this.User.build({ username: 'a user'})
......
...@@ -25,6 +25,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -25,6 +25,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
[Sequelize.TEXT, 'TEXT', 'TEXT'], [Sequelize.TEXT, 'TEXT', 'TEXT'],
[Sequelize.DATE, 'DATE', 'DATETIME'], [Sequelize.DATE, 'DATE', 'DATETIME'],
[Sequelize.NOW, 'NOW', 'NOW'], [Sequelize.NOW, 'NOW', 'NOW'],
[Sequelize.UUID, 'UUID', 'CHAR(36)'],
[Sequelize.BOOLEAN, 'BOOLEAN', 'TINYINT(1)'], [Sequelize.BOOLEAN, 'BOOLEAN', 'TINYINT(1)'],
[Sequelize.BLOB, 'BLOB', 'BLOB'], [Sequelize.BLOB, 'BLOB', 'BLOB'],
......
...@@ -30,7 +30,10 @@ if (dialect.match(/^postgres/)) { ...@@ -30,7 +30,10 @@ if (dialect.match(/^postgres/)) {
Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'}) Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'})
Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'}) Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'})
setTimeout(function () {
done() done()
}, 50)
}) })
it("should not use a combined name", function(done) { it("should not use a combined name", function(done) {
......
var chai = require('chai') var chai = require('chai')
, expect = chai.expect , expect = chai.expect
, assert = chai.assert
, Support = require(__dirname + '/support') , Support = require(__dirname + '/support')
, DataTypes = require(__dirname + "/../lib/data-types") , DataTypes = require(__dirname + "/../lib/data-types")
, dialect = Support.getTestDialect() , dialect = Support.getTestDialect()
...@@ -236,6 +237,13 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () { ...@@ -236,6 +237,13 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
}) })
}) })
it('replaces named parameters with the passed object and ignore those which does not qualify', function(done) {
this.sequelize.query('select :one as foo, :two as bar, \'00:00\' as baz', null, { raw: true }, { one: 1, two: 2 }).success(function(result) {
expect(result).to.deep.equal([{ foo: 1, bar: 2, baz: '00:00' }])
done()
})
})
it('replaces named parameters with the passed object using the same key twice', function(done) { it('replaces named parameters with the passed object using the same key twice', function(done) {
this.sequelize.query('select :one as foo, :two as bar, :one as baz', null, { raw: true }, { one: 1, two: 2 }).success(function(result) { this.sequelize.query('select :one as foo, :two as bar, :one as baz', null, { raw: true }, { one: 1, two: 2 }).success(function(result) {
expect(result).to.deep.equal([{ foo: 1, bar: 2, baz: 1 }]) expect(result).to.deep.equal([{ foo: 1, bar: 2, baz: 1 }])
...@@ -296,6 +304,18 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () { ...@@ -296,6 +304,18 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
done() done()
}) })
}) })
if (Support.getTestDialect() === 'postgres') {
it('supports WITH queries', function(done) {
this
.sequelize
.query("WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t")
.success(function(results) {
expect(results).to.deep.equal([ { "sum": "5050" } ])
done()
})
})
}
}) })
describe('define', function() { describe('define', function() {
...@@ -382,9 +402,13 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () { ...@@ -382,9 +402,13 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
var User2 = this.sequelizeWithInvalidCredentials.define('User', { name: DataTypes.STRING, bio: DataTypes.TEXT }) var User2 = this.sequelizeWithInvalidCredentials.define('User', { name: DataTypes.STRING, bio: DataTypes.TEXT })
User2.sync().complete(function(err) { User2.sync().error(function(err) {
if (dialect === "postgres" || dialect === "postgres-native") { if (dialect === "postgres" || dialect === "postgres-native") {
expect(err.message).to.match(/(role "bar" does not exist)|(password authentication failed for user "bar")/) assert([
'role "bar" does not exist',
'FATAL: role "bar" does not exist',
'password authentication failed for user "bar"'
].indexOf(err.message.trim()) !== -1)
} else { } else {
expect(err.message.toString()).to.match(/.*Access\ denied.*/) expect(err.message.toString()).to.match(/.*Access\ denied.*/)
} }
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!