Socket
Socket
Sign inDemoInstall

sequelize

Package Overview
Dependencies
Maintainers
1
Versions
623
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

sequelize - npm Package Compare versions

Comparing version 1.7.0-alpha2 to 2.0.0-alpha1

lib/dao-validator.js

5

changelog.md

@@ -0,1 +1,4 @@

# v2.0.0 (alpha1) #
- [FEATURE] async validations. [#580](https://github.com/sequelize/sequelize/pull/580). thanks to Interlock
# v1.7.0 #

@@ -11,2 +14,3 @@ - [DEPENDENCIES] Upgraded validator for IPv6 support. [#603](https://github.com/sequelize/sequelize/pull/603). thanks to durango

- [BUG] Allow overriding of default columns. [#635](https://github.com/sequelize/sequelize/pull/635). Thanks to sevastos
- [BUG] Fix where params for belongsTo [#658](https://github.com/sequelize/sequelize/pull/658). Thanks to mweibel
- [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango

@@ -22,2 +26,3 @@ - [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango

- [FEATURE] Added model wide validations. [#640](https://github.com/sequelize/sequelize/pull/640). Thanks to tremby
- [FEATURE] `findOrCreate` now returns an additional flag (`created`), that is true if a model was created, and false if it was found [#648](https://github.com/sequelize/sequelize/pull/648). janmeier

@@ -24,0 +29,0 @@ # v1.6.0 #

6

lib/associations/belongs-to.js

@@ -45,4 +45,6 @@ var Utils = require("./../utils")

if (!Utils._.isUndefined(params)) {
if (!Utils._.isUndefined(params.attributes)) {
params = Utils._.extend({where: {id:id}}, params)
if (!Utils._.isUndefined(params.where)) {
params.where = Utils._.extend({id:id}, params.where)
} else {
params.where = {id: id}
}

@@ -49,0 +51,0 @@ } else {

@@ -216,3 +216,3 @@ var Utils = require("./utils")

var optcpy = Utils._.clone(options)
optcpy.attributes = optcpy.attributes || [Utils.addTicks(this.tableName)+".*"]
optcpy.attributes = optcpy.attributes || [this.QueryInterface.quoteIdentifier(this.tableName)+".*"]

@@ -346,3 +346,3 @@ // whereCollection is used for non-primary key updates

.success(function (instance) {
emitter.emit('success', instance)
emitter.emit('success', instance, true)
})

@@ -353,3 +353,3 @@ .error( function (error) {

} else {
emitter.emit('success', instance)
emitter.emit('success', instance, false)
}

@@ -438,5 +438,8 @@ }).error(function (error) {

* @param {Object} where Options to describe the scope of the search.
* @param {Object} options Possible options are:
- limit: How many rows to delete
- truncate: If set to true, dialects that support it will use TRUNCATE instead of DELETE FROM. If a table is truncated the where and limit options are ignored
* @return {Object} A promise which fires `success`, `error`, `complete` and `sql`.
*/
DAOFactory.prototype.destroy = function(where) {
DAOFactory.prototype.destroy = function(where, options) {
if (this.options.timestamps && this.options.paranoid) {

@@ -448,3 +451,3 @@ var attr = this.options.underscored ? 'deleted_at' : 'deletedAt'

} else {
return this.QueryInterface.bulkDelete(this.tableName, where)
return this.QueryInterface.bulkDelete(this.tableName, where, options)
}

@@ -451,0 +454,0 @@ }

@@ -1,10 +0,8 @@

var Utils = require("./utils")
, Mixin = require("./associations/mixin")
, Validator = require("validator")
, DataTypes = require("./data-types")
var Utils = require("./utils")
, Mixin = require("./associations/mixin")
, DaoValidator = require("./dao-validator")
, DataTypes = require("./data-types")
module.exports = (function() {
var DAO = function(values, options, isNewRecord) {
var self = this
this.dataValues = {}

@@ -141,3 +139,3 @@ this.__options = options

text.push(this.QueryInterface.QueryGenerator.addQuotes(key) + '=>' + (typeof value === "string" ? this.QueryInterface.QueryGenerator.addQuotes(value) : value))
text.push(this.QueryInterface.quoteIdentifier(key) + '=>' + (typeof value === "string" ? this.QueryInterface.quoteIdentifier(value) : value))
}.bind(this))

@@ -154,23 +152,25 @@ values[attrName] = text.join(',')

var errors = this.validate()
return new Utils.CustomEventEmitter(function(emitter) {
this.validate().success(function(errors) {
if (!!errors) {
emitter.emit('error', errors)
} else if (this.isNewRecord) {
this
.QueryInterface
.insert(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory), values)
.proxy(emitter)
} else {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id };
if (!!errors) {
return new Utils.CustomEventEmitter(function(emitter) {
emitter.emit('error', errors)
}).run()
}
else if (this.isNewRecord) {
return this.QueryInterface.insert(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory), values)
} else {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : this.id;
if (identifier === null && this.__options.whereCollection !== null) {
identifier = this.__options.whereCollection;
}
if (identifier === null && this.__options.whereCollection !== null) {
identifier = this.__options.whereCollection;
}
var tableName = this.QueryInterface.QueryGenerator.addSchema(this.__factory)
, query = this.QueryInterface.update(this, tableName, values, identifier)
var tableName = this.QueryInterface.QueryGenerator.addSchema(this.__factory)
, query = this.QueryInterface.update(this, tableName, values, identifier)
return query
}
query.proxy(emitter)
}
}.bind(this))
}.bind(this)).run()
}

@@ -187,3 +187,3 @@

var where = [
this.QueryInterface.QueryGenerator.addQuotes(this.__factory.tableName) + '.' + this.QueryInterface.QueryGenerator.addQuotes('id')+'=?',
this.QueryInterface.quoteIdentifier(this.__factory.tableName) + '.' + this.QueryInterface.quoteIdentifier('id')+'=?',
this.id

@@ -217,73 +217,5 @@ ]

DAO.prototype.validate = function() {
var self = this
var failures = {}
// for each field and value
Utils._.each(self.values, function(value, field) {
// if field has validators
var hasAllowedNull = (self.rawAttributes[field].allowNull && self.rawAttributes[field].allowNull === true && (value === null || value === undefined));
if (self.validators.hasOwnProperty(field) && !hasAllowedNull) {
// for each validator
Utils._.each(self.validators[field], function(details, validatorType) {
var is_custom_fn = false // if true then it's a custom validation method
var fn_method = null // the validation function to call
var fn_args = [] // extra arguments to pass to validation function
var fn_msg = "" // the error message to return if validation fails
// is it a custom validator function?
if (Utils._.isFunction(details)) {
is_custom_fn = true
fn_method = Utils._.bind(details, self, value)
}
// is it a validator module function?
else {
// extra args
fn_args = details.hasOwnProperty("args") ? details.args : details
if (!Array.isArray(fn_args))
fn_args = [fn_args]
// error msg
fn_msg = details.hasOwnProperty("msg") ? details.msg : false
// check method exists
var v = Validator.check(value, fn_msg)
if (!Utils._.isFunction(v[validatorType]))
throw new Error("Invalid validator function: " + validatorType)
// bind to validator obj
fn_method = Utils._.bind(v[validatorType], v)
}
try {
fn_method.apply(null, fn_args)
} catch (err) {
err = err.message
// if we didn't provide a custom error message then augment the default one returned by the validator
if (!fn_msg && !is_custom_fn)
err += ": " + field
// each field can have multiple validation failures stored against it
if (failures.hasOwnProperty(field)) {
failures[field].push(err)
} else {
failures[field] = [err]
}
}
}) // for each validator for this field
} // if field has validator set
}) // for each field
// for each model validator for this DAO
Utils._.each(self.__options.validate, function(validator, validatorType) {
try {
validator.apply(self)
} catch (err) {
failures[validatorType] = [err.message] // TODO: data structure needs to change for 2.0
}
})
return (Utils._.isEmpty(failures) ? null : failures)
return new DaoValidator(this).validate()
}
DAO.prototype.updateAttributes = function(updates, fields) {

@@ -290,0 +222,0 @@ this.setAttributes(updates)

@@ -5,12 +5,2 @@ var Utils = require("../../utils")

var processAndEscapeValue = function(value) {
var processedValue = value
if (value instanceof Date) {
processedValue = Utils.toSqlDate(value)
} else if (typeof value === 'boolean') {
processedValue = value ? 1 : 0
}
return Utils.escape(processedValue)
}
module.exports = (function() {

@@ -34,3 +24,3 @@ var QueryGenerator = {

return QueryGenerator.addQuotes(schema + (!schemaDelimiter ? '.' : schemaDelimiter) + tableName)
return this.quoteIdentifier(schema + (!schemaDelimiter ? '.' : schemaDelimiter) + tableName, false)
},

@@ -69,10 +59,10 @@

primaryKeys.push(attr)
attrStr.push(QueryGenerator.addQuotes(attr) + " " + dataType.replace(/PRIMARY KEY/, ''))
attrStr.push(this.quoteIdentifier(attr) + " " + dataType.replace(/PRIMARY KEY/, ''))
} else if (Utils._.includes(dataType, 'REFERENCES')) {
// MySQL doesn't support inline REFERENCES declarations: move to the end
var m = dataType.match(/^(.+) (REFERENCES.*)$/)
attrStr.push(QueryGenerator.addQuotes(attr) + " " + m[1])
attrStr.push(this.quoteIdentifier(attr) + " " + m[1])
foreignKeys[attr] = m[2]
} else {
attrStr.push(QueryGenerator.addQuotes(attr) + " " + dataType)
attrStr.push(this.quoteIdentifier(attr) + " " + dataType)
}

@@ -83,3 +73,3 @@ }

var values = {
table: QueryGenerator.addQuotes(tableName),
table: this.quoteIdentifier(tableName),
attributes: attrStr.join(", "),

@@ -89,3 +79,3 @@ engine: options.engine,

}
, pkString = primaryKeys.map(function(pk) { return QueryGenerator.addQuotes(pk) }).join(", ")
, pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk) }.bind(this)).join(", ")

@@ -98,3 +88,3 @@ if (pkString.length > 0) {

if(foreignKeys.hasOwnProperty(fkey)) {
values.attributes += ", FOREIGN KEY (" + QueryGenerator.addQuotes(fkey) + ") " + foreignKeys[fkey]
values.attributes += ", FOREIGN KEY (" + this.quoteIdentifier(fkey) + ") " + foreignKeys[fkey]
}

@@ -112,3 +102,3 @@ }

return Utils._.template(query)({
table: QueryGenerator.addQuotes(tableName)
table: this.quoteIdentifier(tableName)
})

@@ -185,10 +175,10 @@ },

options = options || {}
options.table = table = Array.isArray(tableName) ? tableName.map(function(tbl){ return QueryGenerator.addQuotes(tbl) }).join(", ") : QueryGenerator.addQuotes(tableName)
options.table = table = Array.isArray(tableName) ? tableName.map(function(t) { return this.quoteIdentifier(t)}.bind(this)).join(", ") : this.quoteIdentifier(tableName)
options.attributes = options.attributes && options.attributes.map(function(attr){
if(Array.isArray(attr) && attr.length == 2) {
return [attr[0], QueryGenerator.addQuotes(attr[1])].join(' as ')
return [attr[0], this.quoteIdentifier(attr[1])].join(' as ')
} else {
return attr.indexOf(Utils.TICK_CHAR) < 0 ? QueryGenerator.addQuotes(attr) : attr
return attr.indexOf(Utils.TICK_CHAR) < 0 ? this.quoteIdentifiers(attr) : attr
}
}).join(", ")
}.bind(this)).join(", ")
options.attributes = options.attributes || '*'

@@ -201,4 +191,4 @@

var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) {
return "`" + include.as + "`.`" + attr + "` AS `" + include.as + "." + attr + "`"
})
return this.quoteIdentifier(include.as) + "." + this.quoteIdentifier(attr) + " AS " + this.quoteIdentifier(include.as + "." + attr)
}.bind(this))

