diff --git a/sequel/index.js b/sequel/index.js index 0a2dda5..ab52338 100644 --- a/sequel/index.js +++ b/sequel/index.js @@ -36,8 +36,11 @@ var Sequel = module.exports = function(schema, options) { // should use lower or regex logic for querying. this.caseSensitive = options && utils.object.hasOwnProperty(options, 'caseSensitive') ? options.caseSensitive : true; - // Set the escape character, default is " - this.escapeCharacter = options && utils.object.hasOwnProperty(options, 'escapeCharacter') ? options.escapeCharacter : '"'; + // Set the identifier character, default is ` + this.identifierCharacter = options && utils.object.hasOwnProperty(options, 'identifierCharacter') ? options.identifierCharacter : '`'; + + // Set the escape character, default is ' + this.escapeCharacter = options && utils.object.hasOwnProperty(options, 'escapeCharacter') ? options.escapeCharacter : '\''; // Set if the database can return values from things such as an insert this.canReturnValues = options && utils.object.hasOwnProperty(options, 'canReturnValues') ? options.canReturnValues : false; @@ -46,7 +49,7 @@ var Sequel = module.exports = function(schema, options) { this.escapeInserts = options && utils.object.hasOwnProperty(options, 'escapeInserts') ? options.escapeInserts : false; // Determine if aliased tablenames in DELETE queries need to be referenced before the FROM, e.g. - // DELETE `tableName` FROM `tableName` as `otherTableName` WHERE `otherTableName`.`foo` = "bar" + // DELETE `tableName` FROM `tableName` as `otherTableName` WHERE `otherTableName`.`foo` = 'bar' // MySQL and Oracle require this, but it doesn't work in Postgresql. this.declareDeleteAlias = options && utils.object.hasOwnProperty(options, 'declareDeleteAlias') ? options.declareDeleteAlias : true; @@ -116,8 +119,8 @@ Sequel.prototype.find = function find(currentTable, queryObject) { Sequel.prototype.count = function count(currentTable, queryObject) { // Escape table name - var tableName = utils.escapeName(this.schema[currentTable].tableName, this.escapeCharacter, this.schemaName); - var alias = utils.escapeName(this.schema[currentTable].tableName, this.escapeCharacter); + var tableName = utils.escapeName(this.schema[currentTable].tableName, this.identifierCharacter, this.schemaName); + var alias = utils.escapeName(this.schema[currentTable].tableName, this.identifierCharacter); // Step 1: // Build out the Count statements @@ -164,7 +167,7 @@ Sequel.prototype.create = function create(currentTable, data) { var options = { parameterized: this.parameterized, - escapeCharacter: this.escapeCharacter, + identifierCharacter: this.identifierCharacter, escapeInserts: this.escapeInserts, schemaName: this.schemaName }; @@ -175,7 +178,7 @@ Sequel.prototype.create = function create(currentTable, data) { var paramValues = attributes.params.join(', '); // Build Query - var query = 'INSERT INTO ' + utils.escapeName(currentTable, this.escapeCharacter, this.schemaName) + ' (' + columnNames + ') values (' + paramValues + ')'; + var query = 'INSERT INTO ' + utils.escapeName(currentTable, this.identifierCharacter, this.schemaName) + ' (' + columnNames + ') values (' + paramValues + ')'; if(this.canReturnValues) { query += ' RETURNING *'; @@ -192,7 +195,7 @@ Sequel.prototype.update = function update(currentTable, queryObject, data) { var options = { parameterized: this.parameterized, - escapeCharacter: this.escapeCharacter, + identifierCharacter: this.identifierCharacter, escapeInserts: this.escapeInserts, schemaName: this.schemaName }; @@ -200,7 +203,7 @@ Sequel.prototype.update = function update(currentTable, queryObject, data) { // Get the attribute identity (as opposed to the table name) var identity = currentTable; // Create the query with the tablename aliased as the identity (in case they are different) - var query = 'UPDATE ' + utils.escapeName(currentTable, this.escapeCharacter, this.schemaName) + ' AS ' + utils.escapeName(identity, this.escapeCharacter) + ' '; + var query = 'UPDATE ' + utils.escapeName(currentTable, this.identifierCharacter, this.schemaName) + ' AS ' + utils.escapeName(identity, this.identifierCharacter) + ' '; // Transform the Data object into arrays used in a parameterized query var attributes = utils.mapAttributes(data, options); @@ -248,7 +251,7 @@ Sequel.prototype.destroy = function destroy(currentTable, queryObject) { // Get the attribute identity (as opposed to the table name) var identity = currentTable; - var query = 'DELETE ' + (this.declareDeleteAlias ? utils.escapeName(identity, this.escapeCharacter) : '') + ' FROM ' + utils.escapeName(currentTable, this.escapeCharacter, this.schemaName) + ' AS ' + utils.escapeName(identity, this.escapeCharacter) + ' '; + var query = 'DELETE ' + (this.declareDeleteAlias ? utils.escapeName(identity, this.identifierCharacter) : '') + ' FROM ' + utils.escapeName(currentTable, this.identifierCharacter, this.schemaName) + ' AS ' + utils.escapeName(identity, this.identifierCharacter) + ' '; // Build Criteria clause var whereObject = this.simpleWhere(currentTable, queryObject); @@ -273,7 +276,7 @@ Sequel.prototype.destroy = function destroy(currentTable, queryObject) { Sequel.prototype.select = function select(currentTable, queryObject) { var options = { - escapeCharacter: this.escapeCharacter, + identifierCharacter: this.identifierCharacter, caseSensitive: this.caseSensitive, cast: this.cast, wlNext: this.wlNext, @@ -291,7 +294,7 @@ Sequel.prototype.simpleWhere = function simpleWhere(currentTable, queryObject, o var _options = { parameterized: this.parameterized, caseSensitive: this.caseSensitive, - escapeCharacter: this.escapeCharacter, + identifierCharacter: this.identifierCharacter, wlNext: this.wlNext, schemaName: this.schemaName }; @@ -304,7 +307,7 @@ Sequel.prototype.complexWhere = function complexWhere(currentTable, queryObject, var _options = { parameterized: this.parameterized, caseSensitive: this.caseSensitive, - escapeCharacter: this.escapeCharacter, + identifierCharacter: this.identifierCharacter, schemaName: this.schemaName }; diff --git a/sequel/lib/cast.js b/sequel/lib/cast.js index e39d41d..d4e64f1 100644 --- a/sequel/lib/cast.js +++ b/sequel/lib/cast.js @@ -1,7 +1,7 @@ /** * Cast special values to proper types. * - * Ex: Array is stored as "[0,1,2,3]" and should be cast to proper + * Ex: Array is stored as '[0,1,2,3]' and should be cast to proper * array for return values. */ @@ -27,7 +27,7 @@ Query.prototype.cast = function(values) { Query.prototype.castValue = function(key, value, attributes, schema, joinKey) { - // Check if key is a special "join" key, identified with a '__' split + // Check if key is a special 'join' key, identified with a '__' split var attr = key.split('__'); if(attr.length === 2) { @@ -38,7 +38,7 @@ Query.prototype.castValue = function(key, value, attributes, schema, joinKey) { } } - // Lookup Schema "Type" + // Lookup Schema 'Type' if(!schema[key]) return; var type = schema[key].type; if(!type) return; diff --git a/sequel/lib/criteriaProcessor.js b/sequel/lib/criteriaProcessor.js index c158ac3..ead706d 100644 --- a/sequel/lib/criteriaProcessor.js +++ b/sequel/lib/criteriaProcessor.js @@ -43,7 +43,8 @@ var CriteriaProcessor = module.exports = function CriteriaProcessor(currentTable this.paramCount = 1; this.parameterized = true; this.caseSensitive = true; - this.escapeCharacter = '"'; + this.identifierCharacter = '`'; + this.escapeCharacter = '\''; this.wlNext = {}; if(options && utils.object.hasOwnProperty(options, 'parameterized')) { @@ -54,8 +55,8 @@ var CriteriaProcessor = module.exports = function CriteriaProcessor(currentTable this.caseSensitive = options.caseSensitive; } - if(options && utils.object.hasOwnProperty(options, 'escapeCharacter')) { - this.escapeCharacter = options.escapeCharacter; + if(options && utils.object.hasOwnProperty(options, 'identifierCharacter')) { + this.identifierCharacter = options.identifierCharacter; } if(options && utils.object.hasOwnProperty(options, 'paramCount')) { @@ -325,13 +326,13 @@ CriteriaProcessor.prototype._in = function _in(key, val) { // Check case sensitivity to decide if LOWER logic is used if(!caseSensitivity) { if(lower) { - key = 'LOWER(' + utils.escapeName(self.getTableAlias(), self.escapeCharacter, self.schemaName) + '.' + utils.escapeName(key, self.escapeCharacter) + ')'; + key = 'LOWER(' + utils.escapeName(self.getTableAlias(), self.identifierCharacter, self.schemaName) + '.' + utils.escapeName(key, self.identifierCharacter) + ')'; } else { - key = utils.escapeName(self.getTableAlias(), self.escapeCharacter, self.schemaName) + '.' + utils.escapeName(key, self.escapeCharacter); + key = utils.escapeName(self.getTableAlias(), self.identifierCharacter, self.schemaName) + '.' + utils.escapeName(key, self.identifierCharacter); } self.queryString += key + ' IN ('; } else { - self.queryString += utils.escapeName(self.getTableAlias(), self.escapeCharacter, self.schemaName) + '.' + utils.escapeName(key, self.escapeCharacter) + ' IN ('; + self.queryString += utils.escapeName(self.getTableAlias(), self.identifierCharacter, self.schemaName) + '.' + utils.escapeName(key, self.identifierCharacter) + ' IN ('; } // Append each value to query @@ -349,7 +350,7 @@ CriteriaProcessor.prototype._in = function _in(key, val) { } else { if(_.isString(value)) { - value = '"' + utils.escapeString(value) + '"'; + value = utils.wrapValue(utils.escapeString(value), self.escapeCharacter); } self.queryString += value + ','; @@ -377,7 +378,7 @@ CriteriaProcessor.prototype.buildParam = function buildParam (tableName, propert var escape = utils.escapeName, param; - param = escape(tableName, this.escapeCharacter, this.schemaName) + '.' + escape(property, this.escapeCharacter); + param = escape(tableName, this.identifierCharacter, this.schemaName) + '.' + escape(property, this.identifierCharacter); if (caseSensitive) { param = 'LOWER(' + param + ')'; @@ -483,7 +484,7 @@ CriteriaProcessor.prototype.processSimple = function processSimple (tableName, p } if (_.isString(value)) { - value = '"' + utils.escapeString(value) +'"'; + value = utils.wrapValue(utils.escapeString(value), self.escapeCharacter); } this.queryString += parent + ' ' + combinator + ' ' + value; @@ -511,7 +512,7 @@ CriteriaProcessor.prototype.processObject = function processObject (tableName, p // Expand criteria object function expandCriteria (obj) { var child = self.findChild(parent), - sensitiveTypes = ['text', 'string'], // haha, "sensitive types". "I'll watch 'the notebook' with you, babe." + sensitiveTypes = ['text', 'string'], // haha, 'sensitive types'. 'I'll watch \'the notebook\' with you, babe.' lower; _.keys(obj).forEach(function(key) { @@ -606,7 +607,7 @@ CriteriaProcessor.prototype.prepareCriterion = function prepareCriterion(key, va ('00' + value.getMinutes()).slice(-2) + ':' + ('00' + value.getSeconds()).slice(-2); - value = '"' + value + '"'; + value = utils.wrapValue(value, self.escapeCharacter); escapedDate = true; } @@ -621,7 +622,7 @@ CriteriaProcessor.prototype.prepareCriterion = function prepareCriterion(key, va } else { if(_.isString(value) && !escapedDate) { - value = '"' + utils.escapeString(value) + '"'; + value = utils.wrapValue(utils.escapeString(value), self.escapeCharacter); } str = '< ' + value; } @@ -637,7 +638,7 @@ CriteriaProcessor.prototype.prepareCriterion = function prepareCriterion(key, va } else { if(_.isString(value) && !escapedDate) { - value = '"' + utils.escapeString(value) + '"'; + value = utils.wrapValue(utils.escapeString(value), self.escapeCharacter); } str = '<= ' + value; } @@ -653,7 +654,7 @@ CriteriaProcessor.prototype.prepareCriterion = function prepareCriterion(key, va } else { if(_.isString(value) && !escapedDate) { - value = '"' + utils.escapeString(value) + '"'; + value = utils.wrapValue(utils.escapeString(value), self.escapeCharacter); } str = '> ' + value; } @@ -669,7 +670,7 @@ CriteriaProcessor.prototype.prepareCriterion = function prepareCriterion(key, va } else { if(_.isString(value) && !escapedDate) { - value = '"' + utils.escapeString(value) + '"'; + value = utils.wrapValue(utils.escapeString(value), self.escapeCharacter);; } str = '>= ' + value; } @@ -706,7 +707,7 @@ CriteriaProcessor.prototype.prepareCriterion = function prepareCriterion(key, va value.forEach(function(val) { if(_.isString(val)) { - val = '"' + utils.escapeString(val) + '"'; + val = utils.wrapValue(utils.escapeString(val), self.escapeCharacter); } str += val + ','; @@ -724,7 +725,7 @@ CriteriaProcessor.prototype.prepareCriterion = function prepareCriterion(key, va } else { if(_.isString(value)) { - value = '"' + utils.escapeString(value) + '"'; + value = utils.wrapValue(utils.escapeString(value), self.escapeCharacter);; } str = '<> ' + value; @@ -754,7 +755,7 @@ CriteriaProcessor.prototype.prepareCriterion = function prepareCriterion(key, va } else { // Note that wildcards are not escaped out of like criterion intentionally - str = comparator + ' "' + utils.escapeString(value) + '"'; + str = comparator + ' ' + utils.wrapValue(utils.escapeString(value), self.escapeCharacter); } break; @@ -778,7 +779,7 @@ CriteriaProcessor.prototype.prepareCriterion = function prepareCriterion(key, va str = comparator + ' ' + '$' + this.paramCount; } else { - str = comparator + ' "%' + utils.escapeString(value, true) + '%"'; + str = comparator + ' ' + utils.wrapValue('%' + utils.escapeString(value, true) + '%', self.escapeCharacter); } break; @@ -802,7 +803,7 @@ CriteriaProcessor.prototype.prepareCriterion = function prepareCriterion(key, va str = comparator + ' ' + '$' + this.paramCount; } else { - str = comparator + ' "' + utils.escapeString(value, true) + '%"'; + str = comparator + ' ' + utils.wrapValue(utils.escapeString(value, true) + '%', self.escapeCharacter); } break; @@ -826,10 +827,15 @@ CriteriaProcessor.prototype.prepareCriterion = function prepareCriterion(key, va str = comparator + ' ' + '$' + this.paramCount; } else { - str = comparator + ' "%' + utils.escapeString(value, true) + '"'; + str = comparator + ' ' + utils.wrapValue('%' + utils.escapeString(value, true), self.escapeCharacter); } break; + + default: + var err = new Error('Unknown filtering operator: \'' + key + '\'. Should be \'startsWith\', \'>\', \'contains\' or similar'); + err.operator = key; + throw err; } // Bump paramCount @@ -877,7 +883,7 @@ CriteriaProcessor.prototype.sort = function(options) { keys.forEach(function(key) { var direction = options[key] === 1 ? 'ASC' : 'DESC'; - self.queryString += utils.escapeName(self.currentTable, self.escapeCharacter, self.schemaName) + '.' + utils.escapeName(key, self.escapeCharacter) + ' ' + direction + ', '; + self.queryString += utils.escapeName(self.currentTable, self.identifierCharacter, self.schemaName) + '.' + utils.escapeName(key, self.identifierCharacter) + ' ' + direction + ', '; }); // Remove trailing comma @@ -899,7 +905,7 @@ CriteriaProcessor.prototype.group = function(options) { options.forEach(function(key) { // Check whether we are grouping by a column or an expression. if (_.includes(_.keys(self.currentSchema), key)) { - self.queryString += utils.escapeName(self.currentTable, self.escapeCharacter, self.schemaName) + '.' + utils.escapeName(key, self.escapeCharacter) + ', '; + self.queryString += utils.escapeName(self.currentTable, self.identifierCharacter, self.schemaName) + '.' + utils.escapeName(key, self.identifierCharacter) + ', '; } else { self.queryString += key + ', '; } diff --git a/sequel/lib/utils.js b/sequel/lib/utils.js index a142d2e..965bf61 100644 --- a/sequel/lib/utils.js +++ b/sequel/lib/utils.js @@ -42,15 +42,26 @@ utils.object.hasOwnProperty = function(obj, prop) { * peek at https://dev.mysql.com/doc/refman/5.7/en/identifiers.html . */ -utils.escapeName = function escapeName(name, escapeCharacter, schemaName) { - var regex = new RegExp(escapeCharacter, 'g'); - var replacementString = '' + escapeCharacter + escapeCharacter; +utils.escapeName = function escapeName(name, identifierCharacter, schemaName) { + var regex = new RegExp(identifierCharacter, 'g'); + var replacementString = '' + identifierCharacter + identifierCharacter; var replacementDot = '\.'; if (schemaName && schemaName[name]) { - return utils.escapeName(schemaName[name], escapeCharacter) + '.' + - utils.escapeName(name, escapeCharacter); + return utils.escapeName(schemaName[name], identifierCharacter) + '.' + + utils.escapeName(name, identifierCharacter); } - return '' + escapeCharacter + name.replace(regex, replacementString).replace(/\./g, replacementDot) + escapeCharacter; + return '' + identifierCharacter + name.replace(regex, replacementString).replace(/\./g, replacementDot) + identifierCharacter; +}; + +/** +* Return wrapped string value with escapeCharacter +* +* @param {String} value +* @param {String} escapeCharacter +* @return {String} +*/ +utils.wrapValue = function wrapValue(value, escapeCharacter) { + return escapeCharacter + value + escapeCharacter; }; /** @@ -82,13 +93,13 @@ utils.mapAttributes = function(data, options) { var parameterized = options && utils.object.hasOwnProperty(options, 'parameterized') ? options.parameterized : true; // Get the escape character - var escapeCharacter = options && utils.object.hasOwnProperty(options, 'escapeCharacter') ? options.escapeCharacter : '"'; + var identifierCharacter = options && utils.object.hasOwnProperty(options, 'identifierCharacter') ? options.identifierCharacter : '`'; // Determine if we should escape the inserted characters var escapeInserts = options && utils.object.hasOwnProperty(options, 'escapeInserts') ? options.escapeInserts : false; Object.keys(data).forEach(function(key) { - var k = escapeInserts ? (options.escapeCharacter + key + options.escapeCharacter) : key; + var k = escapeInserts ? (options.identifierCharacter + key + options.identifierCharacter) : key; keys.push(k); var value = utils.prepareValue(data[key]); @@ -154,15 +165,15 @@ utils.escapeString = function(value, forLike) { value = value.replace(/[_%\0\n\r\b\t\\\'\"\x1a]/g, function(s) { switch(s) { - case "\0": return "\\0"; - case "\n": return "\\n"; - case "\r": return "\\r"; - case "\b": return "\\b"; - case "\t": return "\\t"; - case "\x1a": return "\\Z"; - case "%": return forLike ? "\\%" : "%"; - case "_": return forLike ? "\\_" : "_"; - default: return "\\"+s; + case '\0': return '\\0'; + case '\n': return '\\n'; + case '\r': return '\\r'; + case '\b': return '\\b'; + case '\t': return '\\t'; + case '\x1a': return '\\Z'; + case '%': return forLike ? '\\%' : '%'; + case '_': return forLike ? '\\_' : '_'; + default: return '\\'+s; } }); diff --git a/sequel/select.js b/sequel/select.js index cd54434..0a28f8a 100644 --- a/sequel/select.js +++ b/sequel/select.js @@ -17,10 +17,15 @@ var SelectBuilder = module.exports = function(schema, currentTable, queryObject, this.schema = schema; this.currentSchema = schema[currentTable].attributes; this.currentTable = currentTable; - this.escapeCharacter = '"'; + this.identifierCharacter = '`'; + this.escapeCharacter = '\''; this.cast = false; this.wlNext = {}; + if(options && hop(options, 'identifierCharacter')) { + this.identifierCharacter = options.identifierCharacter; + } + if(options && hop(options, 'escapeCharacter')) { this.escapeCharacter = options.escapeCharacter; } @@ -61,7 +66,7 @@ SelectBuilder.prototype.buildSimpleSelect = function buildSimpleSelect(queryObje } // Escape table name - var tableName = utils.escapeName(self.schema[self.currentTable].tableName, self.escapeCharacter, self.schemaName); + var tableName = utils.escapeName(self.schema[self.currentTable].tableName, self.identifierCharacter, self.schemaName); var selectKeys = []; var query = 'SELECT '; @@ -100,14 +105,21 @@ SelectBuilder.prototype.buildSimpleSelect = function buildSimpleSelect(queryObje selectKeys.forEach(function(select) { // If there is an alias, set it in the select (used for hasFK associations) if(select.alias) { - query += utils.escapeName(select.table, self.escapeCharacter) + '.' + utils.escapeName(select.key, self.escapeCharacter) + ' AS ' + self.escapeCharacter + select.alias + '___' + select.key + self.escapeCharacter + ', '; + query += utils.escapeName(select.table, self.identifierCharacter) + '.' + utils.escapeName(select.key, self.identifierCharacter) + ' AS ' + self.identifierCharacter + select.alias + '___' + select.key + self.identifierCharacter + ', '; } else { - query += utils.escapeName(select.table, self.escapeCharacter) + '.' + utils.escapeName(select.key, self.escapeCharacter) + ', '; + query += utils.escapeName(select.table, self.identifierCharacter) + '.' + utils.escapeName(select.key, self.identifierCharacter) + ', '; } }); - // Remove the last comma - query = query.slice(0, -2) + ' FROM ' + tableName + ' AS ' + utils.escapeName(self.currentTable, self.escapeCharacter) + ' '; + + // Remove the last comma in a list of items being selected + if(selectKeys.length) { + query = query.slice(0, -2); + } else { + query += '*'; + } + + query = query + ' FROM ' + tableName + ' AS ' + utils.escapeName(self.currentTable, self.identifierCharacter) + ' '; return query; }; @@ -132,7 +144,7 @@ SelectBuilder.prototype.processAggregates = function processAggregates(criteria) var query = 'SELECT '; - var tableName = utils.escapeName(this.currentTable, this.escapeCharacter); + var tableName = utils.escapeName(this.currentTable, this.identifierCharacter); // Append groupBy columns to select statement if(criteria.groupBy) { @@ -141,7 +153,7 @@ SelectBuilder.prototype.processAggregates = function processAggregates(criteria) criteria.groupBy.forEach(function(key, index) { // Check whether we are grouping by a column or an expression. if (_.includes(_.keys(self.currentSchema), key)) { - query += tableName + '.' + utils.escapeName(key, self.escapeCharacter) + ', '; + query += tableName + '.' + utils.escapeName(key, self.identifierCharacter) + ', '; } else { query += key + ' as group' + index + ', '; } @@ -153,7 +165,7 @@ SelectBuilder.prototype.processAggregates = function processAggregates(criteria) var sum = ''; if(Array.isArray(criteria.sum)) { criteria.sum.forEach(function(opt) { - sum = 'SUM(' + tableName + '.' + utils.escapeName(opt, self.escapeCharacter) + ')'; + sum = 'SUM(' + tableName + '.' + utils.escapeName(opt, self.identifierCharacter) + ')'; if(self.cast) { sum = 'CAST(' + sum + ' AS float)'; } @@ -161,7 +173,7 @@ SelectBuilder.prototype.processAggregates = function processAggregates(criteria) }); } else { - sum = 'SUM(' + tableName + '.' + utils.escapeName(criteria.sum, self.escapeCharacter) + ')'; + sum = 'SUM(' + tableName + '.' + utils.escapeName(criteria.sum, self.identifierCharacter) + ')'; if(self.cast) { sum = 'CAST(' + sum + ' AS float)'; } @@ -174,14 +186,14 @@ SelectBuilder.prototype.processAggregates = function processAggregates(criteria) var avg = ''; if(Array.isArray(criteria.average)) { criteria.average.forEach(function(opt){ - avg = 'AVG(' + tableName + '.' + utils.escapeName(opt, self.escapeCharacter) + ')'; + avg = 'AVG(' + tableName + '.' + utils.escapeName(opt, self.identifierCharacter) + ')'; if(self.cast) { avg = 'CAST( ' + avg + ' AS float)'; } query += avg + ' AS ' + opt + ', '; }); } else { - avg = 'AVG(' + tableName + '.' + utils.escapeName(criteria.average, self.escapeCharacter) + ')'; + avg = 'AVG(' + tableName + '.' + utils.escapeName(criteria.average, self.identifierCharacter) + ')'; if(self.cast) { avg = 'CAST( ' + avg + ' AS float)'; } @@ -194,11 +206,11 @@ SelectBuilder.prototype.processAggregates = function processAggregates(criteria) var max = ''; if(Array.isArray(criteria.max)) { criteria.max.forEach(function(opt){ - query += 'MAX(' + tableName + '.' + utils.escapeName(opt, self.escapeCharacter) + ') AS ' + opt + ', '; + query += 'MAX(' + tableName + '.' + utils.escapeName(opt, self.identifierCharacter) + ') AS ' + opt + ', '; }); } else { - query += 'MAX(' + tableName + '.' + utils.escapeName(criteria.max, self.escapeCharacter) + ') AS ' + criteria.max + ', '; + query += 'MAX(' + tableName + '.' + utils.escapeName(criteria.max, self.identifierCharacter) + ') AS ' + criteria.max + ', '; } } @@ -206,11 +218,11 @@ SelectBuilder.prototype.processAggregates = function processAggregates(criteria) if (criteria.min) { if(Array.isArray(criteria.min)) { criteria.min.forEach(function(opt){ - query += 'MIN(' + tableName + '.' + utils.escapeName(opt, self.escapeCharacter) + ') AS ' + opt + ', '; + query += 'MIN(' + tableName + '.' + utils.escapeName(opt, self.identifierCharacter) + ') AS ' + opt + ', '; }); } else { - query += 'MIN(' + tableName + '.' + utils.escapeName(criteria.min, self.escapeCharacter) + ') AS ' + criteria.min + ', '; + query += 'MIN(' + tableName + '.' + utils.escapeName(criteria.min, self.identifierCharacter) + ') AS ' + criteria.min + ', '; } } @@ -218,6 +230,6 @@ SelectBuilder.prototype.processAggregates = function processAggregates(criteria) query = query.slice(0, -2) + ' '; // Add FROM clause - query += 'FROM ' + utils.escapeName(self.schema[self.currentTable].tableName, self.escapeCharacter, self.schemaName) + ' AS ' + tableName + ' '; + query += 'FROM ' + utils.escapeName(self.schema[self.currentTable].tableName, self.identifierCharacter, self.schemaName) + ' AS ' + tableName + ' '; return query; }; diff --git a/sequel/where.js b/sequel/where.js index 39f8e59..acfecc5 100644 --- a/sequel/where.js +++ b/sequel/where.js @@ -49,7 +49,8 @@ var WhereBuilder = module.exports = function WhereBuilder(schema, currentTable, this.schema = schema; this.currentTable = currentTable; - + this.identifierCharacter = '`'; + this.escapeCharacter = '\''; this.wlNext = {}; if(options && hop(options, 'parameterized')) { @@ -60,6 +61,10 @@ var WhereBuilder = module.exports = function WhereBuilder(schema, currentTable, this.caseSensitive = options.caseSensitive; } + if(options && hop(options, 'identifierCharacter')) { + this.identifierCharacter = options.identifierCharacter; + } + if(options && hop(options, 'escapeCharacter')) { this.escapeCharacter = options.escapeCharacter; } @@ -97,16 +102,16 @@ WhereBuilder.prototype.single = function single(queryObject, options) { var strategy = queryObject.instructions[attr].strategy.strategy; var population = queryObject.instructions[attr].instructions[0]; - var alias = utils.escapeName(utils.populationAlias(population.alias), self.escapeCharacter, self.schemaName); + var alias = utils.escapeName(utils.populationAlias(population.alias), self.identifierCharacter, self.schemaName); var parentAlias = _.find(_.values(self.schema), {tableName: population.parent}).tableName; // Handle hasFK if(strategy === 1) { // Set outer join logic - queryString += 'LEFT OUTER JOIN ' + utils.escapeName(population.child, self.escapeCharacter, self.schemaName) + ' AS ' + alias + ' ON '; - queryString += utils.escapeName(parentAlias, self.escapeCharacter) + '.' + utils.escapeName(population.parentKey, self.escapeCharacter); - queryString += ' = ' + alias + '.' + utils.escapeName(population.childKey, self.escapeCharacter); + queryString += 'LEFT OUTER JOIN ' + utils.escapeName(population.child, self.identifierCharacter, self.schemaName) + ' AS ' + alias + ' ON '; + queryString += utils.escapeName(parentAlias, self.identifierCharacter) + '.' + utils.escapeName(population.parentKey, self.identifierCharacter); + queryString += ' = ' + alias + '.' + utils.escapeName(population.childKey, self.identifierCharacter); addSpace = true; } @@ -145,7 +150,7 @@ WhereBuilder.prototype.single = function single(queryObject, options) { var _options = _.assign({ parameterized: this.parameterized, caseSensitive: this.caseSensitive, - escapeCharacter: this.escapeCharacter, + identifierCharacter: this.identifierCharacter, wlNext: this.wlNext }, options); @@ -210,7 +215,7 @@ WhereBuilder.prototype.complex = function complex(queryObject, options) { _options = _.assign({ parameterized: self.parameterized, caseSensitive: self.caseSensitive, - escapeCharacter: self.escapeCharacter, + identifierCharacter: self.identifierCharacter, wlNext: self.wlNext }, options); @@ -233,7 +238,45 @@ WhereBuilder.prototype.complex = function complex(queryObject, options) { // Read the queryObject and get back a query string and params parsedCriteria = criteriaParser.read(population.criteria); - queryString = '(SELECT * FROM ' + utils.escapeName(population.child, self.escapeCharacter, self.schemaName) + ' AS ' + utils.escapeName(populationAlias, self.escapeCharacter) + ' WHERE ' + utils.escapeName(population.childKey, self.escapeCharacter) + ' = ^?^ '; + queryString = '(SELECT '; + if(_.isArray(population.select) && population.select.length) { + var selectKeys = population.select.map(function(projection) { + return { table: population.child, key: projection }; + }); + + _.each(selectKeys, function(projection) { + var projectionAlias = _.find(_.values(self.schema), {tableName: projection.table}).tableName; + + // Find the projection in the schema and make sure it's a valid key + // that can be selected. + var schema = self.schema[projection.table]; + if(!schema || !schema.definition) { + return; + } + + var schemaVal = schema.definition[projection.key]; + if(!schemaVal) { + return; + } + + // If this is a virtual attribute, it can't be selected + if(_.has(schemaVal, 'collection')) { + return; + } + + queryString += utils.escapeName(projectionAlias, self.identifierCharacter) + '.' + + utils.escapeName(projection.key, self.identifierCharacter) + ','; + }); + // remove trailing comma + population.select.length && (queryString.slice(-1) === ',') && (queryString = queryString.slice(0, -1)); + } + else { + queryString += '*'; + } + + // Build the rest of the query string + queryString += ' FROM ' + utils.escapeName(population.child, self.identifierCharacter, self.schemaName) + ' AS ' + utils.escapeName(populationAlias, self.identifierCharacter) + ' WHERE ' + utils.escapeName(population.childKey, self.identifierCharacter) + ' = ^?^ '; + if(parsedCriteria) { // If where criteria was used append an AND clause @@ -267,7 +310,7 @@ WhereBuilder.prototype.complex = function complex(queryObject, options) { _options = _.assign({ parameterized: self.parameterized, caseSensitive: self.caseSensitive, - escapeCharacter: self.escapeCharacter, + identifierCharacter: self.identifierCharacter, wlNext: self.wlNext }, options); @@ -302,16 +345,34 @@ WhereBuilder.prototype.complex = function complex(queryObject, options) { queryString += '(SELECT '; selectKeys.forEach(function(projection) { var projectionAlias = _.find(_.values(self.schema), {tableName: projection.table}).tableName; - queryString += utils.escapeName(projectionAlias, self.escapeCharacter) + '.' + utils.escapeName(projection.key, self.escapeCharacter) + ','; + + // Find the projection in the schema and make sure it's a valid key + // that can be selected. + var schema = self.schema[projection.table]; + if(!schema || !schema.definition) { + return; + } + + var schemaVal = schema.definition[projection.key]; + if(!schemaVal) { + return; + } + + // If this is a virtual attribute, it can't be selected + if(_.has(schemaVal, 'collection')) { + return; + } + + queryString += utils.escapeName(projectionAlias, self.identifierCharacter) + '.' + utils.escapeName(projection.key, self.identifierCharacter) + ','; }); // Add an inner join to give us a key to select from - queryString += utils.escapeName(stage1.child, self.escapeCharacter, self.schemaName) + '.' + utils.escapeName(stage1.childKey, self.escapeCharacter) + ' AS "___' + stage1.childKey + '"'; + queryString += utils.escapeName(stage1.child, self.identifierCharacter, self.schemaName) + '.' + utils.escapeName(stage1.childKey, self.identifierCharacter) + ' AS ' + utils.wrapValue('___' + stage1.childKey, self.escapeCharacter); - queryString += ' FROM ' + utils.escapeName(stage2.child, self.escapeCharacter, self.schemaName) + ' AS ' + utils.escapeName(stage2ChildAlias, self.escapeCharacter) + ' '; - queryString += ' INNER JOIN ' + utils.escapeName(stage1.child, self.escapeCharacter, self.schemaName) + ' ON ' + utils.escapeName(stage2.parent, self.escapeCharacter, self.schemaName); - queryString += '.' + utils.escapeName(stage2.parentKey, self.escapeCharacter) + ' = ' + utils.escapeName(stage2ChildAlias, self.escapeCharacter) + '.' + utils.escapeName(stage2.childKey, self.escapeCharacter); - queryString += ' WHERE ' + utils.escapeName(stage1.child, self.escapeCharacter, self.schemaName) + '.' + utils.escapeName(stage1.childKey, self.escapeCharacter) + ' = ^?^ '; + queryString += ' FROM ' + utils.escapeName(stage2.child, self.identifierCharacter, self.schemaName) + ' AS ' + utils.escapeName(stage2ChildAlias, self.identifierCharacter) + ' '; + queryString += ' INNER JOIN ' + utils.escapeName(stage1.child, self.identifierCharacter, self.schemaName) + ' ON ' + utils.escapeName(stage2.parent, self.identifierCharacter, self.schemaName); + queryString += '.' + utils.escapeName(stage2.parentKey, self.identifierCharacter) + ' = ' + utils.escapeName(stage2ChildAlias, self.identifierCharacter) + '.' + utils.escapeName(stage2.childKey, self.identifierCharacter); + queryString += ' WHERE ' + utils.escapeName(stage1.child, self.identifierCharacter, self.schemaName) + '.' + utils.escapeName(stage1.childKey, self.identifierCharacter) + ' = ^?^ '; if(parsedCriteria) { diff --git a/test/options.js b/test/options.js index 8675552..b16fccd 100644 --- a/test/options.js +++ b/test/options.js @@ -1,7 +1,8 @@ module.exports = { parameterized : false, caseSensitive : false, - escapeCharacter: '`', + identifierCharacter: '`', + escapeCharacter: '\'', casting : false, canReturnValues: false, escapeInserts : true diff --git a/test/queries/complexSelectFilters.js b/test/queries/complexSelectFilters.js index dc2c0e9..63d5847 100644 --- a/test/queries/complexSelectFilters.js +++ b/test/queries/complexSelectFilters.js @@ -64,7 +64,7 @@ module.exports = { find : { // The queryString we expect to be rendered after calling `Sequel.find()` - queryString : 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz`, `__bat`.`color_g` AS `bat___color_g`, `__bat`.`color_h` AS `bat___color_h`, `__bat`.`color_i` AS `bat___color_i`, `__bat`.`id` AS `bat___id`, `__bat`.`createdAt` AS `bat___createdAt`, `__bat`.`updatedAt` AS `bat___updatedAt` FROM `foo` AS `foo` LEFT OUTER JOIN `bat` AS `__bat` ON `foo`.`bat` = `__bat`.`id` WHERE `foo`.`bat` = 1 AND `foo`.`baz` IN (1,2,3,4) AND ((LOWER(`foo`.`color`) = "red") OR (LOWER(`foo`.`color`) = "blue") OR (LOWER(`foo`.`color`) = "grey") OR (LOWER(`foo`.`color`) > "111" )) ', + queryString : 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz`, `__bat`.`color_g` AS `bat___color_g`, `__bat`.`color_h` AS `bat___color_h`, `__bat`.`color_i` AS `bat___color_i`, `__bat`.`id` AS `bat___id`, `__bat`.`createdAt` AS `bat___createdAt`, `__bat`.`updatedAt` AS `bat___updatedAt` FROM `foo` AS `foo` LEFT OUTER JOIN `bat` AS `__bat` ON `foo`.`bat` = `__bat`.`id` WHERE `foo`.`bat` = 1 AND `foo`.`baz` IN (1,2,3,4) AND ((LOWER(`foo`.`color`) = \'red\') OR (LOWER(`foo`.`color`) = \'blue\') OR (LOWER(`foo`.`color`) = \'grey\') OR (LOWER(`foo`.`color`) > \'111\' )) ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 diff --git a/test/queries/complexSelectNestedFilters.js b/test/queries/complexSelectNestedFilters.js index 1b762f6..0c69f07 100644 --- a/test/queries/complexSelectNestedFilters.js +++ b/test/queries/complexSelectNestedFilters.js @@ -87,7 +87,7 @@ module.exports = { find : { // The queryString we expect to be rendered after calling `Sequel.find()` - queryString : 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz`, `__bat`.`color_g` AS `bat___color_g`, `__bat`.`color_h` AS `bat___color_h`, `__bat`.`color_i` AS `bat___color_i`, `__bat`.`id` AS `bat___id`, `__bat`.`createdAt` AS `bat___createdAt`, `__bat`.`updatedAt` AS `bat___updatedAt` FROM `foo` AS `foo` LEFT OUTER JOIN `bat` AS `__bat` ON `foo`.`bat` = `__bat`.`id` WHERE ((LOWER(`foo`.`color`) = "red") OR (LOWER(`foo`.`color`) = "blue") OR (LOWER(`foo`.`color`) = "grey") OR (`__bat`.`color_g` = "yellow" ) OR (`__bat`.`color_g` = "blue" )) AND `__bat`.`color_h` = "red" AND ((`__bat`.`color_i` IN ("pink","purple","green")) OR (`__bat`.`color_i` > "black" ) OR (`__bat`.`color_i` = "yellow")) ', + queryString : 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz`, `__bat`.`color_g` AS `bat___color_g`, `__bat`.`color_h` AS `bat___color_h`, `__bat`.`color_i` AS `bat___color_i`, `__bat`.`id` AS `bat___id`, `__bat`.`createdAt` AS `bat___createdAt`, `__bat`.`updatedAt` AS `bat___updatedAt` FROM `foo` AS `foo` LEFT OUTER JOIN `bat` AS `__bat` ON `foo`.`bat` = `__bat`.`id` WHERE ((LOWER(`foo`.`color`) = \'red\') OR (LOWER(`foo`.`color`) = \'blue\') OR (LOWER(`foo`.`color`) = \'grey\') OR (`__bat`.`color_g` = \'yellow\' ) OR (`__bat`.`color_g` = \'blue\' )) AND `__bat`.`color_h` = \'red\' AND ((`__bat`.`color_i` IN (\'pink\',\'purple\',\'green\')) OR (`__bat`.`color_i` > \'black\' ) OR (`__bat`.`color_i` = \'yellow\')) ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 diff --git a/test/queries/escaped.js b/test/queries/escaped.js index 9a72a4d..887c596 100644 --- a/test/queries/escaped.js +++ b/test/queries/escaped.js @@ -28,7 +28,7 @@ module.exports = [ find: { // The queryString we expect to be rendered after calling Sequel.find() - queryString: 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `foo` AS `foo` WHERE LOWER(`foo`.`color`) LIKE "\\\\\\\\\\\\\\" or 1=1; -- \\%\\_%" ', + queryString: 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `foo` AS `foo` WHERE LOWER(`foo`.`color`) LIKE \'\\\\\\\\\\\\\\" or 1=1; -- \\%\\_%\' ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 @@ -64,7 +64,7 @@ module.exports = [ find: { // The queryString we expect to be rendered after calling Sequel.find() - queryString: 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `foo` AS `foo` WHERE LOWER(`foo`.`color`) LIKE "%\\\\\\\\\\\\\\" or 1=1; -- \\%\\_" ', + queryString: 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `foo` AS `foo` WHERE LOWER(`foo`.`color`) LIKE \'%\\\\\\\\\\\\\\" or 1=1; -- \\%\\_\' ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 @@ -100,7 +100,7 @@ module.exports = [ find: { // The queryString we expect to be rendered after calling Sequel.find() - queryString: 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `foo` AS `foo` WHERE LOWER(`foo`.`color`) LIKE "%\\\\\\\\\\\\\\" or 1=1; -- \\%\\_%" ', + queryString: 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `foo` AS `foo` WHERE LOWER(`foo`.`color`) LIKE \'%\\\\\\\\\\\\\\" or 1=1; -- \\%\\_%\' ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 @@ -138,7 +138,7 @@ module.exports = [ find: { // The queryString we expect to be rendered after calling Sequel.find() - queryString: 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `foo` AS `foo` WHERE LOWER(`foo`.`color`) LIKE "\\\\\\\\\\\\\\" or 1=1; -- %_" ', + queryString: 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `foo` AS `foo` WHERE LOWER(`foo`.`color`) LIKE \'\\\\\\\\\\\\\\" or 1=1; -- %_\' ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 diff --git a/test/queries/simpleSelectNestedFilterAlias.js b/test/queries/simpleSelectNestedFilterAlias.js index fe0adf0..c7f91ba 100644 --- a/test/queries/simpleSelectNestedFilterAlias.js +++ b/test/queries/simpleSelectNestedFilterAlias.js @@ -36,7 +36,7 @@ module.exports = { find : { // The queryString we expect to be rendered after calling `Sequel.find()` - queryString : 'SELECT `oddity`.`meta`, `oddity`.`id`, `oddity`.`createdAt`, `oddity`.`updatedAt`, `oddity`.`stubborn`, `oddity`.`bat` FROM `oddity` AS `oddity` WHERE `__bar`.`meta` = "foo" ', + queryString : 'SELECT `oddity`.`meta`, `oddity`.`id`, `oddity`.`createdAt`, `oddity`.`updatedAt`, `oddity`.`stubborn`, `oddity`.`bat` FROM `oddity` AS `oddity` WHERE `__bar`.`meta` = \'foo\' ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 diff --git a/test/queries/simpleSelectNestedFilterNoAlias.js b/test/queries/simpleSelectNestedFilterNoAlias.js index 33efbda..6f5fed4 100644 --- a/test/queries/simpleSelectNestedFilterNoAlias.js +++ b/test/queries/simpleSelectNestedFilterNoAlias.js @@ -32,7 +32,7 @@ module.exports = { find : { // The queryString we expect to be rendered after calling `Sequel.find()` - queryString : 'SELECT `oddity`.`meta`, `oddity`.`id`, `oddity`.`createdAt`, `oddity`.`updatedAt`, `oddity`.`stubborn`, `oddity`.`bat` FROM `oddity` AS `oddity` WHERE `__bar`.`meta` = "foo" ', + queryString : 'SELECT `oddity`.`meta`, `oddity`.`id`, `oddity`.`createdAt`, `oddity`.`updatedAt`, `oddity`.`stubborn`, `oddity`.`bat` FROM `oddity` AS `oddity` WHERE `__bar`.`meta` = \'foo\' ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 diff --git a/test/queries/simpleSelectNestedFilters.js b/test/queries/simpleSelectNestedFilters.js index de5ee42..b0e3059 100644 --- a/test/queries/simpleSelectNestedFilters.js +++ b/test/queries/simpleSelectNestedFilters.js @@ -56,7 +56,7 @@ module.exports = { find : { // The queryString we expect to be rendered after calling `Sequel.find()` - queryString : 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz`, `__bat`.`color_g` AS `bat___color_g`, `__bat`.`color_h` AS `bat___color_h`, `__bat`.`color_i` AS `bat___color_i`, `__bat`.`id` AS `bat___id`, `__bat`.`createdAt` AS `bat___createdAt`, `__bat`.`updatedAt` AS `bat___updatedAt` FROM `foo` AS `foo` LEFT OUTER JOIN `bat` AS `__bat` ON `foo`.`bat` = `__bat`.`id` WHERE `__bat`.`color_g` = "yellow" AND LOWER(`foo`.`color`) = "red" ', + queryString : 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz`, `__bat`.`color_g` AS `bat___color_g`, `__bat`.`color_h` AS `bat___color_h`, `__bat`.`color_i` AS `bat___color_i`, `__bat`.`id` AS `bat___id`, `__bat`.`createdAt` AS `bat___createdAt`, `__bat`.`updatedAt` AS `bat___updatedAt` FROM `foo` AS `foo` LEFT OUTER JOIN `bat` AS `__bat` ON `foo`.`bat` = `__bat`.`id` WHERE `__bat`.`color_g` = \'yellow\' AND LOWER(`foo`.`color`) = \'red\' ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 diff --git a/test/queries/simpleSelectWhere.js b/test/queries/simpleSelectWhere.js index 3e1f36b..71cd5cc 100644 --- a/test/queries/simpleSelectWhere.js +++ b/test/queries/simpleSelectWhere.js @@ -30,7 +30,7 @@ module.exports = { find : { // The queryString we expect to be rendered after calling `Sequel.find()` - queryString : 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `foo` AS `foo` WHERE LOWER(`foo`.`color`) = "blue" ', + queryString : 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `foo` AS `foo` WHERE LOWER(`foo`.`color`) = \'blue\' ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 diff --git a/test/schemaQueries/complexSelectFilters.js b/test/schemaQueries/complexSelectFilters.js index 6f865a6..c4babd4 100644 --- a/test/schemaQueries/complexSelectFilters.js +++ b/test/schemaQueries/complexSelectFilters.js @@ -64,7 +64,7 @@ module.exports = { find : { // The queryString we expect to be rendered after calling `Sequel.find()` - queryString : 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz`, `__bat`.`color_g` AS `bat___color_g`, `__bat`.`color_h` AS `bat___color_h`, `__bat`.`color_i` AS `bat___color_i`, `__bat`.`id` AS `bat___id`, `__bat`.`createdAt` AS `bat___createdAt`, `__bat`.`updatedAt` AS `bat___updatedAt` FROM `myschema`.`foo` AS `foo` LEFT OUTER JOIN `public`.`bat` AS `__bat` ON `foo`.`bat` = `__bat`.`id` WHERE `foo`.`bat` = 1 AND `foo`.`baz` IN (1,2,3,4) AND ((LOWER(`foo`.`color`) = "red") OR (LOWER(`foo`.`color`) = "blue") OR (LOWER(`foo`.`color`) = "grey") OR (LOWER(`foo`.`color`) > "111" )) ', + queryString : 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz`, `__bat`.`color_g` AS `bat___color_g`, `__bat`.`color_h` AS `bat___color_h`, `__bat`.`color_i` AS `bat___color_i`, `__bat`.`id` AS `bat___id`, `__bat`.`createdAt` AS `bat___createdAt`, `__bat`.`updatedAt` AS `bat___updatedAt` FROM `myschema`.`foo` AS `foo` LEFT OUTER JOIN `public`.`bat` AS `__bat` ON `foo`.`bat` = `__bat`.`id` WHERE `foo`.`bat` = 1 AND `foo`.`baz` IN (1,2,3,4) AND ((LOWER(`foo`.`color`) = \'red\') OR (LOWER(`foo`.`color`) = \'blue\') OR (LOWER(`foo`.`color`) = \'grey\') OR (LOWER(`foo`.`color`) > \'111\' )) ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 diff --git a/test/schemaQueries/complexSelectNestedFilters.js b/test/schemaQueries/complexSelectNestedFilters.js index fb63e2e..22f541c 100644 --- a/test/schemaQueries/complexSelectNestedFilters.js +++ b/test/schemaQueries/complexSelectNestedFilters.js @@ -87,7 +87,7 @@ module.exports = { find : { // The queryString we expect to be rendered after calling `Sequel.find()` - queryString : 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz`, `__bat`.`color_g` AS `bat___color_g`, `__bat`.`color_h` AS `bat___color_h`, `__bat`.`color_i` AS `bat___color_i`, `__bat`.`id` AS `bat___id`, `__bat`.`createdAt` AS `bat___createdAt`, `__bat`.`updatedAt` AS `bat___updatedAt` FROM `myschema`.`foo` AS `foo` LEFT OUTER JOIN `public`.`bat` AS `__bat` ON `foo`.`bat` = `__bat`.`id` WHERE ((LOWER(`foo`.`color`) = "red") OR (LOWER(`foo`.`color`) = "blue") OR (LOWER(`foo`.`color`) = "grey") OR (`__bat`.`color_g` = "yellow" ) OR (`__bat`.`color_g` = "blue" )) AND `__bat`.`color_h` = "red" AND ((`__bat`.`color_i` IN ("pink","purple","green")) OR (`__bat`.`color_i` > "black" ) OR (`__bat`.`color_i` = "yellow")) ', + queryString : 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz`, `__bat`.`color_g` AS `bat___color_g`, `__bat`.`color_h` AS `bat___color_h`, `__bat`.`color_i` AS `bat___color_i`, `__bat`.`id` AS `bat___id`, `__bat`.`createdAt` AS `bat___createdAt`, `__bat`.`updatedAt` AS `bat___updatedAt` FROM `myschema`.`foo` AS `foo` LEFT OUTER JOIN `public`.`bat` AS `__bat` ON `foo`.`bat` = `__bat`.`id` WHERE ((LOWER(`foo`.`color`) = \'red\') OR (LOWER(`foo`.`color`) = \'blue\') OR (LOWER(`foo`.`color`) = \'grey\') OR (`__bat`.`color_g` = \'yellow\' ) OR (`__bat`.`color_g` = \'blue\' )) AND `__bat`.`color_h` = \'red\' AND ((`__bat`.`color_i` IN (\'pink\',\'purple\',\'green\')) OR (`__bat`.`color_i` > \'black\' ) OR (`__bat`.`color_i` = \'yellow\')) ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 diff --git a/test/schemaQueries/escaped.js b/test/schemaQueries/escaped.js index 840c1c3..f06b728 100644 --- a/test/schemaQueries/escaped.js +++ b/test/schemaQueries/escaped.js @@ -28,7 +28,7 @@ module.exports = [ find: { // The queryString we expect to be rendered after calling Sequel.find() - queryString: 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `myschema`.`foo` AS `foo` WHERE LOWER(`foo`.`color`) LIKE "\\\\\\\\\\\\\\" or 1=1; -- \\%\\_%" ', + queryString: 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `myschema`.`foo` AS `foo` WHERE LOWER(`foo`.`color`) LIKE \'\\\\\\\\\\\\\\" or 1=1; -- \\%\\_%\' ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 @@ -64,7 +64,7 @@ module.exports = [ find: { // The queryString we expect to be rendered after calling Sequel.find() - queryString: 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `myschema`.`foo` AS `foo` WHERE LOWER(`foo`.`color`) LIKE "%\\\\\\\\\\\\\\" or 1=1; -- \\%\\_" ', + queryString: 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `myschema`.`foo` AS `foo` WHERE LOWER(`foo`.`color`) LIKE \'%\\\\\\\\\\\\\\" or 1=1; -- \\%\\_\' ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 @@ -100,7 +100,7 @@ module.exports = [ find: { // The queryString we expect to be rendered after calling Sequel.find() - queryString: 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `myschema`.`foo` AS `foo` WHERE LOWER(`foo`.`color`) LIKE "%\\\\\\\\\\\\\\" or 1=1; -- \\%\\_%" ', + queryString: 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `myschema`.`foo` AS `foo` WHERE LOWER(`foo`.`color`) LIKE \'%\\\\\\\\\\\\\\" or 1=1; -- \\%\\_%\' ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 @@ -138,7 +138,7 @@ module.exports = [ find: { // The queryString we expect to be rendered after calling Sequel.find() - queryString: 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `myschema`.`foo` AS `foo` WHERE LOWER(`foo`.`color`) LIKE "\\\\\\\\\\\\\\" or 1=1; -- %_" ', + queryString: 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `myschema`.`foo` AS `foo` WHERE LOWER(`foo`.`color`) LIKE \'\\\\\\\\\\\\\\" or 1=1; -- %_\' ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 diff --git a/test/schemaQueries/simpleSelectNestedFilterAlias.js b/test/schemaQueries/simpleSelectNestedFilterAlias.js index b1be544..864f215 100644 --- a/test/schemaQueries/simpleSelectNestedFilterAlias.js +++ b/test/schemaQueries/simpleSelectNestedFilterAlias.js @@ -36,7 +36,7 @@ module.exports = { find : { // The queryString we expect to be rendered after calling `Sequel.find()` - queryString : 'SELECT `oddity`.`meta`, `oddity`.`id`, `oddity`.`createdAt`, `oddity`.`updatedAt`, `oddity`.`stubborn`, `oddity`.`bat` FROM `anotherschema`.`oddity` AS `oddity` WHERE `__bar`.`meta` = "foo" ', + queryString : 'SELECT `oddity`.`meta`, `oddity`.`id`, `oddity`.`createdAt`, `oddity`.`updatedAt`, `oddity`.`stubborn`, `oddity`.`bat` FROM `anotherschema`.`oddity` AS `oddity` WHERE `__bar`.`meta` = \'foo\' ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 diff --git a/test/schemaQueries/simpleSelectNestedFilterNoAlias.js b/test/schemaQueries/simpleSelectNestedFilterNoAlias.js index 7172cb0..4d64475 100644 --- a/test/schemaQueries/simpleSelectNestedFilterNoAlias.js +++ b/test/schemaQueries/simpleSelectNestedFilterNoAlias.js @@ -32,7 +32,7 @@ module.exports = { find : { // The queryString we expect to be rendered after calling `Sequel.find()` - queryString : 'SELECT `oddity`.`meta`, `oddity`.`id`, `oddity`.`createdAt`, `oddity`.`updatedAt`, `oddity`.`stubborn`, `oddity`.`bat` FROM `anotherschema`.`oddity` AS `oddity` WHERE `__bar`.`meta` = "foo" ', + queryString : 'SELECT `oddity`.`meta`, `oddity`.`id`, `oddity`.`createdAt`, `oddity`.`updatedAt`, `oddity`.`stubborn`, `oddity`.`bat` FROM `anotherschema`.`oddity` AS `oddity` WHERE `__bar`.`meta` = \'foo\' ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 diff --git a/test/schemaQueries/simpleSelectNestedFilters.js b/test/schemaQueries/simpleSelectNestedFilters.js index 6377e3f..1af2208 100644 --- a/test/schemaQueries/simpleSelectNestedFilters.js +++ b/test/schemaQueries/simpleSelectNestedFilters.js @@ -56,7 +56,7 @@ module.exports = { find : { // The queryString we expect to be rendered after calling `Sequel.find()` - queryString : 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz`, `__bat`.`color_g` AS `bat___color_g`, `__bat`.`color_h` AS `bat___color_h`, `__bat`.`color_i` AS `bat___color_i`, `__bat`.`id` AS `bat___id`, `__bat`.`createdAt` AS `bat___createdAt`, `__bat`.`updatedAt` AS `bat___updatedAt` FROM `myschema`.`foo` AS `foo` LEFT OUTER JOIN `public`.`bat` AS `__bat` ON `foo`.`bat` = `__bat`.`id` WHERE `__bat`.`color_g` = "yellow" AND LOWER(`foo`.`color`) = "red" ', + queryString : 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz`, `__bat`.`color_g` AS `bat___color_g`, `__bat`.`color_h` AS `bat___color_h`, `__bat`.`color_i` AS `bat___color_i`, `__bat`.`id` AS `bat___id`, `__bat`.`createdAt` AS `bat___createdAt`, `__bat`.`updatedAt` AS `bat___updatedAt` FROM `myschema`.`foo` AS `foo` LEFT OUTER JOIN `public`.`bat` AS `__bat` ON `foo`.`bat` = `__bat`.`id` WHERE `__bat`.`color_g` = \'yellow\' AND LOWER(`foo`.`color`) = \'red\' ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 diff --git a/test/schemaQueries/simpleSelectWhere.js b/test/schemaQueries/simpleSelectWhere.js index de6ab5b..e6b481e 100644 --- a/test/schemaQueries/simpleSelectWhere.js +++ b/test/schemaQueries/simpleSelectWhere.js @@ -30,7 +30,7 @@ module.exports = { find : { // The queryString we expect to be rendered after calling `Sequel.find()` - queryString : 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `myschema`.`foo` AS `foo` WHERE LOWER(`foo`.`color`) = "blue" ', + queryString : 'SELECT `foo`.`color`, `foo`.`id`, `foo`.`createdAt`, `foo`.`updatedAt`, `foo`.`bar`, `foo`.`bat`, `foo`.`baz` FROM `myschema`.`foo` AS `foo` WHERE LOWER(`foo`.`color`) = \'blue\' ', // The number of queries that will be returned after calling Sequel.find() queriesReturned: 1 diff --git a/test/unit/index.test.js b/test/unit/index.test.js index dce209d..3ff7803 100644 --- a/test/unit/index.test.js +++ b/test/unit/index.test.js @@ -82,6 +82,14 @@ describe('Sequel', function () { }); }); + describe('queries with an unknown operator', function () { + it('throws an error when the operator is unknown', function() { + var sequel = new Sequel(schema); + assert.throws(sequel.find.bind(sequel, 'bar', { id: { 'in': [ 1, 2 ] } }), + Error, 'Unknown filtering operator: \'in\'. Should be \'startsWith\', \'>\', \'contains\' or similar'); + }); + }); + describe('.find() with schema name', function () { var _options = _.extend({}, options, {schemaName: {'foo':'myschema','oddity':'anotherschema','bat':'public'}}); // Loop through the query objects and test them against the `.find()` method.