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

Adding TDS (aka Microsoft SQL) to dialects. #18

Open
wants to merge 4 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
2 changes: 2 additions & 0 deletions lib/Dialects/mysql.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
var util = require("util");

exports.name = 'mysql';

exports.escapeId = function () {
return Array.prototype.slice.apply(arguments).map(function (el) {
if (typeof el == "object") {
Expand Down
2 changes: 2 additions & 0 deletions lib/Dialects/postgresql.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
var util = require("util");

exports.name = 'postgresql';

exports.escapeId = function () {
return Array.prototype.slice.apply(arguments).map(function (el) {
if (typeof el == "object") {
Expand Down
1 change: 1 addition & 0 deletions lib/Dialects/sqlite.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var util = require("util");

exports.name = 'sqlite';
exports.escapeId = require("./mysql").escapeId;

exports.escapeVal = function (val, timeZone) {
Expand Down
136 changes: 136 additions & 0 deletions lib/Dialects/tds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
var util = require("util");

exports.name = 'tds';

exports.escapeId = function () {
return Array.prototype.slice.apply(arguments).map(function (el) {
if (typeof el == "object") {
return el.str.replace(/\?:(id|value)/g, function (m) {
if (m == "?:id") {
return exports.escapeId(el.escapes.shift());
}
// ?:value
return exports.escapeVal(el.escapes.shift());
});
}
return "[" + el.replace(/\[/g, '[[]').replace(/(^|[^\[])\]/g, '[]]') + "]";
}).join(".");
};

exports.escapeVal = function (val, timeZone) {
if (val === undefined || val === null) {
return 'NULL';
}

if (Buffer.isBuffer(val)) {
return bufferToString(val);
}

if (Array.isArray(val)) {
return arrayToList(val, timeZone || "Z");
}

if (util.isDate(val)) {
val = dateToString(val, timeZone || "Z");
} else {
switch (typeof val) {
case 'boolean':
return (val) ? '1' : '0';
case 'number':
return val + '';
case "object":
return objectToValues(val, timeZone || "Z");
case "function":
return val(exports);
}
}

val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
switch(s) {
case "\0": return "\\0";
case "\n": return "\\n";
case "\r": return "\\r";
case "\b": return "\\b";
case "\t": return "\\t";
case "\x1a": return "\\Z";
case "\'": return "'";
default: return "\\" + s;
}
});

return "'" + val.replace("'", "''") + "'";
};

function objectToValues(object, timeZone) {
var values = [];
for (var key in object) {
var value = object[key];

if(typeof value === 'function') {
continue;
}

values.push(exports.escapeId(key) + ' = ' + exports.escapeVal(value, timeZone));
}

return values.join(', ');
}

function arrayToList(array, timeZone) {
return "(" + array.map(function(v) {
if (Array.isArray(v)) return arrayToList(v);
return exports.escapeVal(v, timeZone);
}).join(', ') + ")";
}

function bufferToString(buffer) {
var hex = '';

try {
hex = buffer.toString('hex');
} catch (err) {
// node v0.4.x does not support hex / throws unknown encoding error
for (var i = 0; i < buffer.length; i++) {
var b = buffer[i];
hex += zeroPad(b.toString(16));
}
}

return "0x" + hex;
}

function dateToString(date, timeZone) {
var dt = new Date(date);

if (timeZone != 'local') {
var tz = convertTimezone(timeZone);

dt.setTime(dt.getTime() + (dt.getTimezoneOffset() * 60000));
if (tz !== false) {
dt.setTime(dt.getTime() + (tz * 60000));
}
}

var year = dt.getFullYear();
var month = zeroPad(dt.getMonth() + 1);
var day = zeroPad(dt.getDate());
var hour = zeroPad(dt.getHours());
var minute = zeroPad(dt.getMinutes());
var second = zeroPad(dt.getSeconds());

return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
}

function zeroPad(number) {
return (number < 10) ? '0' + number : number;
}

function convertTimezone(tz) {
if (tz == "Z") return 0;

var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/);
if (m) {
return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60;
}
return false;
}
31 changes: 21 additions & 10 deletions lib/Remove.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ exports.RemoveQuery = RemoveQuery;
function RemoveQuery(Dialect) {
var sql = {
where : [],
order : []
order : [],
use_top: (Dialect.name === 'tds'),
can_use_order_by: (Dialect.name !== 'tds')
};

return {
Expand All @@ -25,7 +27,14 @@ function RemoveQuery(Dialect) {
build: function () {
var query = [], tmp;

query.push("DELETE FROM");
query.push("DELETE");

// TDS only!
if (sql.use_top && sql.hasOwnProperty("limit")) {
query.push("TOP (" + sql.limit + ")");
}

query.push("FROM");
query.push(Dialect.escapeId(sql.table));

query = query.concat(Where.build(Dialect, sql.where));
Expand All @@ -41,20 +50,22 @@ function RemoveQuery(Dialect) {
}
}

if (tmp.length > 0) {
if (tmp.length > 0 && sql.can_use_order_by) {
query.push("ORDER BY " + tmp.join(", "));
}
}

// limit
if (sql.hasOwnProperty("limit")) {
if (sql.hasOwnProperty("offset")) {
query.push("LIMIT " + sql.limit + " OFFSET " + sql.offset);
} else {
query.push("LIMIT " + sql.limit);
if (!sql.use_top) {
if (sql.hasOwnProperty("limit")) {
if (sql.hasOwnProperty("offset")) {
query.push("LIMIT " + sql.limit + " OFFSET " + sql.offset);
} else {
query.push("LIMIT " + sql.limit);
}
} else if (sql.hasOwnProperty("offset")) {
query.push("OFFSET " + sql.offset);
}
} else if (sql.hasOwnProperty("offset")) {
query.push("OFFSET " + sql.offset);
}

return query.join(" ");
Expand Down
93 changes: 71 additions & 22 deletions lib/Select.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ function SelectQuery(Dialect) {
order : [],
group_by : null,
found_rows : false,
where_exists : false
where_exists : false,
use_top : (Dialect.name === 'tds'),
manual_offset : (Dialect.name === 'tds')
};
var get_table_alias = function (table) {
for (var i = 0; i < sql.from.length; i++) {
Expand Down Expand Up @@ -194,8 +196,44 @@ function SelectQuery(Dialect) {
this.fun(fun_stack.pop());
}

// Calculate ORDER BY early, in case we need it for
// manual offset.
//
if (sql.group_by) {
sql.group_by.forEach(function (column) {
if (column[0] == "-") {
sql.order.unshift({ c: column.substr(1), d: "DESC" });
}
});
}

order_by = [];

if (sql.order.length > 0) {
for (i = 0; i < sql.order.length; i++) {
if (Array.isArray(sql.order[i].c)) {
order_by.push(Dialect.escapeId.apply(Dialect, sql.order[i].c) + " " + sql.order[i].d);
} else {
order_by.push(Dialect.escapeId(sql.order[i].c) + " " + sql.order[i].d);
}
}
}

// TDS only - Manual Offset Workaround
if (sql.manual_offset && sql.hasOwnProperty("offset")) {
if (sql.use_top && sql.hasOwnProperty("limit"))
query.push("SELECT TOP " + sql.limit + " * FROM (");
else
query.push("SELECT * FROM (");
}

query.push("SELECT");

// TDS only - TOP instead of LIMIT
if (sql.use_top && sql.hasOwnProperty("limit") && !sql.hasOwnProperty("offset")) {
query.push("TOP " + sql.limit);
}

for (i = 0; i < sql.from.length; i++) {
sql.from[i].a = "t" + (i + 1);
}
Expand Down Expand Up @@ -288,8 +326,23 @@ function SelectQuery(Dialect) {
query.push("SQL_CALC_FOUND_ROWS");
}

// Need to allow for TDS' manual offset, if necessary. Otherwise,
// we can just proceed with an unaltered SELECT stanza.
//
var row_number_clause;

if (sql.manual_offset && sql.hasOwnProperty('offset')) {
row_number_clause = order_by.length > 0 ? order_by.join(", ") : "id ASC";
}

if (tmp.length) {
if (sql.manual_offset && sql.hasOwnProperty('offset')) {
tmp.unshift("ROW_NUMBER() OVER (ORDER BY " + row_number_clause + ") AS p_RN")
}

query.push(tmp.join(", "));
} else if (sql.manual_offset && sql.hasOwnProperty('offset')) {
query.push("ROW_NUMBER() OVER (ORDER BY " + row_number_clause + ") AS p_RN, *");
} else {
query.push("*");
}
Expand Down Expand Up @@ -349,31 +402,27 @@ function SelectQuery(Dialect) {
}).join(", "));
}

// order
if (sql.order.length > 0) {
tmp = [];
for (i = 0; i < sql.order.length; i++) {
if (Array.isArray(sql.order[i].c)) {
tmp.push(Dialect.escapeId.apply(Dialect, sql.order[i].c) + " " + sql.order[i].d);
} else {
tmp.push(Dialect.escapeId(sql.order[i].c) + " " + sql.order[i].d);
}
}
// TDS only - Manual Offset Workaround
if (sql.manual_offset && sql.hasOwnProperty("offset")) {
query.push(") SQ WHERE p_RN > " + sql.offset);
}

if (tmp.length > 0) {
query.push("ORDER BY " + tmp.join(", "));
}
// order
if (order_by.length > 0) {
query.push("ORDER BY " + order_by.join(", "));
}

// limit
if (sql.hasOwnProperty("limit")) {
if (sql.hasOwnProperty("offset")) {
query.push("LIMIT " + sql.limit + " OFFSET " + sql.offset);
} else {
query.push("LIMIT " + sql.limit);
// limit (non-TDS only!)
if (!sql.use_top) {
if (sql.hasOwnProperty("limit")) {
if (!sql.manual_offset && sql.hasOwnProperty("offset")) {
query.push("LIMIT " + sql.limit + " OFFSET " + sql.offset);
} else {
query.push("LIMIT " + sql.limit);
}
} else if (!sql.manual_offset && sql.hasOwnProperty("offset")) {
query.push("OFFSET " + sql.offset);
}
} else if (sql.hasOwnProperty("offset")) {
query.push("OFFSET " + sql.offset);
}

return query.join(" ");
Expand Down
6 changes: 5 additions & 1 deletion lib/Where.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,11 @@ function buildOrGroup(Dialect, where) {
if (Array.isArray(where.w[k])) {
if (where.w[k].length === 0) {
// #274: IN with empty arrays should be a false sentence
query.push("FALSE");
//
// "1=0" will still evaluate to false and plays nice with
// TDS.
//
query.push("1=0");
} else {
query.push(buildComparisonKey(Dialect, where.t, k) + " IN " + Dialect.escapeVal(where.w[k]));
}
Expand Down
16 changes: 8 additions & 8 deletions test/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,26 @@ var Query = require('../');
common.Query = Query;
common.Text = Query.Text;

common.Select = function () {
var q = new (Query.Query)();
common.Select = function (dialect) {
var q = new (Query.Query)(dialect);

return q.select();
};

common.Insert = function () {
var q = new (Query.Query)();
common.Insert = function (dialect) {
var q = new (Query.Query)(dialect);

return q.insert();
};

common.Update = function () {
var q = new (Query.Query)();
common.Update = function (dialect) {
var q = new (Query.Query)(dialect);

return q.update();
};

common.Remove = function () {
var q = new (Query.Query)();
common.Remove = function (dialect) {
var q = new (Query.Query)(dialect);

return q.remove();
};
Expand Down
Loading