@@ -213,5 +203,5 @@ optAttributes = optAttributes.concat(attributes)

var attrRight = include.association.identifier
joinQuery += " LEFT OUTER JOIN `" + table + "` AS `" + as + "` ON `" + tableLeft + "`.`" + attrLeft + "` = `" + tableRight + "`.`" + attrRight + "`"
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight)
})
}.bind(this))

@@ -230,3 +220,3 @@ options.attributes = optAttributes.join(', ')

if (options.group) {
options.group = Array.isArray(options.group) ? options.group.map(function(grp){return QueryGenerator.addQuotes(grp)}).join(', ') : QueryGenerator.addQuotes(options.group)
options.group = Array.isArray(options.group) ? options.group.map(function(t) { return this.quoteIdentifiers(t)}.bind(this)).join(', ') : this.quoteIdentifiers(options.group)
query += " GROUP BY " + options.group

@@ -256,5 +246,5 @@ }

var table = QueryGenerator.addQuotes(tableName)
var attributes = Object.keys(attrValueHash).map(function(attr){return QueryGenerator.addQuotes(attr)}).join(",")
var values = Utils._.values(attrValueHash).map(processAndEscapeValue).join(",")
var table = this.quoteIdentifier(tableName)
var attributes = Object.keys(attrValueHash).map(function(attr){return this.quoteIdentifier(attr)}.bind(this)).join(",")
var values = Utils._.values(attrValueHash).map(function(v) { return this.escape(v) }.bind(this)).join(",")

@@ -271,8 +261,8 @@ var query = "INSERT INTO " + table + " (" + attributes + ") VALUES (" + values + ");"

tuples.push("(" +
Utils._.values(attrValueHash).map(processAndEscapeValue).join(",") +
Utils._.values(attrValueHash).map(function(v) { return this.escape(v) }.bind(this)).join(",") +
")")
})
}.bind(this))
var table = QueryGenerator.addQuotes(tableName)
var attributes = Object.keys(attrValueHashes[0]).map(function(attr){return QueryGenerator.addQuotes(attr)}).join(",")
var table = this.quoteIdentifier(tableName)
var attributes = Object.keys(attrValueHashes[0]).map(function(attr){return this.quoteIdentifier(attr)}.bind(this)).join(",")

@@ -291,10 +281,10 @@ var query = "INSERT INTO " + table + " (" + attributes + ") VALUES " + tuples.join(",") + ";"

var value = attrValueHash[key]
, _value = processAndEscapeValue(value)
, _value = this.escape(value)
values.push(QueryGenerator.addQuotes(key) + "=" + _value)
values.push(this.quoteIdentifier(key) + "=" + _value)
}
var query = "UPDATE " + QueryGenerator.addQuotes(tableName) +
var query = "UPDATE " + this.quoteIdentifier(tableName) +
" SET " + values.join(",") +
" WHERE " + QueryGenerator.getWhereConditions(where)
" WHERE " + this.getWhereConditions(where)

@@ -307,4 +297,9 @@ return query

var table = QueryGenerator.addQuotes(tableName)
where = QueryGenerator.getWhereConditions(where)
var table = this.quoteIdentifier(tableName)
if (options.truncate === true) {
// Truncate does not allow LIMIT and WHERE
return "TRUNCATE " + table
}
where = this.getWhereConditions(where)
var limit = ""

@@ -317,8 +312,6 @@

if(!!options.limit) {
limit = " LIMIT " + Utils.escape(options.limit)
limit = " LIMIT " + this.escape(options.limit)
}
var query = "DELETE FROM " + table + " WHERE " + where + limit
return query
return "DELETE FROM " + table + " WHERE " + where + limit
},

@@ -329,4 +322,4 @@

var table = QueryGenerator.addQuotes(tableName)
where = QueryGenerator.getWhereConditions(where)
var table = this.quoteIdentifier(tableName)
where = this.getWhereConditions(where)

@@ -345,10 +338,10 @@ var query = "DELETE FROM " + table + " WHERE " + where

var value = attrValueHash[key]
, _value = processAndEscapeValue(value)
, _value = this.escape(value)
values.push(QueryGenerator.addQuotes(key) + "=" + QueryGenerator.addQuotes(key) + " + " + _value)
values.push(this.quoteIdentifier(key) + "=" + this.quoteIdentifier(key) + " + " + _value)
}
var table = QueryGenerator.addQuotes(tableName)
var table = this.quoteIdentifier(tableName)
values = values.join(",")
where = QueryGenerator.getWhereConditions(where)
where = this.getWhereConditions(where)

@@ -383,7 +376,7 @@ var query = "UPDATE " + table + " SET " + values + " WHERE " + where

}
})
}.bind(this))
var onlyAttributeNames = attributes.map(function(attribute) {
return (typeof attribute === 'string') ? attribute : attribute.attribute
})
}.bind(this))

@@ -448,3 +441,3 @@ options = Utils._.extend({

//handle qualified key names
var _key = key.split('.').map(function(col){return QueryGenerator.addQuotes(col)}).join(".")
var _key = this.quoteIdentifiers(key)
, _value = null

@@ -455,3 +448,3 @@

if (value.length === 0) { value = [null] }
_value = "(" + value.map(processAndEscapeValue).join(',') + ")"
_value = "(" + value.map(function(v) { return this.escape(v) }.bind(this)).join(',') + ")"

@@ -463,6 +456,6 @@ result.push([_key, _value].join(" IN "))

//using as sentinel for join column => value
_value = value.join.split('.').map(function(col){ return QueryGenerator.addQuotes(col) }).join(".")
_value = this.quoteIdentifiers(value.join)
result.push([_key, _value].join("="))
} else {
_value = processAndEscapeValue(value)
_value = this.escape(value)
result.push((_value == 'NULL') ? _key + " IS NULL" : [_key, _value].join("="))

@@ -487,4 +480,4 @@ }

template = "ENUM(" + Utils._.map(dataType.values, function(value) {
return Utils.escape(value)
}).join(", ") + ")"
return this.escape(value)
}.bind(this)).join(", ") + ")"
} else {

@@ -506,3 +499,3 @@ throw new Error('Values for ENUM haven\'t been defined.')

if ((dataType.defaultValue !== undefined) && (dataType.defaultValue != DataTypes.NOW)) {
template += " DEFAULT " + Utils.escape(dataType.defaultValue)
template += " DEFAULT " + this.escape(dataType.defaultValue)
}

@@ -519,9 +512,9 @@

if(dataType.references) {
template += " REFERENCES " + Utils.addTicks(dataType.references)
template += " REFERENCES " + this.quoteIdentifier(dataType.references)
if(dataType.referencesKey) {
template += " (" + Utils.addTicks(dataType.referencesKey) + ")"
template += " (" + this.quoteIdentifier(dataType.referencesKey) + ")"
} else {
template += " (" + Utils.addTicks('id') + ")"
template += " (" + this.quoteIdentifier('id') + ")"
}

@@ -574,9 +567,19 @@

addQuotes: function(s, quoteChar) {
return Utils.addTicks(s, quoteChar)
quoteIdentifier: function(identifier, force) {
return Utils.addTicks(identifier, "`")
},
removeQuotes: function(s, quoteChar) {
return Utils.removeTicks(s, quoteChar)
quoteIdentifiers: function(identifiers, force) {
return identifiers.split('.').map(function(v) { return this.quoteIdentifier(v, force) }.bind(this)).join('.')
},
escape: function(value) {
if (value instanceof Date) {
value = Utils.toSqlDate(value)
} else if (typeof value === 'boolean') {
value = value ? 1 : 0
}
return Utils.escape(value)
}
}

@@ -583,0 +586,0 @@

@@ -27,3 +27,3 @@ var Utils = require("../../utils")

return QueryGenerator.addQuotes(schema) + '.' + QueryGenerator.addQuotes(tableName)
return this.quoteIdentifier(schema) + '.' + this.quoteIdentifier(tableName)
},

@@ -56,7 +56,7 @@

for (var attr in attributes) {
var dataType = QueryGenerator.pgDataTypeMapping(tableName, attr, attributes[attr])
attrStr.push(QueryGenerator.addQuotes(attr) + " " + dataType)
var dataType = this.pgDataTypeMapping(tableName, attr, attributes[attr])
attrStr.push(this.quoteIdentifier(attr) + " " + dataType)
if (attributes[attr].match(/^ENUM\(/)) {
query = QueryGenerator.pgEnum(tableName, attr, attributes[attr]) + query
query = this.pgEnum(tableName, attr, attributes[attr]) + query
}

@@ -66,3 +66,3 @@ }

var values = {
table: QueryGenerator.addQuotes(tableName),
table: this.quoteIdentifiers(tableName),
attributes: attrStr.join(", ")

@@ -72,4 +72,4 @@ }

var pks = primaryKeys[tableName].map(function(pk){
return QueryGenerator.addQuotes(pk)
}).join(",")
return this.quoteIdentifier(pk)
}.bind(this)).join(",")

@@ -87,3 +87,3 @@ if (pks.length > 0) {

return Utils._.template(query)({
table: QueryGenerator.addQuotes(tableName),
table: this.quoteIdentifiers(tableName),
cascade: options.cascade? " CASCADE" : ""

@@ -96,4 +96,4 @@ })

return Utils._.template(query)({
before: QueryGenerator.addQuotes(before),
after: QueryGenerator.addQuotes(after)
before: this.quoteIdentifier(before),
after: this.quoteIdentifier(after)
})

@@ -107,5 +107,5 @@ },

describeTableQuery: function(tableName) {
var query = 'SELECT column_name as "Field", column_default as "Default", is_nullable as "Null", data_type as "Type" FROM information_schema.columns WHERE table_name = <%= table %>;'
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 %>;'
return Utils._.template(query)({
table: QueryGenerator.addQuotes(tableName, "'")
table: this.escape(tableName)
})

@@ -122,8 +122,8 @@ },

attrString.push(Utils._.template('<%= attrName %> <%= definition %>')({
attrName: QueryGenerator.addQuotes(attrName),
definition: QueryGenerator.pgDataTypeMapping(tableName, attrName, definition)
attrName: this.quoteIdentifier(attrName),
definition: this.pgDataTypeMapping(tableName, attrName, definition)
}))
if (definition.match(/^ENUM\(/)) {
query = QueryGenerator.pgEnum(tableName, attrName, definition) + query
query = this.pgEnum(tableName, attrName, definition) + query
}

@@ -133,3 +133,3 @@ }

return Utils._.template(query)({
tableName: QueryGenerator.addQuotes(tableName),
tableName: this.quoteIdentifiers(tableName),
attributes: attrString.join(', ') })

@@ -141,4 +141,4 @@ },

