diff --git a/sequel/lib/criteriaProcessor.js b/sequel/lib/criteriaProcessor.js index c158ac3..be2cb51 100644 --- a/sequel/lib/criteriaProcessor.js +++ b/sequel/lib/criteriaProcessor.js @@ -36,7 +36,7 @@ var CriteriaProcessor = module.exports = function CriteriaProcessor(currentTable this.currentTable = currentTable; this.schema = schema; - this.currentSchema = schema[currentTable].attributes; + this.currentSchema = schema[currentTable].definition; this.tableScope = null; this.queryString = ''; this.values = []; @@ -428,7 +428,7 @@ CriteriaProcessor.prototype.findChild = function findChild (child) { CriteriaProcessor.prototype.processSimple = function processSimple (tableName, parent, value, combinator, sensitive) { // Set lower logic to true var sensitiveTypes = ['text', 'string'], - currentSchema = this.schema[tableName].attributes, + currentSchema = this.schema[tableName].definition, self = this, parentType, lower; @@ -499,7 +499,7 @@ CriteriaProcessor.prototype.processSimple = function processSimple (tableName, p * @param {string} [alias] */ CriteriaProcessor.prototype.processObject = function processObject (tableName, parent, value, combinator, sensitive) { - var currentSchema = this.schema[tableName].attributes, + var currentSchema = this.schema[tableName].definition, self = this, parentType; diff --git a/sequel/select.js b/sequel/select.js index cd54434..65cd409 100644 --- a/sequel/select.js +++ b/sequel/select.js @@ -15,7 +15,7 @@ var hop = utils.object.hasOwnProperty; var SelectBuilder = module.exports = function(schema, currentTable, queryObject, options) { this.schema = schema; - this.currentSchema = schema[currentTable].attributes; + this.currentSchema = schema[currentTable].definition; this.currentTable = currentTable; this.escapeCharacter = '"'; this.cast = false; @@ -66,14 +66,26 @@ SelectBuilder.prototype.buildSimpleSelect = function buildSimpleSelect(queryObje var selectKeys = []; var query = 'SELECT '; - var attributes = queryObject.select || Object.keys(this.schema[this.currentTable].attributes); + // If there is a select projection, ensure that the primary key is added. + var pk; + _.each(this.schema[this.currentTable].definition, function(val, key) { + if(_.has(val, 'primaryKey') && val.primaryKey) { + pk = key; + } + }); + + if(queryObject.select && !_.includes(queryObject.select, pk)) { + queryObject.select.push(pk); + } + + var attributes = queryObject.select || Object.keys(this.schema[this.currentTable].definition); delete queryObject.select; attributes.forEach(function(key) { // Default schema to {} in case a raw DB column name is sent. This shouldn't happen // after https://github.com/balderdashy/waterline/commit/687c869ad54f499018ab0b038d3de4435c96d1dd // but leaving here as a failsafe. - var schema = self.schema[self.currentTable].attributes[key] || {}; + var schema = self.schema[self.currentTable].definition[key] || {}; if(hop(schema, 'collection')) return; selectKeys.push({ table: self.currentTable, key: schema.columnName || key }); }); @@ -89,10 +101,16 @@ SelectBuilder.prototype.buildSimpleSelect = function buildSimpleSelect(queryObje // Handle hasFK var childAlias = _.find(_.values(self.schema), {tableName: population.child}).tableName; - _.keys(self.schema[childAlias].attributes).forEach(function(key) { - var schema = self.schema[childAlias].attributes[key]; + // Ensure the foreignKey is selected if a custom select was defined + if(population.select && !_.includes(population.select, population.childKey)) { + population.select.push(population.childKey); + } + + var attributes = population.select || _.keys(self.schema[childAlias].definition); + _.each(attributes, function(key) { + var schema = self.schema[childAlias].definition[key]; if(hop(schema, 'collection')) return; - selectKeys.push({ table: population.alias ? "__"+population.alias : population.child, key: schema.columnName || key, alias: population.parentKey }); + selectKeys.push({ table: population.alias ? '__' + population.alias : population.child, key: schema.columnName || key, alias: population.parentKey }); }); }); diff --git a/sequel/where.js b/sequel/where.js index 39f8e59..0ec8d13 100644 --- a/sequel/where.js +++ b/sequel/where.js @@ -71,7 +71,7 @@ var WhereBuilder = module.exports = function WhereBuilder(schema, currentTable, if(options && hop(options, 'schemaName')) { this.schemaName = options.schemaName; - } + } return this; }; @@ -84,8 +84,8 @@ var WhereBuilder = module.exports = function WhereBuilder(schema, currentTable, WhereBuilder.prototype.single = function single(queryObject, options) { if(!queryObject) return { - query: '', - values: [] + query: '', + values: [] }; var self = this; @@ -122,8 +122,8 @@ WhereBuilder.prototype.single = function single(queryObject, options) { // Ensure a sort is always set so that we get back consistent results if(!hop(queryObject, 'sort')) { var childPK; - _.keys(this.schema[this.currentTable].attributes).forEach(function(attr) { - var expandedAttr = self.schema[self.currentTable].attributes[attr]; + _.keys(this.schema[this.currentTable].definition).forEach(function(attr) { + var expandedAttr = self.schema[self.currentTable].definition[attr]; if(!hop(expandedAttr, 'primaryKey')) return; childPK = expandedAttr.columnName || attr; }); @@ -220,8 +220,8 @@ WhereBuilder.prototype.complex = function complex(queryObject, options) { // Ensure a sort is always set so that we get back consistent results if(!hop(population.criteria, 'sort')) { - _.keys(self.schema[populationAlias].attributes).forEach(function(attr) { - var expandedAttr = self.schema[populationAlias].attributes[attr]; + _.keys(self.schema[populationAlias].definition).forEach(function(attr) { + var expandedAttr = self.schema[populationAlias].definition[attr]; if(!hop(expandedAttr, 'primaryKey')) return; childPK = expandedAttr.columnName || attr; }); @@ -233,7 +233,27 @@ 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; + queryString += utils.escapeName(projectionAlias, self.escapeCharacter) + '.' + + utils.escapeName(projection.key, self.escapeCharacter) + ','; + }); + // 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.escapeCharacter, self.schemaName) + ' AS ' + utils.escapeName(populationAlias, self.escapeCharacter) + ' WHERE ' + utils.escapeName(population.childKey, self.escapeCharacter) + ' = ^?^ '; + if(parsedCriteria) { // If where criteria was used append an AND clause @@ -292,12 +312,16 @@ WhereBuilder.prototype.complex = function complex(queryObject, options) { // Look into the schema and build up attributes to select var selectKeys = []; - - _.keys(self.schema[stage2ChildAlias].attributes).forEach(function(key) { - var schema = self.schema[stage2ChildAlias].attributes[key]; - if(hop(schema, 'collection')) return; - selectKeys.push({ table: stage2.child, key: schema.columnName || key }); - }); + if(_.isArray(stage2.select) && stage2.select.length) { + var selectKeys = stage2.select.map(function(projection) { + return { table: stage2.child, key: projection }; + }); + } else { + _.each(self.schema[stage2ChildAlias].attributes, function(val, key) { + if(_.has(val, 'collection')) return; + selectKeys.push({ table: stage2.child, key: val.columnName || key }); + }); + } queryString += '(SELECT '; selectKeys.forEach(function(projection) { diff --git a/test/schema.js b/test/schema.js index 383e851..ae6dcef 100644 --- a/test/schema.js +++ b/test/schema.js @@ -22,6 +22,25 @@ module.exports = { type : "datetime", default: "NOW" } + }, + definition: { + color_a : "string", + color_b : "string", + color_c : "string", + id : { + type : "integer", + autoIncrement: true, + primaryKey : true, + unique : true + }, + createdAt: { + type : "datetime", + default: "NOW" + }, + updatedAt: { + type : "datetime", + default: "NOW" + } } }, bat: { @@ -47,6 +66,25 @@ module.exports = { type : "datetime", default: "NOW" } + }, + definition: { + color_g : "string", + color_h : "string", + color_i : "string", + id : { + type : "integer", + autoIncrement: true, + primaryKey : true, + unique : true + }, + createdAt: { + type : "datetime", + default: "NOW" + }, + updatedAt: { + type : "datetime", + default: "NOW" + } } }, baz: { @@ -72,6 +110,25 @@ module.exports = { type : "datetime", default: "NOW" } + }, + definition: { + color_d : "string", + color_e : "string", + color_f : "string", + id : { + type : "integer", + autoIncrement: true, + primaryKey : true, + unique : true + }, + createdAt: { + type : "datetime", + default: "NOW" + }, + updatedAt: { + type : "datetime", + default: "NOW" + } } }, foo: { @@ -119,6 +176,47 @@ module.exports = { on : "id", onKey : "id" } + }, + definition: { + color : "string", + id : { + type : "integer", + autoIncrement: true, + primaryKey : true, + unique : true + }, + createdAt: { + type : "datetime", + default: "NOW" + }, + updatedAt: { + type : "datetime", + default: "NOW" + }, + bar : { + columnName: "bar", + type : "integer", + foreignKey: true, + references: "bar", + on : "id", + onKey : "id" + }, + bat : { + columnName: "bat", + type : "integer", + foreignKey: true, + references: "bat", + on : "id", + onKey : "id" + }, + baz : { + columnName: "baz", + type : "integer", + foreignKey: true, + references: "baz", + on : "id", + onKey : "id" + } } }, oddity: { @@ -158,6 +256,39 @@ module.exports = { on : "id", onKey : "id" } + }, + definition: { + meta : "string", + id : { + type : "integer", + autoIncrement: true, + primaryKey : true, + unique : true + }, + createdAt: { + type : "datetime", + default: "NOW" + }, + updatedAt: { + type : "datetime", + default: "NOW" + }, + bar : { + columnName: "stubborn", + type : "integer", + foreignKey: true, + references: "bar", + on : "id", + onKey : "id" + }, + bat : { + columnName: "bat", + type : "integer", + foreignKey: true, + references: "bat", + on : "id", + onKey : "id" + } } } };