diff --git a/lib/dialects/mssql/blocks.js b/lib/dialects/mssql/blocks.js new file mode 100644 index 0000000..163fc5c --- /dev/null +++ b/lib/dialects/mssql/blocks.js @@ -0,0 +1,59 @@ +'use strict'; + +var _ = require('underscore'); + +module.exports = function(dialect) { + dialect.blocks.set('limit', function(params) { + return (!isNaN(params.offset)) ? '' : 'top(' + dialect.builder._pushValue(params.limit) + ')'; + }); + + dialect.blocks.set('offset', function(params) { + var pre = (!params.sort) ? 'order by 1 ' : ''; + if (params.limit) { + var str = pre + 'offset ' + dialect.builder._pushValue(params.offset); + str += ' rows fetch next ' + dialect.builder._pushValue(params.limit) + ' rows only'; + return str; + }else { + return pre + 'OFFSET ' + dialect.builder._pushValue(params.offset) + ' rows'; + } + }); + + dialect.blocks.set('returning', function(params) { + var result = dialect.buildBlock('fields', {fields: params.returning}); + + if (result) result = 'output ' + result; + + return result; + }); + + dialect.blocks.set('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, + returning: params.returning || undefined, + 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(', '); + }); +}; diff --git a/lib/dialects/mssql/index.js b/lib/dialects/mssql/index.js index 8374184..569d6d8 100644 --- a/lib/dialects/mssql/index.js +++ b/lib/dialects/mssql/index.js @@ -3,9 +3,47 @@ var BaseDialect = require('../base'); var _ = require('underscore'); var util = require('util'); +var templatesInit = require('./templates'); +var blocksInit = require('./blocks'); var Dialect = module.exports = function(builder) { + + builder._pushValue = function(value) { + if (_.isUndefined(value) || _.isNull(value)) { + return 'null'; + } else if (_.isBoolean(value)) { + return String(Number(value)); + } else if (_.isNumber(value)) { + return String(value); + } else if (_.isString(value) || _.isDate(value)) { + 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 { + if (_.isDate(value)) value = value.toISOString(); + + return '\'' + value + '\''; + } + } else { + throw new Error('Wrong value type "' + (typeof value) + '"'); + } + }; + BaseDialect.call(this, builder); + + // init templates + templatesInit(this); + + // init blocks + blocksInit(this); + }; util.inherits(Dialect, BaseDialect); diff --git a/lib/dialects/mssql/templates.js b/lib/dialects/mssql/templates.js new file mode 100644 index 0000000..bbda467 --- /dev/null +++ b/lib/dialects/mssql/templates.js @@ -0,0 +1,130 @@ +'use strict'; + +var templateChecks = require('../../utils/templateChecks'); +var orRegExp = /^(rollback|abort|replace|fail|ignore)$/i; + +module.exports = function(dialect) { + dialect.templates.set('select', { + pattern: '{with} {withRecursive} select {distinct} {limit} {fields} ' + + 'from {from} {table} {query} {select} {expression} {alias} ' + + '{join} {condition} {group} {having} {sort} {offset}', + defaults: { + fields: {} + }, + validate: function(type, params) { + templateChecks.onlyOneOfProps(type, params, ['with', 'withRecursive']); + templateChecks.propType(type, params, 'with', 'object'); + templateChecks.propType(type, params, 'withRecursive', 'object'); + + templateChecks.propType(type, params, 'distinct', 'boolean'); + + templateChecks.propType(type, params, 'fields', ['array', 'object']); + + templateChecks.propType(type, params, 'from', ['string', 'array', 'object']); + + templateChecks.atLeastOneOfProps(type, params, ['table', 'query', 'select', 'expression']); + templateChecks.onlyOneOfProps(type, params, ['table', 'query', 'select', 'expression']); + + templateChecks.propType(type, params, 'table', 'string'); + templateChecks.propType(type, params, 'query', 'object'); + templateChecks.propType(type, params, 'select', 'object'); + templateChecks.propType(type, params, 'expression', ['string', 'object']); + + templateChecks.propType(type, params, 'alias', ['string', 'object']); + + templateChecks.propType(type, params, 'join', ['array', 'object']); + + templateChecks.propType(type, params, 'condition', ['array', 'object']); + templateChecks.propType(type, params, 'having', ['array', 'object']); + + templateChecks.propType(type, params, 'group', ['string', 'array']); + + templateChecks.propType(type, params, 'sort', ['string', 'array', 'object']); + + templateChecks.propType(type, params, 'offset', ['number', 'string']); + templateChecks.propType(type, params, 'limit', ['number', 'string']); + } + }); + + dialect.templates.add('insert', { + pattern: '{with} {withRecursive} insert {or} into {table} {values} ' + + '{condition}', + validate: function(type, params) { + templateChecks.onlyOneOfProps(type, params, ['with', 'withRecursive']); + templateChecks.propType(type, params, 'with', 'object'); + templateChecks.propType(type, params, 'withRecursive', 'object'); + + templateChecks.propType(type, params, 'or', 'string'); + templateChecks.propMatch(type, params, 'or', orRegExp); + + templateChecks.requiredProp(type, params, 'table'); + templateChecks.propType(type, params, 'table', 'string'); + + templateChecks.requiredProp(type, params, 'values'); + templateChecks.propType(type, params, 'values', ['array', 'object']); + + templateChecks.propType(type, params, 'condition', ['array', 'object']); + + } + }); + + dialect.templates.add('insertValues', { + pattern: '({fields}) {returning} values {values}', + validate: function(type, params) { + templateChecks.requiredProp('values', params, 'fields'); + templateChecks.propType('values', params, 'fields', 'array'); + templateChecks.minPropLength('values', params, 'fields', 1); + + templateChecks.propType(type, params, 'returning', ['array', 'object']); + + templateChecks.requiredProp('values', params, 'values'); + templateChecks.propType('values', params, 'values', 'array'); + templateChecks.minPropLength('values', params, 'values', 1); + } + }); + + dialect.templates.add('update', { + pattern: '{with} {withRecursive} update {or} {table} {alias} {modifier} {returning} ' + + '{condition} ', + validate: function(type, params) { + templateChecks.onlyOneOfProps(type, params, ['with', 'withRecursive']); + templateChecks.propType(type, params, 'with', 'object'); + templateChecks.propType(type, params, 'withRecursive', 'object'); + + templateChecks.propType(type, params, 'or', 'string'); + templateChecks.propMatch(type, params, 'or', orRegExp); + + templateChecks.requiredProp(type, params, 'table'); + templateChecks.propType(type, params, 'table', 'string'); + + templateChecks.propType(type, params, 'returning', ['array', 'object']); + + templateChecks.propType(type, params, 'alias', 'string'); + + templateChecks.requiredProp(type, params, 'modifier'); + templateChecks.propType(type, params, 'modifier', 'object'); + + templateChecks.propType(type, params, 'condition', ['array', 'object']); + + } + }); + + dialect.templates.add('remove', { + pattern: '{with} {withRecursive} delete from {table} {returning} {alias} {condition} ', + validate: function(type, params) { + templateChecks.onlyOneOfProps(type, params, ['with', 'withRecursive']); + templateChecks.propType(type, params, 'with', 'object'); + templateChecks.propType(type, params, 'withRecursive', 'object'); + + templateChecks.requiredProp(type, params, 'table'); + templateChecks.propType(type, params, 'table', 'string'); + + templateChecks.propType(type, params, 'returning', ['array', 'object']); + + templateChecks.propType(type, params, 'alias', 'string'); + + templateChecks.propType(type, params, 'condition', ['array', 'object']); + + } + }); +}; diff --git a/lib/utils/object.js b/lib/utils/object.js index 6c13319..e31215d 100644 --- a/lib/utils/object.js +++ b/lib/utils/object.js @@ -24,4 +24,4 @@ exports.isSimpleValue = function(value) { exports.isObjectObject = function(obj) { return _.isObject(obj) && Object.prototype.toString.call(obj) === '[object Object]'; -} +}; diff --git a/tests/6_dialects/1_mssql.js b/tests/6_dialects/1_mssql.js new file mode 100644 index 0000000..625ef91 --- /dev/null +++ b/tests/6_dialects/1_mssql.js @@ -0,0 +1,63 @@ +'use strict'; + +var jsonSql = require('../../lib')({ + dialect: 'mssql', + namedValues: false +}); +var expect = require('chai').expect; + +describe('MSSQL dialect', function() { + describe('limit', function() { + it('should be ok with `limit` property', function() { + var result = jsonSql.build({ + table: 'test', + fields: ['user'], + limit: 1, + condition: { + 'name': {$eq: 'test'} + } + }); + expect(result.query).to.be.equal('select top(1) "user" from "test" where "name" = $1;'); + }); + + it('should be ok with `limit` and `offset` properties', function() { + var result = jsonSql.build({ + table: 'test', + fields: ['user'], + limit: 4, + offset: 2, + condition: { + 'name': {$eq: 'test'} + } + }); + expect(result.query).to.be.equal('select "user" from "test" where "name" = $1 order by 1' + + ' offset 2 rows fetch next 4 rows only;'); + }); + }); + describe('returning', function() { + it('should be ok with `remove` type', function() { + var result = jsonSql.build({ + type: 'remove', + table: 'test', + returning: ['DELETED.*'], + condition: { + Description: {$eq: 'test'} + } + }); + expect(result.query).to.be.equal('delete from "test" output "DELETED".* where ' + + '"Description" = $1;'); + }); + it('should be ok with `insert` type', function() { + var result = jsonSql.build({ + type: 'insert', + table: 'test', + returning: ['INSERTED.*'], + values: { + Description: 'test', + } + }); + expect(result.query).to.be.equal('insert into "test" ("Description") output ' + + '"INSERTED".* values ($1);'); + }); + }); +});