UNPKG

skul

Version:

SKuL - SQL query manager

1,088 lines (833 loc) 32.7 kB
/** * lib/skul/private.js * * SKuL private methods * * Author: Paul Duthoit * Copyright(c) 2016 Paul Duthoit */ // Dependencies var _ = require('underscore'); var objectPath = require('object-path'); var QueryBuilder = require('./query-builder'); var QueryException = require('./exceptions/query'); var knex = require('knex')({ client: 'mysql' }); var moment = require('moment'); var deepEqual = require('deep-equal'); // Constants var FILTER_KEYWORDS = [ '$and', '$or', '$nor', '$not', '$gt', '$gte', '$lt', '$lte', '$e', '$ne', '$like', '$nlike', '$raw', '$in', '$nin', '$key', '$value', '$filter', '$where' ]; /** * Parse fields * * @param {Model} model * @param {Object} fields * @param {String} parentAlias * @param {String} parentField * * @context {Number} aliasIncrement * @context {QueryBuilder} queryBuilder * @context {Object} queryBuilderColumns * @context {Array} requestedFields * @context {Object} queryBuildingTasks * @context {Object} hashAlias * * @private */ var _parseFields = function(model, fields, parentAlias, parentField) { // Set data var self = this; var invalidQueryData = {}; var structureColumns = model.getStructureColumns(); var oneToOneRelationshipKeys = _.union(model.getRelationshipKeys('belongsTo'), model.getRelationshipKeys('hasOne')); var oneToOneCustomRelationshipKeys = model.getRelationshipKeys('hasOneCustom'); var oneToManyRelationshipKeys = model.getRelationshipKeys('hasMany'); var oneToManyCustomRelationshipKeys = model.getRelationshipKeys('hasManyCustom'); var customKeys = model.getCustomKeys(); // If fields is empty if(_.keys(fields).length === 0) { _.each(structureColumns, function(columnItem) { self.queryBuilderColumns[parentAlias + '.' + columnItem] = parentAlias + '$' + columnItem; self.requestedFields.push(parentAlias + '$' + columnItem); }); } // Loop over fields _.each(fields, function(fieldValue, fieldKey) { // If field is $all if(fieldKey === '$all') { _.each(structureColumns, function(columnItem) { self.queryBuilderColumns[parentAlias + '.' + columnItem] = parentAlias + '$' + columnItem; self.requestedFields.push(parentAlias + '$' + columnItem); }); } // If field is structure column else if(_.contains(structureColumns, fieldKey)) { self.queryBuilderColumns[parentAlias + '.' + fieldKey] = parentAlias + '$' + fieldKey; self.requestedFields.push(parentAlias + '$' + fieldKey); } // If field is one2one relationship else if(_.contains(oneToOneRelationshipKeys, fieldKey)) { // Set data var fieldRelationship = model.getRelationship(fieldKey); var fieldModelStructureColumns = fieldRelationship.model.getStructureColumns(); // Set alias var joinAliasKey = parentField ? (parentField + '.' + fieldKey) : fieldKey; var joinAliasItem = _.find(self.hashAlias, function(obj) { return obj.key === joinAliasKey; }); // If join already exists if(typeof joinAliasItem !== 'undefined') { // Set join alias var joinAlias = joinAliasItem.alias; } // If join does not exists else { // Set join alias var joinAlias = parentAlias + '$' + 'T' + self.aliasIncrement; // Add alias to hash alias self.aliasIncrement++; self.hashAlias.push({ alias: joinAlias, key: joinAliasKey, model: fieldRelationship.model }); // Add left join if(fieldRelationship.type === 'belongsTo') { self.queryBuildingTasks[0].push(function() { self.queryBuilder.addJoin('left', [ fieldRelationship.model.getTableName() + ' AS ' + joinAlias, joinAlias + '.' + (fieldRelationship.key || fieldRelationship.model.getPrimaryKey()), parentAlias + '.' + fieldRelationship.foreignKey ]); }); } else if(fieldRelationship.type === 'hasOne') { self.queryBuildingTasks[0].push(function() { self.queryBuilder.addJoin('left', [ fieldRelationship.model.getTableName() + ' AS ' + joinAlias, parentAlias + '.' + (fieldRelationship.key || model.getPrimaryKey()), joinAlias + '.' + fieldRelationship.foreignKey ]); }); } } // Check if value request more if(objectPath.get(fieldValue, '$fields')) { _parseFields.call(self, fieldRelationship.model, fieldValue['$fields'], joinAlias, joinAliasKey); } else { _parseFields.call(self, fieldRelationship.model, { '$all': 1 }, joinAlias, joinAliasKey); } } // If field is one2one custom relationship else if(_.contains(oneToOneCustomRelationshipKeys, fieldKey)) { // Set data var fieldRelationship = model.getRelationship(fieldKey); var fieldRelationshipModel = fieldRelationship.model['$model']; var fieldRelationshipAlias = fieldRelationship.model['$alias']; var fieldModelStructureColumns = fieldRelationshipModel.getStructureColumns(); var joinAliasKey = parentField ? (parentField + '.' + fieldKey) : fieldKey; var joinAlias = parentAlias + '$' + 'T' + self.aliasIncrement; var aliasRefs = {}; // Add alias to hash alias aliasRefs[fieldRelationshipAlias] = joinAlias; self.aliasIncrement++; self.hashAlias.push({ alias: joinAlias, key: joinAliasKey, model: fieldRelationshipModel }); // Set data var joinRaw = ''; // Loop over joinRaw _.each(fieldRelationship.joinRaw, function(joinItem) { // Add to ref if(!aliasRefs[joinItem['$alias']]) { aliasRefs[joinItem['$alias']] = joinAlias + '__' + joinItem['$alias']; } // If item is string if(typeof joinItem === 'string') { joinRaw += joinItem; } // If item is $string else if(typeof joinItem === 'object' && _.difference([ '$string' ], _.keys(joinItem)).length === 0) { joinRaw += String(joinItem['$string']); } // If item is $model/$alias object else if(typeof joinItem === 'object' && _.difference([ '$model', '$alias' ], _.keys(joinItem)).length === 0) { joinRaw += '`' + joinItem['$model'].getTableName() + '` AS `' + aliasRefs[joinItem['$alias']] + '`'; } // If item is $key/$alias object else if(typeof joinItem === 'object' && _.difference([ '$key', '$alias' ], _.keys(joinItem)).length === 0) { joinRaw += '`' + aliasRefs[joinItem['$alias']] + '`.`' + joinItem['$key'] + '`'; } // If item is $alias object else if(typeof joinItem === 'object' && _.difference([ '$alias' ], _.keys(joinItem)).length === 0) { joinRaw += '`' + aliasRefs[joinItem['$alias']] + '`'; } // If item is $key object else if(typeof joinItem === 'object' && _.difference([ '$key' ], _.keys(joinItem)).length === 0) { // Set column raw joinRaw += '`' + parentAlias + '`.`' + joinItem['$key'] + '`'; // Add query builder columns self.queryBuilderColumns[parentAlias + '.' + joinItem['$key']] = parentAlias + '$' + joinItem['$key']; } }); // Add to subquery building tasks self.queryBuildingTasks[0].push(function() { self.queryBuilder.addJoin('raw', joinRaw); // self.queryBuilder.addGroupByRaw('`' + parentAlias + '`.`' + model.getPrimaryKey() + '`'); ////// FIX BUG }); // Check if value request more if(objectPath.get(fieldValue, '$fields')) { _parseFields.call(self, fieldRelationshipModel, fieldValue['$fields'], joinAlias, joinAliasKey); } else { _parseFields.call(self, fieldRelationshipModel, { '$all': 1 }, joinAlias, joinAliasKey); } } // If field is one2many relationship and is grouped else if(_.contains(oneToManyRelationshipKeys, fieldKey) && objectPath.get(fieldValue, '$as')) { // Set data var fieldRelationship = model.getRelationship(fieldKey); // Set alias var calculatedColumn = parentAlias + '$' + fieldValue['$as']; var joinAlias = parentAlias + '$' + 'T' + self.aliasIncrement; var joinAliasKey = parentField ? (parentField + '.' + fieldKey) : fieldKey; var relationKey = fieldRelationship.key || model.getPrimaryKey(); // Add query builder columns self.queryBuilderColumns[parentAlias + '.' + relationKey] = parentAlias + '$' + relationKey; // Add alias to hash alias self.aliasIncrement++; self.hashAlias.push({ alias: joinAlias, key: joinAliasKey, model: fieldRelationship.model }); // Push to query building tasks self.queryBuildingTasks[1].push(function() { // If already exists if(_.contains(self.requestedFields, calculatedColumn)) { return; } // Set data var subqueryBuilder = new QueryBuilder(); // Set from subqueryBuilder.setFromRaw(self.queryBuilder); subqueryBuilder.setSource({ type: 'groupedOneToMany', field: fieldValue }); // Add column subqueryBuilder.addColumn(knex.raw('`' + self.queryBuilder.getAlias() + '`.*')); // Create subjoin var subjoinFilter = fieldValue['$filter'] || {}; var subjoinFields = {}; var subjoinTableName = fieldRelationship.model.getTableName(); var subjoinAlias = 'T1'; // Add foreign key to subjoin fields subjoinFields[fieldRelationship.foreignKey] = 1; // Set subjoin fields if(objectPath.get(fieldValue, '$count')) { var subJoinOnField = fieldRelationship.foreignKey; } else if(objectPath.get(fieldValue, '$sum')) { var subJoinOnField = fieldValue['$sum']; } else if(objectPath.get(fieldValue, '$avg')) { var subJoinOnField = fieldValue['$avg']; } // Add on key to subjoin fields subjoinFields[subJoinOnField] = 1; // Add calculated column if(objectPath.get(fieldValue, '$count')) { subqueryBuilder.addColumn(knex.raw('COUNT(`' + subjoinAlias + '`.`' + subJoinOnField + '`) AS `' + calculatedColumn + '`')); } else if(objectPath.get(fieldValue, '$sum')) { subqueryBuilder.addColumn(knex.raw('SUM(`' + subjoinAlias + '`.`' + subJoinOnField + '`) AS `' + calculatedColumn + '`')); } else if(objectPath.get(fieldValue, '$avg')) { subqueryBuilder.addColumn(knex.raw('AVG(`' + subjoinAlias + '`.`' + subJoinOnField + '`) AS `' + calculatedColumn + '`')); } // Add join subqueryBuilder.addJoin('left', [ subjoinTableName + ' AS ' + subjoinAlias, self.queryBuilder.getAlias() + '.' + parentAlias + '$' + relationKey, subjoinAlias + '.' + fieldRelationship.foreignKey ]); // Add group by subqueryBuilder.addGroupByRaw('`' + self.queryBuilder.getAlias() + '`.`' + parentAlias + '$' + relationKey + '`'); // Add required field self.requestedFields.push(calculatedColumn); // Set query builder self.queryBuilder = subqueryBuilder; }); } // If field is custom else if(_.contains(customKeys, fieldKey)) { // Set data var fieldCustom = model.getCustom(fieldKey); var customAlias = parentAlias + '$' + fieldKey; var subqueryBuilder = new QueryBuilder(); subqueryBuilder.setSource({ type: 'customKey', field: fieldKey }); var subqueryBuildingTasks = []; // Set from subqueryBuildingTasks.push(function() { subqueryBuilder.setFromRaw(self.queryBuilder); subqueryBuilder.addColumn(knex.raw('`' + self.queryBuilder.getAlias() + '`.*')); }); // Add required field subqueryBuildingTasks.push(function() { self.requestedFields.push(customAlias); }); // If field is provided if(fieldCustom.data['$field'] instanceof Array) { // Set data var columnRaw = ''; // Loop over field _.each(fieldCustom.data['$field'], function(fieldItem) { // If item is string if(typeof fieldItem === 'string') { columnRaw += fieldItem; } // If item is string else if(typeof fieldItem === 'object' && _.difference([ '$string' ], _.keys(fieldItem)).length === 0) { columnRaw += String(fieldItem['$string']); } // If item is object else if(typeof fieldItem === 'object' && _.difference([ '$key', '$alias' ], _.keys(fieldItem)).length === 0) { columnRaw += '`' + fieldItem['$alias'] + '`.`' + fieldItem['$key'] + '`'; } // If item is string else if(typeof fieldItem === 'object' && _.difference([ '$key' ], _.keys(fieldItem)).length === 0) { // Set column raw columnRaw += '`' + '$QB$' + '`.`' + parentAlias + '$' + fieldItem['$key'] + '`'; // Add query builder columns self.queryBuilderColumns[parentAlias + '.' + fieldItem['$key']] = parentAlias + '$' + fieldItem['$key']; } }); // Add column subqueryBuildingTasks.push(function() { // Add query builder alias to column raw columnRaw = columnRaw.replace(/`\$QB\$`/g, self.queryBuilder.getAlias()); // Add column subqueryBuilder.addColumn(knex.raw(columnRaw + ' AS `' + customAlias + '`')); }); } // If join is provided if(fieldCustom.data['$join'] instanceof Array) { // Loop over join _.each(fieldCustom.data['$join'], function(joinItem) { // Set data var joinItemType = joinItem['$type'] || 'inner'; var joinItemModel = joinItem['$model']; var joinItemAlias = joinItem['$alias']; var joinItemKey = joinItem['$key']; var joinItemOn = joinItem['$on']; var joinItemTable = joinItemModel.getTableName(); // Add query builder columns self.queryBuilderColumns[parentAlias + '.' + joinItemOn] = parentAlias + '$' + joinItemOn; // Add join subqueryBuildingTasks.push(function() { subqueryBuilder.addJoin(joinItemType, [ joinItemTable + ' AS ' + joinItemAlias, self.queryBuilder.getAlias() + '.' + parentAlias + '$' + joinItemOn, joinItemAlias + '.' + joinItemKey ]); }); }); } // If joinRaw is provided else if(fieldCustom.data['$joinRaw'] instanceof Array) { // Set data var joinRaw = ''; // Loop over joinRaw _.each(fieldCustom.data['$joinRaw'], function(joinItem) { // If item is string if(typeof joinItem === 'string') { joinRaw += joinItem; } // If item is $string else if(typeof joinItem === 'object' && _.difference([ '$string' ], _.keys(joinItem)).length === 0) { joinRaw += String(joinItem['$string']); } // If item is $model/$alias object else if(typeof joinItem === 'object' && _.difference([ '$model', '$alias' ], _.keys(joinItem)).length === 0) { joinRaw += '`' + joinItem['$model'].getTableName() + '` AS `' + joinItem['$alias'] + '`'; } // If item is $key/$alias object else if(typeof joinItem === 'object' && _.difference([ '$key', '$alias' ], _.keys(joinItem)).length === 0) { joinRaw += '`' + joinItem['$alias'] + '`.`' + joinItem['$key'] + '`'; } // If item is $key object else if(typeof joinItem === 'object' && _.difference([ '$key' ], _.keys(joinItem)).length === 0) { // Set column raw joinRaw += '`' + '$QB$' + '`.`' + parentAlias + '$' + joinItem['$key'] + '`'; // Add query builder columns self.queryBuilderColumns[parentAlias + '.' + joinItem['$key']] = parentAlias + '$' + joinItem['$key']; } }); // Add to subquery building tasks subqueryBuildingTasks.push(function() { // Add query builder alias to join raw joinRaw = joinRaw.replace(/`\$QB\$`/g, self.queryBuilder.getAlias()); // Add join subqueryBuilder.addJoin('raw', joinRaw); }); } // Add query builder columns self.queryBuilderColumns[self.mainAlias + '.' + self.mainModel.getPrimaryKey()] = self.mainAlias + '$' + self.mainModel.getPrimaryKey(); self.queryBuilderColumns[parentAlias + '.' + model.getPrimaryKey()] = parentAlias + '$' + model.getPrimaryKey(); // Add group by subqueryBuildingTasks.push(function() { subqueryBuilder.addGroupByRaw('`' + self.queryBuilder.getAlias() + '`.`' + self.mainAlias + '$' + self.mainModel.getPrimaryKey() + '`'); subqueryBuilder.addGroupByRaw('`' + self.queryBuilder.getAlias() + '`.`' + parentAlias + '$' + model.getPrimaryKey() + '`'); }); // Push to query building tasks self.queryBuildingTasks[1].push(function() { // If already exists if(_.contains(self.requestedFields, customAlias)) { return; } // Run subquery building tasks _.each(subqueryBuildingTasks, function(fun) { return fun(); }); // Set query builder self.queryBuilder = subqueryBuilder; }); } // If field is one2many relationship else if(_.contains(oneToManyRelationshipKeys, fieldKey)) { // Set data var fieldRelationship = model.getRelationship(fieldKey); var relationKey = fieldRelationship.key || model.getPrimaryKey(); // Add required field self.queryBuilderColumns[parentAlias + '.' + relationKey] = parentAlias + '$' + relationKey; self.requestedFields.push(parentAlias + '$' + relationKey); } // If field is one2many custom relationship else if(_.contains(oneToManyCustomRelationshipKeys, fieldKey)) { // Set data var fieldRelationship = model.getRelationship(fieldKey); var relationKey = fieldRelationship.key || model.getPrimaryKey(); // Add required field self.queryBuilderColumns[parentAlias + '.' + relationKey] = parentAlias + '$' + relationKey; self.requestedFields.push(parentAlias + '$' + relationKey); } // If field is undefined else { invalidQueryData[fieldKey] = 'unknown'; } }); // Check if some error occured if(_.keys(invalidQueryData).length > 0) { throw new QueryException('InvalidQueryData', invalidQueryData); } }; /** * Parse filter * * @param {Model} model * @param {Object} filter * @param {String} parentAlias * @param {String} parentField * * @context {Number} aliasIncrement * @context {QueryBuilder} queryBuilder * @context {Object} queryBuilderColumns * @context {Array} requestedFields * @context {Object} queryBuildingTasks * @context {Object} hashAlias * * @private */ var _parseFilter = function(model, filter, parentAlias, parentField) { // Set data var self = this; var whereClause = ''; var filterAlreadyFullfill = false; // If AND if(filter['$and'] instanceof Array) { _.each(filter['$and'], function(andItem, andIndex) { // Stop if AND item is empty if(_.keys(andItem).length === 0) { return; } // Add AND item whereClause += '( ' + _parseFilter.call(self, model, andItem, parentAlias, parentField) + ' ) '; // Add AND keyword if(andIndex < filter['$and'].length-1) { whereClause += 'AND '; } }); } // If OR else if(filter['$or'] instanceof Array) { _.each(filter['$or'], function(orItem, orIndex) { // Stop if OR item is empty if(_.keys(orItem).length === 0) { return; } // Add OR item whereClause += '( ' + _parseFilter.call(self, model, orItem, parentAlias, parentField) + ' ) '; // Add OR keyword if(orIndex < filter['$or'].length-1) { whereClause += 'OR '; } }); } // If $key is defined else if(typeof filter['$key'] !== 'undefined') { // Set data var filterKey = { '$before_key': filter['$before_key'] || '', '$after_key': filter['$after_key'] || '', '$key': filter['$key'] }; var filterValue = filter['$value']; // Parse filter whereClause += _parseFilterItem.call(self, model, filterKey, filterValue, parentAlias, parentField); } else { // Parse item for each filter keys var filterKeys = _.keys(filter); _.each(filterKeys, function(filterKey, filterKeyIndex) { // Set data var filterValue = filter[filterKey]; // If $where is defined if(typeof objectPath.get(filterValue, '$where') !== 'undefined') { // Set data var fieldsToParse = {}; var customField = _.clone(filterValue); // Delete where from custom field delete customField['$where']; // Get parent hash alias item var parentHashAliasItem = _.find(self.hashAlias, function(obj) { return obj.alias === parentAlias; }); // Get some field path var absoluteFieldPath = _.reject([ parentHashAliasItem.key, filterKey ], function(obj) { return !obj; }).join('.'); var fieldsToParsePath = absoluteFieldPath.split('.').join('.$fields.').split('.'); // Set field to parse var fieldsToParse = {}; // Set fields to parse objectPath.set(fieldsToParse, fieldsToParsePath, customField); // Parse fields _parseFields.call(self, model, fieldsToParse, parentAlias, parentField); // Add where whereClause += _parseFilterItem.call(self, model, filterValue['$as'], filterValue['$where'], parentAlias, parentField, true); } else { // Parse filter item whereClause += _parseFilterItem.call(self, model, filterKey, filterValue, parentAlias, parentField); } // Add OR keyword if(filterKeyIndex < filterKeys.length-1) { whereClause += 'AND '; } }); } // Trim where clause whereClause = whereClause.trim(); // Return where clause return whereClause; }; /** * Parse filter item * * @param {Model} model * @param {Mixed} itemKey * @param {Mixed} itemValue * @param {String} itemKey * @param {String} parentAlias * @param {String} parentField * @param {Boolean} [isAnAlias] * * @context {Number} aliasIncrement * @context {QueryBuilder} queryBuilder * @context {Object} queryBuilderColumns * @context {Array} requestedFields * @context {Object} queryBuildingTasks * @context {Object} hashAlias * * @private */ var _parseFilterItem = function(model, itemKey, itemValue, parentAlias, parentField, isAnAlias) { // Transform arguments if(typeof isAnAlias === 'undefined') isAnAlias = false; // Set data var self = this; // Parse filter item var parsedKey = _parseFilterKey.call(self, model, itemKey, parentAlias, parentField, isAnAlias); var parsedValue = _parseFilterValue.call(self, model, itemValue, parentAlias, parentField); // Check cast clauses if(!(new RegExp('CAST\\(.*? AS DATE\\)').exec(parsedKey)) && new RegExp('CAST\\(.*? AS DATE\\)').exec(parsedValue)) { return ' CAST(' + parsedKey + ' AS DATE)' + parsedValue; } else if(!(new RegExp('CAST\\(.*? AS DATETIME\\)').exec(parsedKey)) && new RegExp('CAST\\(.*? AS DATETIME\\)').exec(parsedValue)) { return ' CAST(' + parsedKey + ' AS DATETIME)' + parsedValue; } else if(new RegExp('LIKE').exec(parsedValue)) { return ' CAST(' + parsedKey + ' AS CHAR)' + parsedValue; } else { return parsedKey + parsedValue + ' '; } }; /** * Parse filter key * * @param {Model} model * @param {Mixed} itemKey * @param {String} parentAlias * @param {String} parentField * @param {Boolean} [isAnAlias] * * @context {Number} aliasIncrement * @context {QueryBuilder} queryBuilder * @context {Object} queryBuilderColumns * @context {Array} requestedFields * @context {Object} queryBuildingTasks * @context {Object} hashAlias * * @private */ var _parseFilterKey = function(model, itemKey, parentAlias, parentField, isAnAlias) { // Transform arguments if(typeof isAnAlias === 'undefined') isAnAlias = false; // Set data var self = this; var itemKeyBefore = ''; var itemKeyAfter = ''; // Set item key before/after if(typeof itemKey !== 'string') { itemKeyBefore = objectPath.get(itemKey, '$before_key') || ''; itemKeyAfter = objectPath.get(itemKey, '$after_key') || ''; itemKey = objectPath.get(itemKey, '$key'); } // Get parent hash alias item var parentHashAliasItem = _.find(self.hashAlias, function(obj) { return obj.alias === parentAlias; }); // Get absolute field path var fieldPath = itemKey.split('.'); var fieldName = fieldPath.pop(); var baseFieldPath = fieldPath.join('.'); var absoluteBaseFieldPath = _.reject([ parentHashAliasItem.key, baseFieldPath ], function(obj) { return !obj; }).join('.'); // Get absolute hash alias item var absoluteHashAliasItem = _.find(self.hashAlias, function(obj) { return obj.key === absoluteBaseFieldPath; }); // Find absolute hash alias item if undefined if(typeof absoluteHashAliasItem === 'undefined') { // Set data var absoluteFieldPath = absoluteBaseFieldPath + '.' + fieldName; var fieldsToParsePath = absoluteFieldPath.split('.').join('.$fields.').split('.'); var fieldsToParse = {}; // Set fields to parse objectPath.set(fieldsToParse, fieldsToParsePath, 1); // Parse fields _parseFields.call(self, model, fieldsToParse, parentAlias, parentField); // Get absolute hash alias item absoluteHashAliasItem = _.find(self.hashAlias, function(obj) { return obj.key === absoluteBaseFieldPath; }); } // Check if field is a relationship var fieldRelationship = absoluteHashAliasItem.model.getRelationship(fieldName); var isFieldRelationship = !!(fieldRelationship && _.contains([ 'belongsTo', 'hasOne', 'hasOneCustom', 'hasOneThrough' ], fieldRelationship.type)); // If absolute hash alias item is already defined if(!isAnAlias || isFieldRelationship) { // Set real absolute value if(isFieldRelationship) { absoluteBaseFieldPath = _.reject([ absoluteBaseFieldPath, fieldName ], function(obj) { return !obj; }).join('.'); fieldName = fieldRelationship.model.getPrimaryKey(); } // Set data var absoluteFieldPath = _.reject([ absoluteBaseFieldPath, fieldName ], function(obj) { return !obj; }).join('.'); var fieldsToParsePath = absoluteFieldPath.split('.').join('.$fields.').split('.'); var fieldsToParse = {}; // Set fields to parse objectPath.set(fieldsToParse, fieldsToParsePath, 1); // Parse fields _parseFields.call(self, model, fieldsToParse, parentAlias, parentField); // Get absolute hash alias item if(isFieldRelationship) { absoluteHashAliasItem = _.find(self.hashAlias, function(obj) { return obj.key === absoluteBaseFieldPath; }); } } // Return key return itemKeyBefore + '`' + absoluteHashAliasItem.alias + '$' + fieldName + '`' + itemKeyAfter; }; /** * Parse filter value * * @param {Model} model * @param {Mixed} itemValue * @param {String} parentAlias * @param {String} parentField * * @context {Number} aliasIncrement * @context {QueryBuilder} queryBuilder * @context {Object} queryBuilderColumns * @context {Array} requestedFields * @context {Object} queryBuildingTasks * @context {Object} hashAlias * * @private */ var _parseFilterValue = function(model, itemValue, parentAlias, parentField) { // Data var self = this; var whereClause = ''; // If NOT if(_.contains(_.keys(itemValue), '$not')) { whereClause += ' NOT ' + _parseFilter.call(self, model, itemValue['$not'], parentAlias, parentField) + ' '; } // If > else if(_.contains(_.keys(itemValue), '$gt')) { whereClause += ' > ' + _parseValue(itemValue['$gt']) + ' '; } // If >= else if(_.contains(_.keys(itemValue), '$gte')) { whereClause += ' >= ' + _parseValue(itemValue['$gte']) + ' '; } // If < else if(_.contains(_.keys(itemValue), '$lt')) { whereClause += ' < ' + _parseValue(itemValue['$lt']) + ' '; } // If <= else if(_.contains(_.keys(itemValue), '$lte')) { whereClause += ' <= ' + _parseValue(itemValue['$lte']) + ' '; } // If = else if(_.contains(_.keys(itemValue), '$e')) { // Get parsed value var parsedValue = _parseValue(itemValue['$e']); // Add = itemValue if(parsedValue === null) { whereClause += ' IS NULL '; } else { whereClause += ' = ' + parsedValue + ' '; } } // If <> else if(_.contains(_.keys(itemValue), '$ne')) { // Get parsed value var parsedValue = _parseValue(itemValue['$ne']); // Add <> itemValue if(parsedValue === null) { whereClause += ' IS NOT NULL '; } else { whereClause += ' != ' + parsedValue + ' '; } } // If LIKE else if(_.contains(_.keys(itemValue), '$like')) { if(String(itemValue['$like']).match(/^\%|\%$/)) whereClause += ' LIKE ' + knex.raw('?', [ String(itemValue['$like']) ]) + ' '; else whereClause += ' LIKE ' + knex.raw('?', [ '%' + String(itemValue['$like']) + '%' ]) + ' '; } // If NOT LIKE else if(_.contains(_.keys(itemValue), '$nlike')) { if(String(itemValue['$nlike']).match(/^\%|\%$/)) whereClause += ' NOT LIKE ' + knex.raw('?', [ String(itemValue['$nlike']) ]) + ' '; else whereClause += ' NOT LIKE ' + knex.raw('?', [ '%' + String(itemValue['$nlike']) + '%' ]) + ' '; } // If RAW else if(_.contains(_.keys(itemValue), '$raw')) { whereClause += ' ' + knex.raw(itemValue['$raw']) + ' '; } // If IN else if(_.contains(_.keys(itemValue), '$in') && itemValue['$in'] instanceof Array) { // Add quotes for strings var inItems = _.map(itemValue['$in'], function(val) { return _parseValue(val); }); // If IN item is empty if(inItems.length === 0) { whereClause += ' IN (-1) '; } else { whereClause += ' IN (' + inItems.join(',') + ') '; } } // If NIN else if(_.contains(_.keys(itemValue), '$nin') && itemValue['$nin'] instanceof Array) { // Add quotes for strings ninItems = _.map(itemValue['$nin'], function(val) { return _parseValue(val); }); // If IN item is empty if(ninItems.length === 0) { whereClause += ' NOT IN (-1) '; } else { whereClause += ' NOT IN (' + ninItems.join(',') + ') '; } } // If CURRENT WEEK / MONTH / YEAR else if(_.contains(_.keys(itemValue), '$currentweek')) { whereClause += ' >= CAST(\'' + moment.utc().startOf('isoweek').format('YYYY-MM-DD') + '\' AS DATE) '; } else if(_.contains(_.keys(itemValue), '$currentmonth')) { whereClause += ' >= CAST(\'' + moment.utc().startOf('month').format('YYYY-MM-DD') + '\' AS DATE) '; } else if(_.contains(_.keys(itemValue), '$currentyear')) { whereClause += ' >= CAST(\'' + moment.utc().startOf('year').format('YYYY-MM-DD') + '\' AS DATE) '; } // If LAST WEEK / MONTH / YEAR else if(_.contains(_.keys(itemValue), '$lastweek')) { whereClause += ' BETWEEN CAST(\'' + moment.utc().startOf('isoweek').subtract(1, 'week').format('YYYY-MM-DD') + '\' AS DATE) AND CAST(\'' + moment.utc().endOf('isoweek').subtract(1, 'week').format('YYYY-MM-DD') + '\' AS DATE) '; } else if(_.contains(_.keys(itemValue), '$lastmonth')) { whereClause += ' BETWEEN CAST(\'' + moment.utc().startOf('month').subtract(1, 'month').format('YYYY-MM-DD') + '\' AS DATE) AND CAST(\'' + moment.utc().endOf('month').subtract(1, 'month').format('YYYY-MM-DD') + '\' AS DATE) '; } else if(_.contains(_.keys(itemValue), '$lastyear')) { whereClause += ' BETWEEN CAST(\'' + moment.utc().startOf('year').subtract(1, 'year').format('YYYY-MM-DD') + '\' AS DATE) AND CAST(\'' + moment.utc().endOf('year').subtract(1, 'year').format('YYYY-MM-DD') + '\' AS DATE) '; } // If NULL else if(itemValue === null) { whereClause += ' IS NULL '; } // If value else { whereClause += ' = ' + _parseValue(itemValue) + ' '; } /* // Else else { // If object else if(_.isObject(itemValue['$value']) && itemValue['$value'] !== null) { // Data var parsedFilter = _parseFilter.call(self, model, itemValue['$value'], parentAlias, parentField); if(!(new RegExp('CAST\\(.*? AS DATE\\)').exec(filterField)) && new RegExp('CAST\\(.*? AS DATE\\)').exec(parsedFilter)) { whereClause += 'CAST(' + filterField + ' AS DATE) ' + parsedFilter + ' '; } else if(!(new RegExp('CAST\\(.*? AS DATETIME\\)').exec(filterField)) && new RegExp('CAST\\(.*? AS DATETIME\\)').exec(parsedFilter)) { whereClause += 'CAST(' + filterField + ' AS DATETIME) ' + parsedFilter + ' '; } else { whereClause += filterField + ' ' + parsedFilter + ' '; } } } */ // Return filter string return whereClause; }; /** * Parse value * * @param {Mixed} value * * @private */ var _parseValue = function(value) { // Set data var parsedValue = ''; // If value is a date if(_.isObject(value) && value['$date']) { value = { '$before_value': 'CAST(', '$after_value': ' AS DATE)', '$value': value['$date'] }; } // If value is a datetime else if(_.isObject(value) && value['$datetime']) { value = { '$before_value': 'CAST(', '$after_value': ' AS DATETIME)', '$value': value['$datetime'] }; } // If value is a Date else if(_.isObject(value) && value instanceof Date) { value = { '$before_value': 'CAST(', '$after_value': ' AS DATETIME)', '$value': moment.utc(value).format('YYYY-MM-DD HH:mm:ss') }; } // If value is standard else if(!_.isObject(value)) { value = { '$before_value': '', '$after_value': '', '$value': value }; } // Check if value is null if(value['$value'] === null) { return null; } else if(value['$value'] === '$null') { return null; } // Add value if(typeof value['$value'] === 'number') parsedValue += knex.raw('?', [ value['$value'] ]); else parsedValue += knex.raw('?', [ String(value['$value']) ]); // Add before/after of value parsedValue = value['$before_value'] + parsedValue + value['$after_value']; // Return parsed value return parsedValue; }; // Exports module.exports = { _parseFields: _parseFields, _parseFilter: _parseFilter, _parseFilterValue: _parseFilterValue, _parseValue: _parseValue };