return Utils._.template(query)({
tableName: QueryGenerator.addQuotes(tableName),
attributeName: QueryGenerator.addQuotes(attributeName)
tableName: this.quoteIdentifiers(tableName),
attributeName: this.quoteIdentifier(attributeName)
})

@@ -157,4 +157,4 @@ },

attrSql += Utils._.template(query)({
tableName: QueryGenerator.addQuotes(tableName),
query: QueryGenerator.addQuotes(attributeName) + ' SET NOT NULL'
tableName: this.quoteIdentifiers(tableName),
query: this.quoteIdentifier(attributeName) + ' SET NOT NULL'
})

@@ -165,4 +165,4 @@

attrSql += Utils._.template(query)({
tableName: QueryGenerator.addQuotes(tableName),
query: QueryGenerator.addQuotes(attributeName) + ' DROP NOT NULL'
tableName: this.quoteIdentifiers(tableName),
query: this.quoteIdentifier(attributeName) + ' DROP NOT NULL'
})

@@ -173,4 +173,4 @@ }

attrSql += Utils._.template(query)({
tableName: QueryGenerator.addQuotes(tableName),
query: QueryGenerator.addQuotes(attributeName) + ' SET DEFAULT' + definition.match(/DEFAULT ([^;]+)/)[1]
tableName: this.quoteIdentifiers(tableName),
query: this.quoteIdentifier(attributeName) + ' SET DEFAULT' + definition.match(/DEFAULT ([^;]+)/)[1]
})

@@ -181,4 +181,4 @@

attrSql += Utils._.template(query)({
tableName: QueryGenerator.addQuotes(tableName),
query: QueryGenerator.addQuotes(attributeName) + ' DROP DEFAULT'
tableName: this.quoteIdentifiers(tableName),
query: this.quoteIdentifier(attributeName) + ' DROP DEFAULT'
})

@@ -188,9 +188,9 @@ }

if (definition.match(/^ENUM\(/)) {
query = QueryGenerator.pgEnum(tableName, attributeName, definition) + query
definition = definition.replace(/^ENUM\(.+\)/, Utils.escape("enum_" + tableName + "_" + attributeName))
query = this.pgEnum(tableName, attributeName, definition) + query
definition = definition.replace(/^ENUM\(.+\)/, this.quoteIdentifier("enum_" + tableName + "_" + attributeName))
}
attrSql += Utils._.template(query)({
tableName: QueryGenerator.addQuotes(tableName),
query: QueryGenerator.addQuotes(attributeName) + ' TYPE ' + definition
tableName: this.quoteIdentifiers(tableName),
query: this.quoteIdentifier(attributeName) + ' TYPE ' + definition
})

@@ -210,4 +210,4 @@

attrString.push(Utils._.template('<%= before %> TO <%= after %>')({
before: QueryGenerator.addQuotes(attrBefore),
after: QueryGenerator.addQuotes(attributeName)
before: this.quoteIdentifier(attrBefore),
after: this.quoteIdentifier(attributeName)
}))

@@ -217,3 +217,3 @@ }

return Utils._.template(query)({
tableName: QueryGenerator.addQuotes(tableName),
tableName: this.quoteIdentifiers(tableName),
attributes: attrString.join(', ')

@@ -224,28 +224,14 @@ })

