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

Commit d8b1565d by Jonathan M. Altman

Merge remote-tracking branch 'upstream/master' into basic_function_and_trigger_migrations

2 parents af723ee4 df2ae8b5
......@@ -4,4 +4,6 @@ test*.js
.DS_STORE
node_modules
npm-debug.log
*~
\ No newline at end of file
*~
test/binary/tmp/*
test/tmp/*
......@@ -4,6 +4,7 @@ before_script:
script:
- "make test"
- "make binary"
notifications:
email:
......
......@@ -26,6 +26,8 @@ postgres:
@DIALECT=postgres make test
postgres-native:
@DIALECT=postgres-native make test
binary:
@./test/binary/sequelize.test.bats
# test aliases
......@@ -36,4 +38,4 @@ postgresn: postgres-native
all: sqlite mysql postgres postgres-native
.PHONY: sqlite mysql postgres pgsql postgres-native postgresn all test
\ No newline at end of file
.PHONY: sqlite mysql postgres pgsql postgres-native postgresn all test
#!/usr/bin/env node
const path = require("path")
, fs = require("fs")
, program = require("commander")
, Sequelize = require(__dirname + '/../index')
, moment = require("moment")
, _ = Sequelize.Utils._
var configPath = process.cwd() + '/config'
, environment = process.env.NODE_ENV || 'development'
, migrationsPath = process.cwd() + '/migrations'
, packageJsonPath = __dirname + '/../package.json'
, packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString())
, configFile = configPath + '/config.json'
, configPathExists = fs.existsSync(configPath)
, configFileExists = fs.existsSync(configFile)
var writeConfig = function(config) {
!configPathExists && fs.mkdirSync(configPath)
config = JSON.stringify(config)
config = config.replace('{', '{\n ')
config = config.replace(/,/g, ",\n ")
config = config.replace('}', "\n}")
fs.writeFileSync(configFile, config)
var path = require("path")
, fs = require("fs")
, program = require("commander")
, Sequelize = require(__dirname + '/../index')
, moment = require("moment")
, _ = Sequelize.Utils._
var configuration = {
configFile: process.cwd() + '/config/config.json',
environment: process.env.NODE_ENV || 'development',
version: require(__dirname + '/../package.json').version,
migrationsPath: process.cwd() + '/migrations'
}
var configFileExists = function() {
return fs.existsSync(configuration.configFile)
}
var relativeConfigFile = function() {
return path.relative(process.cwd(), configuration.configFile)
}
var writeDefaultConfig = function(config) {
var configPath = path.dirname(configuration.configFile)
if (!fs.existsSync(configPath)) {
fs.mkdirSync(configPath)
}
config = JSON.stringify({
development: {
username: "root",
password: null,
database: 'database_development',
host: '127.0.0.1'
},
test: {
username: "root",
password: null,
database: 'database_test',
host: '127.0.0.1'
},
production: {
username: "root",
password: null,
database: 'database_production',
host: '127.0.0.1'
}
}, undefined, 2) + "\n"
fs.writeFileSync(configuration.configFile, config)
}
var createMigrationsFolder = function(force) {
if(force) {
console.log('Deleting the migrations folder.')
console.log('Deleting the migrations folder. (--force)')
try {
fs.readdirSync(migrationsPath).forEach(function(filename) {
fs.unlinkSync(migrationsPath + '/' + filename)
fs.readdirSync(configuration.migrationsPath).forEach(function(filename) {
fs.unlinkSync(configuration.migrationsPath + '/' + filename)
})
} catch(e) {}
try {
fs.rmdirSync(migrationsPath)
fs.rmdirSync(configuration.migrationsPath)
console.log('Successfully deleted the migrations folder.')
} catch(e) {}
}
console.log('Creating migrations folder.')
try {
fs.mkdirSync(migrationsPath)
console.log('Successfully create migrations folder.')
fs.mkdirSync(configuration.migrationsPath)
console.log('Successfully created migrations folder at "' + configuration.migrationsPath + '".')
} catch(e) {
console.log('Migrations folder already exist.')
}
}
var readConfig = function() {
var config
try {
config = fs.readFileSync(configFile)
config = fs.readFileSync(configuration.configFile)
} catch(e) {
throw new Error('Error reading "config/config.json".')
throw new Error('Error reading "' + relativeConfigFile() + '".')
}
try {
config = JSON.parse(config)
} catch (e) {
throw new Error('Error parsing "config/config.json" as JSON.')
throw new Error('Error parsing "' + relativeConfigFile() + '" as JSON.')
}
if (config[environment]) {
config = config[environment]
console.log('Loaded configuration file "' + relativeConfigFile() + '".')
if (config[configuration.environment]) {
console.log('Using environment "' + configuration.environment + '".')
config = config[configuration.environment]
}
return config
}
program
.version(packageJson.version)
.version(configuration.version)
.option('-i, --init', 'Initializes the project.')
.option('-e, --env <environment>', 'Specify the environment.')
.option('-m, --migrate', 'Run pending migrations.')
.option('-u, --undo', 'Undo the last migration.')
.option('-f, --force', 'Forces the action to be done.')
.option('-c, --create-migration [migration-name]', 'Creates a new migration.')
.option('--config <config_file>', 'Specifies alternate config file.')
.parse(process.argv)
if(typeof program.config === 'string') {
configuration.configFile = program.config
}
if(typeof program.env === 'string') {
environment = program.env
configuration.environment = program.env
}
console.log("Using environment '" + environment + "'.")
if(program.migrate) {
if(configFileExists) {
if (program.migrate) {
if (configFileExists()) {
var config
, options = {}
......@@ -101,57 +130,58 @@ if(program.migrate) {
if(['database', 'username', 'password'].indexOf(key) == -1) {
options[key] = value
}
if (key === "use_env_variable") {
if (process.env[value]) {
var db_info = process.env[value].match(
/([^:]+):\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
config.database = db_info[6];
config.username = db_info[2];
config.password = db_info[3];
options = _.extend(options, {
host: db_info[4],
port: db_info[5],
dialect: db_info[1],
protocol: db_info[1]
});
}
}
})
options = _.extend(options, { logging: false })
var sequelize = new Sequelize(config.database, config.username, config.password, options)
, migratorOptions = { path: migrationsPath }
, migratorOptions = { path: configuration.migrationsPath }
, migrator = sequelize.getMigrator(migratorOptions)
if(program.undo) {
if (program.undo) {
sequelize.migrator.findOrCreateSequelizeMetaDAO().success(function(Meta) {
Meta.find({ order: 'id DESC' }).success(function(meta) {
if(meta) {
if (meta) {
migrator = sequelize.getMigrator(_.extend(migratorOptions, meta), true)
}
migrator.migrate({ method: 'down' })
migrator.migrate({ method: 'down' }).success(function() {
process.exit(0)
})
})
})
} else {
sequelize.migrate()
sequelize.migrate().success(function() {
process.exit(0)
})
}
} else {
console.log('Cannot find "config/config.json". Have you run "sequelize --init"?')
console.log('Cannot find "' + relativeConfigFile() + '". Have you run "sequelize --init"?')
process.exit(1)
}
} else if(program.init) {
if(!configFileExists || !!program.force) {
writeConfig({
development: {
username: "root",
password: null,
database: 'database_development',
host: '127.0.0.1'
},
test: {
username: "root",
password: null,
database: 'database_test',
host: '127.0.0.1'
},
production: {
username: "root",
password: null,
database: 'database_production',
host: '127.0.0.1'
}
})
if(!configFileExists() || !!program.force) {
writeDefaultConfig()
console.log('Created "config/config.json"')
console.log('Created "' + relativeConfigFile() + '"')
} else {
console.log('"config/config.json" already exists. Run "sequelize --init --force" to overwrite.')
console.log('The file "' + relativeConfigFile() + '" already exists. Run "sequelize --init --force" to overwrite it.')
process.exit(1)
}
......@@ -175,9 +205,11 @@ if(program.migrate) {
" done()",
" }",
"}"
].join('\n')
].join('\n') + "\n"
fs.writeFileSync(migrationsPath + '/' + migrationName, migrationContent)
fs.writeFileSync(configuration.migrationsPath + '/' + migrationName, migrationContent)
console.log('New migration "' + migrationName + '" was added to "' +
path.relative(process.cwd(), configuration.migrationsPath) + '/".')
} else {
console.log('Try "sequelize --help" for usage information.')
console.log('No action specified. Try "sequelize --help" for usage information.')
}
......@@ -30,6 +30,7 @@
- [BUG] For MySQL users, if their collation allows case insensitivity then allow enums to be case insensitive as well [#794](https://github.com/sequelize/sequelize/pull/794). thanks to durango
- [BUG] Custom primary key (not keys, just singular) should no longer be a problem for models when using any of the data retrievals with just a number or through associations [#771](https://github.com/sequelize/sequelize/pull/771). thanks to sdephold & durango
- [BUG] Default schemas should now be utilized when describing tables [#812](https://github.com/sequelize/sequelize/pull/812). thanks to durango
- [BUG] Fixed eager loading for many-to-many associations. [#834](https://github.com/sequelize/sequelize/pull/834). thanks to lemon-tree
- [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango
- [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango
- [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude
......@@ -54,6 +55,7 @@
- [FEATURE] Drop index if exists has been added to sqlite [#766](https://github.com/sequelize/sequelize/pull/776) thanks to coderbuzz
- [FEATURE] bulkCreate() now has a third argument which gives you the ability to validate each row before attempting to bulkInsert [#797](https://github.com/sequelize/sequelize/pull/797). thanks to durango
- [FEATURE] Added `isDirty` to model instances. [#798](https://github.com/sequelize/sequelize/pull/798). Thanks to mstorgaard
- [FEATURE] Added possibility to use env variable for the database connection. [#784](https://github.com/sequelize/sequelize/pull/784). Thanks to sykopomp.
- [REFACTORING] hasMany now uses a single SQL statement when creating and destroying associations, instead of removing each association seperately [690](https://github.com/sequelize/sequelize/pull/690). Inspired by [#104](https://github.com/sequelize/sequelize/issues/104). janmeier
- [REFACTORING] Consistent handling of offset across dialects. Offset is now always applied, and limit is set to max table size of not limit is given [#725](https://github.com/sequelize/sequelize/pull/725). janmeier
- [REFACTORING] Moved Jasmine to Buster and then Buster to Mocha + Chai. sdepold and durango
......
......@@ -47,7 +47,9 @@ module.exports = (function() {
this.foreignIdentifier = Utils._.underscoredIf((this.options.as || this.target.tableName) + 'Id', this.options.underscored)
} else {
this.foreignIdentifier = this.target.associations[this.associationAccessor].identifier
this.target.associations[this.associationAccessor].foreignIdentifier = this.identifier
delete this.source.rawAttributes[this.foreignIdentifier]
delete this.target.associations[this.associationAccessor].source.rawAttributes[this.identifier]
}
// define a new model, which connects the models
......
......@@ -354,14 +354,8 @@ module.exports = (function() {
DAOFactory.prototype.findAndCountAll = function(options) {
var self = this
, opts = Utils._.cloneDeep(options)
, copts = Utils._.extend({}, Utils._.cloneDeep(options) || {}, {
// no limit, offset, order or include for the options given to count()
offset : 0,
limit : 0,
order : null,
include : null
})
// no limit, offset, order, attributes or include for the options given to count()
, copts = Utils._.omit(options || {}, ['offset', 'limit', 'order', 'include', 'attributes'])
return new Utils.CustomEventEmitter(function (emitter) {
var emit = {
......@@ -377,20 +371,21 @@ module.exports = (function() {
, sql : function(s) { // emit SQL
emitter.emit('sql', s);
}
}
}
self.count(copts)
.on('sql', emit.sql)
.error(emit.err)
.success(function(cnt) {
if (cnt === 0)
if (cnt === 0) {
return emit.okay(cnt) // no records, no need for another query
}
self.findAll(options)
.on('sql', emit.sql)
.error(emit.err)
.success(function(rows) {
emit.okay(cnt, rows)
emit.okay(cnt, rows)
})
})
......
......@@ -183,7 +183,7 @@ module.exports = {
}
result.type = 'HSTORE'
result.toString = result.valueOf = function() { return 'TEXT' }
result.toString = result.valueOf = function() { return 'HSTORE' }
return result
}
......
......@@ -5,6 +5,7 @@ var Utils = require("../../utils")
module.exports = (function() {
var QueryGenerator = {
dialect: 'mysql',
addSchema: function(opts) {
var tableName
var schema = (!!opts && !!opts.options && !!opts.options.schema ? opts.options.schema : undefined)
......@@ -187,14 +188,27 @@ module.exports = (function() {
optAttributes = optAttributes.concat(attributes)
var table = include.daoFactory.tableName
var as = include.as
var tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
var attrLeft = 'id'
var tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
var attrRight = include.association.identifier
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight)
if (!include.association.connectorDAO) {
var table = include.daoFactory.tableName
var as = include.as
var tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
var attrLeft = 'id'
var tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
var attrRight = include.association.identifier
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight)
} else {
var table = include.daoFactory.tableName
var as = include.as
var tableLeft = tableName
var identLeft = include.association.identifier
var attrLeft = 'id'
var tableRight = include.as
var identRight = include.association.foreignIdentifier
var attrRight = 'id'
var tableJunction = include.association.connectorDAO.tableName
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(tableJunction) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identLeft)
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identRight)
}
}.bind(this))
options.attributes = optAttributes.join(', ')
......@@ -424,7 +438,7 @@ module.exports = (function() {
} else if (typeof smth === "string") {
result = smth
} else if (Array.isArray(smth)) {
result = Utils.format(smth)
result = Utils.format(smth, this.dialect)
}
return result ? result : '1=1'
......
......@@ -72,8 +72,9 @@ module.exports = (function() {
var emitter = new (require('events').EventEmitter)()
// in case database is slow to connect, prevent orphaning the client
if (this.isConnecting && !this.pooling) {
emitter.emit('success')
// TODO: We really need some sort of queue/flush/drain mechanism
if (this.isConnecting && !this.pooling && this.client === null) {
emitter.emit('success', null)
return emitter
}
......@@ -118,7 +119,7 @@ module.exports = (function() {
if (this.pooling) {
// acquire client from pool
this.poolIdentifier = this.pg.pools.getOrCreate(this.sequelize.config)
this.poolIdentifier = this.pg.pools.getOrCreate(uri)
this.poolIdentifier.connect(connectCallback)
} else {
if (!!this.client) {
......
......@@ -8,6 +8,7 @@ var Utils = require("../../utils")
module.exports = (function() {
var QueryGenerator = {
options: {},
dialect: 'postgres',
addSchema: function(opts) {
var tableName = undefined
......@@ -117,7 +118,7 @@ module.exports = (function() {
schema = 'public';
}
var query = 'SELECT c.column_name as "Field", c.column_default as "Default", c.is_nullable as "Null", c.data_type as "Type", (SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special" FROM information_schema.columns c WHERE table_name = <%= table %> AND table_schema = <%= schema %>'
var query = 'SELECT c.column_name as "Field", c.column_default as "Default", c.is_nullable as "Null", CASE WHEN c.udt_name = \'hstore\' THEN c.udt_name ELSE c.data_type END as "Type", (SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special" FROM information_schema.columns c WHERE table_name = <%= table %> AND table_schema = <%= schema %>'
return Utils._.template(query)({
table: this.escape(tableName),
......@@ -260,14 +261,34 @@ module.exports = (function() {
var joinQuery = ' LEFT OUTER JOIN <%= table %> AS <%= as %> ON <%= tableLeft %>.<%= attrLeft %> = <%= tableRight %>.<%= attrRight %>'
query += Utils._.template(joinQuery)({
table: this.quoteIdentifiers(include.daoFactory.tableName),
as: this.quoteIdentifier(include.as),
tableLeft: this.quoteIdentifiers((include.association.associationType === 'BelongsTo') ? include.as : tableName),
attrLeft: this.quoteIdentifier('id'),
tableRight: this.quoteIdentifiers((include.association.associationType === 'BelongsTo') ? tableName : include.as),
attrRight: this.quoteIdentifier(include.association.identifier)
})
if (!include.association.connectorDAO) {
query += Utils._.template(joinQuery)({
table: this.quoteIdentifiers(include.daoFactory.tableName),
as: this.quoteIdentifier(include.as),
tableLeft: this.quoteIdentifiers((include.association.associationType === 'BelongsTo') ? include.as : tableName),
attrLeft: this.quoteIdentifier('id'),
tableRight: this.quoteIdentifiers((include.association.associationType === 'BelongsTo') ? tableName : include.as),
attrRight: this.quoteIdentifier(include.association.identifier)
})
} else {
query += Utils._.template(joinQuery)({
table: this.quoteIdentifiers(include.association.connectorDAO.tableName),
as: this.quoteIdentifier(include.association.connectorDAO.tableName),
tableLeft: this.quoteIdentifiers(tableName),
attrLeft: this.quoteIdentifier('id'),
tableRight: this.quoteIdentifiers(include.association.connectorDAO.tableName),
attrRight: this.quoteIdentifier(include.association.identifier)
})
query += Utils._.template(joinQuery)({
table: this.quoteIdentifiers(include.daoFactory.tableName),
as: this.quoteIdentifier(include.as),
tableLeft: this.quoteIdentifiers(include.as),
attrLeft: this.quoteIdentifier('id'),
tableRight: this.quoteIdentifiers(include.association.connectorDAO.tableName),
attrRight: this.quoteIdentifier(include.association.foreignIdentifier)
})
}
}.bind(this))
options.attributes = optAttributes.join(', ')
......@@ -787,8 +808,8 @@ module.exports = (function() {
var template = '<%= protocol %>://<%= user %>:<%= password %>@<%= host %><% if(port) { %>:<%= port %><% } %>/<%= database %>'
return Utils._.template(template)({
user: encodeURIComponent(config.username),
password: encodeURIComponent(config.password),
user: config.username,
password: config.password,
database: config.database,
host: config.host,
port: config.port,
......
......@@ -12,6 +12,7 @@ var hashToWhereConditions = MySqlQueryGenerator.hashToWhereConditions
module.exports = (function() {
var QueryGenerator = {
options: {},
dialect: 'sqlite',
addSchema: function(opts) {
var tableName = undefined
......@@ -169,13 +170,27 @@ module.exports = (function() {
optAttributes = optAttributes.concat(attributes)
var table = include.daoFactory.tableName
var as = include.as
var tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
var attrLeft = 'id'
var tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
var attrRight = include.association.identifier
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight) + ""
if (!include.association.connectorDAO) {
var table = include.daoFactory.tableName
var as = include.as
var tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
var attrLeft = 'id'
var tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
var attrRight = include.association.identifier
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight)
} else {
var table = include.daoFactory.tableName
var as = include.as
var tableLeft = tableName
var identLeft = include.association.identifier
var attrLeft = 'id'
var tableRight = include.as
var identRight = include.association.foreignIdentifier
var attrRight = 'id'
var tableJunction = include.association.connectorDAO.tableName
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(tableJunction) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identLeft)
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identRight)
}
}.bind(this))
......
......@@ -58,8 +58,10 @@ module.exports = (function() {
} else {
self.options.logging("Running migrations...")
}
migrations.forEach(function(migration) {
var migrationTime
chainer.add(migration, 'execute', [options], {
before: function(migration) {
if (self.options.logging !== false) {
......@@ -67,6 +69,7 @@ module.exports = (function() {
}
migrationTime = process.hrtime()
},
after: function(migration) {
migrationTime = process.hrtime(migrationTime)
migrationTime = Math.round( (migrationTime[0] * 1000) + (migrationTime[1] / 1000000));
......@@ -75,6 +78,7 @@ module.exports = (function() {
self.options.logging('Completed in ' + migrationTime + 'ms')
}
},
success: function(migration, callback) {
if (options.method === 'down') {
deleteUndoneMigration.call(self, from, migration, callback)
......@@ -233,11 +237,21 @@ module.exports = (function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
self.findOrCreateSequelizeMetaDAO().success(function(SequelizeMeta) {
SequelizeMeta.find({ order: 'id DESC' }).success(function(meta) {
emitter.emit('success', meta ? meta : null)
}).error(function(err) { emitter.emit('error', err) })
}).error(function(err) { emitter.emit(err) })
self
.findOrCreateSequelizeMetaDAO()
.success(function(SequelizeMeta) {
SequelizeMeta
.find({ order: 'id DESC' })
.success(function(meta) {
emitter.emit('success', meta ? meta : null)
})
.error(function(err) {
emitter.emit('error', err)
})
})
.error(function(err) {
emitter.emit('error', err)
})
}).run()
}
......@@ -245,7 +259,8 @@ module.exports = (function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
getLastMigrationFromDatabase.call(self)
getLastMigrationFromDatabase
.call(self)
.success(function(meta) {
emitter.emit('success', meta ? meta.to : null)
})
......
......@@ -210,12 +210,17 @@ module.exports = (function() {
}
Sequelize.prototype.migrate = function(options) {
this.getMigrator().migrate(options)
return this.getMigrator().migrate(options)
}
Sequelize.prototype.query = function(sql, callee, options, replacements) {
if (arguments.length === 4) {
sql = Utils.format([sql].concat(replacements), this.options.dialect)
if (Array.isArray(replacements)) {
sql = Utils.format([sql].concat(replacements), this.options.dialect)
}
else {
sql = Utils.formatNamedParameters(sql, replacements, this.options.dialect)
}
} else if (arguments.length === 3) {
options = options
} else if (arguments.length === 2) {
......
......@@ -106,6 +106,17 @@ SqlString.format = function(sql, values, timeZone, dialect) {
});
};
SqlString.formatNamedParameters = function(sql, values, timeZone, dialect) {
return sql.replace(/\:(\w+)/g, function (value, key) {
if (values.hasOwnProperty(key)) {
return SqlString.escape(values[key], false, timeZone, dialect);
}
else {
throw new Error('Named parameter "' + value + '" has no value in the given object.');
}
});
};
SqlString.dateToString = function(date, timeZone, dialect) {
var dt = new Date(date);
......
......@@ -29,6 +29,22 @@ var Utils = module.exports = {
}
return result
},
/*
* Returns an array with some falsy values removed. The values null, "", undefined and NaN are considered falsey.
*/
compactLite: function(array) {
var index = -1,
length = array ? array.length : 0,
result = [];
while (++index < length) {
var value = array[index];
if (typeof value === "boolean" || value === 0 || value) {
result.push(value);
}
}
return result;
}
})
......@@ -41,6 +57,10 @@ var Utils = module.exports = {
var timeZone = null;
return SqlString.format(arr.shift(), arr, timeZone, dialect)
},
formatNamedParameters: function(sql, parameters, dialect) {
var timeZone = null;
return SqlString.formatNamedParameters(sql, parameters, timeZone, dialect)
},
// smartWhere can accept an array of {where} objects, or a single {where} object.
// The smartWhere function breaks down the collection of where objects into a more
// centralized object for each column so we can avoid duplicates
......@@ -197,7 +217,7 @@ var Utils = module.exports = {
}
}
return lodash.compact([text.join(' AND ')].concat(whereArgs))
return Utils._.compactLite([text.join(' AND ')].concat(whereArgs))
},
getWhereLogic: function(logic) {
switch (logic) {
......
{
"name": "sequelize",
"description": "Multi dialect ORM for Node.JS",
"version": "1.7.0-alpha2",
"version": "1.7.0-alpha3",
"author": "Sascha Depold <sascha@depold.com>",
"contributors": [
{
......
......@@ -381,6 +381,13 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
it("get associated objects with an eager load", function(done) {
this.User.find({where: {username: 'John'}, include: [ this.Task ]}).success(function (john) {
expect(john.tasks).to.have.length(2);
done();
})
})
})
it("removes the reference id, which was added in the first place", function(done) {
......
......@@ -19,7 +19,7 @@ describe(Support.getTestDialectTeaser("HasOne"), function() {
Task.create({ title: 'task', status: 'inactive' }).success(function(task) {
user.setTaskXYZ(task).success(function() {
user.getTaskXYZ({where: ['status = ?', 'active']}).success(function(task) {
expect(task).to.equal(null)
expect(task).to.be.null
done()
})
})
......
......@@ -21,7 +21,8 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
secretValue: DataTypes.STRING,
data: DataTypes.STRING,
intVal: DataTypes.INTEGER,
theDate: DataTypes.DATE
theDate: DataTypes.DATE,
aBool: DataTypes.BOOLEAN
})
this.User.sync({ force: true }).success(function() {
done()
......@@ -999,6 +1000,72 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('should be able to handle false/true values just fine...', function(done) {
var User = this.User
, escapeChar = (dialect === "postgres" || dialect === "postgres-native") ? '"' : '`'
User.bulkCreate([
{username: 'boo5', aBool: false},
{username: 'boo6', aBool: true}
]).success(function() {
User.all({where: [escapeChar + 'aBool' + escapeChar + ' = ?', false]}).success(function(users) {
expect(users).to.have.length(1)
expect(users[0].username).to.equal('boo5')
User.all({where: [escapeChar + 'aBool' + escapeChar + ' = ?', true]}).success(function(_users) {
expect(_users).to.have.length(1)
expect(_users[0].username).to.equal('boo6')
done()
})
})
})
})
it('should be able to handle false/true values through associations as well...', function(done) {
var User = this.User
, escapeChar = (dialect === "postgres" || dialect === "postgres-native") ? '"' : '`'
var Passports = this.sequelize.define('Passports', {
isActive: Sequelize.BOOLEAN
})
User.hasMany(Passports)
Passports.belongsTo(User)
User.sync({ force: true }).success(function() {
Passports.sync({ force: true }).success(function() {
User.bulkCreate([
{username: 'boo5', aBool: false},
{username: 'boo6', aBool: true}
]).success(function() {
Passports.bulkCreate([
{isActive: true},
{isActive: false}
]).success(function() {
User.find(1).success(function(user) {
Passports.find(1).success(function(passport) {
user.setPassports([passport]).success(function() {
User.find(2).success(function(_user) {
Passports.find(2).success(function(_passport) {
_user.setPassports([_passport]).success(function() {
_user.getPassports({where: [escapeChar + 'isActive' + escapeChar + ' = ?', false]}).success(function(theFalsePassport) {
user.getPassports({where: [escapeChar + 'isActive' + escapeChar + ' = ?', true]}).success(function(theTruePassport) {
expect(theFalsePassport).to.have.length(1)
expect(theFalsePassport[0].isActive).to.be.false
expect(theTruePassport).to.have.length(1)
expect(theTruePassport[0].isActive).to.be.true
done()
})
})
})
})
})
})
})
})
})
})
})
})
})
it('should be able to retun a record with primaryKey being null for new inserts', function(done) {
var Session = this.sequelize.define('Session', {
token: { type: DataTypes.TEXT, allowNull: false },
......@@ -2231,6 +2298,16 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
done()
})
})
it("handles attributes", function(done) {
this.User.findAndCountAll({where: "id != " + this.users[0].id, attributes: ['data']}).success(function(info) {
expect(info.count).to.equal(2)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(2)
expect(info.rows[0].selectedValues).to.not.have.property('username')
expect(info.rows[1].selectedValues).to.not.have.property('username')
done()
})
})
})
describe('all', function() {
......
......@@ -2,7 +2,6 @@ var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, dialect = Support.getTestDialect()
, config = require(__dirname + '/../config/config')
, DataTypes = require(__dirname + "/../../lib/data-types")
chai.Assertion.includeStack = true
......@@ -26,6 +25,13 @@ if (dialect.match(/^postgres/)) {
done()
})
it('describeTable should tell me that a column is hstore and not USER-DEFINED', function(done) {
this.sequelize.queryInterface.describeTable('Users').success(function(table) {
expect(table.document.type).to.equal('HSTORE')
done()
})
})
describe('integers', function() {
describe('integer', function() {
beforeEach(function(done) {
......
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + "/../lib/data-types")
, dialect = Support.getTestDialect()
, _ = require('lodash')
, exec = require('child_process').exec
, version = (require(__dirname + '/../package.json')).version
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("Executable"), function() {
describe('call without arguments', function() {
it("prints usage instructions", function(done) {
exec('bin/sequelize', function(err, stdout, stderr) {
expect(stdout).to.include("No action specified. Try \"sequelize --help\" for usage information.")
done()
})
})
})
;(function(flags) {
flags.forEach(function(flag) {
describe(flag, function() {
it("prints the help", function(done) {
exec("bin/sequelize " + flag, function(err, stdout, stderr) {
expect(stdout).to.include("Usage: sequelize [options]")
done()
})
})
})
})
})(["--help", "-h"])
;(function(flags) {
flags.forEach(function(flag) {
describe(flag, function() {
it("prints the help", function(done) {
exec("bin/sequelize " + flag, function(err, stdout, stderr) {
expect(version).to.not.be.empty
expect(stdout).to.include(version)
done()
})
})
})
})
})(['--version', '-V'])
;(function(flags) {
flags.forEach(function(flag) {
describe(flag, function() {
;(function(folders) {
folders.forEach(function(folder) {
it("creates a '" + folder + "' folder", function(done) {
exec("rm -rf ./*", { cwd: __dirname + '/tmp' }, function() {
exec("../../bin/sequelize --init", { cwd: __dirname + '/tmp' }, function() {
exec("ls -ila", { cwd: __dirname + '/tmp' }, function(err, stdout) {
expect(stdout).to.include(folder)
done()
})
})
})
})
})
})(['config', 'migrations'])
it("creates a config.json file", function(done) {
exec("rm -rf ./*", { cwd: __dirname + '/tmp' }, function() {
exec("../../bin/sequelize --init", { cwd: __dirname + '/tmp' }, function() {
exec("ls -ila config", { cwd: __dirname + '/tmp' }, function(err, stdout) {
expect(stdout).to.include('config.json')
done()
})
})
})
})
it("does not overwrite an existing config.json file", function(done) {
exec("rm -rf ./*", { cwd: __dirname + '/tmp' }, function() {
exec("../../bin/sequelize --init", { cwd: __dirname + '/tmp' }, function() {
exec("echo 'foo' > config/config.json", { cwd: __dirname + '/tmp' }, function() {
exec("../../bin/sequelize --init", { cwd: __dirname + '/tmp' }, function(err) {
expect(err.code).to.equal(1)
exec("cat config/config.json", { cwd: __dirname + '/tmp' }, function(err, stdout) {
expect(stdout).to.equal("foo\n")
done()
})
})
})
})
})
})
})
})
})(['--init', '-i'])
;(function(flags) {
flags.forEach(function(flag) {
var prepare = function(callback) {
exec("rm -rf ./*", { cwd: __dirname + '/tmp' }, function() {
exec("../../bin/sequelize --init", { cwd: __dirname + '/tmp' }, function() {
exec("../../bin/sequelize " + flag + " 'foo'", { cwd: __dirname + '/tmp' }, callback)
})
})
}
describe(flag, function() {
it("creates a new file with the current timestamp", function(done) {
prepare(function() {
exec("ls -1 migrations", { cwd: __dirname + '/tmp' }, function(err, stdout) {
var date = new Date()
, format = function(i) { return (parseInt(i, 10) < 10 ? '0' + i : i) }
, sDate = [date.getFullYear(), format(date.getMonth() + 1), format(date.getDate()), format(date.getHours()), format(date.getMinutes())].join('')
expect(stdout).to.match(new RegExp(sDate + "..-foo.js"))
done()
})
})
})
it("adds a skeleton with an up and a down method", function(done) {
prepare(function() {
exec("cat migrations/*-foo.js", { cwd: __dirname + '/tmp' }, function(err, stdout) {
expect(stdout).to.include('up: function(migration, DataTypes, done) {')
expect(stdout).to.include('down: function(migration, DataTypes, done) {')
done()
})
})
})
it("calls the done callback", function(done) {
prepare(function() {
exec("cat migrations/*-foo.js", { cwd: __dirname + '/tmp' }, function(err, stdout) {
expect(stdout).to.include('done()')
expect(stdout.match(/(done\(\))/)).to.have.length(2)
done()
})
})
})
})
})
})(['--create-migration', '-c'])
;(function(flags) {
flags.forEach(function(flag) {
var prepare = function(callback) {
exec("rm -rf ./*", { cwd: __dirname + '/tmp' }, function() {
exec("../../bin/sequelize --init", { cwd: __dirname + '/tmp' }, function() {
exec("cp ../assets/migrations/*-createPerson.js ./migrations/", { cwd: __dirname + '/tmp' }, function() {
exec("cat ../support.js|sed s,/../,/../../, > ./support.js", { cwd: __dirname + '/tmp' }, function() {
var dialect = Support.getTestDialect()
, config = require(__dirname + '/config/config.js')
config.sqlite.storage = __dirname + "/tmp/test.sqlite"
config = _.extend(config, config[dialect], { dialect: dialect })
exec("echo '" + JSON.stringify(config) + "' > config/config.json", { cwd: __dirname + '/tmp' }, function() {
exec("../../bin/sequelize " + flag, { cwd: __dirname + "/tmp" }, callback)
})
})
})
})
})
}
describe(flag, function() {
it("creates a SequelizeMeta table", function(done) {
var sequelize = this.sequelize
if (this.sequelize.options.dialect === 'sqlite') {
var options = this.sequelize.options
options.storage = __dirname + "/tmp/test.sqlite"
sequelize = new Support.Sequelize("", "", "", options)
}
prepare(function() {
sequelize.getQueryInterface().showAllTables().success(function(tables) {
tables = tables.sort()
expect(tables).to.have.length(2)
expect(tables[1]).to.equal("SequelizeMeta")
done()
})
}.bind(this))
})
it("creates the respective table", function(done) {
var sequelize = this.sequelize
if (this.sequelize.options.dialect === 'sqlite') {
var options = this.sequelize.options
options.storage = __dirname + "/tmp/test.sqlite"
sequelize = new Support.Sequelize("", "", "", options)
}
prepare(function() {
sequelize.getQueryInterface().showAllTables().success(function(tables) {
tables = tables.sort()
expect(tables).to.have.length(2)
expect(tables[0]).to.equal("Person")
done()
})
}.bind(this))
})
})
})
})(['--migrate', '-m'])
})
......@@ -177,6 +177,67 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
})
})
it('replaces named parameters with the passed object', function(done) {
this.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, { one: 1, two: 2 }).success(function(result) {
expect(result).to.deep.equal([{ foo: 1, bar: 2 }])
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) {
expect(result).to.deep.equal([{ foo: 1, bar: 2, baz: 1 }])
done()
})
})
it('replaces named parameters with the passed object having a null property', function(done) {
this.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, { one: 1, two: null }).success(function(result) {
expect(result).to.deep.equal([{ foo: 1, bar: null }])
done()
})
})
it('throw an exception when key is missing in the passed object', function(done) {
var self = this
expect(function() {
self.sequelize.query('select :one as foo, :two as bar, :three as baz', null, { raw: true }, { one: 1, two: 2 })
}).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g)
done()
})
it('throw an exception with the passed number', function(done) {
var self = this
expect(function() {
self.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, 2)
}).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g)
done()
})
it('throw an exception with the passed empty object', function(done) {
var self = this
expect(function() {
self.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, {})
}).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g)
done()
})
it('throw an exception with the passed string', function(done) {
var self = this
expect(function() {
self.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, 'foobar')
}).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g)
done()
})
it('throw an exception with the passed date', function(done) {
var self = this
expect(function() {
self.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, new Date())
}).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g)
done()
})
it('handles AS in conjunction with functions just fine', function(done) {
this.sequelize.query('SELECT ' + (dialect === "sqlite" ? 'date(\'now\')' : 'NOW()') + ' AS t').success(function(result) {
expect(moment(result[0].t).isValid()).to.be.true
......
......@@ -116,6 +116,18 @@ var Support = {
var sequelize = Support.createSequelizeInstance({ dialect: Support.getTestDialect() })
// For Postgres' HSTORE functionality and to properly execute it's commands we'll need this...
before(function(done) {
var dialect = Support.getTestDialect()
if (dialect !== "postgres" && dialect !== "postgres-native") {
return done()
}
sequelize.query('CREATE EXTENSION IF NOT EXISTS hstore', null, {raw: true}).success(function() {
done()
})
})
beforeEach(function(done) {
this.sequelize = sequelize
Support.clearDatabase(this.sequelize, function() {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!