Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add MSSQL support #26

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions lib/dialects/mssql/blocks.js
Original file line number Diff line number Diff line change
@@ -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) + ')';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe _.isUndefined check is better here?

return _.isUndefined(params.offset) ? 'top(' + dialect.builder._pushValue(params.limit) + ')' : '';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And why you check offset value, but return limit value?

Copy link

@AsuraDiti AsuraDiti Jul 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MSSQL has no LIMIT/OFFSET Statement like mysql.
There are 2 diffrent ways:
SELECT TOP X Fields... is the same as LIMIT.
But you cannot use OFFSET together with TOP. IN MSSQL 2012 and higher a new statement was added.
SELECT fields from ... ORDER BY A OFFSET Y ROWS FETCH NEXT X ROWS ONLY

That is why he checks for the params.offset. If it is used the top() statement will be removed and the offset/fetch statement will include the 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whitespace is missed here

return pre + 'OFFSET ' + dialect.builder._pushValue(params.offset) + ' rows';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all words should be in lower case, you use offset word above but here OFFSET

}
});

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why returning is needed here?

values: _(values).map(function(row) {
return _(fields).map(function(field) {
return dialect.buildBlock('value', {value: row[field]});
});
})
});
});

dialect.blocks.add('insertValues:values', function(params) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is seems that this block is not needed to override

return _(params.values).map(function(row) {
return '(' + row.join(', ') + ')';
}).join(', ');
});
};
38 changes: 38 additions & 0 deletions lib/dialects/mssql/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't override builder._pushValue, you should override value block and do your specific stuff before parent value block call. See example in postgresql dialect: https://github.com/2do2go/json-sql/blob/master/lib/dialects/postgresql/blocks.js#L6

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);
Expand Down
130 changes: 130 additions & 0 deletions lib/dialects/mssql/templates.js
Original file line number Diff line number Diff line change
@@ -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']);

}
});
};
2 changes: 1 addition & 1 deletion lib/utils/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ exports.isSimpleValue = function(value) {

exports.isObjectObject = function(obj) {
return _.isObject(obj) && Object.prototype.toString.call(obj) === '[object Object]';
}
};
63 changes: 63 additions & 0 deletions tests/6_dialects/1_mssql.js
Original file line number Diff line number Diff line change
@@ -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);');
});
});
});