selectQuery: function(tableName, options) {
var query = "SELECT <%= attributes %> FROM <%= table %>"
, table = null
var query = "SELECT <%= attributes %> FROM <%= table %>",
table = null
options = options || {}
if (Array.isArray(tableName)) {
options.table = table = tableName.map(function(t){
return QueryGenerator.addQuotes(t)
}).join(", ")
} else {
options.table = table = QueryGenerator.addQuotes(tableName)
}
options = options || {}
options.table = table = Array.isArray(tableName) ? tableName.map(function(t) { return this.quoteIdentifiers(t) }.bind(this)).join(", ") : this.quoteIdentifiers(tableName)
options.attributes = options.attributes && options.attributes.map(function(attr) {
if (Array.isArray(attr) && attr.length === 2) {
return [
attr[0],
QueryGenerator.addQuotes(QueryGenerator.removeQuotes(attr[1], '`'))
].join(' as ')
} else if (attr.indexOf('`') >= 0) {
return attr.replace(/`/g, '"')
return [attr[0], this.quoteIdentifier(attr[1])].join(' as ')
} else {
return QueryGenerator.addQuotes(attr)
return attr.indexOf('"') < 0 ? this.quoteIdentifiers(attr) : attr
}
}).join(", ")
}.bind(this)).join(", ")
options.attributes = options.attributes || '*'

@@ -258,19 +244,18 @@

var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) {
var template = Utils._.template('"<%= as %>"."<%= attr %>" AS "<%= as %>.<%= attr %>"')
return template({ as: include.as, attr: attr })
})
return this.quoteIdentifier(include.as) + "." + this.quoteIdentifier(attr) + " AS " + this.quoteIdentifier(include.as + "." + attr, true)
}.bind(this))
optAttributes = optAttributes.concat(attributes)
var joinQuery = ' LEFT OUTER JOIN "<%= table %>" AS "<%= as %>" ON "<%= tableLeft %>"."<%= attrLeft %>" = "<%= tableRight %>"."<%= attrRight %>"'
var joinQuery = ' LEFT OUTER JOIN <%= table %> AS <%= as %> ON <%= tableLeft %>.<%= attrLeft %> = <%= tableRight %>.<%= attrRight %>'
query += Utils._.template(joinQuery)({
table: include.daoFactory.tableName,
as: include.as,
tableLeft: ((include.association.associationType === 'BelongsTo') ? include.as : tableName),
attrLeft: 'id',
tableRight: ((include.association.associationType === 'BelongsTo') ? tableName : include.as),
attrRight: include.association.identifier
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)
})
})
}.bind(this))

@@ -281,3 +266,3 @@ options.attributes = optAttributes.join(', ')

if(options.hasOwnProperty('where')) {
options.where = QueryGenerator.getWhereConditions(options.where, tableName)
options.where = this.getWhereConditions(options.where, tableName)
query += " WHERE <%= where %>"

@@ -287,10 +272,3 @@ }

if(options.group) {
if (Array.isArray(options.group)) {
options.group = options.group.map(function(grp){
return QueryGenerator.addQuotes(grp)
}).join(', ')
} else {
options.group = QueryGenerator.addQuotes(options.group)
}
options.group = Array.isArray(options.group) ? options.group.map(function(t) { return this.quoteIdentifiers(t) }.bind(this)).join(', ') : this.quoteIdentifiers(options.group)
query += " GROUP BY <%= group %>"

@@ -301,4 +279,4 @@ }

options.order = options.order.replace(/([^ ]+)(.*)/, function(m, g1, g2) {
return QueryGenerator.addQuotes(g1) + g2
})
return this.quoteIdentifiers(g1) + g2
}.bind(this))
query += " ORDER BY <%= order %>"

@@ -329,9 +307,9 @@ }

var replacements = {
table: QueryGenerator.addQuotes(tableName)
table: this.quoteIdentifiers(tableName)
, attributes: Object.keys(attrValueHash).map(function(attr){
return QueryGenerator.addQuotes(attr)
}).join(",")
return this.quoteIdentifier(attr)
}.bind(this)).join(",")
, values: Utils._.values(attrValueHash).map(function(value){
return QueryGenerator.pgEscape(value)
}).join(",")
return this.escape(value)
}.bind(this)).join(",")
}

@@ -350,12 +328,12 @@

Utils._.values(attrValueHash).map(function(value){
return QueryGenerator.pgEscape(value)
}).join(",") +
return this.escape(value)
}.bind(this)).join(",") +
")")
})
}.bind(this))
var replacements = {
table: QueryGenerator.addQuotes(tableName)
table: this.quoteIdentifiers(tableName)
, attributes: Object.keys(attrValueHashes[0]).map(function(attr){
return QueryGenerator.addQuotes(attr)
}).join(",")
return this.quoteIdentifier(attr)
}.bind(this)).join(",")
, tuples: tuples.join(",")

@@ -375,9 +353,9 @@ }

var value = attrValueHash[key]
values.push(QueryGenerator.addQuotes(key) + "=" + QueryGenerator.pgEscape(value))
values.push(this.quoteIdentifier(key) + "=" + this.escape(value))
}
var replacements = {
table: QueryGenerator.addQuotes(tableName),
table: this.quoteIdentifiers(tableName),
values: values.join(","),
where: QueryGenerator.getWhereConditions(where)
where: this.getWhereConditions(where)
}

@@ -391,2 +369,6 @@

if (options.truncate === true) {
return "TRUNCATE " + QueryGenerator.quoteIdentifier(tableName)
}
if(Utils._.isUndefined(options.limit)) {

@@ -403,12 +385,12 @@ options.limit = 1;

pks = primaryKeys[tableName].map(function(pk) {
return QueryGenerator.addQuotes(pk)
}).join(',')
return this.quoteIdentifier(pk)
}.bind(this)).join(',')
} else {
pks = QueryGenerator.addQuotes('id')
pks = this.quoteIdentifier('id')
}
var replacements = {
table: QueryGenerator.addQuotes(tableName),
where: QueryGenerator.getWhereConditions(where),
limit: !!options.limit? " LIMIT " + QueryGenerator.pgEscape(options.limit) : "",
table: this.quoteIdentifiers(tableName),
where: this.getWhereConditions(where),
limit: !!options.limit? " LIMIT " + this.escape(options.limit) : "",
primaryKeys: primaryKeys[tableName].length > 1 ? '(' + pks + ')' : pks,

@@ -429,9 +411,9 @@ primaryKeysSelection: pks

var value = attrValueHash[key]
values.push(QueryGenerator.addQuotes(key) + "=" + QueryGenerator.addQuotes(key) + " + " + QueryGenerator.pgEscape(value))
values.push(this.quoteIdentifier(key) + "=" + this.quoteIdentifier(key) + " + " + this.escape(value))
}
var replacements = {
table: QueryGenerator.addQuotes(tableName),
table: this.quoteIdentifiers(tableName),
values: values.join(","),
where: QueryGenerator.getWhereConditions(where)
where: this.getWhereConditions(where)
}

@@ -446,3 +428,3 @@

if (typeof attribute === 'string') {
return QueryGenerator.addQuotes(attribute)
return this.quoteIdentifier(attribute)
} else {

@@ -455,3 +437,3 @@ var result = ""

result += QueryGenerator.addQuotes(attribute.attribute)
result += this.quoteIdentifier(attribute.attribute)

@@ -468,7 +450,7 @@ if (attribute.length) {

}
})
}.bind(this))
var onlyAttributeNames = attributes.map(function(attribute) {
return (typeof attribute === "string") ? attribute : attribute.attribute
})
}.bind(this))

@@ -483,5 +465,5 @@ var indexTable = tableName.split('.')

return Utils._.compact([
"CREATE", options.indicesType, "INDEX", QueryGenerator.addQuotes(options.indexName),
"CREATE", options.indicesType, "INDEX", this.quoteIdentifiers(options.indexName),
(options.indexType ? ('USING ' + options.indexType) : undefined),
"ON", QueryGenerator.addQuotes(tableName), '(' + transformedAttributes.join(', ') + ')'
"ON", this.quoteIdentifiers(tableName), '(' + transformedAttributes.join(', ') + ')'
]).join(' ')

@@ -504,4 +486,4 @@ },

return Utils._.template(sql)({
tableName: QueryGenerator.addQuotes(tableName),
indexName: QueryGenerator.addQuotes(indexName)
tableName: this.quoteIdentifiers(tableName),
indexName: this.quoteIdentifiers(indexName)
})

@@ -515,7 +497,7 @@ },

smth = Utils.prependTableNameToHash(tableName, smth)
result = QueryGenerator.hashToWhereConditions(smth)
result = this.hashToWhereConditions(smth)
}
else if (typeof smth === "number") {
smth = Utils.prependTableNameToHash(tableName, { id: smth })
result = QueryGenerator.hashToWhereConditions(smth)
result = this.hashToWhereConditions(smth)
}

@@ -539,10 +521,8 @@ else if (typeof smth === "string") {

//handle qualified key names
var _key = key.split('.').map(function(col){return QueryGenerator.addQuotes(col)}).join(".")
var _key = this.quoteIdentifiers(key)
, _value = null
if (Array.isArray(value)) {
if (value.length == 0) { value = [null] }
_value = "(" + value.map(function(subValue) {
return QueryGenerator.pgEscape(subValue);
}).join(',') + ")"
if (value.length === 0) { value = [null] }
_value = "(" + value.map(this.escape).join(',') + ")"

@@ -553,6 +533,6 @@ result.push([_key, _value].join(" IN "))

//using as sentinel for join column => value
_value = value.join.split('.').map(function(col){return QueryGenerator.addQuotes(col)}).join(".")
_value = this.quoteIdentifiers(value.join)
result.push([_key, _value].join("="))
} else {
_value = QueryGenerator.pgEscape(value)
_value = this.escape(value)
result.push((_value == 'NULL') ? _key + " IS NULL" : [_key, _value].join("="))

@@ -578,4 +558,4 @@ }

replacements.type = "ENUM(" + Utils._.map(dataType.values, function(value) {
return Utils.escape(value)
}).join(", ") + ")"
return this.escape(value)
}.bind(this)).join(", ") + ")"
} else {

@@ -604,3 +584,3 @@ throw new Error('Values for ENUM haven\'t been defined.')

template += " DEFAULT <%= defaultValue %>"
replacements.defaultValue = QueryGenerator.pgEscape(dataType.defaultValue)
replacements.defaultValue = this.escape(dataType.defaultValue)
}

@@ -618,8 +598,8 @@

template += " REFERENCES <%= referencesTable %> (<%= referencesKey %>)"
replacements.referencesTable = QueryGenerator.addQuotes(dataType.references)
replacements.referencesTable = this.quoteIdentifier(dataType.references)
if(dataType.referencesKey) {
replacements.referencesKey = QueryGenerator.addQuotes(dataType.referencesKey)
replacements.referencesKey = this.quoteIdentifier(dataType.referencesKey)
} else {
replacements.referencesKey = QueryGenerator.addQuotes('id')
replacements.referencesKey = this.quoteIdentifier('id')
}

@@ -683,47 +663,26 @@

removeQuotes: function (s, quoteChar) {
quoteChar = quoteChar || '"'
return s.replace(new RegExp(quoteChar, 'g'), '')
pgEscapeAndQuote: function (val) {
return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'"))
},
addQuotes: function (s, quoteChar) {
quoteChar = quoteChar || '"'
return QueryGenerator.removeQuotes(s, quoteChar)
.split('.')
.map(function(e) { return quoteChar + String(e) + quoteChar })
.join('.')
pgEnum: function (tableName, attr, dataType) {
var enumName = this.pgEscapeAndQuote("enum_" + tableName + "_" + attr)
return "DROP TYPE IF EXISTS " + enumName + "; CREATE TYPE " + enumName + " AS " + dataType.match(/^ENUM\(.+\)/)[0] + "; "
},
pgEscape: function (val) {
if (val === undefined || val === null) {
return 'NULL';
}
fromArray: function(text) {
text = text.replace(/^{/, '').replace(/}$/, '')
var matches = text.match(/("(?:\\.|[^"\\\\])*"|[^,]*)(?:\s*,\s*|\s*$)/ig)
switch (typeof val) {
case 'boolean': return (val) ? 'true' : 'false';
case 'number': return val+'';
case 'object':
if (Array.isArray(val)) {
return 'ARRAY['+ val.map(function(it) { return QueryGenerator.pgEscape(it) }).join(',') +']';
}
if (matches.length < 1) {
return []
}
if (val instanceof Date) {
val = QueryGenerator.pgSqlDate(val);
}
matches = matches.map(function(m){
return m.replace(/",$/, '').replace(/,$/, '').replace(/(^"|"$)/, '')
})
// http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
val = val.replace(/'/g, "''");
return "'"+val+"'";
return matches.slice(0, -1)
},
pgEscapeAndQuote: function (val) {
return QueryGenerator.addQuotes(QueryGenerator.removeQuotes(QueryGenerator.pgEscape(val), "'"))
},
pgEnum: function (tableName, attr, dataType) {
var enumName = QueryGenerator.pgEscapeAndQuote("enum_" + tableName + "_" + attr)
return "DROP TYPE IF EXISTS " + enumName + "; CREATE TYPE " + enumName + " AS " + dataType.match(/^ENUM\(.+\)/)[0] + "; "
},
toHstore: function(text) {

@@ -749,4 +708,4 @@ var obj = {}

pgSqlDate: function (dt) {
var date = [ dt.getUTCFullYear(), QueryGenerator.padInt(dt.getUTCMonth()+1), QueryGenerator.padInt(dt.getUTCDate()) ].join('-')
var time = [ dt.getUTCHours(), QueryGenerator.padInt(dt.getUTCMinutes()), QueryGenerator.padInt(dt.getUTCSeconds())].join(':')
var date = [ dt.getUTCFullYear(), this.padInt(dt.getUTCMonth()+1), this.padInt(dt.getUTCDate()) ].join('-')
var time = [ dt.getUTCHours(), this.padInt(dt.getUTCMinutes()), this.padInt(dt.getUTCSeconds())].join(':')
return date + ' ' + time + '.' + ((dt.getTime() % 1000) * 1000) + 'Z'

@@ -782,7 +741,50 @@ },

if (dataType.match(/^ENUM\(/)) {
dataType = dataType.replace(/^ENUM\(.+\)/, QueryGenerator.pgEscapeAndQuote("enum_" + tableName + "_" + attr))
dataType = dataType.replace(/^ENUM\(.+\)/, this.pgEscapeAndQuote("enum_" + tableName + "_" + attr))
}
return dataType
},
quoteIdentifier: function(identifier, force) {
if(!force && this.options && this.options.quoteIdentifiers === false) { // default is `true`
// In Postgres, if tables or attributes are created double-quoted,
// they are also case sensitive. If they contain any uppercase
// characters, they must always be double-quoted. This makes it
// impossible to write queries in portable SQL if tables are created in
// this way. Hence, we strip quotes if we don't want case sensitivity.
return Utils.removeTicks(identifier, '"')
} else {
return Utils.addTicks(identifier, '"')
}
},
quoteIdentifiers: function(identifiers, force) {
return identifiers.split('.').map(function(t) { return this.quoteIdentifier(t, force) }.bind(this)).join('.')
},
escape: function (val) {
if (val === undefined || val === null) {
return 'NULL';
}
switch (typeof val) {
case 'boolean':
return (val) ? 'true' : 'false';
case 'number':
return val + '';
case 'object':
if (Array.isArray(val)) {
return 'ARRAY['+ val.map(function(it) { return this.escape(it) }.bind(this)).join(',') + ']';
}
}
if (val instanceof Date) {
val = this.pgSqlDate(val);
}
// http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
val = val.replace(/'/g, "''");
return "'" + val + "'";
}
}

@@ -789,0 +791,0 @@

@@ -59,2 +59,3 @@ var Utils = require("../../utils")

var results = []
, self = this
, isTableNameQuery = (this.sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)

@@ -78,3 +79,3 @@ , isRelNameQuery = (this.sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0)

if (this.send('isSelectQuery')) {
if (this.sql.toLowerCase().indexOf('select column_name') === 0) {
if (this.sql.toLowerCase().indexOf('select c.column_name') === 0) {
var result = {}

@@ -86,3 +87,4 @@

allowNull: (_result.Null === 'YES'),
defaultValue: _result.Default
defaultValue: _result.Default,
special: (!!_result.special ? self.sequelize.queryInterface.QueryGenerator.fromArray(_result.special) : [])
}

@@ -102,3 +104,6 @@

if (result[_result.Field].defaultValue.indexOf('::') > -1) {
result[_result.Field].defaultValue = result[_result.Field].defaultValue.split('::')[0]
var split = result[_result.Field].defaultValue.split('::');
if (split[1].toLowerCase() !== "regclass)") {
result[_result.Field].defaultValue = split[0]
}
}

@@ -110,2 +115,16 @@ }

} else {
// Postgres will treat tables as case-insensitive, so fix the case
// of the returned values to match attributes
if(this.sequelize.options.quoteIdentifiers == false) {
var attrsMap = Utils._.reduce(this.callee.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m}, {})
rows.forEach(function(row) {
Utils._.keys(row).forEach(function(key) {
var targetAttr = attrsMap[key]
if(targetAttr != key) {
row[targetAttr] = row[key]
delete row[key]
}
})
})
}
this.emit('success', this.send('handleSelectQuery', rows))

@@ -112,0 +131,0 @@ }

@@ -153,2 +153,7 @@ module.exports = (function() {

- limit -> Maximaum count of lines to delete
- truncate -> boolean - whether to use an 'optimized' mechanism (i.e. TRUNCATE) if available,
note that this should not be the default behaviour because TRUNCATE does not
always play nicely (e.g. InnoDB tables with FK constraints)
(@see http://dev.mysql.com/doc/refman/5.6/en/truncate-table.html).
Note that truncate must ignore limit and where
*/

@@ -267,3 +272,24 @@ deleteQuery: function(tableName, where, options) {

disableForeignKeyConstraintsQuery: function() {
throwMethodUndefined('disableForeignKeyConstraintsQuery')
throwMethodUndefined('disableForeignKeyConstraintsQuery')
},
/*
Escape an identifier (e.g. a table or attribute name)
*/
quoteIdentifier: function(identifier, force) {
throwMethodUndefined('quoteIdentifier')
},
/*
Split an identifier into .-separated tokens and quote each part
*/
quoteIdentifiers: function(identifiers, force) {
throwMethodUndefined('quoteIdentifiers')
},
/*
Escape a value (e.g. a string, number or date)
*/
escape: function(value) {
throwMethodUndefined('quoteIdentifier')
}

@@ -270,0 +296,0 @@

@@ -11,14 +11,2 @@ var Utils = require("../../utils")

var escape = function(str) {
if (typeof str === 'string') {
return "'" + str.replace(/'/g, "''") + "'";
} else if (typeof str === 'boolean') {
return str ? 1 : 0; // SQLite has no type boolean
} else if (str === null || str === undefined) {
return 'NULL';
} else {
return str;
}
};
module.exports = (function() {

@@ -28,15 +16,2 @@ var QueryGenerator = {

removeQuotes: function (s, quoteChar) {
quoteChar = quoteChar || '`'
return s.replace(new RegExp(quoteChar, 'g'), '')
},
addQuotes: function (s, quoteChar) {
quoteChar = quoteChar || '`'
return QueryGenerator.removeQuotes(s, quoteChar)
.split('.')
.map(function(e) { return quoteChar + String(e) + quoteChar })
.join('.')
},
addSchema: function(opts) {

@@ -58,3 +33,3 @@ var tableName = undefined

return QueryGenerator.addQuotes(schema + (!schemaPrefix ? '.' : schemaPrefix) + tableName)
return this.quoteIdentifier(schema) + (!schemaPrefix ? '.' : schemaPrefix) + this.quoteIdentifier(tableName)
},

@@ -97,5 +72,5 @@

primaryKeys.push(attr)
attrStr.push(Utils.addTicks(attr) + " " + dataType.replace(/PRIMARY KEY/, 'NOT NULL'))
attrStr.push(this.quoteIdentifier(attr) + " " + dataType.replace(/PRIMARY KEY/, 'NOT NULL'))
} else {
attrStr.push(Utils.addTicks(attr) + " " + dataType)
attrStr.push(this.quoteIdentifier(attr) + " " + dataType)
}

@@ -106,7 +81,7 @@ }

var values = {
table: Utils.addTicks(tableName),
table: this.quoteIdentifier(tableName),
attributes: attrStr.join(", "),
charset: (options.charset ? "DEFAULT CHARSET=" + options.charset : "")
}
, pkString = primaryKeys.map(function(pk) { return Utils.addTicks(pk) }).join(", ")
, pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk) }.bind(this)).join(", ")

