@ddn/json-sql
Version:
node.js json to sql queries mapper
234 lines (195 loc) • 6.27 kB
JavaScript
'use strict';
var _ = require('underscore');
var dialects = {
base: require('./dialects/base'),
mssql: require('./dialects/mssql'),
postgresql: require('./dialects/postgresql'),
sqlite: require('./dialects/sqlite')
};
var blockRegExp = /\{([a-z0-9]+)\}(.|$)/ig;
var Builder = module.exports = function (options) {
this.configure(options);
};
Builder.prototype._reset = function () {
if (this.options.separatedValues) {
this._placeholderId = 1;
this._values = this.options.namedValues ? {} : [];
} else {
delete this._placeholderId;
delete this._values;
}
this._query = '';
};
Builder.prototype._getPlaceholder = function () {
return (this.options.namedValues ? 'p' : '') + (this._placeholderId++);
};
Builder.prototype._wrapPlaceholder = function (name) {
return this.options.valuesPrefix + name;
};
Builder.prototype._pushValue = function (value) {
var valueType = typeof value;
if (valueType === 'undefined') {
return 'null';
} else if (value === null || valueType === 'number' ||
valueType === 'boolean') {
return String(value);
} else if (valueType === 'string') {
if (this.options.separatedValues) {
var placeholder = this._getPlaceholder();
if (this.options.namedValues) {
this._values[placeholder] = value;
} else {
this._values.push(value);
}
return this._wrapPlaceholder(placeholder);
} else {
return '\'' + value + '\'';
}
} else if (valueType == 'object') {
if (Object.prototype.toString.call(value) == "[object Array]") {
if (value.length > 0) {
return value.join(",")
} else {
return 'null';
}
}
else if (Buffer.isBuffer(value)) {
var placeholder = this._getPlaceholder();
this._values[placeholder] = value;
return this._wrapPlaceholder(placeholder);
//return "X'" + value.toString('hex') + "'";
} else {
var placeholder = this._getPlaceholder();
this._values[placeholder] = value;
return this._wrapPlaceholder(placeholder);
//return ("'" + JSON.stringify(value).replace(/'/g, "''") + "'");
}
} else {
throw new Error('Wrong value type "' + valueType + '"');
}
};
Builder.prototype.configure = function (options) {
options = options || {};
this.options = _(options).defaults({
separatedValues: true,
namedValues: true,
valuesPrefix: '$',
dialect: 'base',
wrappedIdentifiers: true
});
this.setDialect(this.options.dialect);
this._reset();
};
Builder.prototype.buildCondition = function (params) {
var self = this;
var result = '';
var condition = params.condition;
var logicalOperator = params.logicalOperator ||
this.dialect.config.defaultLogicalOperator;
if (_.isObject(condition) && !_.isEmpty(condition)) {
// if condition is array: [{a: 1}, {b: 2}]
if (_.isArray(condition)) {
result = _(condition).map(function (item) {
return self.buildCondition({
condition: item,
operator: params.operator
});
});
// if condition is object
} else {
result = _(condition).map(function (value, field) {
// if field name is operator
if (field[0] === '$') {
// if field name is logical operator: {$logicalOperator: {a: 1}}
if (!self.dialect.logicalOperators.has(field)) {
throw new Error('Unknown logical operator "' + field + '"');
}
return self.buildCondition({
condition: value,
logicalOperator: field,
operator: params.operator
});
// if condition item is object: {a: {$operator: 4}}
} else if (_.isObject(value)) {
var logicalOperatorFn = self.dialect.logicalOperators
.get(self.dialect.config.defaultLogicalOperator);
return logicalOperatorFn(_(value).map(function (operatorValue, operator) {
var operatorFn = self.dialect.conditions.get(operator);
if (!operatorFn) {
throw new Error('Unknown operator "' + operator + '"');
}
return operatorFn(field, operator, operatorValue);
}));
// if condition item type is simple: {a: 1, b: 2}
} else {
var operatorFn = self.dialect.conditions.get(params.operator);
if (!operatorFn) {
throw new Error('Unknown operator "' + params.operator + '"');
}
return operatorFn(field, params.operator, value);
}
});
}
result = this.dialect.logicalOperators
.get(logicalOperator)(_(result).compact());
}
return result;
};
Builder.prototype.buildBlock = function (block, params) {
var blockFn = this.dialect.blocks.get(block);
if (!blockFn) {
throw new Error('Unknown block "' + block + '"');
}
return blockFn(params);
};
Builder.prototype.buildTemplate = function (type, params) {
var self = this;
var template = this.dialect.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 (typeof params[block] !== 'undefined') {
return self.buildBlock(block, params) + space;
} else {
return '';
}
}).trim();
};
Builder.prototype.build = function (params) {
var builder = this;
this._reset();
this._query = this.buildTemplate('query', {queryBody: params}) + ';';
if (this.options.separatedValues) {
return {
query: this._query,
values: this._values,
prefixValues: function () {
var values = {};
_(this.getValuesObject()).each(function (value, name) {
values[builder._wrapPlaceholder(name)] = value;
});
return values;
},
getValuesArray: function () {
return _.isArray(this.values) ? this.values : _(this.values).values();
},
getValuesObject: function () {
return _.isArray(this.values) ? _(_.range(1, this.values.length + 1)).object(this.values) :
this.values;
}
};
} else {
return {query: this._query};
}
};
Builder.prototype.setDialect = function (name) {
if (!dialects[name]) {
throw new Error('Unknown dialect "' + name + '"');
}
this.dialect = new (dialects[name])(this);
};