json-sql-isme2n
Version:
node.js json to sql queries mapper
431 lines (324 loc) • 11 kB
JavaScript
;
var _ = require('underscore');
var objectUtils = require('../../utils/object');
var removeTopBrackets = function(condition) {
if (condition.length && condition[0] === '(' &&
condition[condition.length - 1] === ')') {
condition = condition.slice(1, condition.length - 1);
}
return condition;
};
var termKeys = ['select', 'query', 'field', 'value', 'func', 'expression'];
var isTerm = function(obj) {
return objectUtils.isObjectObject(obj) && objectUtils.hasSome(obj, termKeys);
};
module.exports = function(dialect) {
dialect.blocks.add('distinct', function() {
return 'distinct';
});
dialect.blocks.add('fields', function(params) {
var fields = params.fields || {};
if (!_.isObject(fields)) {
throw new Error('Invalid `fields` property type "' + (typeof fields) + '"');
}
if (_.isEmpty(fields)) return '*';
// If fields is array: ['a', {b: 'c'}, {name: '', table: 't', alias: 'r'}]
if (_.isArray(fields)) {
fields = _(fields).map(function(field) {
if (objectUtils.isSimpleValue(field) || isTerm(field) || _.has(field, 'name')) {
// if field has simple type or is field object: {name: '', table: 't', alias: 'r'}
return dialect.buildBlock('term', {term: field, type: 'field'});
} else {
// if field is non-field object: {b: 'c'}
return dialect.buildBlock('fields', {fields: field});
}
});
// If fields is object: {a: 'u', b: {table: 't', alias: 'c'}}
} else {
// use keys as field names
fields = _(fields).map(function(field, name) {
// if field is not an object value, use it as alias
if (_.isString(field)) field = {alias: field};
// if field does not have name, get it from key
if (!_.has(field, 'name')) field = _.defaults({name: name}, field);
return dialect.buildBlock('term', {term: field, type: 'field'});
});
}
return _(fields).compact().join(', ');
});
dialect.blocks.add('term', function(params) {
var term = params.term;
var type = params.type || 'field';
var isSimpleValue = objectUtils.isSimpleValue(term);
var isArray = _.isArray(term);
if (isSimpleValue && !_.isString(term) || isArray) type = 'value';
if (isSimpleValue || !isTerm(term) || isArray) {
term = _(term).chain().pick('cast', 'alias').extend(_.object([type], [term])).value();
}
type = _(termKeys).find(function(key) {
return _.has(term, key);
});
var result = dialect.buildBlock(type, _(term).pick(type));
if (_.has(term, 'cast')) {
result = 'cast(' + result + ' as ' + term.cast + ')';
}
if (_.has(term, 'alias')) {
result += ' ' + dialect.buildBlock('alias', {alias: term.alias});
}
return result;
});
dialect.blocks.add('table', function(params) {
return dialect.buildBlock('name', {name: params.table});
});
dialect.blocks.add('func', function(params) {
var func = params.func;
if (_.isString(func)) func = {name: func};
if (!_.isObject(func)) {
throw new Error('Invalid `func` property type "' + (typeof func) + '"');
}
if (!_.has(func, 'name')) {
throw new Error('`func.name` property is required');
}
var args = '';
if (_.isArray(func.args)) {
args = _(func.args).map(function(arg) {
return dialect.buildBlock('term', {term: arg, type: 'value'});
}).join(', ');
}
return func.name + '(' + args + ')';
});
dialect.blocks.add('expression', function(params) {
var expression = params.expression;
if (_.isString(expression)) expression = {pattern: expression};
if (!_.isObject(expression)) {
throw new Error('Invalid `expression` property type "' + (typeof expression) + '"');
}
if (!_.has(expression, 'pattern')) {
throw new Error('`expression.pattern` property is required');
}
var values = expression.values || {};
return expression.pattern.replace(/\{([a-z0-9]+)\}/ig, function(fullMatch, block) {
if (!_.has(values, block)) {
throw new Error('Field `' + block + '` is required in `expression.values` property');
}
return dialect.buildBlock('term', {term: values[block], type: 'value'});
}).trim();
});
dialect.blocks.add('field', function(params) {
var field = params.field;
if (_.isString(field)) field = {name: field};
if (!_.isObject(field)) {
throw new Error('Invalid `field` property type "' + (typeof field) + '"');
}
if (!_.has(field, 'name')) {
throw new Error('`field.name` property is required');
}
var result = dialect.buildBlock('name', {name: field.name});
if (_.has(field, 'table')) {
result = dialect.buildBlock('table', {table: field.table}) + '.' + result;
}
return result;
});
dialect.blocks.add('value', function(params) {
var value = params.value;
if (_.isRegExp(value)) value = value.source;
return dialect.builder._pushValue(value);
});
dialect.blocks.add('name', function(params) {
return dialect._wrapIdentifier(params.name);
});
dialect.blocks.add('alias', function(params) {
var alias = params.alias;
if (_.isString(alias)) alias = {name: alias};
if (!_.isObject(alias)) {
throw new Error('Invalid `alias` property type "' + (typeof alias) + '"');
}
if (!_.has(alias, 'name')) {
throw new Error('`alias.name` property is required');
}
var result = 'as ' + dialect._wrapIdentifier(alias.name);
if (_.isArray(alias.columns)) {
result += '(' + _(alias.columns).map(function(column) {
return dialect._wrapIdentifier(column);
}).join(', ') + ')';
}
return result;
});
dialect.blocks.add('condition', function(params) {
var result = dialect.buildCondition({
value: params.condition,
defaultFetchingOperator: '$value'
});
if (result) {
result = 'where ' + removeTopBrackets(result);
}
return result;
});
dialect.blocks.add('modifier', function(params) {
var result = dialect.buildModifier({
modifier: params.modifier
});
if (result) {
result = 'set ' + result;
}
return result;
});
dialect.blocks.add('join', function(params) {
var join = params.join;
var result = '';
// if join is array -> make each joinItem
if (_.isArray(join)) {
result = _(join).map(function(joinItem) {
return dialect.buildTemplate('joinItem', joinItem);
}).join(' ');
// if join is object -> set table name from key and make each joinItem
} else if (_.isObject(join)) {
result = _(join).map(function(joinItem, table) {
if (!objectUtils.hasSome(joinItem, ['table', 'query', 'select', 'expression'])) {
joinItem = _.defaults({table: table}, joinItem);
}
return dialect.buildTemplate('joinItem', joinItem);
}).join(' ');
}
return result;
});
dialect.blocks.add('joinItem:type', function(params) {
return params.type.toLowerCase();
});
dialect.blocks.add('joinItem:on', function(params) {
// `on` block is use `$field` as default query operator because it most used case
var result = dialect.buildCondition({
value: params.on,
defaultFetchingOperator: '$field'
});
if (result) {
result = 'on ' + removeTopBrackets(result);
}
return result;
});
dialect.blocks.add('group', function(params) {
var group = params.group;
var result = '';
if (_.isString(group)) group = [group];
if (_.isArray(group)) {
result = _(group).map(function(field) {
return dialect._wrapIdentifier(field);
}).join(', ');
}
if (result) {
result = 'group by ' + result;
}
return result;
});
dialect.blocks.add('having', function(params) {
var result = dialect.buildCondition({
value: params.having,
defaultFetchingOperator: '$value'
});
if (result) {
result = 'having ' + removeTopBrackets(result);
}
return result;
});
dialect.blocks.add('sort', function(params) {
var sort = params.sort;
var result = '';
if (_.isString(sort)) sort = [sort];
if (_.isArray(sort)) {
result = _(sort).map(function(sortField) {
return dialect._wrapIdentifier(sortField);
}).join(', ');
} else if (_.isObject(sort)) {
result = _(sort).map(function(direction, field) {
return dialect._wrapIdentifier(field) + ' ' + (direction > 0 ? 'asc' : 'desc');
}).join(', ');
}
if (result) {
result = 'order by ' + result;
}
return result;
});
dialect.blocks.add('limit', function(params) {
return 'limit ' + dialect.builder._pushValue(params.limit);
});
dialect.blocks.add('offset', function(params) {
return 'offset ' + dialect.builder._pushValue(params.offset);
});
dialect.blocks.add('or', function(params) {
return 'or ' + params.or;
});
dialect.blocks.add('insert:values', function(params) {
var values = params.values;
if (!_.isArray(values)) values = [values];
var fields = params.fields || _(values)
.chain()
.map(function(row) {
return _(row).keys();
})
.flatten()
.uniq()
.value();
return dialect.buildTemplate('insertValues', {
fields: fields,
values: _(values).map(function(row) {
return _(fields).map(function(field) {
return dialect.buildBlock('value', {value: row[field]});
});
})
});
});
dialect.blocks.add('insertValues:values', function(params) {
return _(params.values).map(function(row) {
return '(' + row.join(', ') + ')';
}).join(', ');
});
dialect.blocks.add('queryBody', function(params) {
var queryBody = params.queryBody || {};
return dialect.buildTemplate(queryBody.type || 'select', queryBody);
});
dialect.blocks.add('query', function(params) {
return dialect.buildTemplate('subQuery', {queryBody: params.query});
});
dialect.blocks.add('select', function(params) {
return dialect.buildTemplate('subQuery', {queryBody: params.select});
});
dialect.blocks.add('queries', function(params) {
return _(params.queries).map(function(query) {
return dialect.buildTemplate('query', {queryBody: query});
}).join(' ' + params.type + (params.all ? ' all' : '') + ' ');
});
function buildWith(withList) {
var result = '';
// if with clause is array -> make each withItem
if (_.isArray(withList)) {
result = _(withList).map(function(withItem) {
return dialect.buildTemplate('withItem', withItem);
}).join(', ');
// if with clause is object -> set name from key and make each withItem
} else if (_.isObject(withList)) {
result = _(withList).map(function(withItem, name) {
if (!withItem.name) {
withItem = _.clone(withItem);
withItem.name = name;
}
return dialect.buildTemplate('withItem', withItem);
}).join(', ');
}
return result;
}
dialect.blocks.add('with', function(params) {
var result = buildWith(params['with']);
if (result) result = 'with ' + result;
return result;
});
dialect.blocks.add('withRecursive', function(params) {
var result = buildWith(params.withRecursive);
if (result) result = 'with recursive ' + result;
return result;
});
dialect.blocks.add('returning', function(params) {
var result = dialect.buildBlock('fields', {fields: params.returning});
if (result) result = 'returning ' + result;
return result;
});
};