@@ -118,8 +93,8 @@ if (pkString.length > 0) {

var sql = Utils._.template(query, values).trim() + ";"
return QueryGenerator.replaceBooleanDefaults(sql)
return this.replaceBooleanDefaults(sql)
},
addColumnQuery: function() {
var sql = MySqlQueryGenerator.addColumnQuery.apply(null, arguments)
return QueryGenerator.replaceBooleanDefaults(sql)
var sql = MySqlQueryGenerator.addColumnQuery.apply(this, arguments)
return this.replaceBooleanDefaults(sql)
},

@@ -137,7 +112,7 @@

var replacements = {
table: Utils.addTicks(tableName),
attributes: Object.keys(attrValueHash).map(function(attr){return Utils.addTicks(attr)}).join(","),
table: this.quoteIdentifier(tableName),
attributes: Object.keys(attrValueHash).map(function(attr){return this.quoteIdentifier(attr)}.bind(this)).join(","),
values: Utils._.values(attrValueHash).map(function(value){
return escape((value instanceof Date) ? Utils.toSqlDate(value) : value)
}).join(",")
return this.escape(value)
}.bind(this)).join(",")
}

@@ -155,10 +130,10 @@

Utils._.values(attrValueHash).map(function(value){
return escape((value instanceof Date) ? Utils.toSqlDate(value) : value)
}).join(",") +
return this.escape(value)
}.bind(this)).join(",") +
")")
})
}.bind(this))
var replacements = {
table: Utils.addTicks(tableName),
attributes: Object.keys(attrValueHashes[0]).map(function(attr){return Utils.addTicks(attr)}).join(","),
table: this.quoteIdentifier(tableName),
attributes: Object.keys(attrValueHashes[0]).map(function(attr){return this.quoteIdentifier(attr)}.bind(this)).join(","),
tuples: tuples

@@ -174,10 +149,10 @@ }

options = options || {}
options.table = table = Array.isArray(tableName) ? tableName.map(function(tbl){ return QueryGenerator.addQuotes(tbl) }).join(", ") : QueryGenerator.addQuotes(tableName)
options.table = table = Array.isArray(tableName) ? tableName.map(function(t) { return this.quoteIdentifier(t)}.bind(this)).join(", ") : this.quoteIdentifier(tableName)
options.attributes = options.attributes && options.attributes.map(function(attr){
if(Array.isArray(attr) && attr.length == 2) {
return [attr[0], QueryGenerator.addQuotes(attr[1])].join(' as ')
return [attr[0], this.quoteIdentifier(attr[1])].join(' as ')
} else {
return attr.indexOf(Utils.TICK_CHAR) < 0 ? QueryGenerator.addQuotes(attr) : attr
return attr.indexOf(Utils.TICK_CHAR) < 0 ? this.quoteIdentifiers(attr) : attr
}
}).join(", ")
}.bind(this)).join(", ")
options.attributes = options.attributes || '*'

@@ -190,4 +165,4 @@

var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) {
return "`" + include.as + "`.`" + attr + "` AS `" + include.as + "." + attr + "`"
})
return this.quoteIdentifier(include.as) + "." + this.quoteIdentifier(attr) + " AS " + this.quoteIdentifier(include.as + "." + attr)
}.bind(this))

@@ -202,5 +177,5 @@ optAttributes = optAttributes.concat(attributes)

var attrRight = include.association.identifier
joinQuery += " LEFT OUTER JOIN `" + table + "` AS `" + as + "` ON `" + tableLeft + "`.`" + attrLeft + "` = `" + tableRight + "`.`" + attrRight + "`"
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight) + ""
})
}.bind(this))

@@ -219,3 +194,3 @@ options.attributes = optAttributes.join(', ')

if (options.group) {
options.group = Array.isArray(options.group) ? options.group.map(function(grp){return QueryGenerator.addQuotes(grp)}).join(', ') : QueryGenerator.addQuotes(options.group)
options.group = Array.isArray(options.group) ? options.group.map(function(t) { return this.quoteIdentifiers(t)}.bind(this)).join(', ') : qa(options.group)
query += " GROUP BY " + options.group

@@ -250,9 +225,9 @@ }

var value = attrValueHash[key]
values.push(Utils.addTicks(key) + "=" + escape((value instanceof Date) ? Utils.toSqlDate(value) : value))
values.push(this.quoteIdentifier(key) + "=" + this.escape(value))
}
var replacements = {
table: Utils.addTicks(tableName),
table: this.quoteIdentifier(tableName),
values: values.join(","),
where: MySqlQueryGenerator.getWhereConditions(where)
where: this.getWhereConditions(where)
}

@@ -268,4 +243,4 @@

var replacements = {
table: Utils.addTicks(tableName),
where: MySqlQueryGenerator.getWhereConditions(where)
table: this.quoteIdentifier(tableName),
where: this.getWhereConditions(where)
}

@@ -284,9 +259,9 @@

var value = attrValueHash[key]
values.push(Utils.addTicks(key) + "=" + Utils.addTicks(key) + "+ " + escape((value instanceof Date) ? Utils.toSqlDate(value) : value))
values.push(this.quoteIdentifier(key) + "=" + this.quoteIdentifier(key) + "+ " + this.escape(value))
}
var replacements = {
table: Utils.addTicks(tableName),
table: this.quoteIdentifier(tableName),
values: values.join(","),
where: MySqlQueryGenerator.getWhereConditions(where)
where: this.getWhereConditions(where)
}

@@ -321,3 +296,3 @@

template += " DEFAULT <%= defaultValue %>"
replacements.defaultValue = Utils.escape(dataType.defaultValue)
replacements.defaultValue = this.escape(dataType.defaultValue)
}

@@ -339,8 +314,8 @@

template += " REFERENCES <%= referencesTable %> (<%= referencesKey %>)"
replacements.referencesTable = Utils.addTicks(dataType.references)
replacements.referencesTable = this.quoteIdentifier(dataType.references)
if(dataType.referencesKey) {
replacements.referencesKey = Utils.addTicks(dataType.referencesKey)
replacements.referencesKey = this.quoteIdentifier(dataType.referencesKey)
} else {
replacements.referencesKey = Utils.addTicks('id')
replacements.referencesKey = this.quoteIdentifier('id')
}

@@ -408,3 +383,3 @@

return hashToWhereConditions(hash).replace(/\\'/g, "''");
return hashToWhereConditions.call(this, hash).replace(/\\'/g, "''");
},

@@ -439,10 +414,10 @@

removeColumnQuery: function(tableName, attributes) {
attributes = QueryGenerator.attributesToSQL(attributes)
attributes = this.attributesToSQL(attributes)
var backupTableName = tableName + "_backup"
var query = [
QueryGenerator.createTableQuery(backupTableName, attributes).replace('CREATE TABLE', 'CREATE TEMPORARY TABLE'),
this.createTableQuery(backupTableName, attributes).replace('CREATE TABLE', 'CREATE TEMPORARY TABLE'),
"INSERT INTO <%= tableName %>_backup SELECT <%= attributeNames %> FROM <%= tableName %>;",
"DROP TABLE <%= tableName %>;",
QueryGenerator.createTableQuery(tableName, attributes),
this.createTableQuery(tableName, attributes),
"INSERT INTO <%= tableName %> SELECT <%= attributeNames %> FROM <%= tableName %>_backup;",

@@ -459,10 +434,10 @@ "DROP TABLE <%= tableName %>_backup;"

renameColumnQuery: function(tableName, attrNameBefore, attrNameAfter, attributes) {
attributes = QueryGenerator.attributesToSQL(attributes)
attributes = this.attributesToSQL(attributes)
var backupTableName = tableName + "_backup"
var query = [
QueryGenerator.createTableQuery(backupTableName, attributes).replace('CREATE TABLE', 'CREATE TEMPORARY TABLE'),
this.createTableQuery(backupTableName, attributes).replace('CREATE TABLE', 'CREATE TEMPORARY TABLE'),
"INSERT INTO <%= tableName %>_backup SELECT <%= attributeNamesImport %> FROM <%= tableName %>;",
"DROP TABLE <%= tableName %>;",
QueryGenerator.createTableQuery(tableName, attributes),
this.createTableQuery(tableName, attributes),
"INSERT INTO <%= tableName %> SELECT <%= attributeNamesExport %> FROM <%= tableName %>_backup;",

@@ -476,6 +451,6 @@ "DROP TABLE <%= tableName %>_backup;"

return (attrNameAfter === attr) ? attrNameBefore + ' AS ' + attr : attr
}).join(', '),
}.bind(this)).join(', '),
attributeNamesExport: Utils._.keys(attributes).map(function(attr) {
return attr
}).join(', ')
}.bind(this)).join(', ')
})

