UNPKG

skul

Version:

SKuL - SQL query manager

1,102 lines (795 loc) 22.3 kB
/* * helpers/url-parser.js * * UrlParser helper * * Author: Paul Duthoit * Copyright(c) 2016 OOCAR */ // Dependencies var _ = require('underscore'); var moment = require('moment'); /** * UrlParser constructor * @api public */ var UrlParser = function() {}; // Constants var URL_REGEX = new RegExp("(\\ *[a-z]+\\()" + "|" + // Logical Query Opener "(\\.[a-z]+\\()" + "|" + // Method Opener "(\\)\\ *)" + "|" + // Method Closer "(\\ *,\\ *)" + "|" + // Query/Argument Separator "(\\ *[a-zA-Z0-9_\\.\\-]+)(?=\\.[a-z]+\\()" + "|" + // Field followed by Method "(\\ *[a-zA-Z0-9_\\.\\-\\$]+)(?=\\ *(,|\\)))" + "|" + // Field alone "(\\ *[a-zA-Z0-9_\\.\\-\\$]+)$" + "|" + // Finished Field "([^\\(\\),\\\\]+)(?:\\\\.[^\\(\\),\\\\]*)*", "g"); // Argument var URL_ARRAY_OPENER = [ 'or(', 'and(', 'nand(', 'nor(', 'xor(', '.in(', '.nin(', '.between(' ]; var URL_VALUE_OPENER = [ '.gt(', '.gte(', '.lt(', '.lte(', '.e(', '.ne(', '.like(', '.nlike(', 'date(', '.lastyear(', '.lastmonth(', '.lastweek(', '.currentyear(', '.currentmonth(', '.currentweek(' ]; var URL_BOOLEAN_OPENER = [ '.count(', '.lastyear(', '.lastmonth(', '.lastweek(', '.currentyear(', '.currentmonth(', '.currentweek(' ]; var URL_OPERATOR_OPENER = [ '.gt(', '.gte(', '.lt(', '.lte(', '.e(', '.ne(', '.like(', '.nlike(', '.in(', '.nin(', '.between(' ]; var URL_FUNCTION_OPENER = [ '.filter(', '.count(', '.sum(', '.avg(', '.as(' ]; var URL_OPENER = _.uniq(URL_ARRAY_OPENER.concat(URL_VALUE_OPENER, URL_FUNCTION_OPENER, URL_BOOLEAN_OPENER)); /** * Parse query * * @params {String} stringToParse * @return {Object} * @api public */ UrlParser.parseQuery = function(stringToParse) { // Datas var query = {}; var lastObj = null; var currentObj = query; var currentKey = null; var currentPosition = 0; // Check if string is empty if(!stringToParse) { return {}; } // Check string to parse if(!new RegExp(URL_REGEX.source, "g").test(stringToParse)) throw "Invalid query request"; // Split string var splitedString = stringToParse.match(URL_REGEX); // Walk through the splited string _.each(splitedString, function(value) { // Trim value value = value.trim(); // Is opener if(currentPosition === 0 && _.contains(URL_OPENER, value)) { // If value is an url function opener if(_.contains(URL_FUNCTION_OPENER, value) && !_.contains(_.keys(currentObj), '$where')) { currentObj['$where'] = {}; } // If current obj has already a function opener if((_.contains(URL_OPERATOR_OPENER, value)) && _.contains(_.keys(currentObj), '$where')) { lastObj = currentObj; currentObj = currentObj['$where']; } // Set current key currentKey = value.substr(0, 1) === '.' ? '$' + value.substr(1, value.length-2) : '$' + value.substr(0, value.length-1); // Set current value if value is an array opener if(_.contains(URL_ARRAY_OPENER, value)) { currentObj[currentKey] = [ '' ]; } // Set current position currentPosition++; } // Is already open else if(currentPosition > 0) { // If another argument if(currentPosition === 1 && value === ',' && currentObj[currentKey] instanceof Array) { // Parse previous value currentObj[currentKey][currentObj[currentKey].length-1] = UrlParser.parseQuery(currentObj[currentKey][currentObj[currentKey].length-1]); // Push new value currentObj[currentKey].push(''); } // If the current object is a closer else if(currentPosition === 1 && value === ')' && currentObj[currentKey] instanceof Array) { // Parse previous value currentObj[currentKey][currentObj[currentKey].length-1] = UrlParser.parseQuery(currentObj[currentKey][currentObj[currentKey].length-1]); } // If the current object is a closer else if(currentPosition === 1 && value === ')') { // Parse previous value if(_.contains(_.map(URL_BOOLEAN_OPENER, function(val) { return '$' + val.replace(/^\.|\($/g, ''); }), currentKey)) { currentObj[currentKey] = 1; } else if(_.contains([ '$sum', '$avg' ], currentKey)) { currentObj[currentKey] = currentObj[currentKey]; } else { currentObj[currentKey] = UrlParser.parseQuery(currentObj[currentKey]); } // Current obj if(lastObj) { currentObj = lastObj; } } // If not the current object closer else { // Set current value if(currentObj[currentKey] instanceof Array) { currentObj[currentKey][currentObj[currentKey].length-1] += value; } else if(_.isUndefined(currentObj[currentKey])){ currentObj[currentKey] = value; } else { currentObj[currentKey] += value; } } // Set current position if(_.contains(URL_OPENER, value)) { currentPosition++; } else if(value === ')') { currentPosition--; } } // Is , else if(currentPosition === 0 && splitedString.length > 1 && value === ',') { // Change query query = { '$and': [ _.clone(query), {} ] }; currentObj = query['$and'][1]; } // Is field else if(currentPosition === 0 && splitedString.length > 1) { currentObj[value] = {}; currentObj = currentObj[value]; } // Is value else if(currentPosition === 0 && splitedString.length === 1) { // Set query if(moment(value, [ 'YYYY-MM-DD', 'YYYY-MM-DD HH:mm:ss' ], true).isValid()) { query = value; } else if(value.match(/^[0-9.]*?$/)) { query = parseFloat(value); } else { query = value; } } }); // Return query return query; }; /** * Parse projection * * @params {String} stringToParse * @return {Object} * @api public */ UrlParser.parseProjection = function(stringToParse) { // Check datas if(typeof(stringToParse) !== "string") throw "stringToParse have to be a string"; // Datas var projection = {}; var currentObj = projection; var currentObjKey = false; var currentObjLevel = 0; var parentObj = false; var parentObjKey = false; var onProjection = false; var onProjectionLevel = 0; var onProjectionString = ""; var onQuery = false; var onQueryLevel = 0; var onQueryString = ""; var onOrderBy = false; var onOrderByLevel = 0; var onOrderByString = ""; var onLimit = false; var onSkip = false; var onAfter = false; var onBefore = false; // Check string to parse if(!new RegExp(URL_REGEX.source, "g").test(stringToParse)) throw "Invalid projection request"; // Split string var splitedString = stringToParse.match(URL_REGEX); // Walk through the splited string splitedString.forEach(function(value) { // Trim value value = value.trim(); // If value is ) if(value === ")") { // On projection if(onProjection) { // Not finished if(onProjectionLevel > 1) { // Update projection datas onProjectionString += value; onProjectionLevel--; } // Finished else { // Extend current obj _.extend(currentObj, UrlParser.parseProjection(onProjectionString)); // Reset projection datas onProjectionString = ""; onProjectionLevel = 0; onProjection = false; // Close obj parentObj = currentObj.__parentObj; parentObjKey = currentObj.__parentObjKey; delete currentObj.__parentObj; delete currentObj.__parentObjKey; currentObj = parentObj; currentObjKey = parentObjKey; currentObjLevel--; } } // On query else if(onQuery) { // Not finished if(onQueryLevel > 1) { // Update query datas onQueryString += value; onQueryLevel--; } // Finished else { // Extend current obj _.extend(currentObj, UrlParser.parseQuery(onQueryString)); // Reset query datas onQueryString = ""; onQueryLevel = 0; onQuery = false; // Close obj parentObj = currentObj.__parentObj; parentObjKey = currentObj.__parentObjKey; delete currentObj.__parentObj; delete currentObj.__parentObjKey; currentObj = parentObj; currentObjKey = parentObjKey; currentObjLevel--; } } // On orderby else if(onOrderBy) { // Not finished if(onOrderByLevel > 1) { // Update orderby datas onOrderByString += value; onOrderByLevel--; } // Finished else { // Set current obj options if(currentObj[currentObjKey] === 1) { currentObj[currentObjKey] = { "$options" : { orderby : UrlParser.parseOrderBy(onOrderByString) } }; } else if(typeof(currentObj[currentObjKey]["$options"]) === "object") { currentObj[currentObjKey]["$options"].orderby = UrlParser.parseOrderBy(onOrderByString); } else { currentObj[currentObjKey]["$options"] = { orderby : UrlParser.parseOrderBy(onOrderByString) }; } // Reset orderby datas onOrderByString = ""; onOrderByLevel = 0; onOrderBy = false; } } // On limit else if(onLimit) { // Close limit onLimit = false; } // On skip else if(onSkip) { // Close skip onSkip = false; } // On after else if(onAfter) { // Close after onAfter = false; } // On before else if(onBefore) { // Close before onBefore = false; } // Close current obj else { // Close obj parentObj = currentObj.__parentObj; parentObjKey = currentObj.__parentObjKey; delete currentObj.__parentObj; delete currentObj.__parentObjKey; currentObj = parentObj; currentObjKey = parentObjKey; currentObjLevel--; } } // If value is .limit( else if(value === ".limit(") { // On projection if(onProjection) { // Update projection datas onProjectionString += value; onProjectionLevel++; } // On query else if(onQuery) { // Update query datas onQueryString += value; onQueryLevel++; } // On orderby else if(onOrderBy) { // Update orderby datas onOrderByString += value; onOrderByLevel++; } // Open limit else { // Set current obj options if(currentObj[currentObjKey] === 1) { currentObj[currentObjKey] = { "$options" : { limit : 0 } }; } else if(typeof(currentObj[currentObjKey]["$options"]) === "object") { currentObj[currentObjKey]["$options"].limit = 0; } else { currentObj[currentObjKey]["$options"] = { limit : 0 }; } // Update state onLimit = true; } } // If value is .skip( else if(value === ".skip(") { // On projection if(onProjection) { // Update projection datas onProjectionString += value; onProjectionLevel++; } // On query else if(onQuery) { // Update query datas onQueryString += value; onQueryLevel++; } // On orderby else if(onOrderBy) { // Update orderby datas onOrderByString += value; onOrderByLevel++; } // Open skip else { // Set current obj options if(currentObj[currentObjKey] === 1) { currentObj[currentObjKey] = { "$options" : { skip : 0 } }; } else if(typeof(currentObj[currentObjKey]["$options"]) === "object") { currentObj[currentObjKey]["$options"].skip = 0; } else { currentObj[currentObjKey]["$options"] = { skip : 0 }; } // Update state onSkip = true; } } // If value is .after( else if(value === ".after(") { // On projection if(onProjection) { // Update projection datas onProjectionString += value; onProjectionLevel++; } // On query else if(onQuery) { // Update query datas onQueryString += value; onQueryLevel++; } // On orderby else if(onOrderBy) { // Update orderby datas onOrderByString += value; onOrderByLevel++; } // Open after else { // Set current obj options if(currentObj[currentObjKey] === 1) { currentObj[currentObjKey] = { "$options" : { after : "" } }; } else if(typeof(currentObj[currentObjKey]["$options"]) === "object") { currentObj[currentObjKey]["$options"].after = ""; } else { currentObj[currentObjKey]["$options"] = { after : "" }; } // Update state onAfter = true; } } // If value is .before( else if(value === ".before(") { // On projection if(onProjection) { // Update projection datas onProjectionString += value; onProjectionLevel++; } // On query else if(onQuery) { // Update query datas onQueryString += value; onQueryLevel++; } // On orderby else if(onOrderBy) { // Update orderby datas onOrderByString += value; onOrderByLevel++; } // Open before else { // Set current obj options if(currentObj[currentObjKey] === 1) { currentObj[currentObjKey] = { "$options" : { before : "" } }; } else if(typeof(currentObj[currentObjKey]["$options"]) === "object") { currentObj[currentObjKey]["$options"].before = ""; } else { currentObj[currentObjKey]["$options"] = { before : "" }; } // Update state onBefore = true; } } // If value is .orderby( else if(value === ".orderby(") { // On projection if(onProjection) { // Update projection datas onProjectionString += value; onProjectionLevel++; } // On query else if(onQuery) { // Update query datas onQueryString += value; onQueryLevel++; } // On orderby else if(onOrderBy) { // Update orderby datas onOrderByString += value; onOrderByLevel++; } // Open before else { // Update state onOrderByLevel++; onOrderBy = true; } } // If value is .fields( else if(value === ".fields(") { // On projection if(onProjection) { // Update projection datas onProjectionString += value; onProjectionLevel++; } // On query else if(onQuery) { // Update query datas onQueryString += value; onQueryLevel++; } // On orderby else if(onOrderBy) { // Update orderby datas onOrderByString += value; onOrderByLevel++; } // Open projection else { // Set current obj fields if(currentObj[currentObjKey] === 1) { currentObj[currentObjKey] = { "$fields" : { __parentObj : currentObj, __parentObjKey : currentObjKey } }; } else if(typeof(currentObj[currentObjKey]["$fields"]) === "object") { currentObj[currentObjKey]["$fields"].__parentObj = currentObj; currentObj[currentObjKey]["$fields"].__parentObjKey = currentObjKey; } else { currentObj[currentObjKey]["$fields"] = { __parentObj : currentObj, __parentObjKey : currentObjKey }; } // Update obj datas currentObj = currentObj[currentObjKey]["$fields"]; currentObjKey = false; currentObjLevel++; // Update projection datas onProjectionLevel++; onProjection = true; } } // If value is .embedded( else if(value === ".embedded(") { // On projection if(onProjection) { // Update projection datas onProjectionString += value; onProjectionLevel++; } // On query else if(onQuery) { // Update query datas onQueryString += value; onQueryLevel++; } // On orderby else if(onOrderBy) { // Update orderby datas onOrderByString += value; onOrderByLevel++; } // Open projection else { // Set current obj embedded if(currentObj[currentObjKey] === 1) { currentObj[currentObjKey] = { "$embedded" : { __parentObj : currentObj, __parentObjKey : currentObjKey } }; } else if(typeof(currentObj[currentObjKey]["$embedded"]) === "object") { currentObj[currentObjKey]["$embedded"].__parentObj = currentObj; currentObj[currentObjKey]["$embedded"].__parentObjKey = currentObjKey; } else { currentObj[currentObjKey]["$embedded"] = { __parentObj : currentObj, __parentObjKey : currentObjKey }; } // Update obj datas currentObj = currentObj[currentObjKey]["$embedded"]; currentObjKey = false; currentObjLevel++; // Update projection datas onProjectionLevel++; onProjection = true; } } // If value is .filter( else if(value === ".filter(") { // On projection if(onProjection) { // Update projection datas onProjectionString += value; onProjectionLevel++; } // On query else if(onQuery) { // Update query datas onQueryString += value; onQueryLevel++; } // On orderby else if(onOrderBy) { // Update orderby datas onOrderByString += value; onOrderByLevel++; } // Open query else { // Set current obj query if(currentObj[currentObjKey] === 1) { currentObj[currentObjKey] = { "$filter" : { __parentObj : currentObj, __parentObjKey : currentObjKey } }; } else if(typeof(currentObj[currentObjKey]["$filter"]) === "object") { currentObj[currentObjKey]["$filter"].__parentObj = currentObj; currentObj[currentObjKey]["$filter"].__parentObjKey = currentObjKey; } else { currentObj[currentObjKey]["$filter"] = { __parentObj : currentObj, __parentObjKey : currentObjKey }; } // Update obj datas currentObj = currentObj[currentObjKey]["$filter"]; currentObjKey = false; currentObjLevel++; // Update query datas onQueryLevel++; onQuery = true; } } // If value is . else if(value === ".") { // On projection if(onProjection) { // Update projection datas onProjectionString += value; } // On query else if(onQuery) { // Update query datas onQueryString += value; } // On orderby else if(onOrderBy) { // Update orderby datas onOrderByString += value; } // Open field else { // Set current obj fields if(currentObj[currentObjKey] === 1) { currentObj[currentObjKey] = { "$fields": { __parentObj : currentObj, __parentObjKey : currentObjKey } }; } else if(typeof(currentObj[currentObjKey]["$fields"]) === "object") { currentObj[currentObjKey]["$fields"].__parentObj = currentObj; currentObj[currentObjKey]["$fields"].__parentObjKey = currentObjKey; } else { currentObj[currentObjKey]["$fields"] = { __parentObj : currentObj, __parentObjKey : currentObjKey }; } // Update obj datas currentObj = currentObj[currentObjKey]["$fields"]; currentObjKey = false; currentObjLevel++; } } // If value is , else if(value === ",") { // On projection if(onProjection) { // Update projection datas onProjectionString += value; } // On query else if(onQuery) { // Update query datas onQueryString += value; } // On orderby else if(onOrderBy) { // Update orderby datas onOrderByString += value; } // Change obj else { // Close current obj while(currentObjLevel > 0) { parentObj = currentObj.__parentObj; parentObjKey = currentObj.__parentObjKey; delete currentObj.__parentObj; delete currentObj.__parentObjKey; currentObj = parentObj; currentObjKey = parentObjKey; currentObjLevel--; } // Update obj datas currentObjKey = false; } } // If value is other else if(value !== "") { // On projection if(onProjection) { // Update projection datas onProjectionString += value; if(value.indexOf("(") !== -1) { onProjectionLevel++; } } // On query else if(onQuery) { // Update query datas onQueryString += value; if(value.indexOf("(") !== -1) { onQueryLevel++; } } // On orderby else if(onOrderBy) { // Update orderby datas onOrderByString += value; if(value.indexOf("(") !== -1) { onOrderBy++; } } // On limit else if(onLimit) { // Set current obj limit options currentObj[currentObjKey]["$options"].limit = parseInt(value); } // On skip else if(onSkip) { // Set current obj skip options currentObj[currentObjKey]["$options"].skip = parseInt(value); } // On after else if(onAfter) { // Set current obj after options currentObj[currentObjKey]["$options"].after = value; } // On before else if(onBefore) { // Set current obj before options currentObj[currentObjKey]["$options"].before = value; } // Open field else { // Split value value.split(".").forEach(function(item, key) { if(key > 0) { // Set current obj fields if(currentObj[currentObjKey] === 1) { currentObj[currentObjKey] = { "$fields" : { __parentObj : currentObj, __parentObjKey : currentObjKey } }; } else if(typeof(currentObj[currentObjKey]["$fields"]) === "object") { currentObj[currentObjKey]["$fields"].__parentObj = currentObj; currentObj[currentObjKey]["$fields"].__parentObjKey = currentObjKey; } else { currentObj[currentObjKey]["$fields"] = { __parentObj : currentObj, __parentObjKey : currentObjKey }; } currentObj = currentObj[currentObjKey]["$fields"]; currentObjKey = false; currentObjLevel++; } // Update obj datas currentObjKey = item; if(typeof(currentObj[item]) !== "object") { currentObj[item] = 1; } }); } } }); // Delete helpers while(currentObjLevel > 0) { parentObj = currentObj.__parentObj; parentObjKey = currentObj.__parentObjKey; delete currentObj.__parentObj; delete currentObj.__parentObjKey; currentObj = parentObj; currentObjKey = parentObjKey; currentObjLevel--; } // Return projection return projection; }; /** * Parse orderBy * * @params {String} stringToParse * @return {Object} * @api public */ UrlParser.parseOrderBy = function(stringToParse) { // Check datas if(typeof(stringToParse) !== "string") throw "stringToParse have to be a string"; /* // Datas var orderBy = {}; */ // Split string // var splitedString = stringToParse.split(/\ *,\ */); /* // Walk through splited string splitedString.forEach(function(value) { // Value is desc field if(value.slice(0, 1) === "-") { orderBy[value.slice(1)] = -1; } // Value is asc field else { orderBy[value] = 1; } }); // Return orderby return orderBy; */ // Return orderby return stringToParse.split(/\ *,\ */); }; // Exports module.exports = UrlParser;