UNPKG

water-orm

Version:

A monolith version of Standalone waterline ORM

340 lines (267 loc) 11.3 kB
/** * Module dependecies */ var _ = require('lodash'); var CriteriaParser = require('./lib/criteriaProcessor'); var utils = require('./lib/utils'); var hop = utils.object.hasOwnProperty; /** * Build WHERE query clause * * `Where` conditions may use key/value model attributes for simple query * look ups as well as more complex conditions. * * The following conditions are supported along with simple criteria: * * Conditions: * [And, Or, Like, Not] * * Criteria Operators: * [<, <=, >, >=, !] * * Criteria Helpers: * [lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual, not, like, contains, startsWith, endsWith] * * ####Example * * where: { * name: 'foo', * age: { * '>': 25 * }, * like: { * name: '%foo%' * }, * or: [ * { like: { foo: '%foo%' } }, * { like: { bar: '%bar%' } } * ], * name: [ 'foo', 'bar;, 'baz' ], * age: { * not: 40 * } * } */ var WhereBuilder = module.exports = function WhereBuilder(schema, currentTable, options) { this.schema = schema; this.currentTable = currentTable; this.wlNext = {}; if(options && hop(options, 'parameterized')) { this.parameterized = options.parameterized; } if(options && hop(options, 'caseSensitive')) { this.caseSensitive = options.caseSensitive; } if(options && hop(options, 'escapeCharacter')) { this.escapeCharacter = options.escapeCharacter; } // Add support for WL Next features if(options && hop(options, 'wlNext')) { this.wlNext = options.wlNext; } if(options && hop(options, 'schemaName')) { this.schemaName = options.schemaName; } return this; }; /** * Build a Simple Where clause */ WhereBuilder.prototype.single = function single(queryObject, options) { if(!queryObject) return { query: '', values: [] }; var self = this; var queryString = ''; var addSpace = false; // Add any hasFK strategy joins to the main query _.keys(queryObject.instructions).forEach(function(attr) { 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 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); addSpace = true; } }); if(addSpace) { queryString += ' '; } var tmpCriteria = _.cloneDeep(queryObject); delete tmpCriteria.instructions; // 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]; if(!hop(expandedAttr, 'primaryKey')) return; childPK = expandedAttr.columnName || attr; }); queryObject.sort = {}; queryObject.sort[childPK] = -1; } // Read the queryObject and get back a query string and params // Use the tmpCriteria here because all the joins have been removed var parsedCriteria = {}; // Build up a WHERE queryString if(tmpCriteria.where) { queryString += 'WHERE '; } // Mixin the parameterized flag into options var _options = _.assign({ parameterized: this.parameterized, caseSensitive: this.caseSensitive, escapeCharacter: this.escapeCharacter, wlNext: this.wlNext }, options); this.criteriaParser = new CriteriaParser(this.currentTable, this.schema, _options); parsedCriteria = this.criteriaParser.read(tmpCriteria); queryString += parsedCriteria.query; // Remove trailing AND if it exists if(queryString.slice(-4) === 'AND ') { queryString = queryString.slice(0, -5); } // Remove trailing OR if it exists if(queryString.slice(-3) === 'OR ') { queryString = queryString.slice(0, -4); } var values; if(parsedCriteria && _.isArray(parsedCriteria.values)) { values = parsedCriteria.values; } else { values = []; } return { query: queryString, values: values }; }; /** * Build a template for a complex UNION query. This is needed when populating using * SKIP, SORT and LIMIT. */ WhereBuilder.prototype.complex = function complex(queryObject, options) { var self = this; var queries = []; // Look up the child instructions and build out a template for each based on the type of join. if(!queryObject) return ''; _.keys(queryObject.instructions).forEach(function(attr) { var queryString = ''; var criteriaParser; var parsedCriteria; var childPK; var _options; var strategy = queryObject.instructions[attr].strategy.strategy; // Handle viaFK if(strategy === 2) { var population = queryObject.instructions[attr].instructions[0]; var populationAlias = _.find(_.values(self.schema), {tableName: population.child}).tableName; // Mixin the parameterized flag into options _options = _.assign({ parameterized: self.parameterized, caseSensitive: self.caseSensitive, escapeCharacter: self.escapeCharacter, wlNext: self.wlNext }, options); // Build the WHERE part of the query string criteriaParser = new CriteriaParser(populationAlias, self.schema, _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]; if(!hop(expandedAttr, 'primaryKey')) return; childPK = expandedAttr.columnName || attr; }); population.criteria.sort = {}; population.criteria.sort[childPK] = 1; } // 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) + ' = ^?^ '; if(parsedCriteria) { // If where criteria was used append an AND clause if(population.criteria.where && _.keys(population.criteria.where).length > 0) { queryString += 'AND '; } queryString += parsedCriteria.query; } queryString += ')'; // Add to the query list queries.push({ qs: queryString, instructions: population, attrName: attr, values: parsedCriteria.values }); } // Handle viaJunctor else if(strategy === 3) { var stage1 = queryObject.instructions[attr].instructions[0]; var stage2 = queryObject.instructions[attr].instructions[1]; var stage1ChildAlias = _.find(_.values(self.schema), {tableName: stage1.child}).tableName; var stage2ChildAlias = _.find(_.values(self.schema), {tableName: stage2.child}).tableName; // Mixin the parameterized flag into options _options = _.assign({ parameterized: self.parameterized, caseSensitive: self.caseSensitive, escapeCharacter: self.escapeCharacter, wlNext: self.wlNext }, options); // Build the WHERE part of the query string criteriaParser = new CriteriaParser(stage2ChildAlias, self.schema, _options); // Ensure a sort is always set so that we get back consistent results if(!hop(stage2.criteria, 'sort')) { _.keys(self.schema[stage2ChildAlias].attributes).forEach(function(attr) { var expandedAttr = self.schema[stage2ChildAlias].attributes[attr]; if(!hop(expandedAttr, 'primaryKey')) return; childPK = expandedAttr.columnName || attr; }); stage2.criteria.sort = {}; stage2.criteria.sort[childPK] = 1; } // Read the queryObject and get back a query string and params parsedCriteria = criteriaParser.read(stage2.criteria); // 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 }); }); 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) + ','; }); // 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 += ' 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) + ' = ^?^ '; if(parsedCriteria) { // If where criteria was used append an AND clause if(stage2.criteria.where && _.keys(stage2.criteria.where).length > 0) { queryString += 'AND '; } queryString += parsedCriteria.query; } queryString += ')'; // Add to the query list queries.push({ qs: queryString, instructions: queryObject.instructions[attr].instructions, attrName: attr, values: parsedCriteria.values }); } }); return queries; };