@@ -486,3 +461,28 @@ },

return sql.replace(/DEFAULT '?false'?/g, "DEFAULT 0").replace(/DEFAULT '?true'?/g, "DEFAULT 1")
},
quoteIdentifier: function(identifier, force) {
return Utils.addTicks(identifier, "`")
},
quoteIdentifiers: function(identifiers, force) {
return identifiers.split('.').map(function(v) { return this.quoteIdentifier(v, force) }.bind(this)).join('.')
},
escape: function(value) {
if (value instanceof Date) {
value = Utils.toSqlDate(value)
}
if (typeof value === 'string') {
return "'" + value.replace(/'/g, "''") + "'";
} else if (typeof value === 'boolean') {
return value ? 1 : 0; // SQLite has no type boolean
} else if (value === null || value === undefined) {
return 'NULL';
} else {
return value;
}
}
}

@@ -489,0 +489,0 @@

@@ -9,9 +9,9 @@ var util = require("util")

if (fct) {
if(process.domain) {
return process.domain.bind(fct);
}
if (process.domain) {
return process.domain.bind(fct)
}
}
return fct;
};
return fct
}

@@ -58,2 +58,3 @@ module.exports = (function() {

// emit the events on the foreign emitter once events got triggered for me
CustomEventEmitter.prototype.proxy = function(emitter) {

@@ -78,5 +79,3 @@ proxyEventKeys.forEach(function (eventKey) {

return CustomEventEmitter;
return CustomEventEmitter
})()

@@ -283,4 +283,4 @@ var Utils = require('./utils')

QueryInterface.prototype.bulkDelete = function(tableName, identifier) {
var sql = this.QueryGenerator.deleteQuery(tableName, identifier, {limit: null})
QueryInterface.prototype.bulkDelete = function(tableName, identifier, options) {
var sql = this.QueryGenerator.deleteQuery(tableName, identifier, Utils._.defaults(options || {}, {limit: null}))
return queryAndEmit.call(this, sql, 'bulkDelete')

@@ -362,2 +362,29 @@ }

// Helper methods useful for querying
/**
* Escape an identifier (e.g. a table or attribute name). If force is true,
* the identifier will be quoted even if the `quoteIdentifiers` option is
* false.
*/
QueryInterface.prototype.quoteIdentifier = function(identifier, force) {
return this.QueryGenerator.quoteIdentifier(identifier, force)
}
/**
* Split an identifier into .-separated tokens and quote each part.
* If force is true, the identifier will be quoted even if the
* `quoteIdentifiers` option is false.
*/
QueryInterface.prototype.quoteIdentifiers = function(identifiers, force) {
return this.QueryGenerator.quoteIdentifiers(identifiers, force)
}
/**
* Escape a value (e.g. a string, number or date)
*/
QueryInterface.prototype.escape = function(value) {
return this.QueryGenerator.escape(value)
}
// private

@@ -364,0 +391,0 @@

@@ -29,2 +29,3 @@ var url = require("url")

@param {Object} [options.pool={}] Something.
@param {Boolean} [options.quoteIdentifiers=true] Set to `false` to make table names and attributes case-insensitive on Postgres and skip double quoting of them.

@@ -82,3 +83,4 @@ @example

replication: false,
pool: {}
pool: {},
quoteIdentifiers: true
}, options || {})

@@ -85,0 +87,0 @@

@@ -38,14 +38,2 @@ var util = require("util")

},
TICK_CHAR: '`',
addTicks: function(s, tickChar) {
tickChar = tickChar || Utils.TICK_CHAR
return tickChar + Utils.removeTicks(s, tickChar) + tickChar
},
removeTicks: function(s, tickChar) {
tickChar = tickChar || Utils.TICK_CHAR
return s.replace(new RegExp(tickChar, 'g'), "")
},
escape: function(s) {
return SqlString.escape(s, true, "local").replace(/\\"/g, '"')
},
format: function(arr, dialect) {

@@ -184,2 +172,18 @@ var timeZone = null;

return now
},
// Note: Use the `quoteIdentifier()` and `escape()` methods on the
// `QueryInterface` instead for more portable code.
TICK_CHAR: '`',
addTicks: function(s, tickChar) {
tickChar = tickChar || Utils.TICK_CHAR
return tickChar + Utils.removeTicks(s, tickChar) + tickChar
},
removeTicks: function(s, tickChar) {
tickChar = tickChar || Utils.TICK_CHAR
return s.replace(new RegExp(tickChar, 'g'), "")
},
escape: function(s) {
return SqlString.escape(s, true, "local").replace(/\\"/g, '"')
}

@@ -190,2 +194,2 @@ }

Utils.QueryChainer = require("./query-chainer")
Utils.Lingo = require("lingo")
Utils.Lingo = require("lingo")
{
"name": "sequelize",
"description": "Multi dialect ORM for Node.JS",
"version": "1.7.0-alpha2",
"version": "2.0.0-alpha1",
"author": "Sascha Depold <sascha@depold.com>",

@@ -6,0 +6,0 @@ "contributors": [

@@ -11,3 +11,6 @@ var config = require("../config/config")

var setup = function() {
User = sequelize.define('User', { username: Sequelize.STRING })
User = sequelize.define('User', { username: Sequelize.STRING, enabled: {
type: Sequelize.BOOLEAN,
defaultValue: true
}})
Task = sequelize.define('Task', { title: Sequelize.STRING })

@@ -92,2 +95,25 @@ }

it('extends the id where param with the supplied where params', function() {
Task.belongsTo(User, {as: 'User'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(done)
})
})
Helpers.async(function(done) {
User.create({username: 'asd', enabled: false}).success(function(u) {
Task.create({title: 'a task'}).success(function(t) {
t.setUser(u).success(function() {
t.getUser({where: {enabled: true}}).success(function(user) {
expect(user).toEqual(null)
done()
})
})
})
})
})
})
it("handles self associations", function() {

@@ -94,0 +120,0 @@ Helpers.async(function(done) {

@@ -311,7 +311,8 @@ var config = require("./config/config")

}).success(function(user) {
var emitter = user.updateAttributes({name: 'foobar'})
emitter.success(function() {
expect(emitter.query.sql).toMatch(/WHERE [`"]identifier[`"]..identifier./)
done()
})
user
.updateAttributes({name: 'foobar'})
.on('sql', function(sql) {
expect(sql).toMatch(/WHERE [`"]identifier[`"]..identifier./)
done()
})
})

@@ -318,0 +319,0 @@ })

@@ -303,2 +303,8 @@ var config = require("../config/config")

expectation: "DELETE FROM `myTable` WHERE `id`=1 LIMIT 1"
},{
arguments: ['myTable', undefined, {truncate: true}],
expectation: "TRUNCATE `myTable`"
},{
arguments: ['myTable', 1, {limit: 10, truncate: true}],
expectation: "TRUNCATE `myTable`"
}, {

@@ -396,3 +402,4 @@ arguments: ['myTable', 1, {limit: 10}],

var context = test.context || {options: {}};
var conditions = QueryGenerator[suiteTitle].apply(context, test.arguments)
QueryGenerator.options = context.options
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)

@@ -399,0 +406,0 @@ expect(conditions).toEqual(test.expectation)

@@ -71,2 +71,30 @@ var config = require("../config/config")

},
// Variants when quoteIdentifiers is false
{
arguments: [{id: {type: 'INTEGER', references: 'Bar'}}],
expectation: {id: 'INTEGER REFERENCES Bar (id)'},
context: {options: {quoteIdentifiers: false}}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', referencesKey: 'pk'}}],
expectation: {id: 'INTEGER REFERENCES Bar (pk)'},
context: {options: {quoteIdentifiers: false}}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', onDelete: 'CASCADE'}}],
expectation: {id: 'INTEGER REFERENCES Bar (id) ON DELETE CASCADE'},
context: {options: {quoteIdentifiers: false}}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', onUpdate: 'RESTRICT'}}],
expectation: {id: 'INTEGER REFERENCES Bar (id) ON UPDATE RESTRICT'},
context: {options: {quoteIdentifiers: false}}
},
{
arguments: [{id: {type: 'INTEGER', allowNull: false, defaultValue: 1, references: 'Bar', onDelete: 'CASCADE', onUpdate: 'RESTRICT'}}],
expectation: {id: 'INTEGER NOT NULL DEFAULT 1 REFERENCES Bar (id) ON DELETE CASCADE ON UPDATE RESTRICT'},
context: {options: {quoteIdentifiers: false}}
},
],

@@ -77,3 +105,3 @@

arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255));"
expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255));",
},

@@ -95,2 +123,29 @@ {

expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255), \"otherId\" INTEGER REFERENCES \"otherTable\" (\"id\") ON DELETE CASCADE ON UPDATE NO ACTION);"
},
// Variants when quoteIdentifiers is false
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
expectation: "CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255));",
context: {options: {quoteIdentifiers: false}}
},
{
arguments: ['mySchema.myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
expectation: "CREATE TABLE IF NOT EXISTS mySchema.myTable (title VARCHAR(255), name VARCHAR(255));",
context: {options: {quoteIdentifiers: false}}
},
{
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}],
expectation: "DROP TYPE IF EXISTS enum_myTable_title; CREATE TYPE enum_myTable_title AS ENUM(\"A\", \"B\", \"C\"); CREATE TABLE IF NOT EXISTS myTable (title enum_myTable_title, name VARCHAR(255));",
context: {options: {quoteIdentifiers: false}}
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}],
expectation: "CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255), id INTEGER , PRIMARY KEY (id));",
context: {options: {quoteIdentifiers: false}}
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES otherTable (id) ON DELETE CASCADE ON UPDATE NO ACTION'}],
expectation: "CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255), otherId INTEGER REFERENCES otherTable (id) ON DELETE CASCADE ON UPDATE NO ACTION);",
context: {options: {quoteIdentifiers: false}}
}

@@ -115,2 +170,24 @@ ],

expectation: "DROP TABLE IF EXISTS \"mySchema\".\"myTable\" CASCADE;"
},
// Variants when quoteIdentifiers is false
{
arguments: ['myTable'],
expectation: "DROP TABLE IF EXISTS myTable;",
context: {options: {quoteIdentifiers: false}}
},
{
arguments: ['mySchema.myTable'],
expectation: "DROP TABLE IF EXISTS mySchema.myTable;",
context: {options: {quoteIdentifiers: false}}
},
{
arguments: ['myTable', {cascade: true}],
expectation: "DROP TABLE IF EXISTS myTable CASCADE;",
context: {options: {quoteIdentifiers: false}}
},
{
arguments: ['mySchema.myTable', {cascade: true}],
expectation: "DROP TABLE IF EXISTS mySchema.myTable CASCADE;",
context: {options: {quoteIdentifiers: false}}
}

