UNPKG

json-sql-isme2n

Version:

node.js json to sql queries mapper

354 lines (282 loc) 8.7 kB
'use strict'; var _ = require('underscore'); var ValuesStore = require('../../utils/valuesStore'); var objectUtils = require('../../utils/object'); var templatesInit = require('./templates'); var blocksInit = require('./blocks'); var operatorsInit = require('./operators'); var modifiersInit = require('./modifiers'); var blockRegExp = /\{([a-z0-9]+)\}(.|$)/ig; var Dialect = module.exports = function(builder) { this.builder = builder; this.templates = new ValuesStore(); this.blocks = new ValuesStore(); this.operators = { comparison: new ValuesStore(), logical: new ValuesStore(), fetching: new ValuesStore(), state: new ValuesStore() }; this.modifiers = new ValuesStore(); // init templates templatesInit(this); // init blocks blocksInit(this); // init operators operatorsInit(this); // init modifiers modifiersInit(this); this.identifierPartsRegexp = new RegExp( '(\\' + this.config.identifierPrefix + '[^\\' + this.config.identifierSuffix + ']*\\' + this.config.identifierSuffix + '|[^\\.]+)', 'g' ); this.wrappedIdentifierPartRegexp = new RegExp( '^\\' + this.config.identifierPrefix + '.*\\' + this.config.identifierSuffix + '$' ); }; Dialect.prototype.config = { identifierPrefix: '"', identifierSuffix: '"' }; Dialect.prototype._wrapIdentifier = function(name) { if (this.builder.options.wrappedIdentifiers) { var self = this; var nameParts = name.match(this.identifierPartsRegexp); return _(nameParts).map(function(namePart) { if (namePart !== '*' && !self.wrappedIdentifierPartRegexp.test(namePart)) { namePart = self.config.identifierPrefix + namePart + self.config.identifierSuffix; } return namePart; }).join('.'); } return name; }; Dialect.prototype.buildLogicalOperator = function(params) { var self = this; var operator = params.operator; var value = params.value; if (objectUtils.isSimpleValue(value)) { value = _.object([params.defaultFetchingOperator], [value]); } if (_.isEmpty(value)) return ''; var result; if (_.isArray(value)) { // if value is array: [{a: 1}, {b: 2}] process each item as logical operator result = _(value).map(function(item) { return self.buildOperator({ context: 'logical', contextOperator: operator, operator: '$and', value: item, states: [], defaultFetchingOperator: params.defaultFetchingOperator }); }); } else { result = _(value).map(function(item, field) { // if field name is not a operator convert it to {$field: {name: 'a', $eq: 'b'}} if (field[0] !== '$') { if (objectUtils.isSimpleValue(item) || _.isArray(item)) { item = {$eq: item}; } item = _.defaults({name: field}, item); field = '$field'; } return self.buildOperator({ context: 'logical', contextOperator: operator, operator: field, value: item, states: [], defaultFetchingOperator: params.defaultFetchingOperator }); }); } return this.operators.logical.get(operator).fn(_.compact(result)); }; Dialect.prototype.buildComparisonOperator = function(params) { var self = this; var operator = params.operator; _(params.states).each(function(state) { operator = self.operators.state.get(state).getOperator(operator); }); var operatorParams = this.operators.comparison.get(operator); var value = this.buildEndFetchingOperator({ context: 'comparison', contextOperator: operator, value: params.value, states: params.states, defaultFetchingOperator: operatorParams.defaultFetchingOperator || params.defaultFetchingOperator }); return operatorParams.fn(params.field, value); }; Dialect.prototype.buildFetchingOperator = function(params) { var operator = params.operator; var value = params.value; var field = this.operators.fetching.get(operator).fn(value, params.end); var result; if (params.end || objectUtils.isSimpleValue(value)) { result = field; } else { result = this.buildOperatorsGroup({ context: 'fetching', contextOperator: operator, operator: '$and', field: field, value: value, states: params.states, defaultFetchingOperator: params.defaultFetchingOperator }); } return result; }; Dialect.prototype.buildEndFetchingOperator = function(params) { var self = this; var value = params.value; var operator; if (objectUtils.isObjectObject(value)) { // get first query operator operator = _(value).findKey(function(item, operator) { return operator[0] === '$' && self.operators.fetching.has(operator); }); if (operator) { value = value[operator]; } } return this.buildOperator(_.extend({}, params, { operator: operator || params.defaultFetchingOperator, value: value, end: true })); }; Dialect.prototype.buildStateOperator = function(params) { return this.buildOperatorsGroup(_.extend({}, params, { context: 'state', contextOperator: params.operator, operator: '$and', states: params.states.concat(params.operator) })); }; Dialect.prototype.buildOperatorsGroup = function(params) { var self = this; var value = params.value; var result; if (objectUtils.isObjectObject(value)) { result = this.operators.logical.get(params.operator).fn( _(value) .chain() .map(function(item, operator) { if (operator[0] !== '$') return ''; if (self.operators.fetching.has(operator)) { // convert {a: {$field: 'b'}} to {a: {$eq: {$field: 'b'}}} item = _.object([operator], [item]); operator = '$eq'; } return self.buildOperator(_.extend({}, params, { operator: operator, value: item })); }) .compact() .value() ); if (!result) result = params.field; } else { result = this.buildEndFetchingOperator(params); } return result; }; Dialect.prototype.buildOperator = function(params) { var isContextValid = function(expectedContexts, context) { return _.contains(expectedContexts, context); }; var context = params.context; var operator = params.operator; var result; var contexts = _(this.operators).mapObject(function(operatorsGroup) { return operatorsGroup.has(operator); }); if (!_(contexts).some()) { throw new Error('Unknown operator "' + operator + '"'); } if (contexts.logical && isContextValid(['null', 'logical'], context)) { result = this.buildLogicalOperator(params); } else if (contexts.fetching && isContextValid(['logical', 'comparison'], context)) { result = this.buildFetchingOperator(params); } else if (contexts.comparison && isContextValid(['fetching', 'state'], context)) { result = this.buildComparisonOperator(params); } else if (contexts.state && isContextValid(['fetching', 'state'], context)) { result = this.buildStateOperator(params); } else { var errMessage = 'Unexpected operator "' + operator + '" at ' + (context === 'null' ? 'null ' : '') + 'context'; if (params.contextOperator) { errMessage += ' of operator "' + params.contextOperator + '"'; } throw new Error(errMessage); } return result; }; Dialect.prototype.buildCondition = function(params) { return this.buildOperator({ context: 'null', operator: '$and', value: params.value, states: [], defaultFetchingOperator: params.defaultFetchingOperator }); }; Dialect.prototype.buildModifier = function(params) { var self = this; return _(params.modifier) .chain() .map(function(values, field) { var modifier; if (field[0] === '$') { modifier = field; } else { modifier = '$set'; values = _.object([field], [values]); } var modifierFn = self.modifiers.get(modifier); if (!modifierFn) { throw new Error('Unknown modifier "' + modifier + '"'); } return _(values).map(function(value, field) { field = self._wrapIdentifier(field); value = self.buildBlock('term', {term: value, type: 'value'}); return modifierFn(field, value); }); }) .flatten() .compact() .value() .join(', '); }; Dialect.prototype.buildBlock = function(block, params) { var blockFn = this.blocks.get(block); if (!blockFn) { throw new Error('Unknown block "' + block + '"'); } return blockFn(params); }; Dialect.prototype.buildTemplate = function(type, params) { var self = this; var template = this.templates.get(type); if (!template) { throw new Error('Unknown template type "' + type + '"'); } params = _.defaults({}, params, template.defaults); if (template.validate) { template.validate(type, params); } return template.pattern.replace(blockRegExp, function(fullMatch, block, space) { if (_.isUndefined(params[block])) { return ''; } else { if (self.blocks.has(type + ':' + block)) block = type + ':' + block; return self.buildBlock(block, params) + space; } }).trim(); };