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

Commit 3ec387e9 by Martin Aspeli

Refactor quoting of identifiers and strings, with optional non-quoting of

identifiers in Postgres
1 parent 67348fc4
......@@ -213,7 +213,7 @@ module.exports = (function() {
//right now, the caller (has-many-double-linked) is in charge of the where clause
DAOFactory.prototype.findAllJoin = function(joinTableName, options) {
var optcpy = Utils._.clone(options)
optcpy.attributes = optcpy.attributes || [Utils.addTicks(this.tableName)+".*"]
optcpy.attributes = optcpy.attributes || [this.QueryInterface.quoteIdentifier(this.tableName)+".*"]
// whereCollection is used for non-primary key updates
this.options.whereCollection = optcpy.where || null;
......
......@@ -136,7 +136,7 @@ module.exports = (function() {
throw new Error('Value for HSTORE must be a string or number.')
}
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))
values[attrName] = text.join(',')
}
......@@ -180,7 +180,7 @@ module.exports = (function() {
*/
DAO.prototype.reload = function() {
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
]
......
......@@ -2,16 +2,6 @@ var Utils = require("../../utils")
, DataTypes = require("../../data-types")
, util = require("util")
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() {
var QueryGenerator = {
addSchema: function(opts) {
......@@ -30,7 +20,7 @@ module.exports = (function() {
return tableName
}
return QueryGenerator.addQuotes(schema + (!schemaDelimiter ? '.' : schemaDelimiter) + tableName)
return this.quoteIdentifier(schema + (!schemaDelimiter ? '.' : schemaDelimiter) + tableName, false)
},
createSchema: function() {
......@@ -64,25 +54,25 @@ module.exports = (function() {
if (Utils._.includes(dataType, 'PRIMARY KEY')) {
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)
}
}
}
var values = {
table: QueryGenerator.addQuotes(tableName),
table: this.quoteIdentifier(tableName),
attributes: attrStr.join(", "),
engine: options.engine,
charset: (options.charset ? "DEFAULT CHARSET=" + options.charset : "")
}
, pkString = primaryKeys.map(function(pk) { return QueryGenerator.addQuotes(pk) }).join(", ")
, pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk) }.bind(this)).join(", ")
if (pkString.length > 0) {
values.attributes += ", PRIMARY KEY (" + pkString + ")"
......@@ -90,7 +80,7 @@ module.exports = (function() {
for (var fkey in foreignKeys) {
if(foreignKeys.hasOwnProperty(fkey)) {
values.attributes += ", FOREIGN KEY (" + QueryGenerator.addQuotes(fkey) + ") " + foreignKeys[fkey]
values.attributes += ", FOREIGN KEY (" + this.quoteIdentifier(fkey) + ") " + foreignKeys[fkey]
}
}
......@@ -103,7 +93,7 @@ module.exports = (function() {
var query = "DROP TABLE IF EXISTS <%= table %>;"
return Utils._.template(query)({
table: QueryGenerator.addQuotes(tableName)
table: this.quoteIdentifier(tableName)
})
},
......@@ -175,14 +165,14 @@ module.exports = (function() {
joinQuery = ""
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 || '*'
if (options.include) {
......@@ -190,8 +180,8 @@ module.exports = (function() {
options.include.forEach(function(include) {
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))
optAttributes = optAttributes.concat(attributes)
......@@ -201,9 +191,9 @@ module.exports = (function() {
var attrLeft = 'id'
var tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
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))
options.attributes = optAttributes.join(', ')
}
......@@ -217,7 +207,7 @@ module.exports = (function() {
}
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
}
......@@ -242,9 +232,9 @@ module.exports = (function() {
insertQuery: function(tableName, attrValueHash) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
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(",")
var query = "INSERT INTO " + table + " (" + attributes + ") VALUES (" + values + ");"
......@@ -256,12 +246,12 @@ module.exports = (function() {
Utils._.forEach(attrValueHashes, function(attrValueHash) {
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(",")
var query = "INSERT INTO " + table + " (" + attributes + ") VALUES " + tuples.join(",") + ";"
......@@ -275,14 +265,14 @@ module.exports = (function() {
for (var key in attrValueHash) {
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)
return query
},
......@@ -290,13 +280,13 @@ module.exports = (function() {
deleteQuery: function(tableName, where, options) {
options = options || {}
var table = QueryGenerator.addQuotes(tableName)
var table = this.quoteIdentifier(tableName)
if (options.truncate === true) {
// Truncate does not allow LIMIT and WHERE
return "TRUNCATE " + table
}
where = QueryGenerator.getWhereConditions(where)
where = this.getWhereConditions(where)
var limit = ""
if(Utils._.isUndefined(options.limit)) {
......@@ -304,7 +294,7 @@ module.exports = (function() {
}
if(!!options.limit) {
limit = " LIMIT " + Utils.escape(options.limit)
limit = " LIMIT " + this.escape(options.limit)
}
return "DELETE FROM " + table + " WHERE " + where + limit
......@@ -313,8 +303,8 @@ module.exports = (function() {
bulkDeleteQuery: function(tableName, where, options) {
options = options || {}
var table = QueryGenerator.addQuotes(tableName)
where = QueryGenerator.getWhereConditions(where)
var table = this.quoteIdentifier(tableName)
where = this.getWhereConditions(where)
var query = "DELETE FROM " + table + " WHERE " + where
......@@ -328,14 +318,14 @@ module.exports = (function() {
for (var key in attrValueHash) {
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)
var query = "UPDATE " + table + " SET " + values + " WHERE " + where
......@@ -365,11 +355,11 @@ module.exports = (function() {
return result
}
})
}.bind(this))
var onlyAttributeNames = attributes.map(function(attribute) {
return (typeof attribute === 'string') ? attribute : attribute.attribute
})
}.bind(this))
options = Utils._.extend({
indicesType: null,
......@@ -429,23 +419,23 @@ module.exports = (function() {
var value = hash[key]
//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)) {
// is value an array?
if (value.length === 0) { value = [null] }
_value = "(" + value.map(processAndEscapeValue).join(',') + ")"
_value = "(" + value.map(function(v) { return this.escape(v) }.bind(this)).join(',') + ")"
result.push([_key, _value].join(" IN "))
} else if ((value) && (typeof value == 'object') && !(value instanceof Date)) {
// is value an object?
//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("="))
}
}
......@@ -465,8 +455,8 @@ module.exports = (function() {
if (dataType.type.toString() === DataTypes.ENUM.toString()) {
if (Array.isArray(dataType.values) && (dataType.values.length > 0)) {
template = "ENUM(" + Utils._.map(dataType.values, function(value) {
return Utils.escape(value)
}).join(", ") + ")"
return this.escape(value)
}.bind(this)).join(", ") + ")"
} else {
throw new Error('Values for ENUM haven\'t been defined.')
}
......@@ -483,7 +473,7 @@ module.exports = (function() {
}
if ((dataType.defaultValue !== undefined) && (dataType.defaultValue != DataTypes.NOW)) {
template += " DEFAULT " + Utils.escape(dataType.defaultValue)
template += " DEFAULT " + this.escape(dataType.defaultValue)
}
if (dataType.unique) {
......@@ -495,13 +485,13 @@ module.exports = (function() {
}
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') + ")"
}
if(dataType.onDelete) {
......@@ -549,13 +539,23 @@ module.exports = (function() {
return Utils._.template(sql, {})
},
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)
}
}
return Utils._.extend(Utils._.clone(require("../query-generator")), QueryGenerator)
......
......@@ -24,7 +24,7 @@ module.exports = (function() {
return tableName
}
return QueryGenerator.addQuotes(schema) + '.' + QueryGenerator.addQuotes(tableName)
return this.quoteIdentifier(schema) + '.' + this.quoteIdentifier(tableName)
},
createSchema: function(schema) {
......@@ -52,22 +52,22 @@ module.exports = (function() {
, attrStr = []
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
}
}
var values = {
table: QueryGenerator.addQuotes(tableName),
table: this.quoteIdentifiers(tableName),
attributes: attrStr.join(", ")
}
var pks = primaryKeys[tableName].map(function(pk){
return QueryGenerator.addQuotes(pk)
}).join(",")
return this.quoteIdentifier(pk)
}.bind(this)).join(",")
if (pks.length > 0) {
values.attributes += ", PRIMARY KEY (" + pks + ")"
......@@ -80,7 +80,7 @@ module.exports = (function() {
options = options || {}
var query = "DROP TABLE IF EXISTS <%= table %><%= cascade %>;"
return Utils._.template(query)({
table: QueryGenerator.addQuotes(tableName),
table: this.quoteIdentifiers(tableName),
cascade: options.cascade? " CASCADE" : ""
})
},
......@@ -88,8 +88,8 @@ module.exports = (function() {
renameTableQuery: function(before, after) {
var query = "ALTER TABLE <%= before %> RENAME TO <%= after %>;"
return Utils._.template(query)({
before: QueryGenerator.addQuotes(before),
after: QueryGenerator.addQuotes(after)
before: this.quoteIdentifier(before),
after: this.quoteIdentifier(after)
})
},
......@@ -100,7 +100,7 @@ module.exports = (function() {
describeTableQuery: function(tableName) {
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)
})
},
......@@ -112,25 +112,25 @@ module.exports = (function() {
var definition = attributes[attrName]
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
}
}
return Utils._.template(query)({
tableName: QueryGenerator.addQuotes(tableName),
tableName: this.quoteIdentifiers(tableName),
attributes: attrString.join(', ') })
},
removeColumnQuery: function(tableName, attributeName) {
var query = "ALTER TABLE <%= tableName %> DROP COLUMN <%= attributeName %>;"
return Utils._.template(query)({
tableName: QueryGenerator.addQuotes(tableName),
attributeName: QueryGenerator.addQuotes(attributeName)
tableName: this.quoteIdentifiers(tableName),
attributeName: this.quoteIdentifier(attributeName)
})
},
......@@ -144,40 +144,40 @@ module.exports = (function() {
if (definition.indexOf('NOT NULL') > 0) {
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'
})
definition = definition.replace('NOT NULL', '').trim()
} else {
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'
})
}
if (definition.indexOf('DEFAULT') > 0) {
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]
})
definition = definition.replace(/(DEFAULT[^;]+)/, '').trim()
} else {
attrSql += Utils._.template(query)({
tableName: QueryGenerator.addQuotes(tableName),
query: QueryGenerator.addQuotes(attributeName) + ' DROP DEFAULT'
tableName: this.quoteIdentifiers(tableName),
query: this.quoteIdentifier(attributeName) + ' DROP DEFAULT'
})
}
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
})
sql.push(attrSql)
......@@ -192,44 +192,30 @@ module.exports = (function() {
for (var attributeName in attributes) {
attrString.push(Utils._.template('<%= before %> TO <%= after %>')({
before: QueryGenerator.addQuotes(attrBefore),
after: QueryGenerator.addQuotes(attributeName)
before: this.quoteIdentifier(attrBefore),
after: this.quoteIdentifier(attributeName)
}))
}
return Utils._.template(query)({
tableName: QueryGenerator.addQuotes(tableName),
tableName: this.quoteIdentifiers(tableName),
attributes: attrString.join(', ')
})
},
selectQuery: function(tableName, options) {
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)
}
var query = "SELECT <%= attributes %> FROM <%= table %>",
table = null
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 || '*'
if (options.include) {
......@@ -237,48 +223,40 @@ module.exports = (function() {
options.include.forEach(function(include) {
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))
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 %>"
}
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 %>"
}
if(options.order) {
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 %>"
}
......@@ -304,13 +282,13 @@ module.exports = (function() {
, returning = removeSerialsFromHash(tableName, attrValueHash)
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(",")
}
return Utils._.template(query)(replacements)
......@@ -324,16 +302,16 @@ module.exports = (function() {
removeSerialsFromHash(tableName, attrValueHash)
tuples.push("(" +
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(",")
}
......@@ -348,13 +326,13 @@ module.exports = (function() {
for (var key in attrValueHash) {
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)
}
return Utils._.template(query)(replacements)
......@@ -364,7 +342,7 @@ module.exports = (function() {
options = options || {}
if (options.truncate === true) {
return "TRUNCATE " + QueryGenerator.addQuotes(tableName)
return "TRUNCATE " + QueryGenerator.quoteIdentifier(tableName)
}
if(Utils._.isUndefined(options.limit)) {
......@@ -378,16 +356,16 @@ module.exports = (function() {
var pks;
if (primaryKeys[tableName] && primaryKeys[tableName].length > 0) {
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,
primaryKeysSelection: pks
}
......@@ -403,13 +381,13 @@ module.exports = (function() {
for (var key in attrValueHash) {
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)
}
return Utils._.template(query)(replacements)
......@@ -419,7 +397,7 @@ module.exports = (function() {
addIndexQuery: function(tableName, attributes, options) {
var transformedAttributes = attributes.map(function(attribute) {
if (typeof attribute === 'string') {
return QueryGenerator.addQuotes(attribute)
return this.quoteIdentifier(attribute)
} else {
var result = ""
......@@ -427,7 +405,7 @@ module.exports = (function() {
throw new Error('The following index attribute has no attribute: ' + util.inspect(attribute))
}
result += QueryGenerator.addQuotes(attribute.attribute)
result += this.quoteIdentifier(attribute.attribute)
if (attribute.length) {
result += '(' + attribute.length + ')'
......@@ -439,11 +417,11 @@ module.exports = (function() {
return result
}
})
}.bind(this))
var onlyAttributeNames = attributes.map(function(attribute) {
return (typeof attribute === "string") ? attribute : attribute.attribute
})
}.bind(this))
var indexTable = tableName.split('.')
options = Utils._.extend({
......@@ -453,9 +431,9 @@ module.exports = (function() {
}, options || {})
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(' ')
},
......@@ -473,8 +451,8 @@ module.exports = (function() {
}
return Utils._.template(sql)({
tableName: QueryGenerator.addQuotes(tableName),
indexName: QueryGenerator.addQuotes(indexName)
tableName: this.quoteIdentifiers(tableName),
indexName: this.quoteIdentifiers(indexName)
})
},
......@@ -483,11 +461,11 @@ module.exports = (function() {
if (Utils.isHash(smth)) {
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)
}
else if (typeof smth === "string") {
result = smth
......@@ -506,23 +484,21 @@ module.exports = (function() {
var value = hash[key]
//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(',') + ")"
result.push([_key, _value].join(" IN "))
}
else if ((value) && (typeof value === "object")) {
//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("="))
}
}
......@@ -543,8 +519,8 @@ module.exports = (function() {
if (dataType.type.toString() === DataTypes.ENUM.toString()) {
if (Array.isArray(dataType.values) && (dataType.values.length > 0)) {
replacements.type = "ENUM(" + Utils._.map(dataType.values, function(value) {
return Utils.escape(value)
}).join(", ") + ")"
return this.escape(value)
}.bind(this)).join(", ") + ")"
} else {
throw new Error('Values for ENUM haven\'t been defined.')
}
......@@ -568,7 +544,7 @@ module.exports = (function() {
if (dataType.defaultValue !== undefined) {
template += " DEFAULT <%= defaultValue %>"
replacements.defaultValue = QueryGenerator.pgEscape(dataType.defaultValue)
replacements.defaultValue = this.escape(dataType.defaultValue)
}
if (dataType.unique) {
......@@ -581,12 +557,12 @@ module.exports = (function() {
if(dataType.references) {
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')
}
if(dataType.onDelete) {
......@@ -645,48 +621,12 @@ module.exports = (function() {
})
},
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('.')
},
pgEscape: 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 QueryGenerator.pgEscape(it) }).join(',') +']';
}
}
if (val instanceof Date) {
val = QueryGenerator.pgSqlDate(val);
}
// http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
val = val.replace(/'/g, "''");
return "'"+val+"'";
},
pgEscapeAndQuote: function (val) {
return QueryGenerator.addQuotes(QueryGenerator.removeQuotes(QueryGenerator.pgEscape(val), "'"))
return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'"))
},
pgEnum: function (tableName, attr, dataType) {
var enumName = QueryGenerator.pgEscapeAndQuote("enum_" + tableName + "_" + attr)
var enumName = this.pgEscapeAndQuote("enum_" + tableName + "_" + attr)
return "DROP TYPE IF EXISTS " + enumName + "; CREATE TYPE " + enumName + " AS " + dataType.match(/^ENUM\(.+\)/)[0] + "; "
},
......@@ -725,8 +665,8 @@ module.exports = (function() {
},
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'
},
......@@ -757,11 +697,54 @@ module.exports = (function() {
}
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 + "'";
}
}
// Private
......
......@@ -108,6 +108,20 @@ module.exports = (function() {
this.emit('success', result)
} 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))
}
} else if (this.send('isShowOrDescribeQuery')) {
......
......@@ -268,7 +268,28 @@ module.exports = (function() {
Globally disable foreign key constraints
*/
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')
}
}
......
......@@ -8,35 +8,10 @@ var MySqlQueryGenerator = Utils._.extend(
var hashToWhereConditions = MySqlQueryGenerator.hashToWhereConditions
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() {
var QueryGenerator = {
options: {},
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) {
var tableName = undefined
var schema = (!!opts && !!opts.options && !!opts.options.schema ? opts.options.schema : undefined)
......@@ -53,7 +28,7 @@ module.exports = (function() {
return tableName
}
return QueryGenerator.addQuotes(schema + (!schemaPrefix ? '.' : schemaPrefix) + tableName)
return this.quoteIdentifier(schema) + (!schemaPrefix ? '.' : schemaPrefix) + this.quoteIdentifier(tableName)
},
createSchema: function() {
......@@ -91,31 +66,31 @@ module.exports = (function() {
if (Utils._.includes(dataType, 'PRIMARY KEY') && needsMultiplePrimaryKeys) {
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)
}
}
}
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(", ")
if (pkString.length > 0) {
values.attributes += ", PRIMARY KEY (" + pkString + ")"
}
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)
},
showTablesQuery: function() {
......@@ -128,11 +103,11 @@ module.exports = (function() {
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>);";
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(",")
}
return Utils._.template(query)(replacements)
......@@ -145,14 +120,14 @@ module.exports = (function() {
Utils._.forEach(attrValueHashes, function(attrValueHash) {
tuples.push("(" +
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
}
......@@ -163,14 +138,14 @@ module.exports = (function() {
joinQuery = ""
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 || '*'
if (options.include) {
......@@ -178,8 +153,8 @@ module.exports = (function() {
options.include.forEach(function(include) {
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))
optAttributes = optAttributes.concat(attributes)
......@@ -189,9 +164,9 @@ module.exports = (function() {
var attrLeft = 'id'
var tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
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))
options.attributes = optAttributes.join(', ')
}
......@@ -205,7 +180,7 @@ module.exports = (function() {
}
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
}
......@@ -235,13 +210,13 @@ module.exports = (function() {
for (var key in attrValueHash) {
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)
}
return Utils._.template(query)(replacements)
......@@ -252,8 +227,8 @@ module.exports = (function() {
var query = "DELETE FROM <%= table %> WHERE <%= where %>"
var replacements = {
table: Utils.addTicks(tableName),
where: MySqlQueryGenerator.getWhereConditions(where)
table: this.quoteIdentifier(tableName),
where: this.getWhereConditions(where)
}
return Utils._.template(query)(replacements)
......@@ -267,13 +242,13 @@ module.exports = (function() {
for (var key in attrValueHash) {
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)
}
return Utils._.template(query)(replacements)
......@@ -303,7 +278,7 @@ module.exports = (function() {
if (dataType.defaultValue !== undefined) {
template += " DEFAULT <%= defaultValue %>"
replacements.defaultValue = Utils.escape(dataType.defaultValue)
replacements.defaultValue = this.escape(dataType.defaultValue)
}
if (dataType.unique) {
......@@ -320,12 +295,12 @@ module.exports = (function() {
if(dataType.references) {
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')
}
if(dataType.onDelete) {
......@@ -388,7 +363,7 @@ module.exports = (function() {
}
}
return hashToWhereConditions(hash).replace(/\\'/g, "''");
return hashToWhereConditions.call(this, hash).replace(/\\'/g, "''");
},
showIndexQuery: function(tableName) {
......@@ -418,14 +393,14 @@ module.exports = (function() {
},
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;",
"DROP TABLE <%= tableName %>_backup;"
].join("")
......@@ -437,14 +412,14 @@ module.exports = (function() {
},
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;",
"DROP TABLE <%= tableName %>_backup;"
].join("")
......@@ -453,16 +428,41 @@ module.exports = (function() {
tableName: tableName,
attributeNamesImport: Utils._.keys(attributes).map(function(attr) {
return (attrNameAfter === attr) ? attrNameBefore + ' AS ' + attr : attr
}).join(', '),
}.bind(this)).join(', '),
attributeNamesExport: Utils._.keys(attributes).map(function(attr) {
return attr
}).join(', ')
}.bind(this)).join(', ')
})
},
replaceBooleanDefaults: function(sql) {
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;
}
}
}
return Utils._.extend({}, MySqlQueryGenerator, QueryGenerator)
......
......@@ -358,6 +358,33 @@ module.exports = (function() {
}
}
// 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
var queryAndEmit = function(sqlOrQueryParams, methodName, options, emitter) {
......
......@@ -26,6 +26,7 @@ module.exports = (function() {
@param {Boolean} [options.native=false] A flag that defines if native library shall be used or not.
@param {Boolean} [options.replication=false] I have absolutely no idea.
@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.
@example
// without password and options
......@@ -78,7 +79,8 @@ module.exports = (function() {
queue: true,
native: false,
replication: false,
pool: {}
pool: {},
quoteIdentifiers: true
}, options || {})
if (this.options.logging === true) {
......
......@@ -35,18 +35,6 @@ var Utils = module.exports = {
addEventEmitter: function(_class) {
util.inherits(_class, require('events').EventEmitter)
},
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) {
var timeZone = null;
return SqlString.format(arr.shift(), arr, timeZone, dialect)
......@@ -180,6 +168,22 @@ var Utils = module.exports = {
var now = new Date()
now.setMilliseconds(0)
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, '"')
}
}
......
......@@ -398,7 +398,8 @@ describe('QueryGenerator', function() {
it(title, function() {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
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)
})
......
......@@ -68,12 +68,40 @@ describe('QueryGenerator', function() {
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'}
},
// 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}}
},
],
createTableQuery: [
{
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));",
},
{
arguments: ['mySchema.myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
......@@ -90,6 +118,33 @@ describe('QueryGenerator', function() {
{
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);"
},
// 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}}
}
],
......@@ -109,6 +164,28 @@ describe('QueryGenerator', function() {
{
arguments: ['mySchema.myTable', {cascade: true}],
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}}
}
],
......@@ -165,6 +242,78 @@ describe('QueryGenerator', function() {
}, {
arguments: ['mySchema.myTable', {where: {name: "foo';DROP TABLE mySchema.myTable;"}}],
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}}
}
],
......@@ -205,7 +354,55 @@ describe('QueryGenerator', function() {
}, {
arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}],
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}}
}
],
bulkInsertQuery: [
......@@ -245,6 +442,53 @@ describe('QueryGenerator', function() {
}, {
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 *;"
},
// 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}}
}
],
......@@ -282,6 +526,49 @@ describe('QueryGenerator', function() {
}, {
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 *"
},
// 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}}
}
],
......@@ -313,6 +600,37 @@ describe('QueryGenerator', function() {
}, {
arguments: ['myTable', {name: 'foo'}, {limit: null}],
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}}
}
],
......@@ -336,6 +654,32 @@ describe('QueryGenerator', function() {
}, {
arguments: ['mySchema.User', ['username', 'isAdmin']],
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}}
}
],
......@@ -360,6 +704,21 @@ describe('QueryGenerator', function() {
}, {
arguments: ['User', 'mySchema.user_foo_bar'],
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}}
}
],
......@@ -371,7 +730,19 @@ describe('QueryGenerator', function() {
{
arguments: [{ id: [] }],
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}}
},
]
}
......@@ -382,7 +753,9 @@ describe('QueryGenerator', function() {
it(title, function() {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
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)
})
})
......
......@@ -206,7 +206,7 @@ describe('QueryGenerator', function() {
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;'"
}, {
arguments: ['myTable', {name: 'foo'}, {limit: null}],
expectation: "DELETE FROM `myTable` WHERE `name`='foo'"
......@@ -221,8 +221,8 @@ describe('QueryGenerator', function() {
it(title, function() {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
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)
})
})
......
......@@ -9,6 +9,7 @@ buster.spec.expose()
buster.testRunner.timeout = 500
describe(Helpers.getTestDialectTeaser("HasMany"), function() {
before(function(done) {
var self = this
......
......@@ -65,4 +65,60 @@ if (dialect.match(/^postgres/)) {
})
})
})
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)
})
})
})
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!