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

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