@@ -172,2 +249,74 @@ ],

expectation: "SELECT * FROM \"mySchema\".\"myTable\" WHERE \"mySchema\".\"myTable\".\"name\"='foo'';DROP TABLE mySchema.myTable;';"
},
// Variants when quoteIdentifiers is false
{
arguments: ['myTable'],
expectation: "SELECT * FROM myTable;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {attributes: ['id', 'name']}],
expectation: "SELECT id, name FROM myTable;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {where: {id: 2}}],
expectation: "SELECT * FROM myTable WHERE myTable.id=2;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {where: {name: 'foo'}}],
expectation: "SELECT * FROM myTable WHERE myTable.name='foo';",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {where: {name: "foo';DROP TABLE myTable;"}}],
expectation: "SELECT * FROM myTable WHERE myTable.name='foo'';DROP TABLE myTable;';",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {where: 2}],
expectation: "SELECT * FROM myTable WHERE myTable.id=2;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['foo', { attributes: [['count(*)', 'count']] }],
expectation: 'SELECT count(*) as count FROM foo;',
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {where: "foo='bar'"}],
expectation: "SELECT * FROM myTable WHERE foo='bar';",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {order: "id DESC"}],
expectation: "SELECT * FROM myTable ORDER BY id DESC;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {group: "name"}],
expectation: "SELECT * FROM myTable GROUP BY name;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {group: ["name"]}],
expectation: "SELECT * FROM myTable GROUP BY name;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {group: ["name","title"]}],
expectation: "SELECT * FROM myTable GROUP BY name, title;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {limit: 10}],
expectation: "SELECT * FROM myTable LIMIT 10;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {limit: 10, offset: 2}],
expectation: "SELECT * FROM myTable LIMIT 10 OFFSET 2;",
context: {options: {quoteIdentifiers: false}}
}, {
title: 'uses offset even if no limit was passed',
arguments: ['myTable', {offset: 2}],
expectation: "SELECT * FROM myTable OFFSET 2;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['mySchema.myTable'],
expectation: "SELECT * FROM mySchema.myTable;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['mySchema.myTable', {where: {name: "foo';DROP TABLE mySchema.myTable;"}}],
expectation: "SELECT * FROM mySchema.myTable WHERE mySchema.myTable.name='foo'';DROP TABLE mySchema.myTable;';",
context: {options: {quoteIdentifiers: false}}
}

@@ -213,3 +362,51 @@ ],

expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'';DROP TABLE mySchema.myTable;') RETURNING *;"
},
// Variants when quoteIdentifiers is false
{
arguments: ['myTable', {name: 'foo'}],
expectation: "INSERT INTO myTable (name) VALUES ('foo') RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}],
expectation: "INSERT INTO myTable (name) VALUES ('foo'';DROP TABLE myTable;') RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}],
expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.0Z') RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: 'foo', foo: 1}],
expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1) RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: 'foo', nullValue: null}],
expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL) RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: 'foo', nullValue: null}],
expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL) RETURNING *;",
context: {options: {omitNull: false, quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: 'foo', nullValue: null}],
expectation: "INSERT INTO myTable (name) VALUES ('foo') RETURNING *;",
context: {options: {omitNull: true, quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: 'foo', nullValue: undefined}],
expectation: "INSERT INTO myTable (name) VALUES ('foo') RETURNING *;",
context: {options: {omitNull: true, quoteIdentifiers: false}}
}, {
arguments: ['mySchema.myTable', {name: 'foo'}],
expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo') RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['mySchema.myTable', {name: JSON.stringify({info: 'Look ma a " quote'})}],
expectation: "INSERT INTO mySchema.myTable (name) VALUES ('{\"info\":\"Look ma a \\\" quote\"}') RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}],
expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'';DROP TABLE mySchema.myTable;') RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}
],

@@ -254,2 +451,49 @@

expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'';DROP TABLE mySchema.myTable;'),('bar') RETURNING *;"
},
// Variants when quoteIdentifiers is false
{
arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]],
expectation: "INSERT INTO myTable (name) VALUES ('foo'),('bar') RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', [{name: "foo';DROP TABLE myTable;"}, {name: 'bar'}]],
expectation: "INSERT INTO myTable (name) VALUES ('foo'';DROP TABLE myTable;'),('bar') RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', [{name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55))}]],
expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.0Z'),('bar','2012-03-27 10:01:55.0Z') RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', [{name: 'foo', foo: 1}, {name: 'bar', foo: 2}]],
expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1),('bar',2) RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]],
expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL) RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]],
expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL) RETURNING *;",
context: {options: {quoteIdentifiers: false, omitNull: false}},
}, {
arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]],
expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL) RETURNING *;",
context: {options: {omitNull: true, quoteIdentifiers: false}} // Note: We don't honour this because it makes little sense when some rows may have nulls and others not
}, {
arguments: ['myTable', [{name: 'foo', nullValue: undefined}, {name: 'bar', nullValue: undefined}]],
expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL) RETURNING *;",
context: {options: {omitNull: true, quoteIdentifiers: false}} // Note: As above
}, {
arguments: ['mySchema.myTable', [{name: 'foo'}, {name: 'bar'}]],
expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'),('bar') RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['mySchema.myTable', [{name: JSON.stringify({info: 'Look ma a " quote'})}, {name: JSON.stringify({info: 'Look ma another " quote'})}]],
expectation: "INSERT INTO mySchema.myTable (name) VALUES ('{\"info\":\"Look ma a \\\" quote\"}'),('{\"info\":\"Look ma another \\\" quote\"}') RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['mySchema.myTable', [{name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'bar'}]],
expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'';DROP TABLE mySchema.myTable;'),('bar') RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}

@@ -292,2 +536,45 @@ ],

expectation: "UPDATE \"mySchema\".\"myTable\" SET \"name\"='foo'';DROP TABLE mySchema.myTable;' WHERE \"name\"='foo' RETURNING *"
},
// Variants when quoteIdentifiers is false
{
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}],
expectation: "UPDATE myTable SET name='foo',birthday='2011-03-27 10:01:55.0Z' WHERE id=2 RETURNING *",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, 2],
expectation: "UPDATE myTable SET name='foo',birthday='2011-03-27 10:01:55.0Z' WHERE id=2 RETURNING *",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {bar: 2}, {name: 'foo'}],
expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}],
expectation: "UPDATE myTable SET name='foo'';DROP TABLE myTable;' WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "UPDATE myTable SET bar=2,nullValue=NULL WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "UPDATE myTable SET bar=2,nullValue=NULL WHERE name='foo' RETURNING *",
context: {options: {omitNull: false, quoteIdentifiers: false}},
}, {
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *",
context: {options: {omitNull: true, quoteIdentifiers: false}},
}, {
arguments: ['myTable', {bar: 2, nullValue: undefined}, {name: 'foo'}],
expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *",
context: {options: {omitNull: true, quoteIdentifiers: false}},
}, {
arguments: ['mySchema.myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}],
expectation: "UPDATE mySchema.myTable SET name='foo',birthday='2011-03-27 10:01:55.0Z' WHERE id=2 RETURNING *",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'foo'}],
expectation: "UPDATE mySchema.myTable SET name='foo'';DROP TABLE mySchema.myTable;' WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}}
}

@@ -304,2 +591,8 @@ ],

}, {
arguments: ['myTable', undefined, {truncate: true}],
expectation: "TRUNCATE \"myTable\""
}, {
arguments: ['myTable', 1, {limit: 10, truncate: true}],
expectation: "TRUNCATE \"myTable\""
}, {
arguments: ['myTable', 1, {limit: 10}],

@@ -319,2 +612,33 @@ expectation: "DELETE FROM \"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"myTable\" WHERE \"id\"=1 LIMIT 10)"

expectation: "DELETE FROM \"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"myTable\" WHERE \"name\"='foo')"
},
// Variants when quoteIdentifiers is false
{
arguments: ['myTable', {name: 'foo'}],
expectation: "DELETE FROM myTable WHERE id IN (SELECT id FROM myTable WHERE name='foo' LIMIT 1)",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', 1],
expectation: "DELETE FROM myTable WHERE id IN (SELECT id FROM myTable WHERE id=1 LIMIT 1)",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', 1, {limit: 10}],
expectation: "DELETE FROM myTable WHERE id IN (SELECT id FROM myTable WHERE id=1 LIMIT 10)",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}],
expectation: "DELETE FROM myTable WHERE id IN (SELECT id FROM myTable WHERE name='foo'';DROP TABLE myTable;' LIMIT 10)",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['mySchema.myTable', {name: 'foo'}],
expectation: "DELETE FROM mySchema.myTable WHERE id IN (SELECT id FROM mySchema.myTable WHERE name='foo' LIMIT 1)",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {limit: 10}],
expectation: "DELETE FROM mySchema.myTable WHERE id IN (SELECT id FROM mySchema.myTable WHERE name='foo'';DROP TABLE mySchema.myTable;' LIMIT 10)",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: 'foo'}, {limit: null}],
expectation: "DELETE FROM myTable WHERE id IN (SELECT id FROM myTable WHERE name='foo')",
context: {options: {quoteIdentifiers: false}}
}

@@ -343,2 +667,28 @@ ],

expectation: 'CREATE INDEX \"user_username_is_admin\" ON \"mySchema\".\"User\" (\"username\", \"isAdmin\")'
},
// Variants when quoteIdentifiers is false
{
arguments: ['User', ['username', 'isAdmin']],
expectation: 'CREATE INDEX user_username_is_admin ON User (username, isAdmin)',
context: {options: {quoteIdentifiers: false}}
}, {
arguments: [
'User', [
{ attribute: 'username', length: 10, order: 'ASC'},
'isAdmin'
]
],
expectation: "CREATE INDEX user_username_is_admin ON User (username(10) ASC, isAdmin)",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: [
'User', ['username', 'isAdmin'], { indicesType: 'FULLTEXT', indexName: 'bar'}
],
expectation: "CREATE FULLTEXT INDEX bar ON User (username, isAdmin)",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['mySchema.User', ['username', 'isAdmin']],
expectation: 'CREATE INDEX user_username_is_admin ON mySchema.User (username, isAdmin)',
context: {options: {quoteIdentifiers: false}}
}

@@ -368,2 +718,17 @@ ],

