UNPKG

skul

Version:

SKuL - SQL query manager

542 lines (407 loc) 14.3 kB
/* * lib/skul/parser.js * * SKuL parser methods * * Author: Paul Duthoit * Copyright(c) 2016 Paul Duthoit */ // Dependencies var _ = require('underscore'); var objectPath = require('object-path'); var QueryException = require('./exceptions/query'); /* * Parse fields * * @params {Object} fields * * @return {Array} * @api public */ module.exports.parseFields = function(fields) { // Check arguments if(typeof fields !== "object") throw new Error("fields have to be an object"); // Data var parsedFields = []; var invalidQueryData = {}; var defaultKeys = this.getDefaultKeys(); var structureColumns = this.getStructureColumns(); var customKeys = this.getCustomKeys(); var virtuals = this.getVirtuals(); var virtualKeys = this.getVirtualKeys(); var allRelationshipKeys = this.getRelationshipKeys(); var belongsToRelationshipKeys = this.getRelationshipKeys('belongsTo'); var belongsToRelationships = this.getRelationships('belongsTo'); // Check if fields is empty if(Object.keys(fields).length === 0) { fields = { '$default': 1 }; } // Loop over fields keys _.each(fields, function(obj, key) { // If $all if(key === '$all' && obj === 1) { parsedFields = parsedFields.concat(structureColumns); } // If $all else if(key === '$default' && obj === 1) { // Push simple keys parsedFields = parsedFields.concat(_.intersection(structureColumns, defaultKeys)); // Push virtual default keys _.each(_.intersection(virtualKeys, defaultKeys), function(key) { parsedFields = parsedFields.concat(_.keys(virtuals[key].requiredKeys)); }); // Push belongsTo relationship default keys _.each(_.intersection(belongsToRelationshipKeys, defaultKeys), function(key) { parsedFields.push(belongsToRelationships[key].foreignKey); }); } // If requested field is a virtual field else if(obj !== 0 && _.contains(virtualKeys, key)) { parsedFields = parsedFields.concat(_.keys(virtuals[key].requiredKeys)); } // If requested field is an available field else if(obj !== 0 && !_.contains(allRelationshipKeys, key) && !_.contains(customKeys, key) && _.contains(structureColumns, key)) { parsedFields.push(key); } // If requested field is a belongsTo relationship else if(obj !== 0 && _.contains(belongsToRelationshipKeys, key)) { parsedFields.push(belongsToRelationships[key].foreignKey); } // If requested field is an invalid field else if(obj !== 0 && !_.contains(allRelationshipKeys, key) && !_.contains(customKeys, key) && !_.contains(structureColumns, key)) { invalidQueryData[key] = 'unknown'; } }); // Reject if has some invalid fields if(Object.keys(invalidQueryData).length > 0) { throw new QueryException('InvalidQueryData', invalidQueryData); } // Avoid duplicate fields parsedFields = _.uniq(parsedFields); // Check if primary key is present if(!_.contains(parsedFields, this.primaryKey) && this.primaryKey !== "") { parsedFields.unshift(this.primaryKey); } // Return parsed fields return parsedFields; }; /* * Parse virtuals * * @params {Object} fields * * @return {Object} * @api public */ module.exports.parseVirtuals = function(fields) { // Check arguments if(typeof fields !== "object") throw new Error("fields have to be an object"); // Data var parsedFields = {}; var defaultKeys = this.getDefaultKeys(); var structureColumns = this.getStructureColumns(); var virtualKeys = this.getVirtualKeys(); var belongsToRelationshipKeys = this.getRelationshipKeys('belongsTo'); var belongsToRelationships = this.getRelationships('belongsTo'); // Check if fields is empty if(Object.keys(fields).length === 0) { fields = { '$default': 1 }; } // Loop over fields keys _.each(fields, function(obj, key) { // If key is a virtual field if(!_.contains([ '$all, $default' ], key) && _.contains(virtualKeys, key) && obj !== 0) { parsedFields[key] = obj; } // If key is a relationship field else if(!_.contains([ '$all, $default' ], key) && _.contains(belongsToRelationshipKeys, key) && obj !== 0) { // Data var relModel = belongsToRelationships[key].model; var relVirtuals = relModel.parseVirtuals(obj['$fields'] || {}); // Add to parsed fields parsedFields[key] = { '$fields': {}, '$model': relModel }; // Loop over reltionship virtuals _.each(relVirtuals, function(virtualObj, virtualKey) { // Add to parsed fields parsedFields[key]['$fields'][virtualKey] = virtualObj; }); } // If key is $default else if(key === '$default' && _.intersection(defaultKeys, virtualKeys).length > 0) { // Loop over each intersection between defaults and virtuals _.each(_.intersection(defaultKeys, virtualKeys), function(fieldKey) { parsedFields[fieldKey] = obj; }); } // If key is $all else if(key === '$all' && _.intersection(structureColumns, virtualKeys).length > 0) { // Loop over each intersection between availables and virtuals _.each(_.intersection(structureColumns, virtualKeys), function(fieldKey) { parsedFields[fieldKey] = obj; }); } }); // Return parsed fields return parsedFields; }; /* * Parse customs * * @params {Object} fields * * @return {Array} * @api public */ module.exports.parseCustoms = function(fields) { // Check arguments if(typeof fields !== "object") throw new Error("fields have to be an object"); // Data var parsedFields = {}; var customs = this.getCustoms(); var customKeys = this.getCustomKeys(); var defaultKeys = this.getDefaultKeys(); var defaultCustomKeys = _.intersection(customKeys, defaultKeys); // Check if fields is empty if(Object.keys(fields).length === 0) { fields = { '$default': 1 }; } // Loop over fields keys _.each(fields, function(fieldObj, fieldKey) { // If $all if(fieldKey === '$all' && fieldObj === 1) { _.each(customKeys, function(key) { parsedFields[key] = customs[key]; }); } // If $all else if(fieldKey === '$default' && fieldObj === 1) { _.each(defaultCustomKeys, function(key) { parsedFields[key] = customs[key]; }); } // If requested field is an available custom field else if(fieldObj !== 0 && _.contains(customKeys, fieldKey)) { parsedFields[fieldKey] = customs[fieldKey]; } }); // Return parsed fields return parsedFields; }; /* * Parse relationships * * @params {Object} fields * * @return {Object} * @api public */ module.exports.parseRelationships = function(type, fields) { // Check arguments if(arguments.length === 1) { fields = type; type = undefined; } // Check arguments if(!(typeof type === "string" && _.contains([ 'belongsTo', 'hasOne', 'hasOneCustom', 'hasMany', 'hasManyCustom' ], type)) && typeof type !== "undefined") throw new Error("type have to be a string defined as belongsTo, hasOne, hasOneCustom, hasMany or hasManyCustom"); if(typeof fields !== "object") throw new Error("fieldPath have to be an object"); // Data var parsedFields = {}; var relationshipKeys = this.getRelationshipKeys(type); // Loop over fields keys _.each(fields, function(obj, key) { // If key is a relationship field if(!_.contains([ '$all, $default' ], key) && _.contains(relationshipKeys, key) && obj !== 0) { parsedFields[key] = obj; } }); // Return parsed fields return parsedFields; }; /* * Parse select options * * @params {Object} options * * @return {Object} * @api public */ module.exports.parseSelectOptions = function(options) { // Check arguments length if(arguments.length === 0) { options = {}; } // Transform arguments if(options === null || typeof options === "undefined") options = {}; // Check arguments if(!_.isObject(options)) throw new Error('options have to be an object'); // Data var self = this; // Get skip if(_.isUndefined(options.skip)) { if(!_.isUndefined(this.defaultSelectOptions.skip) && this.defaultSelectOptions.skip !== null) { options.skip = this.defaultSelectOptions.skip; } else { options.skip = null; } } // Get limit if(_.isUndefined(options.limit)) { if(!_.isUndefined(this.defaultSelectOptions.limit) && this.defaultSelectOptions.limit !== null) { options.limit = this.defaultSelectOptions.limit; } else { options.limit = null; } } // Get orderby if(_.isUndefined(options.orderby)) { if(!_.isUndefined(this.defaultSelectOptions.orderby) && this.defaultSelectOptions.orderby !== null) { options.orderby = this.defaultSelectOptions.orderby; } else { options.orderby = null; } } // If orderby is not empty if(options.orderby !== null) { // If orderby is not an array if(!(options.orderby instanceof Array)) { options.orderby = [ options.orderby ]; } // Data var _getStructureColumnsLvl = 0; var maxStructureColumnsLvl = _.max(_.map(options.orderby, function(value) { return value.split('.').length; })); // Get available fields var _getStructureColumns = function(model) { // Increment field lvl _getStructureColumnsLvl++; // Data var structureColumns = []; // Get main available fields var mainStructureColumns = model.getStructureColumns(); var mainCustomKeys = model.getCustomKeys(); // Add main and custom fields structureColumns = structureColumns.concat(mainStructureColumns, mainCustomKeys); // Check lvl if(_getStructureColumnsLvl > maxStructureColumnsLvl) { _getStructureColumnsLvl--; return; } // Add relationship belongsTo fields var belongsToRelationships = model.getRelationships('belongsTo'); // Loop over relationships _.each(belongsToRelationships, function(rel, key) { // Get relationship available fields var relStructureColumns = _getStructureColumns(rel.model); relStructureColumns = _.map(relStructureColumns, function(obj) { return key + '.' + obj; }); // Add relationship available fields structureColumns = structureColumns.concat(relStructureColumns); }); // Add relationship hasOne fields var hasOneRelationships = model.getRelationships('hasOne'); // Loop over relationships _.each(hasOneRelationships, function(rel, key) { // Get relationship available fields var relStructureColumns = _getStructureColumns(rel.model); relStructureColumns = _.map(relStructureColumns, function(obj) { return key + '.' + obj; }); // Add relationship available fields structureColumns = structureColumns.concat(relStructureColumns); }); // Add relationship hasOneCustom fields var hasOneCustomRelationships = model.getRelationships('hasOneCustom'); // Loop over relationships _.each(hasOneCustomRelationships, function(rel, key) { // Get relationship available fields var relStructureColumns = _getStructureColumns(rel.model); relStructureColumns = _.map(relStructureColumns, function(obj) { return key + '.' + obj; }); // Add relationship available fields structureColumns = structureColumns.concat(relStructureColumns); }); // Decrement field lvl _getStructureColumnsLvl--; // Return available fields return structureColumns; }; // Get available fields var structureColumns = _getStructureColumns(self); // Check orderby var orderByWrongKeys = _.difference(_.map(options.orderby, function(value) { return value.replace(/^\-/''); }), structureColumns); // If some wrong fields found if(orderByWrongKeys.length > 0) { // Data var invalidQueryData = {}; // For each wrong fields _.each(orderByWrongKeys, function(key) { invalidQueryData[key] = 'unknown'; }); // Reject throw new QueryException('InvalidQueryData', invalidQueryData); } } // Return options return options; }; /* * Parse select result * * @params {Array} data * @params {Array} hashAlias * @params {Boolean} isFlatten * * @return {Array} * @api public */ module.exports.parseSelectResult = function(data, hashAlias, isFlatten) { // Data var dataToParse = data; var isArray = dataToParse instanceof Array; // Check if data to parse is an array if(!isArray) { dataToParse = [ dataToParse ]; } // Transform data dataToParse = _.map(dataToParse, function(dataItem) { // Data var transformedDataItem = {}; var nullNested = []; // Loop over data item fields _.each(dataItem, function(dataItemValue, dataItemKey) { // Data var splitedKey = dataItemKey.split('$'); var fieldName = splitedKey.pop(); var fieldAlias = (_.find(hashAlias, function(obj) { return obj.alias === splitedKey.join('$'); }) || {}); var fieldBase = fieldAlias.key; var fieldModel = fieldAlias.model; var fieldModelPrimaryKey = fieldModel.getPrimaryKey(); var fieldPath = _.reject([ fieldBase, fieldName ], function(obj) { return obj === ''; }).join('.'); // Check if primary is empty if(fieldName === fieldModelPrimaryKey && dataItemValue === null && !_.contains(nullNested, fieldBase)) { nullNested.push(fieldBase); } // Set transformed data if(isFlatten) { transformedDataItem[fieldPath] = dataItemValue; } else { objectPath.set(transformedDataItem, fieldPath, dataItemValue); } }); // Loop over null nested _.each(nullNested, function(nestedItem) { if(typeof objectPath.get(transformedDataItem, nestedItem) !== "undefined") { objectPath.set(transformedDataItem, nestedItem, null); } }); // Return transformed data return transformedDataItem; }); // Return data if(isArray) { return dataToParse; } else { return dataToParse[0]; } };