From 64fe70ea4870c510bec6d6eabe6faee0b34ce72e Mon Sep 17 00:00:00 2001 From: Allan Amstadt Date: Mon, 2 Jan 2017 11:08:22 +0100 Subject: [PATCH 1/9] Fixed support for mssql limit/offset --- lib/dialects/mssql/index.js | 57 +++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/lib/dialects/mssql/index.js b/lib/dialects/mssql/index.js index 8374184..ac47a20 100644 --- a/lib/dialects/mssql/index.js +++ b/lib/dialects/mssql/index.js @@ -3,9 +3,66 @@ var BaseDialect = require('../base'); var _ = require('underscore'); var util = require('util'); +var templateChecks = require('../../utils/templateChecks'); var Dialect = module.exports = function(builder) { BaseDialect.call(this, builder); + + this.blocks.set('limit', function(params) { + return (params.offset) ? '' : 'TOP(' + builder._pushValue(params.limit) + ')'; + }); + + this.blocks.set('offset', function(params) { + var pre = (!params.sort) ? 'ORDER BY 1 ' : ''; + if (params.limit) { + return pre + 'OFFSET ' + params.offset + ' ROWS FETCH NEXT ' + params.limit + ' ROWS ONLY'; + }else { + return pre + 'OFFSET ' + params.offset + ' ROWS'; + } + }); + + this.templates.set('select', { + pattern: '{with} {withRecursive} select {limit} {distinct} {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']); + } + }); + }; util.inherits(Dialect, BaseDialect); diff --git a/package.json b/package.json index 695f6ac..166b77e 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "database" ], "dependencies": { - "underscore": "1.8.2" + "underscore": "1.8.3" }, "devDependencies": { "chai": "2.2.0", From bbbb4f6c6ea72ed717a926baf13d468c7e2cf2fd Mon Sep 17 00:00:00 2001 From: Allan Amstadt Date: Mon, 2 Jan 2017 11:50:26 +0100 Subject: [PATCH 2/9] Changed default valuesPrefix for mssql to "@" Mssql is now using builder._pushValue for limit/offset Replaced whitspaces with tabs --- lib/dialects/mssql/index.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/dialects/mssql/index.js b/lib/dialects/mssql/index.js index ac47a20..4884b00 100644 --- a/lib/dialects/mssql/index.js +++ b/lib/dialects/mssql/index.js @@ -6,22 +6,25 @@ var util = require('util'); var templateChecks = require('../../utils/templateChecks'); var Dialect = module.exports = function(builder) { + builder.options.valuesPrefix = '@'; BaseDialect.call(this, builder); - this.blocks.set('limit', function(params) { + this.blocks.set('limit', function(params) { return (params.offset) ? '' : 'TOP(' + builder._pushValue(params.limit) + ')'; }); - this.blocks.set('offset', function(params) { - var pre = (!params.sort) ? 'ORDER BY 1 ' : ''; - if (params.limit) { - return pre + 'OFFSET ' + params.offset + ' ROWS FETCH NEXT ' + params.limit + ' ROWS ONLY'; - }else { - return pre + 'OFFSET ' + params.offset + ' ROWS'; - } + this.blocks.set('offset', function(params) { + var pre = (!params.sort) ? 'ORDER BY 1 ' : ''; + if (params.limit) { + var str = pre + 'OFFSET ' + builder._pushValue(params.offset); + str += ' ROWS FETCH NEXT ' + builder._pushValue(params.limit) + ' ROWS ONLY'; + return str; + }else { + return pre + 'OFFSET ' + builder._pushValue(params.offset) + ' ROWS'; + } }); - this.templates.set('select', { + this.templates.set('select', { pattern: '{with} {withRecursive} select {limit} {distinct} {fields} ' + 'from {from} {table} {query} {select} {expression} {alias} ' + '{join} {condition} {group} {having} {sort} {offset}', @@ -58,7 +61,7 @@ var Dialect = module.exports = function(builder) { templateChecks.propType(type, params, 'sort', ['string', 'array', 'object']); - templateChecks.propType(type, params, 'offset', ['number', 'string']); + templateChecks.propType(type, params, 'offset', ['number', 'string']); templateChecks.propType(type, params, 'limit', ['number', 'string']); } }); From b01ed3a136ef838a63eedd3c80867cb137970241 Mon Sep 17 00:00:00 2001 From: Allan Amstadt Date: Fri, 6 Jan 2017 15:00:05 +0100 Subject: [PATCH 3/9] Fixed MSSQL limit support --- lib/dialects/mssql/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dialects/mssql/index.js b/lib/dialects/mssql/index.js index 4884b00..24828fb 100644 --- a/lib/dialects/mssql/index.js +++ b/lib/dialects/mssql/index.js @@ -10,7 +10,7 @@ var Dialect = module.exports = function(builder) { BaseDialect.call(this, builder); this.blocks.set('limit', function(params) { - return (params.offset) ? '' : 'TOP(' + builder._pushValue(params.limit) + ')'; + return (!isNaN(params.offset)) ? '' : 'TOP(' + builder._pushValue(params.limit) + ')'; }); this.blocks.set('offset', function(params) { From 20600ff641852e36c0d358c5b23965a65db232a8 Mon Sep 17 00:00:00 2001 From: Allan Amstadt Date: Thu, 2 Mar 2017 17:04:46 +0100 Subject: [PATCH 4/9] Fixed MSSQl boolean support --- lib/dialects/mssql/index.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lib/dialects/mssql/index.js b/lib/dialects/mssql/index.js index 24828fb..46f49cc 100644 --- a/lib/dialects/mssql/index.js +++ b/lib/dialects/mssql/index.js @@ -7,6 +7,35 @@ var templateChecks = require('../../utils/templateChecks'); var Dialect = module.exports = function(builder) { builder.options.valuesPrefix = '@'; + + 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); this.blocks.set('limit', function(params) { From c62c500da553a3ab31c8d25b1e43a7ab13d846a1 Mon Sep 17 00:00:00 2001 From: Allan Amstadt Date: Wed, 10 May 2017 16:29:21 +0200 Subject: [PATCH 5/9] Added returning support for mssql. --- lib/dialects/mssql/blocks.js | 59 ++++++++++++++ lib/dialects/mssql/index.js | 124 +++++++++--------------------- lib/dialects/mssql/templates.js | 131 ++++++++++++++++++++++++++++++++ tests/6_dialects/1_mssql.js | 60 +++++++++++++++ 4 files changed, 287 insertions(+), 87 deletions(-) create mode 100644 lib/dialects/mssql/blocks.js create mode 100644 lib/dialects/mssql/templates.js create mode 100644 tests/6_dialects/1_mssql.js diff --git a/lib/dialects/mssql/blocks.js b/lib/dialects/mssql/blocks.js new file mode 100644 index 0000000..102f3c5 --- /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 46f49cc..a8bddd8 100644 --- a/lib/dialects/mssql/index.js +++ b/lib/dialects/mssql/index.js @@ -3,97 +3,47 @@ var BaseDialect = require('../base'); var _ = require('underscore'); var util = require('util'); +var templatesInit = require('./templates'); +var blocksInit = require('./blocks'); var templateChecks = require('../../utils/templateChecks'); var Dialect = module.exports = function(builder) { - builder.options.valuesPrefix = '@'; - - 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); - - this.blocks.set('limit', function(params) { - return (!isNaN(params.offset)) ? '' : 'TOP(' + builder._pushValue(params.limit) + ')'; - }); - - this.blocks.set('offset', function(params) { - var pre = (!params.sort) ? 'ORDER BY 1 ' : ''; - if (params.limit) { - var str = pre + 'OFFSET ' + builder._pushValue(params.offset); - str += ' ROWS FETCH NEXT ' + builder._pushValue(params.limit) + ' ROWS ONLY'; - return str; - }else { - return pre + 'OFFSET ' + builder._pushValue(params.offset) + ' ROWS'; - } - }); - - this.templates.set('select', { - pattern: '{with} {withRecursive} select {limit} {distinct} {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']); - } - }); + 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); }; diff --git a/lib/dialects/mssql/templates.js b/lib/dialects/mssql/templates.js new file mode 100644 index 0000000..0330749 --- /dev/null +++ b/lib/dialects/mssql/templates.js @@ -0,0 +1,131 @@ +'use strict'; + +var _ = require('underscore'); +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 {limit} {distinct} {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/tests/6_dialects/1_mssql.js b/tests/6_dialects/1_mssql.js new file mode 100644 index 0000000..41b69da --- /dev/null +++ b/tests/6_dialects/1_mssql.js @@ -0,0 +1,60 @@ +'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);'); + }); + }); +}); From 2022daf19b5ca1b6bc26081150c7dda1999b60c0 Mon Sep 17 00:00:00 2001 From: Allan Amstadt Date: Wed, 10 May 2017 16:33:48 +0200 Subject: [PATCH 6/9] Fixed code style --- lib/dialects/mssql/blocks.js | 54 +++++++++---------- lib/dialects/mssql/templates.js | 87 +++++++++++++++---------------- tests/6_dialects/1_mssql.js | 92 ++++++++++++++++----------------- 3 files changed, 116 insertions(+), 117 deletions(-) diff --git a/lib/dialects/mssql/blocks.js b/lib/dialects/mssql/blocks.js index 102f3c5..a89b4c6 100644 --- a/lib/dialects/mssql/blocks.js +++ b/lib/dialects/mssql/blocks.js @@ -19,41 +19,41 @@ module.exports = function(dialect) { }); dialect.blocks.set('returning', function(params) { - var result = dialect.buildBlock('fields', {fields: params.returning}); + var result = dialect.buildBlock('fields', {fields: params.returning}); - if (result) result = 'output ' + result; + if (result) result = 'output ' + result; - return result; - }); + return result; + }); dialect.blocks.set('insert:values', function(params) { - var values = params.values; + var values = params.values; - if (!_.isArray(values)) values = [values]; + if (!_.isArray(values)) values = [values]; - var fields = params.fields || _(values) - .chain() - .map(function(row) { - return _(row).keys(); - }) - .flatten() - .uniq() - .value(); + var fields = params.fields || _(values) + .chain() + .map(function(row) { + return _(row).keys(); + }) + .flatten() + .uniq() + .value(); - return dialect.buildTemplate('insertValues', { - fields: fields, + 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]}); - }); - }) - }); - }); + 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(', '); - }); + return _(params.values).map(function(row) { + return '(' + row.join(', ') + ')'; + }).join(', '); + }); }; diff --git a/lib/dialects/mssql/templates.js b/lib/dialects/mssql/templates.js index 0330749..4fba772 100644 --- a/lib/dialects/mssql/templates.js +++ b/lib/dialects/mssql/templates.js @@ -47,28 +47,27 @@ module.exports = function(dialect) { } }); - 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'); + 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.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, 'table'); + templateChecks.propType(type, params, 'table', 'string'); - templateChecks.requiredProp(type, params, 'values'); - templateChecks.propType(type, params, 'values', ['array', 'object']); + templateChecks.requiredProp(type, params, 'values'); + templateChecks.propType(type, params, 'values', ['array', 'object']); - templateChecks.propType(type, params, 'condition', ['array', 'object']); + templateChecks.propType(type, params, 'condition', ['array', 'object']); - } - }); + } + }); dialect.templates.add('insertValues', { pattern: '({fields}) {returning} values {values}', @@ -85,47 +84,47 @@ module.exports = function(dialect) { } }); - 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'); + 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.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, 'table'); + templateChecks.propType(type, params, 'table', 'string'); templateChecks.propType(type, params, 'returning', ['array', 'object']); - templateChecks.propType(type, params, 'alias', 'string'); + templateChecks.propType(type, params, 'alias', 'string'); - templateChecks.requiredProp(type, params, 'modifier'); - templateChecks.propType(type, params, 'modifier', 'object'); + templateChecks.requiredProp(type, params, 'modifier'); + templateChecks.propType(type, params, 'modifier', 'object'); - templateChecks.propType(type, params, 'condition', ['array', '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'); + 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.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, 'alias', 'string'); - templateChecks.propType(type, params, 'condition', ['array', 'object']); + templateChecks.propType(type, params, 'condition', ['array', 'object']); - } - }); + } + }); }; diff --git a/tests/6_dialects/1_mssql.js b/tests/6_dialects/1_mssql.js index 41b69da..d3ec491 100644 --- a/tests/6_dialects/1_mssql.js +++ b/tests/6_dialects/1_mssql.js @@ -1,60 +1,60 @@ 'use strict'; var jsonSql = require('../../lib')({ - dialect: 'mssql', - namedValues: false + 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'], + 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;'); - }); + 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'], + 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;'); - }); + 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);'); + }); }); - 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);'); - }); - }); }); From 6bd2f6a41b7bbddc1a8ca41d6efe9c8827c4f716 Mon Sep 17 00:00:00 2001 From: Allan Amstadt Date: Thu, 11 May 2017 10:26:25 +0200 Subject: [PATCH 7/9] Fixed code style Replaced spaces with tabs Reverted package.json --- lib/dialects/mssql/blocks.js | 106 +++++++++---------- lib/dialects/mssql/index.js | 71 +++++++------ lib/dialects/mssql/templates.js | 178 ++++++++++++++++---------------- package.json | 2 +- tests/6_dialects/1_mssql.js | 105 ++++++++++--------- 5 files changed, 232 insertions(+), 230 deletions(-) diff --git a/lib/dialects/mssql/blocks.js b/lib/dialects/mssql/blocks.js index a89b4c6..163fc5c 100644 --- a/lib/dialects/mssql/blocks.js +++ b/lib/dialects/mssql/blocks.js @@ -3,57 +3,57 @@ 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(', '); - }); + 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 a8bddd8..569d6d8 100644 --- a/lib/dialects/mssql/index.js +++ b/lib/dialects/mssql/index.js @@ -5,45 +5,44 @@ var _ = require('underscore'); var util = require('util'); var templatesInit = require('./templates'); var blocksInit = require('./blocks'); -var templateChecks = require('../../utils/templateChecks'); 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); + 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); }; diff --git a/lib/dialects/mssql/templates.js b/lib/dialects/mssql/templates.js index 4fba772..79dbd0f 100644 --- a/lib/dialects/mssql/templates.js +++ b/lib/dialects/mssql/templates.js @@ -1,130 +1,130 @@ 'use strict'; -var _ = require('underscore'); 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 {limit} {distinct} {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'); + dialect.templates.set('select', { + pattern: '{with} {withRecursive} select {limit} {distinct} {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, 'distinct', 'boolean'); - templateChecks.propType(type, params, 'fields', ['array', 'object']); + templateChecks.propType(type, params, 'fields', ['array', 'object']); - templateChecks.propType(type, params, 'from', ['string', '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.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, '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, 'alias', ['string', 'object']); - templateChecks.propType(type, params, 'join', ['array', '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, 'condition', ['array', 'object']); + templateChecks.propType(type, params, 'having', ['array', 'object']); - templateChecks.propType(type, params, 'group', ['string', 'array']); + templateChecks.propType(type, params, 'group', ['string', 'array']); - templateChecks.propType(type, params, 'sort', ['string', 'array', 'object']); + templateChecks.propType(type, params, 'sort', ['string', 'array', 'object']); - templateChecks.propType(type, params, 'offset', ['number', 'string']); - templateChecks.propType(type, params, 'limit', ['number', 'string']); - } - }); + 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'); + 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.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, 'table'); + templateChecks.propType(type, params, 'table', 'string'); - templateChecks.requiredProp(type, params, 'values'); - templateChecks.propType(type, params, 'values', ['array', 'object']); + templateChecks.requiredProp(type, params, 'values'); + templateChecks.propType(type, params, 'values', ['array', 'object']); - templateChecks.propType(type, params, 'condition', ['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); + 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.propType(type, params, 'returning', ['array', 'object']); - templateChecks.requiredProp('values', params, 'values'); - templateChecks.propType('values', params, 'values', 'array'); - templateChecks.minPropLength('values', params, 'values', 1); - } - }); + 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'); + 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.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, 'table'); + templateChecks.propType(type, params, 'table', 'string'); - templateChecks.propType(type, params, 'returning', ['array', 'object']); + templateChecks.propType(type, params, 'returning', ['array', 'object']); - templateChecks.propType(type, params, 'alias', 'string'); + templateChecks.propType(type, params, 'alias', 'string'); - templateChecks.requiredProp(type, params, 'modifier'); - templateChecks.propType(type, params, 'modifier', 'object'); + templateChecks.requiredProp(type, params, 'modifier'); + templateChecks.propType(type, params, 'modifier', 'object'); - templateChecks.propType(type, params, 'condition', ['array', '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'); + 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.requiredProp(type, params, 'table'); + templateChecks.propType(type, params, 'table', 'string'); - templateChecks.propType(type, params, 'returning', ['array', 'object']); + templateChecks.propType(type, params, 'returning', ['array', 'object']); - templateChecks.propType(type, params, 'alias', 'string'); + templateChecks.propType(type, params, 'alias', 'string'); - templateChecks.propType(type, params, 'condition', ['array', 'object']); + templateChecks.propType(type, params, 'condition', ['array', 'object']); - } - }); + } + }); }; diff --git a/package.json b/package.json index 166b77e..695f6ac 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "database" ], "dependencies": { - "underscore": "1.8.3" + "underscore": "1.8.2" }, "devDependencies": { "chai": "2.2.0", diff --git a/tests/6_dialects/1_mssql.js b/tests/6_dialects/1_mssql.js index d3ec491..625ef91 100644 --- a/tests/6_dialects/1_mssql.js +++ b/tests/6_dialects/1_mssql.js @@ -1,60 +1,63 @@ 'use strict'; var jsonSql = require('../../lib')({ - dialect: 'mssql', - namedValues: false + 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;'); - }); + 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);'); - }); - }); + 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);'); + }); + }); }); From f12a5c6aaffad18837acdb5ffabdcf50f8d251c5 Mon Sep 17 00:00:00 2001 From: Oliver Jourmel Date: Sat, 26 Aug 2017 14:34:46 -0700 Subject: [PATCH 8/9] Refactor mssql dialect as per pull #26 feedback This commit refactors the `mssql` dialect as per the feedback from @artzhookov on https://github.com/2do2go/json-sql/pull/26. - Include all test files in gulp lint tasks - Move mssql `value` block override to blocks.js - Update mssql `limit` block to use `_.isUndefined` - Clean up minor mssql sql letter case and format - Set the `mssql` `identifier` `prefix` and `suffix` This commit also introduces two changes to the `base` dialect. - Refactor the `base `insert:values` `block` function to clean up the logic - Refactor the `mssql` `inserted:values` `block` function to match the `base` function, and also include `returning` params, as it is needed. (mssql puts `output` keyword before `values` keyword) - Update `buildTemplate` to include an edge case where a `buildBlock` function does not return any value. This prevents unnecessary whitespace in the final query string. These changes are not strictly necessary. The `mssql` `insert:values` `block` could use the same structure as the original `base` `block`, and unnecessary whitespace has no issues in terms of functionality, simply ascetics. This commit refactors the `mssql` tests as per the changes made. Test coverage and layout is not ideal, but functional. --- gulpfile.js | 2 +- lib/dialects/base/blocks.js | 17 ++++--- lib/dialects/base/index.js | 3 +- lib/dialects/mssql/blocks.js | 85 ++++++++++++++++++++++----------- lib/dialects/mssql/index.js | 35 ++------------ lib/dialects/mssql/templates.js | 10 ++-- tests/6_dialects/1_mssql.js | 20 ++++---- 7 files changed, 92 insertions(+), 80 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 5e6064f..36b6912 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -18,7 +18,7 @@ gulp.task('coverage', function(callback) { .pipe(istanbul()) .pipe(istanbul.hookRequire()) .on('finish', function() { - gulp.src(['./tests/*.js'], {read: false}) + gulp.src(['./tests/**/*.js'], {read: false}) .pipe(mocha({ reporter: 'spec', bail: true diff --git a/lib/dialects/base/blocks.js b/lib/dialects/base/blocks.js index 6b29b1c..5421138 100644 --- a/lib/dialects/base/blocks.js +++ b/lib/dialects/base/blocks.js @@ -333,10 +333,11 @@ module.exports = function(dialect) { dialect.blocks.add('insert:values', function(params) { var values = params.values; + var fields = params.fields; if (!_.isArray(values)) values = [values]; - var fields = params.fields || _(values) + fields = fields || _(values) .chain() .map(function(row) { return _(row).keys(); @@ -345,13 +346,15 @@ module.exports = function(dialect) { .uniq() .value(); + values = _(values).map(function(row) { + return _(fields).map(function(field) { + return dialect.buildBlock('value', {value: row[field]}); + }); + }); + return dialect.buildTemplate('insertValues', { - fields: fields, - values: _(values).map(function(row) { - return _(fields).map(function(field) { - return dialect.buildBlock('value', {value: row[field]}); - }); - }) + values: values, + fields: fields }); }); diff --git a/lib/dialects/base/index.js b/lib/dialects/base/index.js index 5518974..8cb38ab 100644 --- a/lib/dialects/base/index.js +++ b/lib/dialects/base/index.js @@ -347,7 +347,8 @@ Dialect.prototype.buildTemplate = function(type, params) { return ''; } else { if (self.blocks.has(type + ':' + block)) block = type + ':' + block; - return self.buildBlock(block, params) + space; + var result = self.buildBlock(block, params); + return result === '' ? result : result + space; } }).trim(); }; diff --git a/lib/dialects/mssql/blocks.js b/lib/dialects/mssql/blocks.js index 163fc5c..06e5251 100644 --- a/lib/dialects/mssql/blocks.js +++ b/lib/dialects/mssql/blocks.js @@ -3,19 +3,52 @@ var _ = require('underscore'); module.exports = function(dialect) { + var parentValueBlock = dialect.blocks.get('value'); + + dialect.blocks.set('value', function(params) { + var value = params.value; + + var result; + if (_.isUndefined(value) || _.isNull(value)) { + result = 'null'; + } else if (_.isBoolean(value)) { + result = String(Number(value)); + } else if (_.isNumber(value)) { + result = String(value); + } else if (_.isString(value) || _.isDate(value)) { + if (dialect.builder.options.separatedValues) { + result = dialect.builder._pushValue(value); + } else { + if (_.isDate(value)) value = value.toISOString(); + result = '\'' + value + '\''; + } + } else { + result = parentValueBlock(params); + } + + return result; + }); + dialect.blocks.set('limit', function(params) { - return (!isNaN(params.offset)) ? '' : 'top(' + dialect.builder._pushValue(params.limit) + ')'; + var result = ''; + if (_.isUndefined(params.offset)) { + result = 'top(' + dialect.builder._pushValue(params.limit) + ')'; + } + return result; }); dialect.blocks.set('offset', function(params) { - var pre = (!params.sort) ? 'order by 1 ' : ''; + var prefix = (!params.sort) ? 'order by 1 ' : ''; + var offset = 'offset ' + dialect.builder._pushValue(params.offset) + ' '; + var limit = ''; + var suffix = 'rows'; + 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'; + limit = 'rows fetch next ' + dialect.builder._pushValue(params.limit) + ' '; + suffix = 'rows only'; } + + return prefix + offset + limit + suffix; }); dialect.blocks.set('returning', function(params) { @@ -28,32 +61,30 @@ module.exports = function(dialect) { dialect.blocks.set('insert:values', function(params) { var values = params.values; + var fields = params.fields; + var returning = params.returning; if (!_.isArray(values)) values = [values]; - var fields = params.fields || _(values) - .chain() - .map(function(row) { - return _(row).keys(); - }) - .flatten() - .uniq() - .value(); + fields = fields || _(values) + .chain() + .map(function(row) { + return _(row).keys(); + }) + .flatten() + .uniq() + .value(); + + values = _(values).map(function(row) { + return _(fields).map(function(field) { + return dialect.buildBlock('value', {value: row[field]}); + }); + }); return dialect.buildTemplate('insertValues', { + values: values, fields: fields, - returning: params.returning || undefined, - values: _(values).map(function(row) { - return _(fields).map(function(field) { - return dialect.buildBlock('value', {value: row[field]}); - }); - }) + returning: returning }); }); - - 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 569d6d8..4675e9b 100644 --- a/lib/dialects/mssql/index.js +++ b/lib/dialects/mssql/index.js @@ -7,35 +7,6 @@ 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 @@ -43,9 +14,11 @@ var Dialect = module.exports = function(builder) { // init blocks blocksInit(this); - }; util.inherits(Dialect, BaseDialect); -Dialect.prototype.config = _({}).extend(BaseDialect.prototype.config, {}); +Dialect.prototype.config = _({}).extend(BaseDialect.prototype.config, { + identifierPrefix: '[', + identifierSuffix: ']' +}); diff --git a/lib/dialects/mssql/templates.js b/lib/dialects/mssql/templates.js index 79dbd0f..0741576 100644 --- a/lib/dialects/mssql/templates.js +++ b/lib/dialects/mssql/templates.js @@ -46,9 +46,9 @@ module.exports = function(dialect) { } }); - dialect.templates.add('insert', { + dialect.templates.set('insert', { pattern: '{with} {withRecursive} insert {or} into {table} {values} ' + - '{condition}', + '{condition}', validate: function(type, params) { templateChecks.onlyOneOfProps(type, params, ['with', 'withRecursive']); templateChecks.propType(type, params, 'with', 'object'); @@ -68,7 +68,7 @@ module.exports = function(dialect) { } }); - dialect.templates.add('insertValues', { + dialect.templates.set('insertValues', { pattern: '({fields}) {returning} values {values}', validate: function(type, params) { templateChecks.requiredProp('values', params, 'fields'); @@ -83,7 +83,7 @@ module.exports = function(dialect) { } }); - dialect.templates.add('update', { + dialect.templates.set('update', { pattern: '{with} {withRecursive} update {or} {table} {alias} {modifier} {returning} ' + '{condition} ', validate: function(type, params) { @@ -109,7 +109,7 @@ module.exports = function(dialect) { } }); - dialect.templates.add('remove', { + dialect.templates.set('remove', { pattern: '{with} {withRecursive} delete from {table} {returning} {alias} {condition} ', validate: function(type, params) { templateChecks.onlyOneOfProps(type, params, ['with', 'withRecursive']); diff --git a/tests/6_dialects/1_mssql.js b/tests/6_dialects/1_mssql.js index 625ef91..0020cd4 100644 --- a/tests/6_dialects/1_mssql.js +++ b/tests/6_dialects/1_mssql.js @@ -17,7 +17,7 @@ describe('MSSQL dialect', function() { 'name': {$eq: 'test'} } }); - expect(result.query).to.be.equal('select top(1) "user" from "test" where "name" = $1;'); + 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() { @@ -30,7 +30,7 @@ describe('MSSQL dialect', function() { 'name': {$eq: 'test'} } }); - expect(result.query).to.be.equal('select "user" from "test" where "name" = $1 order by 1' + + expect(result.query).to.be.equal('select [user] from [test] where [name] = $1 order by 1' + ' offset 2 rows fetch next 4 rows only;'); }); }); @@ -39,25 +39,29 @@ describe('MSSQL dialect', function() { var result = jsonSql.build({ type: 'remove', table: 'test', - returning: ['DELETED.*'], + returning: [ + {table: 'deleted', name: '*'} + ], condition: { Description: {$eq: 'test'} } }); - expect(result.query).to.be.equal('delete from "test" output "DELETED".* where ' + - '"Description" = $1;'); + 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.*'], + returning: [ + {table: 'inserted', name: '*'} + ], values: { Description: 'test', } }); - expect(result.query).to.be.equal('insert into "test" ("Description") output ' + - '"INSERTED".* values ($1);'); + expect(result.query).to.be.equal('insert into [test] ([Description]) output ' + + '[inserted].* values ($1);'); }); }); }); From 99c711a05dc83c0a1a14c2797da1bd15add9caca Mon Sep 17 00:00:00 2001 From: Oliver Jourmel Date: Sat, 26 Aug 2017 17:00:59 -0700 Subject: [PATCH 9/9] Update `mssql` dialect test suite This commit updates the tests for the `mssql` dialect to improve coverage. Additionally, some refactoring is done to the `mssql` dialect to remove unnecessary code. --- lib/dialects/mssql/blocks.js | 13 +- tests/6_dialects/1_mssql.js | 237 ++++++++++++++++++++++++++++++----- 2 files changed, 208 insertions(+), 42 deletions(-) diff --git a/lib/dialects/mssql/blocks.js b/lib/dialects/mssql/blocks.js index 06e5251..afcf3b4 100644 --- a/lib/dialects/mssql/blocks.js +++ b/lib/dialects/mssql/blocks.js @@ -9,19 +9,8 @@ module.exports = function(dialect) { var value = params.value; var result; - if (_.isUndefined(value) || _.isNull(value)) { - result = 'null'; - } else if (_.isBoolean(value)) { + if (_.isBoolean(value)) { result = String(Number(value)); - } else if (_.isNumber(value)) { - result = String(value); - } else if (_.isString(value) || _.isDate(value)) { - if (dialect.builder.options.separatedValues) { - result = dialect.builder._pushValue(value); - } else { - if (_.isDate(value)) value = value.toISOString(); - result = '\'' + value + '\''; - } } else { result = parentValueBlock(params); } diff --git a/tests/6_dialects/1_mssql.js b/tests/6_dialects/1_mssql.js index 0020cd4..bd5b9ea 100644 --- a/tests/6_dialects/1_mssql.js +++ b/tests/6_dialects/1_mssql.js @@ -7,61 +7,238 @@ var jsonSql = require('../../lib')({ var expect = require('chai').expect; describe('MSSQL dialect', function() { - describe('limit', function() { + describe('limit / offset', function() { it('should be ok with `limit` property', function() { var result = jsonSql.build({ - table: 'test', - fields: ['user'], + table: 'users', + fields: ['id', 'name'], limit: 1, condition: { - 'name': {$eq: 'test'} + 'active': {$eq: '1'} } }); - expect(result.query).to.be.equal('select top(1) [user] from [test] where [name] = $1;'); + expect(result.query).to.be.equal( + 'select top(1) [id], [name] from [users] where [active] = $1;' + ); + }); + + it('should be ok with `limit` and `sort` properties', function() { + var result = jsonSql.build({ + table: 'users', + fields: ['id', 'name'], + limit: 1, + sort: { + 'lastLogin': -1 + } + }); + expect(result.query).to.be.equal( + 'select top(1) [id], [name] from [users] order by [lastLogin] desc;' + ); + }); + + it('should be ok with `offset` property', function() { + var result = jsonSql.build({ + table: 'users', + fields: ['id', 'name'], + offset: 2, + }); + expect(result.query).to.be.equal( + 'select [id], [name] from [users] order by 1 offset 2 rows;' + ); + }); + it('should be ok with `offset` and `sort` properties', function() { + var result = jsonSql.build({ + table: 'users', + fields: ['id', 'name'], + offset: 2, + sort: { + 'lastLogin': -1 + } + }); + expect(result.query).to.be.equal( + 'select [id], [name] from [users] order by [lastLogin] desc offset 2 rows;' + ); }); it('should be ok with `limit` and `offset` properties', function() { var result = jsonSql.build({ - table: 'test', - fields: ['user'], + table: 'users', + fields: ['id', 'name'], + limit: 4, + offset: 2 + }); + expect(result.query).to.be.equal( + 'select [id], [name] from [users] order by 1 offset 2 rows fetch next 4 rows only;' + ); + }); + + it('should be ok with `limit` and `offset` and `sort` properties', function() { + var result = jsonSql.build({ + table: 'users', + fields: ['id', 'name'], limit: 4, offset: 2, - condition: { - 'name': {$eq: 'test'} + sort: { + 'lastLogin': -1 } }); - expect(result.query).to.be.equal('select [user] from [test] where [name] = $1 order by 1' + - ' offset 2 rows fetch next 4 rows only;'); + expect(result.query).to.be.equal( + 'select [id], [name] from [users] order by [lastLogin] desc ' + + 'offset 2 rows fetch next 4 rows only;' + ); }); }); - describe('returning', function() { - it('should be ok with `remove` type', function() { + describe('Insert', function() { + it('should be ok with different `values` property', function() { + var date = new Date(); var result = jsonSql.build({ - type: 'remove', - table: 'test', - returning: [ - {table: 'deleted', name: '*'} - ], - condition: { - Description: {$eq: 'test'} + type: 'insert', + table: 'users', + values: { + id: 1, + name: 'Max', + date: date, + lastLogin: null, + active: true } }); - expect(result.query).to.be.equal('delete from [test] output [deleted].* where ' + - '[Description] = $1;'); + expect(result.query).to.be.equal( + 'insert into [users] ([id], [name], [date], [lastLogin], [active]) ' + + 'values (1, $1, $2, null, 1);' + ); }); - it('should be ok with `insert` type', function() { + + it('should be ok with different `values` property with option `separatedValues` = false', + function() { + var options = jsonSql.options; + jsonSql.configure({ + dialect: 'mssql', + separatedValues: false + }); + var date = new Date(); + var result = jsonSql.build({ + type: 'insert', + table: 'users', + values: { + id: 1, + name: 'Max', + date: date, + lastLogin: null, + active: true + } + }); + expect(result.query).to.be.equal( + 'insert into [users] ([id], [name], [date], [lastLogin], [active]) ' + + 'values (1, \'Max\', \'' + date.toISOString() + '\', null, 1);' + ); + jsonSql.configure(options); + } + ); + + it('should be ok with array `values` property', function() { + var result = jsonSql.build({ + type: 'insert', + table: 'users', + values: [ + { id: 1, name: 'Max' }, + { id: 2, name: 'Jane' } + ] + }); + expect(result.query).to.be.equal('insert into [users] ([id], [name]) values (1, $1), (2, $2);'); + }); + + + it('should throw error with a null `returning` property', function() { + expect(function() { + jsonSql.build({ + type: 'insert', + table: 'users', + values: { + name: 'Max' + }, + returning: null + }); + }).to.throw( + '`returning` property should have one of expected types: "array", "object" ' + + 'in `insertValues` clause' + ); + }); + + it('should be ok with `returning` property', function() { var result = jsonSql.build({ type: 'insert', - table: 'test', + table: 'users', + values: { + name: 'Max' + }, returning: [ - {table: 'inserted', name: '*'} + {table: 'inserted', name: 'id'} ], - values: { - Description: 'test', - } }); - expect(result.query).to.be.equal('insert into [test] ([Description]) output ' + - '[inserted].* values ($1);'); + expect(result.query).to.be.equal( + 'insert into [users] ([name]) output [inserted].[id] values ($1);' + ); + }); + + }); + + describe('Update', function() { + it('should throw error with a null `returning` property', function() { + expect(function() { + jsonSql.build({ + type: 'update', + table: 'users', + modifier: { + $dec: { + age: 3 + } + }, + returning: null + }); + }).to.throw( + '`returning` property should have one of expected types: "array", "object" ' + + 'in `update` clause' + ); + }); + + it('should be ok with `returning` property', function() { + var result = jsonSql.build({ + type: 'update', + table: 'users', + modifier: { + $dec: { + age: 3 + } + }, + returning: ['inserted.*', 'deleted.*'] + }); + expect(result.query).to.be.equal( + 'update [users] set [age] = [age] - 3 output [inserted].*, [deleted].*;' + ); + }); + }); + + describe('Delete', function() { + it('should throw error with a null `returning` property', function() { + expect(function() { + jsonSql.build({ + type: 'remove', + table: 'users', + returning: null + }); + }).to.throw( + '`returning` property should have one of expected types: "array", "object" ' + + 'in `remove` clause' + ); + }); + + it('should be ok with `returning` property', function() { + var result = jsonSql.build({ + type: 'remove', + table: 'users', + returning: ['deleted.*'] + }); + expect(result.query).to.be.equal('delete from [users] output [deleted].*;'); }); }); });