expectation: "DROP INDEX IF EXISTS \"mySchema\".\"user_foo_bar\""
},
// Variants when quoteIdentifiers is false
{
arguments: ['User', 'user_foo_bar'],
expectation: "DROP INDEX IF EXISTS user_foo_bar",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['User', ['foo', 'bar']],
expectation: "DROP INDEX IF EXISTS user_foo_bar",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['User', 'mySchema.user_foo_bar'],
expectation: "DROP INDEX IF EXISTS mySchema.user_foo_bar",
context: {options: {quoteIdentifiers: false}}
}

@@ -380,3 +745,15 @@ ],

expectation: "\"id\" IN (NULL)"
}
},
// Variants when quoteIdentifiers is false
{
arguments: [{ id: [1,2,3] }],
expectation: "id IN (1,2,3)",
context: {options: {quoteIdentifiers: false}}
},
{
arguments: [{ id: [] }],
expectation: "id IN (NULL)",
context: {options: {quoteIdentifiers: false}}
},
]

@@ -392,3 +769,5 @@ }

var context = test.context || {options: {}};
var conditions = QueryGenerator[suiteTitle].apply(context, test.arguments)
QueryGenerator.options = context.options
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)
expect(conditions).toEqual(test.expectation)

@@ -395,0 +774,0 @@ })

@@ -202,2 +202,5 @@ var Sequelize = require("../../index")

}, {
arguments: ['myTable', 1, {truncate: true}],
expectation: "DELETE FROM `myTable` WHERE `id`=1"
}, {
arguments: ['myTable', 1, {limit: 10}],

@@ -207,3 +210,3 @@ expectation: "DELETE FROM `myTable` WHERE `id`=1"

arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}],
expectation: "DELETE FROM `myTable` WHERE `name`='foo\\';DROP TABLE myTable;'"
expectation: "DELETE FROM `myTable` WHERE `name`='foo'';DROP TABLE myTable;'"
}, {

@@ -223,4 +226,4 @@ arguments: ['myTable', {name: 'foo'}, {limit: null}],

var context = test.context || {options: {}};
var conditions = QueryGenerator[suiteTitle].apply(context, test.arguments)
QueryGenerator.options = context.options
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)
expect(conditions).toEqual(test.expectation)

@@ -227,0 +230,0 @@ })

@@ -12,2 +12,3 @@ if (typeof require === 'function') {

describe(Helpers.getTestDialectTeaser("HasMany"), function() {
before(function(done) {

@@ -14,0 +15,0 @@ var self = this

@@ -23,3 +23,3 @@ if (typeof require === 'function') {

describe('setAssociation', function() {
it('clears the association if null is passed', function(done) {
it('//clears the association if null is passed', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })

@@ -26,0 +26,0 @@ , Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING })

@@ -219,5 +219,7 @@ if(typeof require === 'function') {

username: user.username
}).success(function (_user) {
}).success(function (_user, created) {
expect(_user.id).toEqual(user.id)
expect(_user.username).toEqual('Username')
expect(created).toBeFalse()
done()

@@ -236,6 +238,8 @@ })

this.User.create(data).success(function (user) {
self.User.findOrCreate(data).success(function (_user) {
self.User.findOrCreate(data).success(function (_user, created) {
expect(_user.id).toEqual(user.id)
expect(_user.username).toEqual('Username')
expect(_user.data).toEqual('ThisIsData')
expect(created).toBeFalse()
done()

@@ -255,5 +259,7 @@ })

this.User.findOrCreate(data, default_values).success(function (user) {
this.User.findOrCreate(data, default_values).success(function (user, created) {
expect(user.username).toEqual('Username')
expect(user.data).toEqual('ThisIsData')
expect(created).toBeTrue()
done()

@@ -260,0 +266,0 @@ })

@@ -425,3 +425,3 @@ if (typeof require === 'function') {

expect(err.validateTest[0]).toBeDefined()
expect(err.validateTest[0].indexOf('Invalid integer:')).toBeGreaterThan(-1)
expect(err.validateTest[0].indexOf('Invalid integer')).toBeGreaterThan(-1)
done()

@@ -428,0 +428,0 @@ })

@@ -10,3 +10,3 @@ if(typeof require === 'function') {

describe(Helpers.getTestDialectTeaser("DAO"), function() {
describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
describe('validations', function() {

@@ -205,3 +205,3 @@ before(function(done) {

it('correctly specifies an instance as invalid using a value of "' + failingValue + '" for the validation "' + validator + '"', function() {
it('correctly specifies an instance as invalid using a value of "' + failingValue + '" for the validation "' + validator + '"', function(done) {
var validations = {}

@@ -226,7 +226,11 @@ , message = validator + "(" + failingValue + ")"

var failingUser = UserFail.build({ name : failingValue })
, errors = failingUser.validate()
, errors = undefined;
expect(errors).not.toBeNull()
expect(errors).toEqual({ name : [message] })
})
failingUser.validate().done( function(err,_errors) {
expect(_errors).not.toBeNull();
expect(_errors).toEqual({ name : [message] });
done();
});
});
}

@@ -240,3 +244,3 @@

it('correctly specifies an instance as valid using a value of "' + succeedingValue + '" for the validation "' + validator + '"', function() {
it('correctly specifies an instance as valid using a value of "' + succeedingValue + '" for the validation "' + validator + '"', function(done) {
var validations = {}

@@ -259,4 +263,11 @@

var successfulUser = UserSuccess.build({ name: succeedingValue })
expect(successfulUser.validate()).toBeNull()
var successfulUser = UserSuccess.build({ name: succeedingValue });
successfulUser.validate().success( function() {
expect(arguments.length).toBe(0);
done();
}).error(function(err) {
expect(err).toBe({})
done();
});
})

@@ -267,3 +278,3 @@ }

it('correctly validates using custom validation methods', function() {
it('correctly validates using custom validation methods', function(done) {
var User = this.sequelize.define('User' + Math.random(), {

@@ -273,5 +284,7 @@ name: {

validate: {
customFn: function(val) {
customFn: function(val, next) {
if (val !== "2") {
throw new Error("name should equal '2'")
next("name should equal '2'")
} else {
next()
}

@@ -284,12 +297,19 @@ }

var failingUser = User.build({ name : "3" })
, errors = failingUser.validate()
expect(errors).not.toBeNull(null)
expect(errors).toEqual({ name: ["name should equal '2'"] })
failingUser.validate().success(function(errors) {
expect(errors).toEqual({ name: ["name should equal '2'"] })
var successfulUser = User.build({ name : "2" })
expect(successfulUser.validate()).toBeNull()
var successfulUser = User.build({ name : "2" })
successfulUser.validate().success(function() {
expect(arguments.length).toBe(0);
done();
}).error(function(err) {
expect(err).toBe({});
done();
})
});
})
it('skips other validations if allowNull is true and the value is null', function() {
it('skips other validations if allowNull is true and the value is null', function(done) {
var User = this.sequelize.define('User' + Math.random(), {

@@ -305,16 +325,18 @@ age: {

var failingUser = User.build({ age: -1 })
, errors = failingUser.validate()
User
.build({ age: -1 })
.validate()
.success(function(errors) {
expect(errors).not.toBeNull(null)
expect(errors).toEqual({ age: ['must be positive'] })
expect(errors).not.toBeNull(null)
expect(errors).toEqual({ age: ['must be positive'] })
var successfulUser1 = User.build({ age: null })
expect(successfulUser1.validate()).toBeNull()
var successfulUser2 = User.build({ age: 1 })
expect(successfulUser2.validate()).toBeNull()
User.build({ age: null }).validate().success(function() {
User.build({ age: 1 }).validate().success(function() {
done()
})
})
})
})
it('validates a model with custom model-wide validation methods', function() {
it('validates a model with custom model-wide validation methods', function(done) {
var Foo = this.sequelize.define('Foo' + Math.random(), {

@@ -331,5 +353,7 @@ field1: {

validate: {
xnor: function() {
xnor: function(done) {
if ((this.field1 === null) === (this.field2 === null)) {
throw new Error('xnor failed');
done('xnor failed')
} else {
done()
}

@@ -340,11 +364,19 @@ }

var failingFoo = Foo.build({ field1: null, field2: null })
, errors = failingFoo.validate()
expect(errors).not.toBeNull()
expect(errors).toEqual({ 'xnor': ['xnor failed'] })
Foo
.build({ field1: null, field2: null })
.validate()
.success(function(errors) {
expect(errors).not.toBeNull()
expect(errors).toEqual({ 'xnor': ['xnor failed'] })
var successfulFoo = Foo.build({ field1: 33, field2: null })
expect(successfulFoo.validate()).toBeNull()
Foo
.build({ field1: 33, field2: null })
.validate()
.success(function(errors) {
expect(errors).not.toBeDefined()
done()
})
})
})
})
})

@@ -68,2 +68,58 @@ if(typeof require === 'function') {

})
describe('[POSTGRES] Unquoted identifiers', function() {
before(function(done) {
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize, DataTypes) {
self.sequelize = sequelize
self.sequelize.options.quoteIdentifiers = false
self.User = sequelize.define('User', {
username: DataTypes.STRING,
fullName: DataTypes.STRING // Note mixed case
})
},
onComplete: function() {
// We can create a table with non-quoted identifiers
self.User.sync({ force: true }).success(done)
}
})
})
it("can insert and select", function(done) {
var self = this
self.User
.create({ username: 'user', fullName: "John Smith" })
.success(function(user) {
// We can insert into a table with non-quoted identifiers
expect(user.id).toBeDefined()
expect(user.id).not.toBeNull()
expect(user.username).toEqual('user')
expect(user.fullName).toEqual('John Smith')
// We can query by non-quoted identifiers
self.User.find({
where: {fullName: "John Smith"}
})
.success(function(user2) {
// We can map values back to non-quoted identifiers
expect(user2.id).toEqual(user.id)
expect(user2.username).toEqual('user')
expect(user2.fullName).toEqual('John Smith')
done();
})
.error(function(err) {
console.log(err)
})
})
.error(function(err) {
console.log(err)
})
})
})
}

@@ -290,3 +290,3 @@ if (typeof require === 'function') {

expect(err.validateTest[0]).toBeDefined()
expect(err.validateTest[0].indexOf('Invalid integer:')).toBeGreaterThan(-1)
expect(err.validateTest[0].indexOf('Invalid integer')).toBeGreaterThan(-1)
done()

@@ -293,0 +293,0 @@ })

@@ -96,3 +96,4 @@ if(typeof require === 'function') {

username: Helpers.Sequelize.STRING,
isAdmin: Helpers.Sequelize.BOOLEAN
isAdmin: Helpers.Sequelize.BOOLEAN,
enumVals: Helpers.Sequelize.ENUM('hello', 'world')
}).success(done)

@@ -107,2 +108,3 @@ })

var isAdmin = metadata.isAdmin
var enumVals = metadata.enumVals

@@ -117,2 +119,7 @@ expect(username.type).toEqual(dialect === 'postgres' ? 'CHARACTER VARYING' : 'VARCHAR(255)')

if (dialect === 'postgres') {
expect(enumVals.special).toBeArray();
expect(enumVals.special.length).toEqual(2);
}
done()

@@ -119,0 +126,0 @@ })

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc