From 0bfeb9e97e7d61798cbc9763ebd470536ec51ff5 Mon Sep 17 00:00:00 2001 From: code-vicar Date: Tue, 5 Mar 2013 20:54:38 -0800 Subject: [PATCH 001/239] Initial commit --- .gitignore | 14 ++++++++++++++ README.md | 4 ++++ 2 files changed, 18 insertions(+) create mode 100644 .gitignore create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f356293 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz + +pids +logs +results + +npm-debug.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..f348f7f --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +jugglingdb-mssql +================ + +MsSQL adapter for the jugglingdb ORM \ No newline at end of file From 0ee78f3a88e4d0b5708a58821d0b0612d5355d4a Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Tue, 5 Mar 2013 21:21:04 -0800 Subject: [PATCH 002/239] Initial commit --- .gitignore | 3 + README.md | 25 +- lib/mssql.js | 670 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 21 ++ 4 files changed, 718 insertions(+), 1 deletion(-) create mode 100644 lib/mssql.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore index f356293..b1e03dc 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,7 @@ pids logs results +*.sublime* +node_modules/ + npm-debug.log diff --git a/README.md b/README.md index f348f7f..32eb300 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,27 @@ jugglingdb-mssql ================ -MsSQL adapter for the jugglingdb ORM \ No newline at end of file +MsSQL adapter for the jugglingdb ORM + + +MIT +--- +Copyright (C) 2013 by Scott Vickers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/lib/mssql.js b/lib/mssql.js new file mode 100644 index 0000000..c06b9e1 --- /dev/null +++ b/lib/mssql.js @@ -0,0 +1,670 @@ +/* Module dependencies */ +var mssql = require("msnodesql"); +var jdb = require("jugglingdb"); +var util = require("util"); + +exports.initialize = function initializeSchema(schema, callback) { + //need msnodesql installed, and a host server and a database + if (!mssql || !schema.settings.host || !schema.settings.database){ return; } + + var conn_str = "Driver={SQL Server Native Client 11.0};Server="+schema.settings.host+";"; + var trusted_str = "Trusted_Connection={Yes};Database={"+schema.settings.database+"};"; + + //if we have a username and password then we use a credential connection string + if (schema.settings.username && schema.settings.password) { + conn_str += "UID="+schema.settings.username+";PWD="+schema.settings.password+";Database={"+schema.settings.database+"};" + } else { + conn_str += trusted_str + } + + mssql.open(conn_str, function(err, conn){ + if (err) + throw err + + //debugger; + schema.client = conn; + schema.adapter = new MsSQL(schema.client); + schema.adapter.schema = schema; + callback(); + }); +}; + +function MsSQL(client) { + this.name = "mssql"; + this._models = {}; + this._pkids = {}; + this.client = client; +} + +util.inherits(MsSQL, jdb.BaseSQL); + +MsSQL.prototype.query = function (sql, optionsOrCallback, Callback) { + //debugger; + var hasOptions = true; + var options = null; + var cb = null; + if (typeof optionsOrCallback === "function") { + hasOptions = false; + cb = optionsOrCallback; + } else { + options = optionsOrCallback; + cb = Callback; + } + if (!this.schema.connected) { + return this.schema.on('connected', function () { + if (hasOptions) { + this.query(sql, options, cb); + } else { + this.query(sql, cb); + } + }.bind(this)); + } + var client = this.client; + var time = Date.now(); + var log = this.log; + if (typeof cb !== 'function') { + throw new Error('callback should be a function'); + } + + var innerCB = function (err, data) { + if (log) log(sql, time); + cb(err, data); + }; + + if (hasOptions) { + this.client.query(sql, options, innerCB); + } else { + this.client.query(sql, innerCB); + } + +}; + +MsSQL.prototype.defineProperty = function (model, prop, params) { + debugger; + var tblName = this.tableEscaped(model); + if (params.primaryKey) { + this._pkids[model] = params.primaryKey; + delete params.primaryKey; + } else { + this._pkids[model] = tblName+"ID"; + } + this._models[model].properties[prop] = params; +}; + +/** + * Must invoke callback(err, id) + */ +MsSQL.prototype.create = function (model, data, callback) { + debugger; + var fieldsAndData = this.buildInsert(model, data); + var tblName = this.tableEscaped(model); + var sql = "INSERT INTO " + tblName + + " (" + fieldsAndData.fields + ")" + + " VALUES (" + fieldsAndData.paramPlaceholders + ")" + + " SELECT IDENT_CURRENT('" + tblName + "') AS insertId"; + + this.query(sql, fieldsAndData.params, function (err, results) { + callback(err, results[0].insertId); + }); +}; + +MsSQL.prototype.updateOrCreate = function (model, data, callback) { + debugger; + var mssql = this; + var objModel = this._models[model]; + var props = objModel.properties; + var tblName = this.tableEscaped(model); + var modelPKID = this._pkids[model]; + var id = 0; + var fieldNames = []; + var fieldValues = []; + var fieldValuesPlaceholders = []; + var combined = []; + Object.keys(data).forEach(function (key) { + if (props[key] || key === modelPKID) { + if (key === modelPKID) { + id = data[key]; + } else { + fieldNames.push(key); + fieldValues.push(mssql.toDatabase(props[key], data[key])); + fieldValuesPlaceholders.push("(?)"); + combined.push(key + "=(?)"); + } + } + }); + var sql = ""; + if (id > 0) { + sql = "UPDATE " + tblName; + sql += " SET " + combined.join(); + sql += " WHERE " + modelPKID + " = (?)"; + sql += "SELECT " + id + " AS pkid"; + fieldValues.push(id); + } else { + sql = "INSERT INTO " + tblName; + sql += " (" + fieldNames.join() + ")"; + sql += " VALUES (" + fieldValuesPlaceholders.join() + ")"; + sql += " SELECT IDENT_CURRENT('" + tblName + "') AS pkid"; + } + + this.query(sql, fieldValues, function (err, results) { + if (!err) { + data[modelPKID] = results[0].pkid; + } + callback(err, data); + }); +}; + +MsSQL.prototype.updateAttributes = function updateAttrs(model, id, data, cb) { + data[this._pkids[model]] = id; + this.save(model, data, cb); +}; + +MsSQL.prototype.exists = function (model, id, callback) { + var objModel = this._models[model]; + var tblName = this.tableEscaped(model); + var modelPKID = typeof objModel.primaryKey !== "undefined" && objModel.primaryKey !== null ? objModel.primaryKey : tblName + "ID"; + var sql = "SELECT COUNT(*) cnt FROM "+tblName+" WHERE "+modelPKID+" = (?)" + //console.log(sql); + this.query(sql, [id], function (err, results) { + if (err) return callback(err); + callback(null, results[0].cnt >= 1); + }); +}; + +MsSQL.prototype.find = function (model, id, callback) { + debugger; + var objModel = this._models[model]; + var tblName = this.tableEscaped(model); + var modelPKID = typeof objModel.primaryKey !== "undefined" && objModel.primaryKey !== null ? objModel.primaryKey : tblName + "ID"; + var sql = "SELECT * FROM "+tblName+" WHERE "+modelPKID+" = (?)"; + //console.log(sql); + this.query(sql, [id], function (err, results) { + //debugger; + callback(err, this.fromDatabase(model, results[0])); + }.bind(this)); +}; + +MsSQL.prototype.buildInsert = function (model, data) { + var insertIntoFields = []; + var paramPlaceholders = []; + var params = []; + var props = this._models[model].properties; + Object.keys(data).forEach(function (key) { + if (props[key]) { + insertIntoFields.push(this.escapeKey(key)); + paramPlaceholders.push("(?)"); + params.push(this.toDatabase(props[key], data[key])); + } + }.bind(this)); + + return { fields:insertIntoFields.join(), paramPlaceholders:paramPlaceholders.join(), params:params }; +} + +function dateToMsSql(val) { + return val.getUTCFullYear() + '-' + + fillZeros(val.getUTCMonth() + 1) + '-' + + fillZeros(val.getUTCDate()) + ' ' + + fillZeros(val.getUTCHours()) + ':' + + fillZeros(val.getUTCMinutes()) + ':' + + fillZeros(val.getUTCSeconds()); + + function fillZeros(v) { + return v < 10 ? '0' + v : v; + } +} + +MsSQL.prototype.toDatabase = function (prop, val) { + if (val === null) { + return 'NULL'; + } + if (val.constructor.name === 'Object') { + var operator = Object.keys(val)[0] + val = val[operator]; + if (operator === 'between') { + return this.toDatabase(prop, val[0]) + + ' AND ' + + this.toDatabase(prop, val[1]); + } else if (operator == 'inq' || operator == 'nin') { + if (!(val.propertyIsEnumerable('length')) && typeof val === 'object' && typeof val.length === 'number') { //if value is array + return val.join(','); + } else { + return val; + } + } + } + if (!prop) { + return val; + } + if (prop.type.name === 'Number') { + return val; + } + if (prop.type.name === 'Date') { + if (!val) { + return 'NULL'; + } + if (!val.toUTCString) { + val = new Date(val); + } + return '"' + dateToMsSql(val) + '"'; + } + if (prop.type.name == "Boolean") { + return val ? 1 : 0; + } + return val.toString(); +}; + +MsSQL.prototype.fromDatabase = function (model, data) { + if (!data) { + return null; + } + var props = this._models[model].properties; + Object.keys(data).forEach(function (key) { + var val = data[key]; + if (props[key]) { + if (props[key].type.name === 'Date' && val !== null) { + val = new Date(val.toString().replace(/GMT.*$/, 'GMT')); + } + } + data[key] = val; + }); + return data; +}; + +MsSQL.prototype.escapeName = function (name) { + return name.replace(/\./g, '_'); +}; + +MsSQL.prototype.escapeKey = function (key) { + return key; +}; + +MsSQL.prototype.all = function all(model, filter, callback) { + + var sql = "SELECT * FROM " + this.tableEscaped(model); + var self = this; + var props = this._models[model].properties; + + if (filter) { + + if (filter.where) { + sql += " " + buildWhere(filter.where); + } + + if (filter.order) { + sql += " " + buildOrderBy(filter.order); + } + + } + + this.query(sql, function (err, data) { + if (err) { + return callback(err, []); + } + + var objs = data.map(function (obj) { + return self.fromDatabase(model, obj); + }); + if (filter && filter.include) { + this._models[model].model.include(objs, filter.include, callback); + } else { + callback(null, objs); + } + }.bind(this)); + + return sql; + + function buildWhere(conds) { + var cs = []; + Object.keys(conds).forEach(function (key) { + var keyEscaped = self.escapeKey(key); + var val = self.toDatabase(props[key], conds[key]); + if (conds[key] === null) { + cs.push(keyEscaped + ' IS NULL'); + } else if (conds[key].constructor.name === 'Object') { + var condType = Object.keys(conds[key])[0]; + var sqlCond = keyEscaped; + if ((condType == 'inq' || condType == 'nin') && val.length == 0) { + cs.push(condType == 'inq' ? 0 : 1); + return true; + } + switch (condType) { + case 'gt': + sqlCond += ' > '; + break; + case 'gte': + sqlCond += ' >= '; + break; + case 'lt': + sqlCond += ' < '; + break; + case 'lte': + sqlCond += ' <= '; + break; + case 'between': + sqlCond += ' BETWEEN '; + break; + case 'inq': + sqlCond += ' IN '; + break; + case 'nin': + sqlCond += ' NOT IN '; + break; + case 'neq': + sqlCond += ' != '; + break; + } + sqlCond += (condType == 'inq' || condType == 'nin') ? '(' + val + ')' : val; + cs.push(sqlCond); + } else { + cs.push(keyEscaped + ' = ' + val); + } + }); + if (cs.length === 0) { + return ''; + } + return 'WHERE ' + cs.join(' AND '); + } + + function buildOrderBy(order) { + if (typeof order === 'string') order = [order]; + return 'ORDER BY ' + order.join(', '); + } + +}; + +// MsSQL.prototype.autoupdate = function (cb) { +// var self = this; +// var wait = 0; +// Object.keys(this._models).forEach(function (model) { +// wait += 1; +// self.query('SHOW FIELDS FROM ' + self.tableEscaped(model), function (err, fields) { +// self.query('SHOW INDEXES FROM ' + self.tableEscaped(model), function (err, indexes) { +// if (!err && fields.length) { +// self.alterTable(model, fields, indexes, done); +// } else { +// self.createTable(model, done); +// } +// }); +// }); +// }); + +// function done(err) { +// if (err) { +// console.log(err); +// } +// if (--wait === 0 && cb) { +// cb(); +// } +// } +// }; + +// MsSQL.prototype.isActual = function (cb) { +// var ok = false; +// var self = this; +// var wait = 0; +// Object.keys(this._models).forEach(function (model) { +// wait += 1; +// self.query('SHOW FIELDS FROM ' + model, function (err, fields) { +// self.query('SHOW INDEXES FROM ' + model, function (err, indexes) { +// self.alterTable(model, fields, indexes, done, true); +// }); +// }); +// }); + +// function done(err, needAlter) { +// if (err) { +// console.log(err); +// } +// ok = ok || needAlter; +// if (--wait === 0 && cb) { +// cb(null, !ok); +// } +// } +// }; + +// MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, checkOnly) { +// var self = this; +// var m = this._models[model]; +// var propNames = Object.keys(m.properties).filter(function (name) { +// return !!m.properties[name]; +// }); +// var indexNames = m.settings.indexes ? Object.keys(m.settings.indexes).filter(function (name) { +// return !!m.settings.indexes[name]; +// }) : []; +// var sql = []; +// var ai = {}; + +// if (actualIndexes) { +// actualIndexes.forEach(function (i) { +// var name = i.Key_name; +// if (!ai[name]) { +// ai[name] = { +// info: i, +// columns: [] +// }; +// } +// ai[name].columns[i.Seq_in_index - 1] = i.Column_name; +// }); +// } +// var aiNames = Object.keys(ai); + +// // change/add new fields +// propNames.forEach(function (propName) { +// if (propName === 'id') return; +// var found; +// actualFields.forEach(function (f) { +// if (f.Field === propName) { +// found = f; +// } +// }); + +// if (found) { +// actualize(propName, found); +// } else { +// sql.push('ADD COLUMN `' + propName + '` ' + self.propertySettingsSQL(model, propName)); +// } +// }); + +// // drop columns +// actualFields.forEach(function (f) { +// var notFound = !~propNames.indexOf(f.Field); +// if (f.Field === 'id') return; +// if (notFound || !m.properties[f.Field]) { +// sql.push('DROP COLUMN `' + f.Field + '`'); +// } +// }); + +// // remove indexes +// aiNames.forEach(function (indexName) { +// if (indexName === 'id' || indexName === 'PRIMARY') return; +// if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || m.properties[indexName] && !m.properties[indexName].index) { +// sql.push('DROP INDEX `' + indexName + '`'); +// } else { +// // first: check single (only type and kind) +// if (m.properties[indexName] && !m.properties[indexName].index) { +// // TODO +// return; +// } +// // second: check multiple indexes +// var orderMatched = true; +// if (indexNames.indexOf(indexName) !== -1) { +// m.settings.indexes[indexName].columns.split(/,\s*/).forEach(function (columnName, i) { +// if (ai[indexName].columns[i] !== columnName) orderMatched = false; +// }); +// } +// if (!orderMatched) { +// sql.push('DROP INDEX `' + indexName + '`'); +// delete ai[indexName]; +// } +// } +// }); + +// // add single-column indexes +// propNames.forEach(function (propName) { +// var i = m.properties[propName].index; +// if (!i) { +// return; +// } +// var found = ai[propName] && ai[propName].info; +// if (!found) { +// var type = ''; +// var kind = ''; +// if (i.type) { +// type = 'USING ' + i.type; +// } +// if (i.kind) { +// // kind = i.kind; +// } +// if (kind && type) { +// sql.push('ADD ' + kind + ' INDEX `' + propName + '` (`' + propName + '`) ' + type); +// } else { +// sql.push('ADD ' + kind + ' INDEX `' + propName + '` ' + type + ' (`' + propName + '`) '); +// } +// } +// }); + +// // add multi-column indexes +// indexNames.forEach(function (indexName) { +// var i = m.settings.indexes[indexName]; +// var found = ai[indexName] && ai[indexName].info; +// if (!found) { +// var type = ''; +// var kind = ''; +// if (i.type) { +// type = 'USING ' + i.type; +// } +// if (i.kind) { +// kind = i.kind; +// } +// if (kind && type) { +// sql.push('ADD ' + kind + ' INDEX `' + indexName + '` (' + i.columns + ') ' + type); +// } else { +// sql.push('ADD ' + kind + ' INDEX ' + type + ' `' + indexName + '` (' + i.columns + ')'); +// } +// } +// }); + +// if (sql.length) { +// var query = 'ALTER TABLE ' + self.tableEscaped(model) + ' ' + sql.join(',\n'); +// if (checkOnly) { +// done(null, true, {statements: sql, query: query}); +// } else { +// this.query(query, done); +// } +// } else { +// done(); +// } + +// function actualize(propName, oldSettings) { +// var newSettings = m.properties[propName]; +// if (newSettings && changed(newSettings, oldSettings)) { +// sql.push('CHANGE COLUMN `' + propName + '` `' + propName + '` ' + self.propertySettingsSQL(model, propName)); +// } +// } + +// function changed(newSettings, oldSettings) { +// if (oldSettings.Null === 'YES' && (newSettings.allowNull === false || newSettings.null === false)) return true; +// if (oldSettings.Null === 'NO' && !(newSettings.allowNull === false || newSettings.null === false)) return true; +// if (oldSettings.Type.toUpperCase() !== datatype(newSettings)) return true; +// return false; +// } +// }; + +MsSQL.prototype.propertiesSQL = function (model) { + debugger; + var self = this; + var objModel = this._models[model]; + var modelPKID = typeof objModel.primaryKey !== "undefined" && objModel.primaryKey !== null ? objModel.primaryKey : tblName + "ID"; + + var sql = ["'"+modelPKID+"' INT IDENTITY PRIMARY KEY"]; + Object.keys(this._models[model].properties).forEach(function (prop) { + if (prop === modelPKID) { + return; + } + sql.push("'" + prop + "'" + self.propertySettingsSQL(model, prop)); + }); + // Declared in model index property indexes. + Object.keys(this._models[model].properties).forEach(function (prop) { + var i = self._models[model].properties[prop].index; + if (i) { + sql.push(self.singleIndexSettingsSQL(model, prop)); + } + }); + // Settings might not have an indexes property. + var dxs = this._models[model].settings.indexes; + if(dxs){ + Object.keys(this._models[model].settings.indexes).forEach(function(prop){ + sql.push(self.indexSettingsSQL(model, prop)); + }); + } + return sql.join(',\n '); +}; + +MsSQL.prototype.singleIndexSettingsSQL = function (model, prop) { + // Recycled from alterTable single indexes above, more or less. + var i = this._models[model].properties[prop].index; + var type = ''; + var kind = ''; + if (i.type) { + type = 'USING ' + i.type; + } + if (i.kind) { + kind = i.kind; + } + if (kind && type) { + return (kind + ' INDEX `' + prop + '` (`' + prop + '`) ' + type); + } else { + return (kind + ' INDEX `' + prop + '` ' + type + ' (`' + prop + '`) '); + } +}; + +MsSQL.prototype.indexSettingsSQL = function (model, prop) { + // Recycled from alterTable multi-column indexes above, more or less. + var i = this._models[model].settings.indexes[prop]; + var type = ''; + var kind = ''; + if (i.type) { + type = 'USING ' + i.type; + } + if (i.kind) { + kind = i.kind; + } + if (kind && type) { + return (kind + ' INDEX `' + prop + '` (' + i.columns + ') ' + type); + } else { + return (kind + ' INDEX ' + type + ' `' + prop + '` (' + i.columns + ')'); + } +}; + +MsSQL.prototype.propertySettingsSQL = function (model, prop) { + var p = this._models[model].properties[prop]; + return datatype(p) + ' ' + + (p.allowNull === false || p['null'] === false ? 'NOT NULL' : 'NULL'); +}; + +function datatype(p) { + var dt = ''; + switch (p.type.name) { + default: + case 'String': + case 'JSON': + dt = 'VARCHAR(' + (p.limit || 255) + ')'; + break; + case 'Text': + dt = 'TEXT'; + break; + case 'Number': + dt = 'INT'; + break; + case 'Date': + dt = 'DATETIME'; + break; + case 'Boolean': + dt = 'TINYINT(1)'; + break; + case 'Point': + dt = 'POINT'; + break; + } + return dt; +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..5602beb --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "jugglingdb-mssql", + "version": "0.0.1", + "description": "MsSQL adapter for JugglingDB", + "main": "index.js", + //no tests yet + //"scripts": { + // "test": "nodeunit test/*" + //}, + "dependencies": { + "jugglingdb": "latest", + "util":"~0.4.9", + "msnodesql":"~0.2.1" + }, + "repository": { + "type": "git", + "url": "https://github.com/code-vicar/jugglingdb-mssql.git" + }, + "author": "Scott Vickers", + "license": "MIT" +} \ No newline at end of file From fe24543913bcc03c1f9cb5d5cb9a1a2f7b2d8e46 Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Tue, 5 Mar 2013 21:26:24 -0800 Subject: [PATCH 003/239] add index.js --- README.md | 2 +- index.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 index.js diff --git a/README.md b/README.md index 32eb300..e5e6324 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ jugglingdb-mssql MsSQL adapter for the jugglingdb ORM -MIT +MIT License --- Copyright (C) 2013 by Scott Vickers diff --git a/index.js b/index.js new file mode 100644 index 0000000..a4a1362 --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +module.exports = require("./lib/mssql.js"); \ No newline at end of file From cd57a8576a1fbc1352780bd2e8ea3affb5eac182 Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Tue, 5 Mar 2013 21:31:43 -0800 Subject: [PATCH 004/239] remove comments --- package.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/package.json b/package.json index 5602beb..04e3cad 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,6 @@ "version": "0.0.1", "description": "MsSQL adapter for JugglingDB", "main": "index.js", - //no tests yet - //"scripts": { - // "test": "nodeunit test/*" - //}, "dependencies": { "jugglingdb": "latest", "util":"~0.4.9", From 3cef05355091e975382fae7825f16f17d26c308d Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Tue, 5 Mar 2013 21:32:21 -0800 Subject: [PATCH 005/239] add not fully tested --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e5e6324..46bc802 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ jugglingdb-mssql MsSQL adapter for the jugglingdb ORM +Early stages, not fully tested. + MIT License --- From 2707815dd595d059100d04e71ae33896fc83b480 Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Tue, 5 Mar 2013 21:55:01 -0800 Subject: [PATCH 006/239] add Usage section --- README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 46bc802..d3edb04 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,27 @@ MsSQL adapter for the jugglingdb ORM Early stages, not fully tested. +Usage +--- +To use it you need jugglingdb@0.2.x and msnodesql + +1. Setup dependencies in package.json + { + ... + "dependencies":{ + "msnodesql":"~0.2.1", + "jugglingdb": "~0.2.0", + "jugglingdb-mssql":"latest" + } + ... + } +2. Use: + var Schema = require("jugglingdb").Schema; + var schema = new Schema("mssql", {host:"YourSqlServer", database:"YourDatabase"}); + ... MIT License --- -Copyright (C) 2013 by Scott Vickers - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights From 287e97fdeadbfac8f9b2cec0152fdace274a03dc Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Tue, 5 Mar 2013 21:58:40 -0800 Subject: [PATCH 007/239] wrap in pre and code blocks --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d3edb04..8eccaff 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,10 @@ Early stages, not fully tested. Usage --- -To use it you need jugglingdb@0.2.x and msnodesql +To use it you need
jugglingdb@0.2.x
and msnodesql 1. Setup dependencies in package.json +

     {
       ...
       "dependencies":{
@@ -19,10 +20,13 @@ To use it you need jugglingdb@0.2.x and msnodesql
       }
       ...
     }
+  	
2. Use: +

     var Schema = require("jugglingdb").Schema;
     var schema = new Schema("mssql", {host:"YourSqlServer", database:"YourDatabase"});
     ...
+    
MIT License --- From 71a189a7dc40ac1c6ade838e1b04ed090cf4e33b Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Wed, 6 Mar 2013 00:38:15 -0800 Subject: [PATCH 008/239] overwrite 'define' method to track model pkid --- lib/mssql.js | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index c06b9e1..6672a8b 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -39,7 +39,7 @@ function MsSQL(client) { util.inherits(MsSQL, jdb.BaseSQL); MsSQL.prototype.query = function (sql, optionsOrCallback, Callback) { - //debugger; + debugger; var hasOptions = true; var options = null; var cb = null; @@ -79,15 +79,13 @@ MsSQL.prototype.query = function (sql, optionsOrCallback, Callback) { }; +MsSQL.prototype.define = function (descr) { + if (!descr.settings) descr.settings = {}; + this._models[descr.model.modelName] = descr; + this._pkids[descr.model.modelName] = descr.model.modelName + "ID"; +}; + MsSQL.prototype.defineProperty = function (model, prop, params) { - debugger; - var tblName = this.tableEscaped(model); - if (params.primaryKey) { - this._pkids[model] = params.primaryKey; - delete params.primaryKey; - } else { - this._pkids[model] = tblName+"ID"; - } this._models[model].properties[prop] = params; }; @@ -149,6 +147,7 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { this.query(sql, fieldValues, function (err, results) { if (!err) { data[modelPKID] = results[0].pkid; + data.id = results[0].pkid; } callback(err, data); }); @@ -162,7 +161,7 @@ MsSQL.prototype.updateAttributes = function updateAttrs(model, id, data, cb) { MsSQL.prototype.exists = function (model, id, callback) { var objModel = this._models[model]; var tblName = this.tableEscaped(model); - var modelPKID = typeof objModel.primaryKey !== "undefined" && objModel.primaryKey !== null ? objModel.primaryKey : tblName + "ID"; + var modelPKID = this._pkids[model]; var sql = "SELECT COUNT(*) cnt FROM "+tblName+" WHERE "+modelPKID+" = (?)" //console.log(sql); this.query(sql, [id], function (err, results) { @@ -175,11 +174,13 @@ MsSQL.prototype.find = function (model, id, callback) { debugger; var objModel = this._models[model]; var tblName = this.tableEscaped(model); - var modelPKID = typeof objModel.primaryKey !== "undefined" && objModel.primaryKey !== null ? objModel.primaryKey : tblName + "ID"; + var modelPKID = this._pkids[model]; var sql = "SELECT * FROM "+tblName+" WHERE "+modelPKID+" = (?)"; //console.log(sql); this.query(sql, [id], function (err, results) { //debugger; + // if (!err) + // results[0].id = results[0][modelPKID]; callback(err, this.fromDatabase(model, results[0])); }.bind(this)); }; @@ -245,7 +246,7 @@ MsSQL.prototype.toDatabase = function (prop, val) { if (!val.toUTCString) { val = new Date(val); } - return '"' + dateToMsSql(val) + '"'; + return '\'' + dateToMsSql(val) + '\''; } if (prop.type.name == "Boolean") { return val ? 1 : 0; @@ -258,8 +259,12 @@ MsSQL.prototype.fromDatabase = function (model, data) { return null; } var props = this._models[model].properties; + var modelPKID = this._pkids[model]; Object.keys(data).forEach(function (key) { var val = data[key]; + if (key === modelPKID){ + data.id = val; + } if (props[key]) { if (props[key].type.name === 'Date' && val !== null) { val = new Date(val.toString().replace(/GMT.*$/, 'GMT')); @@ -301,6 +306,8 @@ MsSQL.prototype.all = function all(model, filter, callback) { return callback(err, []); } + data.id = data[this._pkids[model]]; + var objs = data.map(function (obj) { return self.fromDatabase(model, obj); }); From b3ff45115901fe1effd9397b42bb1d8009ea5ae2 Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Wed, 6 Mar 2013 00:52:07 -0800 Subject: [PATCH 009/239] update package version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 04e3cad..aa9f4c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jugglingdb-mssql", - "version": "0.0.1", + "version": "0.0.2", "description": "MsSQL adapter for JugglingDB", "main": "index.js", "dependencies": { From d1062ad233aaaf8694bd57052132d248ce723332 Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Thu, 7 Mar 2013 00:40:19 -0800 Subject: [PATCH 010/239] add tableNameID flag --- lib/mssql.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 6672a8b..b2151bb 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -25,6 +25,7 @@ exports.initialize = function initializeSchema(schema, callback) { schema.client = conn; schema.adapter = new MsSQL(schema.client); schema.adapter.schema = schema; + schema.adapter.tableNameID = true; callback(); }); }; @@ -33,13 +34,14 @@ function MsSQL(client) { this.name = "mssql"; this._models = {}; this._pkids = {}; + this._fks = {}; this.client = client; } util.inherits(MsSQL, jdb.BaseSQL); MsSQL.prototype.query = function (sql, optionsOrCallback, Callback) { - debugger; + //debugger; var hasOptions = true; var options = null; var cb = null; @@ -89,11 +91,17 @@ MsSQL.prototype.defineProperty = function (model, prop, params) { this._models[model].properties[prop] = params; }; +// MsSQL.prototype.defineForeignKey = function (className, key, cb) { +// this._fks[model] = this._fks[model] || {}; +// this._fks[model][className] = key; +// cb(null, Number); +// }; + /** * Must invoke callback(err, id) */ MsSQL.prototype.create = function (model, data, callback) { - debugger; + //debugger; var fieldsAndData = this.buildInsert(model, data); var tblName = this.tableEscaped(model); var sql = "INSERT INTO " + tblName + @@ -107,7 +115,7 @@ MsSQL.prototype.create = function (model, data, callback) { }; MsSQL.prototype.updateOrCreate = function (model, data, callback) { - debugger; + //debugger; var mssql = this; var objModel = this._models[model]; var props = objModel.properties; @@ -171,7 +179,7 @@ MsSQL.prototype.exists = function (model, id, callback) { }; MsSQL.prototype.find = function (model, id, callback) { - debugger; + //debugger; var objModel = this._models[model]; var tblName = this.tableEscaped(model); var modelPKID = this._pkids[model]; From b52545b1d5ec487754c445f4cf27aa2d0e64842c Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Thu, 7 Mar 2013 01:00:29 -0800 Subject: [PATCH 011/239] set tableNameID flag in schema settings --- lib/mssql.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index b2151bb..591fc74 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -25,7 +25,7 @@ exports.initialize = function initializeSchema(schema, callback) { schema.client = conn; schema.adapter = new MsSQL(schema.client); schema.adapter.schema = schema; - schema.adapter.tableNameID = true; + schema.adapter.tableNameID = schema.settings.tableNameID; callback(); }); }; @@ -84,7 +84,11 @@ MsSQL.prototype.query = function (sql, optionsOrCallback, Callback) { MsSQL.prototype.define = function (descr) { if (!descr.settings) descr.settings = {}; this._models[descr.model.modelName] = descr; - this._pkids[descr.model.modelName] = descr.model.modelName + "ID"; + id = "id"; + if (this.tableNameID) { + id = descr.model.modelName + "ID"; + } + this._pkids[descr.model.modelName] = id; }; MsSQL.prototype.defineProperty = function (model, prop, params) { From cd58a2d2294bfa822a517d77828453f9aa5ede89 Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Thu, 7 Mar 2013 01:01:23 -0800 Subject: [PATCH 012/239] update to minor version 0.0.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aa9f4c6..33bf876 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jugglingdb-mssql", - "version": "0.0.2", + "version": "0.0.3", "description": "MsSQL adapter for JugglingDB", "main": "index.js", "dependencies": { From 9aec44e0d3e3eb7de58f8bf5523b9612dc19f49b Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Thu, 7 Mar 2013 21:30:06 -0800 Subject: [PATCH 013/239] add 'max' condition to 'buildWhere' function --- lib/mssql.js | 191 +++++++++++++++++++++++++++------------------------ 1 file changed, 102 insertions(+), 89 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 591fc74..f5d1f74 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -227,43 +227,45 @@ function dateToMsSql(val) { } MsSQL.prototype.toDatabase = function (prop, val) { - if (val === null) { - return 'NULL'; - } - if (val.constructor.name === 'Object') { - var operator = Object.keys(val)[0] - val = val[operator]; - if (operator === 'between') { - return this.toDatabase(prop, val[0]) + - ' AND ' + - this.toDatabase(prop, val[1]); - } else if (operator == 'inq' || operator == 'nin') { - if (!(val.propertyIsEnumerable('length')) && typeof val === 'object' && typeof val.length === 'number') { //if value is array - return val.join(','); - } else { - return val; - } + if (val === null) { + return 'NULL'; + } + if (val.constructor.name === 'Object') { + var operator = Object.keys(val)[0] + val = val[operator]; + if (operator === 'between') { + return this.toDatabase(prop, val[0]) + + ' AND ' + + this.toDatabase(prop, val[1]); + } else if (operator == 'inq' || operator == 'nin') { + if (!(val.propertyIsEnumerable('length')) && typeof val === 'object' && typeof val.length === 'number') { //if value is array + return val.join(','); + } else { + return val; } + } else if (operator === "max") { + return val.field; } - if (!prop) { - return val; - } - if (prop.type.name === 'Number') { - return val; - } - if (prop.type.name === 'Date') { - if (!val) { - return 'NULL'; - } - if (!val.toUTCString) { - val = new Date(val); - } - return '\'' + dateToMsSql(val) + '\''; + } + if (!prop) { + return val; + } + if (prop.type.name === 'Number') { + return val; + } + if (prop.type.name === 'Date') { + if (!val) { + return 'NULL'; } - if (prop.type.name == "Boolean") { - return val ? 1 : 0; + if (!val.toUTCString) { + val = new Date(val); } - return val.toString(); + return '\'' + dateToMsSql(val) + '\''; + } + if (prop.type.name == "Boolean") { + return val ? 1 : 0; + } + return val.toString(); }; MsSQL.prototype.fromDatabase = function (model, data) { @@ -302,15 +304,13 @@ MsSQL.prototype.all = function all(model, filter, callback) { var props = this._models[model].properties; if (filter) { - if (filter.where) { - sql += " " + buildWhere(filter.where); + sql += " " + this.buildWhere(filter.where, props); } if (filter.order) { sql += " " + buildOrderBy(filter.order); } - } this.query(sql, function (err, data) { @@ -332,58 +332,6 @@ MsSQL.prototype.all = function all(model, filter, callback) { return sql; - function buildWhere(conds) { - var cs = []; - Object.keys(conds).forEach(function (key) { - var keyEscaped = self.escapeKey(key); - var val = self.toDatabase(props[key], conds[key]); - if (conds[key] === null) { - cs.push(keyEscaped + ' IS NULL'); - } else if (conds[key].constructor.name === 'Object') { - var condType = Object.keys(conds[key])[0]; - var sqlCond = keyEscaped; - if ((condType == 'inq' || condType == 'nin') && val.length == 0) { - cs.push(condType == 'inq' ? 0 : 1); - return true; - } - switch (condType) { - case 'gt': - sqlCond += ' > '; - break; - case 'gte': - sqlCond += ' >= '; - break; - case 'lt': - sqlCond += ' < '; - break; - case 'lte': - sqlCond += ' <= '; - break; - case 'between': - sqlCond += ' BETWEEN '; - break; - case 'inq': - sqlCond += ' IN '; - break; - case 'nin': - sqlCond += ' NOT IN '; - break; - case 'neq': - sqlCond += ' != '; - break; - } - sqlCond += (condType == 'inq' || condType == 'nin') ? '(' + val + ')' : val; - cs.push(sqlCond); - } else { - cs.push(keyEscaped + ' = ' + val); - } - }); - if (cs.length === 0) { - return ''; - } - return 'WHERE ' + cs.join(' AND '); - } - function buildOrderBy(order) { if (typeof order === 'string') order = [order]; return 'ORDER BY ' + order.join(', '); @@ -391,6 +339,71 @@ MsSQL.prototype.all = function all(model, filter, callback) { }; +MsSQL.prototype.buildWhere = function(conds, props) { + // debugger; + var self = this; + var cs = []; + Object.keys(conds).forEach(function (key) { + var keyEscaped = self.escapeKey(key); + var val = self.toDatabase(props[key], conds[key]); + if (conds[key] === null) { + cs.push(keyEscaped + ' IS NULL'); + } else if (conds[key].constructor.name === 'Object') { + var condType = Object.keys(conds[key])[0]; + var sqlCond = keyEscaped; + if ((condType == 'inq' || condType == 'nin') && val.length == 0) { + cs.push(condType == 'inq' ? 0 : 1); + return true; + } + if (condType === "max") { + var tbl = conds[key].max.from; + var subClause = conds[key].max.where; + sqlCond += " = (SELECT MAX(" + val + ") FROM " + tbl; + if (subClause) { + sqlCond += " " + self.buildWhere(subClause, props); + } + sqlCond += ")"; + cs.push(sqlCond); + return true; + } + switch (condType) { + case 'gt': + sqlCond += ' > '; + break; + case 'gte': + sqlCond += ' >= '; + break; + case 'lt': + sqlCond += ' < '; + break; + case 'lte': + sqlCond += ' <= '; + break; + case 'between': + sqlCond += ' BETWEEN '; + break; + case 'inq': + sqlCond += ' IN '; + break; + case 'nin': + sqlCond += ' NOT IN '; + break; + case 'neq': + sqlCond += ' != '; + break; + } + sqlCond += (condType == 'inq' || condType == 'nin') ? '(' + val + ')' : val; + cs.push(sqlCond); + } else { + cs.push(keyEscaped + ' = ' + val); + } + }); + if (cs.length === 0) { + return ''; + } + return 'WHERE ' + cs.join(' AND '); +}; + // MsSQL.prototype.autoupdate = function (cb) { // var self = this; // var wait = 0; @@ -590,7 +603,7 @@ MsSQL.prototype.all = function all(model, filter, callback) { // }; MsSQL.prototype.propertiesSQL = function (model) { - debugger; + // debugger; var self = this; var objModel = this._models[model]; var modelPKID = typeof objModel.primaryKey !== "undefined" && objModel.primaryKey !== null ? objModel.primaryKey : tblName + "ID"; From 742872a05d4916a9b1bbb58406c423f7740f879c Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Thu, 7 Mar 2013 21:30:23 -0800 Subject: [PATCH 014/239] upgrade version to 0.0.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 33bf876..09f3a78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jugglingdb-mssql", - "version": "0.0.3", + "version": "0.0.4", "description": "MsSQL adapter for JugglingDB", "main": "index.js", "dependencies": { From 31b3a636ac7a82cbf5d8442c6cbf3159b42eb2af Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Sat, 9 Mar 2013 13:21:54 -0800 Subject: [PATCH 015/239] add nodeunit tests --- test/mssql.js | 145 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 test/mssql.js diff --git a/test/mssql.js b/test/mssql.js new file mode 100644 index 0000000..f02e375 --- /dev/null +++ b/test/mssql.js @@ -0,0 +1,145 @@ +var jdb = require('jugglingdb'), + Schema = jdb.Schema, + commonTest = jdb.test, + db = require("../db/dbconfig"); + +var adapter = require("../"); +schemaSettings = { host:db.server, database:db.db, username:db.user, password:db.pwd }; +var schema = new Schema(adapter, schemaSettings); + +exports.mssql = function(test) { + //test.expect(5); + + schema.on("connected", function() { + + var Post, User, Passport, Log, Dog; + + User = schema.define('User', { + name: { type: String, index: true }, + email: { type: String, index: true }, + bio: Schema.Text, + approved: Boolean, + joinedAt: Date, + age: Number, + passwd: { type: String, index: true } + }); + + Dog = schema.define('Dog', { + name : { type: String, limit: 64, allowNull: false } + }); + + Log = schema.define('Log', { + ownerId : { type: Number, allowNull: true }, + name : { type: String, limit: 64, allowNull: false } + }); + + Log.belongsTo(Dog, {as: 'owner', foreignKey: 'ownerId'}); + + schema.extendModel('User', { + settings: { type: Schema.JSON }, + extra: Object + }); + + var newuser = new User({settings: {hey: 'you'}}); + //test.ok(newuser.settings); + + Post = schema.define('Post', { + title: { type: String, length: 255, index: true }, + subject: { type: String }, + content: { type: Schema.Text }, + date: { type: Date, default: function () { return new Date }, index: true }, + published: { type: Boolean, default: false, index: true }, + likes: [], + related: [RelatedPost] + }, {table: 'posts'}); + + function RelatedPost() { } + RelatedPost.prototype.someMethod = function () { + return this.parent; + }; + + Post.validateAsync('title', function (err, done) { + process.nextTick(done); + }); + + User.hasMany(Post, {as: 'posts', foreignKey: 'userId'}); + // creates instance methods: + // user.posts(conds) + // user.posts.build(data) // like new Post({userId: user.id}); + // user.posts.create(data) // build and save + // user.posts.find + + // User.hasOne('latestPost', {model: Post, foreignKey: 'postId'}); + + // User.hasOne(Post, {as: 'latestPost', foreignKey: 'latestPostId'}); + // creates instance methods: + // user.latestPost() + // user.latestPost.build(data) + // user.latestPost.create(data) + + Post.belongsTo(User, {as: 'author', foreignKey: 'userId'}); + // creates instance methods: + // post.author(callback) -- getter when called with function + // post.author() -- sync getter when called without params + // post.author(user) -- setter when called with object + + Passport = schema.define('Passport', { + number: String + }); + + Passport.belongsTo(User, {as: 'owner', foreignKey: 'ownerId'}); + User.hasMany(Passport, {as: 'passports', foreignKey: 'ownerId'}); + + var user = new User; + + //test.ok(User instanceof Function); + + // class methods + //test.ok(User.find instanceof Function); + //test.ok(User.create instanceof Function); + + // instance methods + //test.ok(user.save instanceof Function); + schema.automigrate(function (err) { + if (err) { + console.log('Error while migrating'); + console.log(err); + } else { + //test.done(); + } + }); + }); +} + + +//demonstrate that msnodesql doesn't choke on '\r\n' or the GO keyword +exports.goTest = function() { + + var mssql = require("msnodesql"); + + var conn_str = "Driver={SQL Server Native Client 11.0};Server="+db.server+";"; + + //if we have a username and password then we use a credential connection string + if (db.user && db.db) { + conn_str += "UID="+db.user+";PWD="+db.pwd+";Database={"+db.db+"};" + } else { + conn_str += trusted_str + } + + mssql.open(conn_str, function(err, conn){ + if (err) + throw err + + conn.query("\r\nSELECT * FROM Passport\r\nGO\r\nSELECT * FROM Log\r\nGO\r\n", function(err, results) { + console.log(results.length); + console.log(results); + }); + + }); + +} + + +exports.mssql(null); +//exports.goTest(); +//commonTest(module.exports, schema); \ No newline at end of file From 60874a32499d770026f706831e8dfb6cac74d839 Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Sat, 9 Mar 2013 13:24:56 -0800 Subject: [PATCH 016/239] implement automigrate, createTable, dropTable, createIndexes. Better support for custom primaryKeys. General updates throughout. --- lib/mssql.js | 770 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 449 insertions(+), 321 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index f5d1f74..70b6490 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -3,6 +3,9 @@ var mssql = require("msnodesql"); var jdb = require("jugglingdb"); var util = require("util"); +var name = "mssql"; + +exports.name = name; exports.initialize = function initializeSchema(schema, callback) { //need msnodesql installed, and a host server and a database if (!mssql || !schema.settings.host || !schema.settings.database){ return; } @@ -31,15 +34,17 @@ exports.initialize = function initializeSchema(schema, callback) { }; function MsSQL(client) { - this.name = "mssql"; + this.name = name; this._models = {}; this._pkids = {}; - this._fks = {}; + this._idxNames = {}; this.client = client; } util.inherits(MsSQL, jdb.BaseSQL); +MsSQL.newline = "\r\n"; + MsSQL.prototype.query = function (sql, optionsOrCallback, Callback) { //debugger; var hasOptions = true; @@ -48,9 +53,12 @@ MsSQL.prototype.query = function (sql, optionsOrCallback, Callback) { if (typeof optionsOrCallback === "function") { hasOptions = false; cb = optionsOrCallback; + // console.log(sql); } else { options = optionsOrCallback; cb = Callback; + // console.log(options); + // console.log(sql); } if (!this.schema.connected) { return this.schema.on('connected', function () { @@ -81,24 +89,42 @@ MsSQL.prototype.query = function (sql, optionsOrCallback, Callback) { }; +// MsSQL.prototype.command = function (sql, callback) { +// return this.query(sql, callback); +// }; + +//params +// descr = { +// model: ... +// properties: ... +// settings: ... +// } MsSQL.prototype.define = function (descr) { - if (!descr.settings) descr.settings = {}; - this._models[descr.model.modelName] = descr; - id = "id"; - if (this.tableNameID) { - id = descr.model.modelName + "ID"; + if (!descr.settings) descr.settings = {}; + + this._models[descr.model.modelName] = descr; + + //default pkid is "id" + var id = "id"; + //override the default with another convention, 'TableName'ID, if defined in the adapter settings + if (this.tableNameID) { + id = descr.model.modelName + "ID"; + } + //override both defaults if a primaryKey is specified in a property + Object.keys(descr.properties).forEach(function(propName) { + var propVal = descr.properties[propName]; + if (typeof propVal === "object" && propVal.primaryKey) { + return id = propName; } - this._pkids[descr.model.modelName] = id; -}; + }); + this._pkids[descr.model.modelName] = id; -MsSQL.prototype.defineProperty = function (model, prop, params) { - this._models[model].properties[prop] = params; + //track database index names for this model + this._idxNames[descr.model.modelName] = []; }; -// MsSQL.prototype.defineForeignKey = function (className, key, cb) { -// this._fks[model] = this._fks[model] || {}; -// this._fks[model][className] = key; -// cb(null, Number); +// MsSQL.prototype.defineProperty = function (model, prop, params) { +// this._models[model].properties[prop] = params; // }; /** @@ -108,93 +134,106 @@ MsSQL.prototype.create = function (model, data, callback) { //debugger; var fieldsAndData = this.buildInsert(model, data); var tblName = this.tableEscaped(model); - var sql = "INSERT INTO " + tblName + - " (" + fieldsAndData.fields + ")" + - " VALUES (" + fieldsAndData.paramPlaceholders + ")" + - " SELECT IDENT_CURRENT('" + tblName + "') AS insertId"; + var sql = "INSERT INTO [dbo].[" + tblName + "] (" + fieldsAndData.fields + ")" + MsSQL.newline; + sql += "VALUES (" + fieldsAndData.paramPlaceholders + ");" + MsSQL.newline; + sql += "SELECT IDENT_CURRENT('" + tblName + "') AS insertId;"; this.query(sql, fieldsAndData.params, function (err, results) { - callback(err, results[0].insertId); + if (err) return callback(err); + //msnodesql will execute the callback for each statement that get's executed, we're only interested in the one that returns with the insertId + if (results[0].insertId) { + callback(null, results[0].insertId); + } }); }; MsSQL.prototype.updateOrCreate = function (model, data, callback) { //debugger; - var mssql = this; - var objModel = this._models[model]; - var props = objModel.properties; + var self = this; + var props = this._models[model].properties; var tblName = this.tableEscaped(model); var modelPKID = this._pkids[model]; - var id = 0; + var id = data[modelPKID]; var fieldNames = []; var fieldValues = []; var fieldValuesPlaceholders = []; var combined = []; Object.keys(data).forEach(function (key) { - if (props[key] || key === modelPKID) { - if (key === modelPKID) { - id = data[key]; - } else { - fieldNames.push(key); - fieldValues.push(mssql.toDatabase(props[key], data[key])); + if (props[key]) { + if (key !== modelPKID) { + fieldNames.push("[" + key + "]"); + fieldValues.push(self.toDatabase(props[key], data[key])); fieldValuesPlaceholders.push("(?)"); combined.push(key + "=(?)"); } } }); var sql = ""; - if (id > 0) { - sql = "UPDATE " + tblName; - sql += " SET " + combined.join(); - sql += " WHERE " + modelPKID + " = (?)"; - sql += "SELECT " + id + " AS pkid"; + if (id > 0) { //assume that since we've been given an id that they're trying to update this object + sql = "UPDATE [dbo].[" + tblName + "]" + MsSQL.newline; + sql += "SET " + combined.join() + MsSQL.newline; + sql += "WHERE [" + modelPKID + "] = (?);" + MsSQL.newline; + sql += "SELECT " + id + " AS pkid;"; fieldValues.push(id); } else { - sql = "INSERT INTO " + tblName; - sql += " (" + fieldNames.join() + ")"; - sql += " VALUES (" + fieldValuesPlaceholders.join() + ")"; - sql += " SELECT IDENT_CURRENT('" + tblName + "') AS pkid"; + sql = "INSERT INTO [dbo].[" + tblName + "] (" + fieldNames.join() + ")" + MsSQL.newline; + sql += "VALUES (" + fieldValuesPlaceholders.join() + ");" + MsSQL.newline; + sql += "SELECT IDENT_CURRENT('" + tblName + "') AS pkid;"; } this.query(sql, fieldValues, function (err, results) { - if (!err) { + if (err) return callback(err); + //msnodesql will execute the callback for each statement that get's executed, we're only interested in the one that returns with the pkid + if (results[0].pkid) { data[modelPKID] = results[0].pkid; - data.id = results[0].pkid; + callback(err, data); } - callback(err, data); }); }; -MsSQL.prototype.updateAttributes = function updateAttrs(model, id, data, cb) { - data[this._pkids[model]] = id; - this.save(model, data, cb); +//redundant, same functionality as "updateOrCreate" right now. Maybe in the future some validation will happen here. +MsSQL.prototype.save = function (model, data, callback) { + this.updateOrCreate(model, data, callback); +}; + +MsSQL.prototype.updateAttributes = function (model, id, data, cb) { + var tblName = this.tableEscaped(model); + var modelPKID = this._pkids[model]; + data[modelPKID] = id; + this.exists(model, id, function(err, yn) { + if (err) { + console.log(err); + return cb("An error occurred when checking for the existance of this record"); + } + if (yn) { + //only call this after verifying that the record exists, we don't want to create it if it doesn't. + return this.updateOrCreate(model, data, cb); + } + return callback("A " + tblName + " doesn't exist with a " + modelPKID + " of " + id , id); + }); }; MsSQL.prototype.exists = function (model, id, callback) { - var objModel = this._models[model]; - var tblName = this.tableEscaped(model); - var modelPKID = this._pkids[model]; - var sql = "SELECT COUNT(*) cnt FROM "+tblName+" WHERE "+modelPKID+" = (?)" - //console.log(sql); - this.query(sql, [id], function (err, results) { - if (err) return callback(err); - callback(null, results[0].cnt >= 1); - }); + var tblName = this.tableEscaped(model); + var modelPKID = this._pkids[model]; + var sql = "SELECT COUNT(*) cnt FROM [dbo].[" + tblName + "] WHERE [" + modelPKID + "] = (?)" + //console.log(sql); + this.query(sql, [id], function (err, results) { + if (err) return callback(err); + callback(null, results[0].cnt >= 1); + }); }; MsSQL.prototype.find = function (model, id, callback) { - //debugger; - var objModel = this._models[model]; - var tblName = this.tableEscaped(model); - var modelPKID = this._pkids[model]; - var sql = "SELECT * FROM "+tblName+" WHERE "+modelPKID+" = (?)"; - //console.log(sql); - this.query(sql, [id], function (err, results) { - //debugger; - // if (!err) - // results[0].id = results[0][modelPKID]; - callback(err, this.fromDatabase(model, results[0])); - }.bind(this)); + //debugger; + var tblName = this.tableEscaped(model); + var modelPKID = this._pkids[model]; + var sql = "SELECT * FROM [dbo].[" + tblName + "] WHERE [" + modelPKID + "] = (?)"; + //console.log(sql); + this.query(sql, [id], function (err, results) { + if (err) return callback(err); + callback(null, this.fromDatabase(model, results[0])); + }.bind(this)); }; MsSQL.prototype.buildInsert = function (model, data) { @@ -202,9 +241,12 @@ MsSQL.prototype.buildInsert = function (model, data) { var paramPlaceholders = []; var params = []; var props = this._models[model].properties; + var modelPKID = this._pkids[model]; + //remove the pkid column if it's in the data, since we're going to insert a new record, not update an existing one. + delete data[modelPKID]; Object.keys(data).forEach(function (key) { if (props[key]) { - insertIntoFields.push(this.escapeKey(key)); + insertIntoFields.push("[" + key + "]"); paramPlaceholders.push("(?)"); params.push(this.toDatabase(props[key], data[key])); } @@ -213,6 +255,7 @@ MsSQL.prototype.buildInsert = function (model, data) { return { fields:insertIntoFields.join(), paramPlaceholders:paramPlaceholders.join(), params:params }; } +//unchanged from MySql adapter, credit to dgsan function dateToMsSql(val) { return val.getUTCFullYear() + '-' + fillZeros(val.getUTCMonth() + 1) + '-' + @@ -269,24 +312,23 @@ MsSQL.prototype.toDatabase = function (prop, val) { }; MsSQL.prototype.fromDatabase = function (model, data) { - if (!data) { - return null; + if (!data) { + return null; + } + //create an "id" property in the data for backwards compatibility with juggling-db + data.id = data[this._pkids[model]]; + var props = this._models[model].properties; + //look for date values in the data, convert them from the database to a javascript date object + Object.keys(data).forEach(function (key) { + var val = data[key]; + if (props[key]) { + if (props[key].type.name === 'Date' && val !== null) { + val = new Date(val.toString().replace(/GMT.*$/, 'GMT')); + } } - var props = this._models[model].properties; - var modelPKID = this._pkids[model]; - Object.keys(data).forEach(function (key) { - var val = data[key]; - if (key === modelPKID){ - data.id = val; - } - if (props[key]) { - if (props[key].type.name === 'Date' && val !== null) { - val = new Date(val.toString().replace(/GMT.*$/, 'GMT')); - } - } - data[key] = val; - }); - return data; + data[key] = val; + }); + return data; }; MsSQL.prototype.escapeName = function (name) { @@ -297,46 +339,43 @@ MsSQL.prototype.escapeKey = function (key) { return key; }; -MsSQL.prototype.all = function all(model, filter, callback) { - - var sql = "SELECT * FROM " + this.tableEscaped(model); - var self = this; - var props = this._models[model].properties; - - if (filter) { - if (filter.where) { - sql += " " + this.buildWhere(filter.where, props); - } +MsSQL.prototype.all = function (model, filter, callback) { + var sql = "SELECT * FROM [dbo].[" + this.tableEscaped(model) + "]" + MsSQL.newline; + var self = this; + var props = this._models[model].properties; - if (filter.order) { - sql += " " + buildOrderBy(filter.order); - } + if (filter) { + if (filter.where) { + sql += this.buildWhere(filter.where, props) + MsSQL.newline; } - this.query(sql, function (err, data) { - if (err) { - return callback(err, []); - } + if (filter.order) { + sql += this.buildOrderBy(filter.order) + MsSQL.newline; + } + } - data.id = data[this._pkids[model]]; + this.query(sql, function (err, data) { + if (err) return callback(err); - var objs = data.map(function (obj) { - return self.fromDatabase(model, obj); - }); - if (filter && filter.include) { - this._models[model].model.include(objs, filter.include, callback); - } else { - callback(null, objs); - } - }.bind(this)); + //convert database types to js types + data = self.fromDatabase(model, data); - return sql; - - function buildOrderBy(order) { - if (typeof order === 'string') order = [order]; - return 'ORDER BY ' + order.join(', '); + //check for eager loading relationships + if (filter && filter.include) { + this._models[model].model.include(data, filter.include, callback); + } else { + callback(null, data); } + }.bind(this)); + + return sql; +}; +MsSQL.prototype.buildOrderBy = function (order) { + if (typeof order === 'string') { + order = [order]; + } + return 'ORDER BY ' + order.join(','); }; MsSQL.prototype.buildWhere = function(conds, props) { @@ -454,248 +493,337 @@ MsSQL.prototype.buildWhere = function(conds, props) { // } // }; -// MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, checkOnly) { -// var self = this; -// var m = this._models[model]; -// var propNames = Object.keys(m.properties).filter(function (name) { -// return !!m.properties[name]; -// }); -// var indexNames = m.settings.indexes ? Object.keys(m.settings.indexes).filter(function (name) { -// return !!m.settings.indexes[name]; -// }) : []; -// var sql = []; -// var ai = {}; - -// if (actualIndexes) { -// actualIndexes.forEach(function (i) { -// var name = i.Key_name; -// if (!ai[name]) { -// ai[name] = { -// info: i, -// columns: [] -// }; -// } -// ai[name].columns[i.Seq_in_index - 1] = i.Column_name; -// }); -// } -// var aiNames = Object.keys(ai); - -// // change/add new fields -// propNames.forEach(function (propName) { -// if (propName === 'id') return; -// var found; -// actualFields.forEach(function (f) { -// if (f.Field === propName) { -// found = f; -// } -// }); +MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, checkOnly) { + var self = this; + var m = this._models[model]; + var propNames = Object.keys(m.properties).filter(function (name) { + return !!m.properties[name]; + }); + var indexNames = m.settings.indexes ? Object.keys(m.settings.indexes).filter(function (name) { + return !!m.settings.indexes[name]; + }) : []; + var sql = []; + var ai = {}; + + if (actualIndexes) { + actualIndexes.forEach(function (i) { + var name = i.Key_name; + if (!ai[name]) { + ai[name] = { + info: i, + columns: [] + }; + } + ai[name].columns[i.Seq_in_index - 1] = i.Column_name; + }); + } + var aiNames = Object.keys(ai); + + // change/add new fields + propNames.forEach(function (propName) { + if (propName === 'id') return; + var found; + actualFields.forEach(function (f) { + if (f.Field === propName) { + found = f; + } + }); -// if (found) { -// actualize(propName, found); -// } else { -// sql.push('ADD COLUMN `' + propName + '` ' + self.propertySettingsSQL(model, propName)); -// } -// }); + if (found) { + actualize(propName, found); + } else { + sql.push('ADD COLUMN `' + propName + '` ' + self.propertySettingsSQL(model, propName)); + } + }); -// // drop columns -// actualFields.forEach(function (f) { -// var notFound = !~propNames.indexOf(f.Field); -// if (f.Field === 'id') return; -// if (notFound || !m.properties[f.Field]) { -// sql.push('DROP COLUMN `' + f.Field + '`'); -// } -// }); + // drop columns + actualFields.forEach(function (f) { + var notFound = !~propNames.indexOf(f.Field); + if (f.Field === 'id') return; + if (notFound || !m.properties[f.Field]) { + sql.push('DROP COLUMN `' + f.Field + '`'); + } + }); -// // remove indexes -// aiNames.forEach(function (indexName) { -// if (indexName === 'id' || indexName === 'PRIMARY') return; -// if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || m.properties[indexName] && !m.properties[indexName].index) { -// sql.push('DROP INDEX `' + indexName + '`'); -// } else { -// // first: check single (only type and kind) -// if (m.properties[indexName] && !m.properties[indexName].index) { -// // TODO -// return; -// } -// // second: check multiple indexes -// var orderMatched = true; -// if (indexNames.indexOf(indexName) !== -1) { -// m.settings.indexes[indexName].columns.split(/,\s*/).forEach(function (columnName, i) { -// if (ai[indexName].columns[i] !== columnName) orderMatched = false; -// }); -// } -// if (!orderMatched) { -// sql.push('DROP INDEX `' + indexName + '`'); -// delete ai[indexName]; -// } -// } -// }); + // remove indexes + aiNames.forEach(function (indexName) { + if (indexName === 'id' || indexName === 'PRIMARY') return; + if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || m.properties[indexName] && !m.properties[indexName].index) { + sql.push('DROP INDEX `' + indexName + '`'); + } else { + // first: check single (only type and kind) + if (m.properties[indexName] && !m.properties[indexName].index) { + // TODO + return; + } + // second: check multiple indexes + var orderMatched = true; + if (indexNames.indexOf(indexName) !== -1) { + m.settings.indexes[indexName].columns.split(/,\s*/).forEach(function (columnName, i) { + if (ai[indexName].columns[i] !== columnName) orderMatched = false; + }); + } + if (!orderMatched) { + sql.push('DROP INDEX `' + indexName + '`'); + delete ai[indexName]; + } + } + }); -// // add single-column indexes -// propNames.forEach(function (propName) { -// var i = m.properties[propName].index; -// if (!i) { -// return; -// } -// var found = ai[propName] && ai[propName].info; -// if (!found) { -// var type = ''; -// var kind = ''; -// if (i.type) { -// type = 'USING ' + i.type; -// } -// if (i.kind) { -// // kind = i.kind; -// } -// if (kind && type) { -// sql.push('ADD ' + kind + ' INDEX `' + propName + '` (`' + propName + '`) ' + type); -// } else { -// sql.push('ADD ' + kind + ' INDEX `' + propName + '` ' + type + ' (`' + propName + '`) '); -// } -// } -// }); + // add single-column indexes + propNames.forEach(function (propName) { + var i = m.properties[propName].index; + if (!i) { + return; + } + var found = ai[propName] && ai[propName].info; + if (!found) { + var type = ''; + var kind = ''; + if (i.type) { + type = 'USING ' + i.type; + } + if (i.kind) { + // kind = i.kind; + } + if (kind && type) { + sql.push('ADD ' + kind + ' INDEX `' + propName + '` (`' + propName + '`) ' + type); + } else { + sql.push('ADD ' + kind + ' INDEX `' + propName + '` ' + type + ' (`' + propName + '`) '); + } + } + }); -// // add multi-column indexes -// indexNames.forEach(function (indexName) { -// var i = m.settings.indexes[indexName]; -// var found = ai[indexName] && ai[indexName].info; -// if (!found) { -// var type = ''; -// var kind = ''; -// if (i.type) { -// type = 'USING ' + i.type; -// } -// if (i.kind) { -// kind = i.kind; -// } -// if (kind && type) { -// sql.push('ADD ' + kind + ' INDEX `' + indexName + '` (' + i.columns + ') ' + type); -// } else { -// sql.push('ADD ' + kind + ' INDEX ' + type + ' `' + indexName + '` (' + i.columns + ')'); -// } -// } -// }); + // add multi-column indexes + indexNames.forEach(function (indexName) { + var i = m.settings.indexes[indexName]; + var found = ai[indexName] && ai[indexName].info; + if (!found) { + var type = ''; + var kind = ''; + if (i.type) { + type = 'USING ' + i.type; + } + if (i.kind) { + kind = i.kind; + } + if (kind && type) { + sql.push('ADD ' + kind + ' INDEX `' + indexName + '` (' + i.columns + ') ' + type); + } else { + sql.push('ADD ' + kind + ' INDEX ' + type + ' `' + indexName + '` (' + i.columns + ')'); + } + } + }); -// if (sql.length) { -// var query = 'ALTER TABLE ' + self.tableEscaped(model) + ' ' + sql.join(',\n'); -// if (checkOnly) { -// done(null, true, {statements: sql, query: query}); -// } else { -// this.query(query, done); -// } -// } else { -// done(); -// } + if (sql.length) { + var query = 'ALTER TABLE ' + self.tableEscaped(model) + ' ' + sql.join(',' + MsSQL.newline); + if (checkOnly) { + done(null, true, {statements: sql, query: query}); + } else { + this.query(query, done); + } + } else { + done(); + } -// function actualize(propName, oldSettings) { -// var newSettings = m.properties[propName]; -// if (newSettings && changed(newSettings, oldSettings)) { -// sql.push('CHANGE COLUMN `' + propName + '` `' + propName + '` ' + self.propertySettingsSQL(model, propName)); -// } -// } + function actualize(propName, oldSettings) { + var newSettings = m.properties[propName]; + if (newSettings && changed(newSettings, oldSettings)) { + sql.push('CHANGE COLUMN `' + propName + '` `' + propName + '` ' + self.propertySettingsSQL(model, propName)); + } + } -// function changed(newSettings, oldSettings) { -// if (oldSettings.Null === 'YES' && (newSettings.allowNull === false || newSettings.null === false)) return true; -// if (oldSettings.Null === 'NO' && !(newSettings.allowNull === false || newSettings.null === false)) return true; -// if (oldSettings.Type.toUpperCase() !== datatype(newSettings)) return true; -// return false; -// } -// }; + function changed(newSettings, oldSettings) { + if (oldSettings.Null === 'YES' && (newSettings.allowNull === false || newSettings.null === false)) return true; + if (oldSettings.Null === 'NO' && !(newSettings.allowNull === false || newSettings.null === false)) return true; + if (oldSettings.Type.toUpperCase() !== datatype(newSettings)) return true; + return false; + } +}; MsSQL.prototype.propertiesSQL = function (model) { - // debugger; - var self = this; - var objModel = this._models[model]; - var modelPKID = typeof objModel.primaryKey !== "undefined" && objModel.primaryKey !== null ? objModel.primaryKey : tblName + "ID"; - - var sql = ["'"+modelPKID+"' INT IDENTITY PRIMARY KEY"]; - Object.keys(this._models[model].properties).forEach(function (prop) { - if (prop === modelPKID) { - return; - } - sql.push("'" + prop + "'" + self.propertySettingsSQL(model, prop)); - }); - // Declared in model index property indexes. - Object.keys(this._models[model].properties).forEach(function (prop) { - var i = self._models[model].properties[prop].index; - if (i) { - sql.push(self.singleIndexSettingsSQL(model, prop)); - } - }); - // Settings might not have an indexes property. - var dxs = this._models[model].settings.indexes; - if(dxs){ - Object.keys(this._models[model].settings.indexes).forEach(function(prop){ - sql.push(self.indexSettingsSQL(model, prop)); - }); + // debugger; + var self = this; + var objModel = this._models[model]; + var modelPKID = this._pkids[model]; + + var sql = ["[" + modelPKID + "] [int] IDENTITY(1,1) NOT NULL"]; + Object.keys(this._models[model].properties).forEach(function (prop) { + if (prop === modelPKID) { + return; } - return sql.join(',\n '); + sql.push("[" + prop + "] " + self.propertySettingsSQL(model, prop)); + }); + var joinedSql = sql.join("," + MsSQL.newline + " "); + var cmd = "PRIMARY KEY CLUSTERED" + MsSQL.newline + "(" + MsSQL.newline; + cmd += " [" + modelPKID + "] ASC" + MsSQL.newline; + cmd += ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]" + + joinedSql += "," + MsSQL.newline + cmd; + + return joinedSql; }; -MsSQL.prototype.singleIndexSettingsSQL = function (model, prop) { - // Recycled from alterTable single indexes above, more or less. - var i = this._models[model].properties[prop].index; - var type = ''; - var kind = ''; - if (i.type) { - type = 'USING ' + i.type; - } - if (i.kind) { - kind = i.kind; - } - if (kind && type) { - return (kind + ' INDEX `' + prop + '` (`' + prop + '`) ' + type); - } else { - return (kind + ' INDEX `' + prop + '` ' + type + ' (`' + prop + '`) '); - } +MsSQL.prototype.singleIndexSettingsSQL = function (model, prop, add) { + // Recycled from alterTable single indexes above, more or less. + var tblName = this.tableEscaped(model); + var i = this._models[model].properties[prop].index; + var type = 'ASC'; + var kind = 'NONCLUSTERED'; + var unique = false; + if (i.type) { + type = i.type; + } + if (i.kind) { + kind = i.kind; + } + if (i.unique) { + unique = true; + } + var name = prop + "_" + kind + "_" + type + "_idx" + if (i.name) { + name = i.name; + } + this._idxNames[model].push[name]; + var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON [dbo].[" + tblName + "]" + MsSQL.newline; + cmd += "(" + MsSQL.newline; + cmd += " [" + prop + "] " + type; + cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];" + MsSQL.newline; + return cmd; }; MsSQL.prototype.indexSettingsSQL = function (model, prop) { - // Recycled from alterTable multi-column indexes above, more or less. - var i = this._models[model].settings.indexes[prop]; - var type = ''; - var kind = ''; - if (i.type) { - type = 'USING ' + i.type; - } - if (i.kind) { - kind = i.kind; - } - if (kind && type) { - return (kind + ' INDEX `' + prop + '` (' + i.columns + ') ' + type); - } else { - return (kind + ' INDEX ' + type + ' `' + prop + '` (' + i.columns + ')'); - } + // Recycled from alterTable multi-column indexes above, more or less. + var tblName = this.tableEscaped(model); + var i = this._models[model].settings.indexes[prop]; + var type = 'ASC'; + var kind = 'NONCLUSTERED'; + var unique = false; + if (i.type) { + type = i.type; + } + if (i.kind) { + kind = i.kind; + } + if (i.unique) { + unique = true; + } + var splitcolumns = i.columns.split(","); + var columns = []; + var name = ""; + splitcolumns.forEach(function(elem, ind) { + var trimmed = elem.trim(); + name += trimmed + "_"; + trimmed = "[" + trimmed + "] " + type; + columns.push(trimmed); + }); + + name += kind + "_" + type + "_idx" + this._idxNames[model].push[name]; + + var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON [dbo].[" + tblName + "]" + MsSQL.newline; + cmd += "(" + MsSQL.newline; + cmd += columns.join("," + MsSQL.newline); + cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];" + MsSQL.newline; + return cmd; }; MsSQL.prototype.propertySettingsSQL = function (model, prop) { - var p = this._models[model].properties[prop]; - return datatype(p) + ' ' + - (p.allowNull === false || p['null'] === false ? 'NOT NULL' : 'NULL'); + var p = this._models[model].properties[prop]; + return datatype(p) + ' ' + + (p.allowNull === false || p['null'] === false ? 'NOT NULL' : 'NULL'); +}; + +MsSQL.prototype.automigrate = function (cb) { + var self = this; + var wait = 0; + Object.keys(this._models).forEach(function (model) { + wait += 1; + self.dropTable(model, function (err) { + // console.log('drop', model); + if (err) console.log(err); + self.createTable(model, function (err) { + // console.log('create', model); + if (err) console.log(err); + done(); + }); + }); + }); + if (wait === 0) cb(); + + function done() { + if (--wait === 0 && cb) { + cb(); + } + } +}; + +MsSQL.prototype.dropTable = function (model, cb) { + var tblName = this.tableEscaped(model); + var cmd ="IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[" + tblName + "]') AND type in (N'U'))"; + cmd += MsSQL.newline + "BEGIN" + MsSQL.newline; + cmd += " DROP TABLE [dbo].[" + tblName + "]"; + cmd += MsSQL.newline + "END"; + //console.log(cmd); + this.command(cmd, cb); +}; + +MsSQL.prototype.createTable = function (model, cb) { + var tblName = this.tableEscaped(model); + var cmd = "SET ANSI_NULLS ON;" + MsSQL.newline + "SET QUOTED_IDENTIFIER ON;" + MsSQL.newline + "SET ANSI_PADDING ON;" + MsSQL.newline; + cmd += "IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[" + tblName + "]') AND type in (N'U'))" + MsSQL.newline + "BEGIN" + MsSQL.newline; + cmd += "CREATE TABLE [dbo].[" + this.tableEscaped(model) + "] ("; + cmd += MsSQL.newline + " " + this.propertiesSQL(model) + MsSQL.newline; + cmd += ") ON [PRIMARY]" + MsSQL.newline + "END;" + MsSQL.newline; + //console.log(cmd); + cmd += this.createIndexes(model); + this.command(cmd, cb); }; +MsSQL.prototype.createIndexes = function(model) { + var self = this; + var sql = []; + // Declared in model index property indexes. + Object.keys(this._models[model].properties).forEach(function (prop) { + var i = self._models[model].properties[prop].index; + if (i) { + sql.push(self.singleIndexSettingsSQL(model, prop)); + } + }); + + // Settings might not have an indexes property. + var dxs = this._models[model].settings.indexes; + if(dxs) { + Object.keys(this._models[model].settings.indexes).forEach(function(prop){ + sql.push(self.indexSettingsSQL(model, prop)); + }); + } + + return sql.join(MsSQL.newline); +} + function datatype(p) { var dt = ''; switch (p.type.name) { default: case 'String': case 'JSON': - dt = 'VARCHAR(' + (p.limit || 255) + ')'; + dt = '[varchar](' + (p.limit || 255) + ')'; break; case 'Text': - dt = 'TEXT'; + dt = '[text]'; break; case 'Number': - dt = 'INT'; + dt = '[int]'; break; case 'Date': - dt = 'DATETIME'; + dt = '[datetime]'; break; case 'Boolean': - dt = 'TINYINT(1)'; + dt = '[bit]'; break; case 'Point': - dt = 'POINT'; + dt = '[float]'; break; } return dt; From 360275c86ecacd521d113e0b3522942eb5e10dec Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Sat, 9 Mar 2013 13:25:32 -0800 Subject: [PATCH 017/239] upgrade to minor version 1 --- .gitignore | 1 + package.json | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index b1e03dc..9672873 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,6 @@ results *.sublime* node_modules/ +db/ npm-debug.log diff --git a/package.json b/package.json index 09f3a78..29bc693 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,16 @@ { "name": "jugglingdb-mssql", - "version": "0.0.4", + "version": "0.1.0", "description": "MsSQL adapter for JugglingDB", "main": "index.js", "dependencies": { - "jugglingdb": "latest", + "jugglingdb": "~0.2.0-22", "util":"~0.4.9", "msnodesql":"~0.2.1" }, + "devDependencies": { + "nodeunit":"~0.7.4" + }, "repository": { "type": "git", "url": "https://github.com/code-vicar/jugglingdb-mssql.git" From 4669312e0b691a73afb2e33d4ca287b13c9fe7e0 Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Thu, 14 Mar 2013 11:48:05 -0700 Subject: [PATCH 018/239] fix some issues with updateAttributes, and saving --- lib/mssql.js | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 70b6490..52b339b 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -61,13 +61,13 @@ MsSQL.prototype.query = function (sql, optionsOrCallback, Callback) { // console.log(sql); } if (!this.schema.connected) { - return this.schema.on('connected', function () { - if (hasOptions) { - this.query(sql, options, cb); - } else { - this.query(sql, cb); - } - }.bind(this)); + return this.schema.on('connected', function () { + if (hasOptions) { + this.query(sql, options, cb); + } else { + this.query(sql, cb); + } + }.bind(this)); } var client = this.client; var time = Date.now(); @@ -141,8 +141,8 @@ MsSQL.prototype.create = function (model, data, callback) { this.query(sql, fieldsAndData.params, function (err, results) { if (err) return callback(err); //msnodesql will execute the callback for each statement that get's executed, we're only interested in the one that returns with the insertId - if (results[0].insertId) { - callback(null, results[0].insertId); + if (results.insertId) { + callback(null, results.insertId); } }); }; @@ -153,6 +153,7 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { var props = this._models[model].properties; var tblName = this.tableEscaped(model); var modelPKID = this._pkids[model]; + //get the correct id of the item using the pkid that they specified var id = data[modelPKID]; var fieldNames = []; var fieldValues = []; @@ -160,7 +161,8 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { var combined = []; Object.keys(data).forEach(function (key) { if (props[key]) { - if (key !== modelPKID) { + //check for the "id" key also, for backwards compatibility with the jugglingdb hardcoded id system + if (key !== "id" && key !== modelPKID) { fieldNames.push("[" + key + "]"); fieldValues.push(self.toDatabase(props[key], data[key])); fieldValuesPlaceholders.push("(?)"); @@ -184,8 +186,9 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { this.query(sql, fieldValues, function (err, results) { if (err) return callback(err); //msnodesql will execute the callback for each statement that get's executed, we're only interested in the one that returns with the pkid - if (results[0].pkid) { - data[modelPKID] = results[0].pkid; + if (results.pkid) { + data[modelPKID] = results.pkid; + data.id = results.pkid; callback(err, data); } }); @@ -197,9 +200,14 @@ MsSQL.prototype.save = function (model, data, callback) { }; MsSQL.prototype.updateAttributes = function (model, id, data, cb) { + var self = this; var tblName = this.tableEscaped(model); var modelPKID = this._pkids[model]; - data[modelPKID] = id; + //jugglingdb abstract class may have sent up a null value for this id if we aren't using the standard "id" name for the pkid. + // if that is the case then set the id to the correct value from the data using the actual pkid name. + if (id === null) { + id = data[modelPKID]; + } this.exists(model, id, function(err, yn) { if (err) { console.log(err); @@ -207,9 +215,9 @@ MsSQL.prototype.updateAttributes = function (model, id, data, cb) { } if (yn) { //only call this after verifying that the record exists, we don't want to create it if it doesn't. - return this.updateOrCreate(model, data, cb); + return self.updateOrCreate(model, data, cb); } - return callback("A " + tblName + " doesn't exist with a " + modelPKID + " of " + id , id); + return cb("A " + tblName + " doesn't exist with a " + modelPKID + " of " + id , id); }); }; @@ -244,6 +252,8 @@ MsSQL.prototype.buildInsert = function (model, data) { var modelPKID = this._pkids[model]; //remove the pkid column if it's in the data, since we're going to insert a new record, not update an existing one. delete data[modelPKID]; + //delete the hardcoded id property that jugglindb automatically creates + delete data.id Object.keys(data).forEach(function (key) { if (props[key]) { insertIntoFields.push("[" + key + "]"); @@ -493,6 +503,8 @@ MsSQL.prototype.buildWhere = function(conds, props) { // } // }; + +//not working yet MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, checkOnly) { var self = this; var m = this._models[model]; From 4918f90e57834d813f77373cfdc3e467d200c30e Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Thu, 14 Mar 2013 11:48:29 -0700 Subject: [PATCH 019/239] upgrade to version 0.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 29bc693..d8a3721 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jugglingdb-mssql", - "version": "0.1.0", + "version": "0.1.1", "description": "MsSQL adapter for JugglingDB", "main": "index.js", "dependencies": { From db5aef12225eac081d0972b8ff9587d30a4c8175 Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Thu, 14 Mar 2013 12:55:57 -0700 Subject: [PATCH 020/239] don't wrap nulls in strings --- lib/mssql.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 52b339b..75717e1 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -281,7 +281,8 @@ function dateToMsSql(val) { MsSQL.prototype.toDatabase = function (prop, val) { if (val === null) { - return 'NULL'; + // return 'NULL'; + return null; } if (val.constructor.name === 'Object') { var operator = Object.keys(val)[0] @@ -308,7 +309,8 @@ MsSQL.prototype.toDatabase = function (prop, val) { } if (prop.type.name === 'Date') { if (!val) { - return 'NULL'; + return null; + // return 'NULL'; } if (!val.toUTCString) { val = new Date(val); From 646beabf96a8c075924263aa8aa3c4b449b0c5c2 Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Thu, 14 Mar 2013 12:56:36 -0700 Subject: [PATCH 021/239] upgrade to version 0.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d8a3721..3f6b69d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jugglingdb-mssql", - "version": "0.1.1", + "version": "0.1.2", "description": "MsSQL adapter for JugglingDB", "main": "index.js", "dependencies": { From e0b8f727e71b504c7640c00778ec3dfc6f52f5cb Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Sat, 30 Mar 2013 13:52:43 -0700 Subject: [PATCH 022/239] add test script file, implement all common tests exposed by jugglingdb --- README.md | 2 +- lib/mssql.js | 124 ++++++++++++++++++++++++++-------- package.json | 2 +- test/commontests.js | 157 ++++++++++++++++++++++++++++++++++++++++++++ test/mssql.js | 145 ---------------------------------------- test/tests.js | 4 ++ 6 files changed, 258 insertions(+), 176 deletions(-) create mode 100644 test/commontests.js delete mode 100644 test/mssql.js create mode 100644 test/tests.js diff --git a/README.md b/README.md index 8eccaff..1206c7e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ jugglingdb-mssql MsSQL adapter for the jugglingdb ORM -Early stages, not fully tested. +Now passing all tests exposed by the jugglingdb framework! Usage --- diff --git a/lib/mssql.js b/lib/mssql.js index 75717e1..7a47bf2 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -89,6 +89,10 @@ MsSQL.prototype.query = function (sql, optionsOrCallback, Callback) { }; +MsSQL.prototype.disconnect = function disconnect() { + this.client.close(); +}; + // MsSQL.prototype.command = function (sql, callback) { // return this.query(sql, callback); // }; @@ -138,17 +142,23 @@ MsSQL.prototype.create = function (model, data, callback) { sql += "VALUES (" + fieldsAndData.paramPlaceholders + ");" + MsSQL.newline; sql += "SELECT IDENT_CURRENT('" + tblName + "') AS insertId;"; + // console.log(sql); + // console.log(fieldsAndData.params); this.query(sql, fieldsAndData.params, function (err, results) { - if (err) return callback(err); + //console.log(err); + if (err) { return callback(err); } + //console.log(results); //msnodesql will execute the callback for each statement that get's executed, we're only interested in the one that returns with the insertId - if (results.insertId) { - callback(null, results.insertId); + if (results.length > 0 && results[0].insertId) { + //console.log('new id: ' + results[0].insertId); + callback(null, results[0].insertId); } }); }; MsSQL.prototype.updateOrCreate = function (model, data, callback) { //debugger; + //console.log('updateOrCreate'); var self = this; var props = this._models[model].properties; var tblName = this.tableEscaped(model); @@ -171,27 +181,45 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { } }); var sql = ""; - if (id > 0) { //assume that since we've been given an id that they're trying to update this object - sql = "UPDATE [dbo].[" + tblName + "]" + MsSQL.newline; - sql += "SET " + combined.join() + MsSQL.newline; - sql += "WHERE [" + modelPKID + "] = (?);" + MsSQL.newline; - sql += "SELECT " + id + " AS pkid;"; - fieldValues.push(id); + if (id > 0) { + self.exists(model, id, function(err, yn) { + if (err) { return callback(err); } + if (yn) { + //update + sql = "UPDATE [dbo].[" + tblName + "]" + MsSQL.newline; + sql += "SET " + combined.join() + MsSQL.newline; + sql += "WHERE [" + modelPKID + "] = (?);" + MsSQL.newline; + sql += "SELECT " + id + " AS pkid;"; + fieldValues.push(id); + } else { + //insert with identity_insert + sql = "SET IDENTITY_INSERT [dbo].[" + tblName + "] ON;" + MsSQL.newline; + sql += "INSERT INTO [dbo].[" + tblName + "] ([" + modelPKID + "]," + fieldNames.join() + ")" + MsSQL.newline; + sql += "VALUES (" + id + "," + fieldValuesPlaceholders.join() + ");" + MsSQL.newline; + sql += "SET IDENTITY_INSERT [dbo].[" + tblName + "] OFF;" + MsSQL.newline; + sql += "SELECT " + id + " AS pkid;"; + } + doQuery(sql, fieldValues); + }); } else { + //insert sql = "INSERT INTO [dbo].[" + tblName + "] (" + fieldNames.join() + ")" + MsSQL.newline; sql += "VALUES (" + fieldValuesPlaceholders.join() + ");" + MsSQL.newline; sql += "SELECT IDENT_CURRENT('" + tblName + "') AS pkid;"; + doQuery(sql, fieldValues); } - this.query(sql, fieldValues, function (err, results) { - if (err) return callback(err); - //msnodesql will execute the callback for each statement that get's executed, we're only interested in the one that returns with the pkid - if (results.pkid) { - data[modelPKID] = results.pkid; - data.id = results.pkid; - callback(err, data); - } - }); + var doQuery = function(sql, fieldValues) { + self.query(sql, fieldValues, function (err, results) { + if (err) { return callback(err); } + //msnodesql will execute the callback for each statement that get's executed, we're only interested in the one that returns with the pkid + if (results.length > 0 && results[0].pkid) { + data[modelPKID] = results[0].pkid; + data.id = results[0].pkid; + callback(err, data); + } + }); + } }; //redundant, same functionality as "updateOrCreate" right now. Maybe in the future some validation will happen here. @@ -207,7 +235,10 @@ MsSQL.prototype.updateAttributes = function (model, id, data, cb) { // if that is the case then set the id to the correct value from the data using the actual pkid name. if (id === null) { id = data[modelPKID]; + } else { + data[modelPKID] = id; } + //console.log(id); this.exists(model, id, function(err, yn) { if (err) { console.log(err); @@ -267,34 +298,52 @@ MsSQL.prototype.buildInsert = function (model, data) { //unchanged from MySql adapter, credit to dgsan function dateToMsSql(val) { - return val.getUTCFullYear() + '-' + - fillZeros(val.getUTCMonth() + 1) + '-' + - fillZeros(val.getUTCDate()) + ' ' + + return (val.getUTCMonth() + 1) + '-' + + val.getUTCDate() + '-' + + val.getUTCFullYear() + ' ' + fillZeros(val.getUTCHours()) + ':' + fillZeros(val.getUTCMinutes()) + ':' + - fillZeros(val.getUTCSeconds()); + fillZeros(val.getUTCSeconds()) + '.00'; function fillZeros(v) { return v < 10 ? '0' + v : v; } } -MsSQL.prototype.toDatabase = function (prop, val) { - if (val === null) { +//toDatabase is used for formatting data when inserting/updating records +// it is also used when building a where clause for filtering selects +// in the case of update/insert we want the data to be returned raw +// because we are using the msnodesql driver's parameterization feature +// in the case of building a where clause we want to wrap strings in single quotes +// so they can be concatenated into the sql statement +// use the 'wrap' parameter to tell the function which case it's handling (false=raw, true=single quotes) +MsSQL.prototype.toDatabase = function (prop, val, wrap) { + if (val === null || typeof val === 'undefined') { // return 'NULL'; return null; } - if (val.constructor.name === 'Object') { + if (val.constructor && val.constructor.name === 'Object') { var operator = Object.keys(val)[0] val = val[operator]; if (operator === 'between') { - return this.toDatabase(prop, val[0]) + + //the between operator is never used for insert/updates + // therefore always pass the wrap=true parameter when formatting the values + return this.toDatabase(prop, val[0], true) + ' AND ' + - this.toDatabase(prop, val[1]); + this.toDatabase(prop, val[1], true); } else if (operator == 'inq' || operator == 'nin') { + //always wrap inq/nin values in single quotes when they are string types, it's never used for insert/updates if (!(val.propertyIsEnumerable('length')) && typeof val === 'object' && typeof val.length === 'number') { //if value is array + //check if it is an array of string, because in that cause we need to wrap them in single quotes + if (typeof val[0] === 'string') { + return "'" + val.join("','") + "'"; + } return val.join(','); } else { + if (typeof val === 'string') + { + val = "'" + val + "'"; + } return val; } } else if (operator === "max") { @@ -302,6 +351,9 @@ MsSQL.prototype.toDatabase = function (prop, val) { } } if (!prop) { + if (typeof val === 'string' && wrap) { + val = "'" + val + "'"; + } return val; } if (prop.type.name === 'Number') { @@ -315,11 +367,21 @@ MsSQL.prototype.toDatabase = function (prop, val) { if (!val.toUTCString) { val = new Date(val); } - return '\'' + dateToMsSql(val) + '\''; + //return val; + //console.log('\'' + dateToMsSql(val) + '\''); + val = dateToMsSql(val); + if (wrap) { + val = "'" + val + "'"; + } + return val; } if (prop.type.name == "Boolean") { return val ? 1 : 0; } + + if (wrap) { + return "'" + val.toString() + "'"; + } return val.toString(); }; @@ -334,6 +396,9 @@ MsSQL.prototype.fromDatabase = function (model, data) { Object.keys(data).forEach(function (key) { var val = data[key]; if (props[key]) { + if (props[key].type.name === 'Boolean' && val !== null) { + val = (true && val); //convert to a boolean type from number + } if (props[key].type.name === 'Date' && val !== null) { val = new Date(val.toString().replace(/GMT.*$/, 'GMT')); } @@ -359,6 +424,7 @@ MsSQL.prototype.all = function (model, filter, callback) { if (filter) { if (filter.where) { sql += this.buildWhere(filter.where, props) + MsSQL.newline; + console.log(sql); } if (filter.order) { @@ -396,7 +462,7 @@ MsSQL.prototype.buildWhere = function(conds, props) { var cs = []; Object.keys(conds).forEach(function (key) { var keyEscaped = self.escapeKey(key); - var val = self.toDatabase(props[key], conds[key]); + var val = self.toDatabase(props[key], conds[key], true); if (conds[key] === null) { cs.push(keyEscaped + ' IS NULL'); } else if (conds[key].constructor.name === 'Object') { diff --git a/package.json b/package.json index 3f6b69d..487a613 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jugglingdb-mssql", - "version": "0.1.2", + "version": "0.1.3", "description": "MsSQL adapter for JugglingDB", "main": "index.js", "dependencies": { diff --git a/test/commontests.js b/test/commontests.js new file mode 100644 index 0000000..d06b8f0 --- /dev/null +++ b/test/commontests.js @@ -0,0 +1,157 @@ +var jdb = require('jugglingdb'), + Schema = jdb.Schema, + commonTest = jdb.test, + db = require("../db/dbconfig"); + +var adapter = require("../"); +var schemaSettings = { host:db.server, database:db.db, username:db.user, password:db.pwd }; +var schema = new Schema(adapter, schemaSettings); + +//run the tests exposed by jugglingdb +commonTest(module.exports, schema); + +//skip the order test from jugglingdb, it wasn't working right +commonTest.skip('should handle ORDER clause'); + +//re-implement the order test as pretty much the same thing, but run an automigration beforehand +commonTest.it('should automigrate', function(test) { + schema.automigrate(function (err) { + test.ifError(err); + test.done(); + }); +}); + +commonTest.it('should be able to ORDER results', function(test) { + var titles = [ { title: 'Title A', subject: "B" }, + { title: 'Title Z', subject: "A" }, + { title: 'Title M', subject: "C" }, + { title: 'Title A', subject: "A" }, + { title: 'Title B', subject: "A" }, + { title: 'Title C', subject: "D" }]; + + var dates = [ + new Date(1000 * 5 ), + new Date(1000 * 9), + new Date(1000 * 0), + new Date(1000 * 17), + new Date(1000 * 10), + new Date(1000 * 9) + ]; + + titles.forEach(function (t, i) { + schema.models.Post.create({title: t.title, subject: t.subject, date: dates[i]}, done); + }); + + var i = 0, tests = 0; + function done(err, obj) { + if (++i === titles.length) { + doFilterAndSortTest(); + doFilterAndSortReverseTest(); + doStringTest(); + doNumberTest(); + doMultipleSortTest(); + doMultipleReverseSortTest(); + } + } + + function compare(a, b) { + if (a.title < b.title) return -1; + if (a.title > b.title) return 1; + return 0; + } + + function doStringTest() { + tests += 1; + schema.models.Post.all({order: 'title'}, function (err, posts) { + if (err) console.log(err); + test.equal(posts.length, 6); + titles.sort(compare).forEach(function (t, i) { + if (posts[i]) test.equal(posts[i].title, t.title); + }); + finished(); + }); + } + + function doNumberTest() { + tests += 1; + schema.models.Post.all({order: 'date'}, function (err, posts) { + if (err) console.log(err); + test.equal(posts.length, 6); + dates.sort(numerically).forEach(function (d, i) { + if (posts[i]) + test.equal(posts[i].date.toString(), d.toString(), 'doNumberTest'); + }); + finished(); + }); + } + + function doFilterAndSortTest() { + tests += 1; + schema.models.Post.all({where: {date: new Date(1000 * 9)}, order: 'title', limit: 3}, function (err, posts) { + if (err) console.log(err); + console.log(posts.length); + test.equal(posts.length, 2, 'Exactly 2 posts returned by query'); + [ 'Title C', 'Title Z' ].forEach(function (t, i) { + if (posts[i]) { + test.equal(posts[i].title, t, 'doFilterAndSortTest'); + } + }); + finished(); + }); + } + + function doFilterAndSortReverseTest() { + tests += 1; + schema.models.Post.all({where: {date: new Date(1000 * 9)}, order: 'title DESC', limit: 3}, function (err, posts) { + if (err) console.log(err); + test.equal(posts.length, 2, 'Exactly 2 posts returned by query'); + [ 'Title Z', 'Title C' ].forEach(function (t, i) { + if (posts[i]) { + test.equal(posts[i].title, t, 'doFilterAndSortReverseTest'); + } + }); + finished(); + }); + } + + function doMultipleSortTest() { + tests += 1; + schema.models.Post.all({order: "title ASC, subject ASC"}, function(err, posts) { + if (err) console.log(err); + test.equal(posts.length, 6); + test.equal(posts[0].title, "Title A"); + test.equal(posts[0].subject, "A"); + test.equal(posts[1].title, "Title A"); + test.equal(posts[1].subject, "B"); + test.equal(posts[5].title, "Title Z"); + finished(); + }); + } + + function doMultipleReverseSortTest() { + tests += 1; + schema.models.Post.all({order: "title ASC, subject DESC"}, function(err, posts) { + if (err) console.log(err); + test.equal(posts.length, 6); + test.equal(posts[0].title, "Title A"); + test.equal(posts[0].subject, "B"); + test.equal(posts[1].title,"Title A"); + test.equal(posts[1].subject, "A"); + test.equal(posts[5].title, "Title Z"); + finished(); + }); + } + + var fin = 0; + function finished() { + if (++fin === tests) { + test.done(); + } + } + + // TODO: do mixed test, do real dates tests, ensure that dates stored in UNIX timestamp format + + function numerically(a, b) { + return a - b; + } +}); \ No newline at end of file diff --git a/test/mssql.js b/test/mssql.js deleted file mode 100644 index f02e375..0000000 --- a/test/mssql.js +++ /dev/null @@ -1,145 +0,0 @@ -var jdb = require('jugglingdb'), - Schema = jdb.Schema, - commonTest = jdb.test, - db = require("../db/dbconfig"); - -var adapter = require("../"); -schemaSettings = { host:db.server, database:db.db, username:db.user, password:db.pwd }; -var schema = new Schema(adapter, schemaSettings); - -exports.mssql = function(test) { - //test.expect(5); - - schema.on("connected", function() { - - var Post, User, Passport, Log, Dog; - - User = schema.define('User', { - name: { type: String, index: true }, - email: { type: String, index: true }, - bio: Schema.Text, - approved: Boolean, - joinedAt: Date, - age: Number, - passwd: { type: String, index: true } - }); - - Dog = schema.define('Dog', { - name : { type: String, limit: 64, allowNull: false } - }); - - Log = schema.define('Log', { - ownerId : { type: Number, allowNull: true }, - name : { type: String, limit: 64, allowNull: false } - }); - - Log.belongsTo(Dog, {as: 'owner', foreignKey: 'ownerId'}); - - schema.extendModel('User', { - settings: { type: Schema.JSON }, - extra: Object - }); - - var newuser = new User({settings: {hey: 'you'}}); - //test.ok(newuser.settings); - - Post = schema.define('Post', { - title: { type: String, length: 255, index: true }, - subject: { type: String }, - content: { type: Schema.Text }, - date: { type: Date, default: function () { return new Date }, index: true }, - published: { type: Boolean, default: false, index: true }, - likes: [], - related: [RelatedPost] - }, {table: 'posts'}); - - function RelatedPost() { } - RelatedPost.prototype.someMethod = function () { - return this.parent; - }; - - Post.validateAsync('title', function (err, done) { - process.nextTick(done); - }); - - User.hasMany(Post, {as: 'posts', foreignKey: 'userId'}); - // creates instance methods: - // user.posts(conds) - // user.posts.build(data) // like new Post({userId: user.id}); - // user.posts.create(data) // build and save - // user.posts.find - - // User.hasOne('latestPost', {model: Post, foreignKey: 'postId'}); - - // User.hasOne(Post, {as: 'latestPost', foreignKey: 'latestPostId'}); - // creates instance methods: - // user.latestPost() - // user.latestPost.build(data) - // user.latestPost.create(data) - - Post.belongsTo(User, {as: 'author', foreignKey: 'userId'}); - // creates instance methods: - // post.author(callback) -- getter when called with function - // post.author() -- sync getter when called without params - // post.author(user) -- setter when called with object - - Passport = schema.define('Passport', { - number: String - }); - - Passport.belongsTo(User, {as: 'owner', foreignKey: 'ownerId'}); - User.hasMany(Passport, {as: 'passports', foreignKey: 'ownerId'}); - - var user = new User; - - //test.ok(User instanceof Function); - - // class methods - //test.ok(User.find instanceof Function); - //test.ok(User.create instanceof Function); - - // instance methods - //test.ok(user.save instanceof Function); - schema.automigrate(function (err) { - if (err) { - console.log('Error while migrating'); - console.log(err); - } else { - //test.done(); - } - }); - }); -} - - -//demonstrate that msnodesql doesn't choke on '\r\n' or the GO keyword -exports.goTest = function() { - - var mssql = require("msnodesql"); - - var conn_str = "Driver={SQL Server Native Client 11.0};Server="+db.server+";"; - - //if we have a username and password then we use a credential connection string - if (db.user && db.db) { - conn_str += "UID="+db.user+";PWD="+db.pwd+";Database={"+db.db+"};" - } else { - conn_str += trusted_str - } - - mssql.open(conn_str, function(err, conn){ - if (err) - throw err - - conn.query("\r\nSELECT * FROM Passport\r\nGO\r\nSELECT * FROM Log\r\nGO\r\n", function(err, results) { - console.log(results.length); - console.log(results); - }); - - }); - -} - - -exports.mssql(null); -//exports.goTest(); -//commonTest(module.exports, schema); \ No newline at end of file diff --git a/test/tests.js b/test/tests.js new file mode 100644 index 0000000..57febc9 --- /dev/null +++ b/test/tests.js @@ -0,0 +1,4 @@ +var reporter = require('nodeunit').reporters.nested; +process.chdir(__dirname); + +reporter.run(['commontest.js']); \ No newline at end of file From d37bdd21346b63c682a08b9bc9f1e2eed0351973 Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Sat, 30 Mar 2013 13:59:36 -0700 Subject: [PATCH 023/239] comment out console.log debugging statement --- lib/mssql.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mssql.js b/lib/mssql.js index 7a47bf2..30e1904 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -424,7 +424,7 @@ MsSQL.prototype.all = function (model, filter, callback) { if (filter) { if (filter.where) { sql += this.buildWhere(filter.where, props) + MsSQL.newline; - console.log(sql); + //console.log(sql); } if (filter.order) { From 9d925d76b88b4c84562b48e75335dde73683f59c Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Sat, 30 Mar 2013 14:01:06 -0700 Subject: [PATCH 024/239] upgrade version to 0.1.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 487a613..48876e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jugglingdb-mssql", - "version": "0.1.3", + "version": "0.1.4", "description": "MsSQL adapter for JugglingDB", "main": "index.js", "dependencies": { From 6515f3a1e511eec6561a7eb30382d6dfa16e5289 Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Sun, 31 Mar 2013 15:22:06 -0700 Subject: [PATCH 025/239] add count method, add count test --- lib/mssql.js | 16 ++++++++++++++++ test/commontests.js | 9 +++++++++ test/tests.js | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/mssql.js b/lib/mssql.js index 30e1904..487242b 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -263,6 +263,22 @@ MsSQL.prototype.exists = function (model, id, callback) { }); }; +MsSQL.prototype.count = function (model, cb, where) { + var sql = "SELECT COUNT(*) cnt FROM [dbo].[" + this.tableEscaped(model) + "]" + MsSQL.newline; + var props = this._models[model].properties; + + if (where !== null) { + sql += this.buildWhere(where, props) + MsSQL.newline; + } + + this.query(sql, function (err, data) { + if (err) return callback(err); + cb(null, data[0].cnt); + }); + + return sql; +}; + MsSQL.prototype.find = function (model, id, callback) { //debugger; var tblName = this.tableEscaped(model); diff --git a/test/commontests.js b/test/commontests.js index d06b8f0..102e956 100644 --- a/test/commontests.js +++ b/test/commontests.js @@ -154,4 +154,13 @@ commonTest.it('should be able to ORDER results', function(test) { function numerically(a, b) { return a - b; } +}); + +commonTest.it('should be able count', function(test) { + test.expect(2); + schema.models.Post.count({title:'Title A'}, function(err, cnt) { + test.ifError(err); + test.equal(cnt, 2); + test.done(); + }); }); \ No newline at end of file diff --git a/test/tests.js b/test/tests.js index 57febc9..218906e 100644 --- a/test/tests.js +++ b/test/tests.js @@ -1,4 +1,4 @@ var reporter = require('nodeunit').reporters.nested; process.chdir(__dirname); -reporter.run(['commontest.js']); \ No newline at end of file +reporter.run(['commontests.js']); \ No newline at end of file From 3b38603234f979ce9015c09a21089a6c2e42367c Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Sun, 31 Mar 2013 15:47:46 -0700 Subject: [PATCH 026/239] add destroy method, add destroy test --- lib/mssql.js | 20 +++++++++++++++++++- test/commontests.js | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 487242b..60ce9a5 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -272,13 +272,31 @@ MsSQL.prototype.count = function (model, cb, where) { } this.query(sql, function (err, data) { - if (err) return callback(err); + if (err) { return cb(err); } cb(null, data[0].cnt); }); return sql; }; +MsSQL.prototype.destroyAll = function(model, cb) { + var sql = "DELETE FROM [dbo].[" + this.tableEscaped(model) + "]"; + this.query(sql, function(err, data) { + //don't bother returning data, it's a delete statement + if (err) { return cb(err); } + cb(null); + }); +}; + +MsSQL.prototype.destroy = function(model, id, cb) { + var sql = "DELETE FROM [dbo].[" + this.tableEscaped(model) + "]" + MsSQL.newline; + sql += "WHERE [" + this._pkids[model] + "] = (?)"; + this.query(sql, [id], function(err, data) { + if (err) { return cb(err); } + cb(null); + }); +}; + MsSQL.prototype.find = function (model, id, callback) { //debugger; var tblName = this.tableEscaped(model); diff --git a/test/commontests.js b/test/commontests.js index 102e956..d0ce3f1 100644 --- a/test/commontests.js +++ b/test/commontests.js @@ -156,11 +156,43 @@ commonTest.it('should be able to ORDER results', function(test) { } }); -commonTest.it('should be able count', function(test) { +commonTest.it("should count posts", function(test) { test.expect(2); - schema.models.Post.count({title:'Title A'}, function(err, cnt) { + schema.models.Post.count({title:"Title A"}, function(err, cnt) { test.ifError(err); test.equal(cnt, 2); test.done(); }); +}); + +commonTest.it("should delete a post", function(test) { + schema.models.Post.all({ + where:{ + "title":"Title Z" + } + }, function(err, posts) { + test.ifError(err); + test.equal(posts.length, 1); + id = posts[0].id; + posts[0].destroy(function(err) { + test.ifError(err); + schema.models.Post.find(id, function(err, post) { + test.ifError(err); + test.equal(post, null); + test.done(); + }); + }); + }); +}); + +commonTest.it("should delete all posts", function(test) { + test.expect(3); + schema.models.Post.destroyAll(function(err) { + test.ifError(err); + schema.models.Post.count(function(err, cnt){ + test.ifError(err); + test.equal(cnt, 0); + test.done(); + }); + }); }); \ No newline at end of file From f766b79dcb56ca41186d1a2fc4f38eee833320df Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Sun, 31 Mar 2013 16:53:56 -0700 Subject: [PATCH 027/239] add comment about id setting --- lib/mssql.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/mssql.js b/lib/mssql.js index 60ce9a5..8dee79f 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -215,7 +215,8 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { //msnodesql will execute the callback for each statement that get's executed, we're only interested in the one that returns with the pkid if (results.length > 0 && results[0].pkid) { data[modelPKID] = results[0].pkid; - data.id = results[0].pkid; + //#jdb id compatibility# + data.id = results[0].pkid; //set the id property also, to play nice with the jugglingdb abstract class implementation. callback(err, data); } }); From d4c6b5800e265cdb09a171414b22dd23f687dca6 Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Sun, 31 Mar 2013 17:26:59 -0700 Subject: [PATCH 028/239] add test for custom primary key support, update readme --- README.md | 46 ++++++++++++++++++++++++++++++++++++++------- test/commontests.js | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1206c7e..3ca823f 100644 --- a/README.md +++ b/README.md @@ -9,24 +9,56 @@ Usage --- To use it you need
jugglingdb@0.2.x
and msnodesql -1. Setup dependencies in package.json -

+1. Setup dependencies in package.json:
+  
+    
     {
       ...
       "dependencies":{
-      	"msnodesql":"~0.2.1",
+        "msnodesql":"~0.2.1",
         "jugglingdb": "~0.2.0",
         "jugglingdb-mssql":"latest"
       }
       ...
     }
-  	
-2. Use: -

+    
+  
+2. Use: +
+    
     var Schema = require("jugglingdb").Schema;
     var schema = new Schema("mssql", {host:"YourSqlServer", database:"YourDatabase"});
     ...
-    
+
+
+3. Primary Keys: + using anything other than the default 'id' as the primary key + will cause the hasMany and belongsTo relationships in jugglingdb to + not work, and possibly other oddities as well (so you probably + shouldn't use it until it's officially supported). + + to specify a custom primary key name for a model use +
+    
+      var AppliesTo = schema.define("AppliesTo", {
+        AppliesToID: {
+          type:Number,
+          primaryKey:true
+        },
+        Title: {
+          type:String,
+          limit:100
+        },
+        Identifier: {
+          type:String,
+          limit:100
+        },
+        Editable: {
+          type:Number
+        }
+      });
+    
+  
MIT License --- diff --git a/test/commontests.js b/test/commontests.js index d0ce3f1..5adb11b 100644 --- a/test/commontests.js +++ b/test/commontests.js @@ -195,4 +195,37 @@ commonTest.it("should delete all posts", function(test) { test.done(); }); }); +}); + +//custom primary keys not quite working :(, hopefully 1602 will implement that functionality in jugglingdb soon. +commonTest.it("should support custom primary key", function(test) { + test.expect(3); + var AppliesTo = schema.define("AppliesTo", { + AppliesToID: { + type:Number, + primaryKey:true + }, + Title: { + type:String, + limit:100 + }, + Identifier: { + type:String, + limit:100 + }, + Editable: { + type:Number + } + }); + + schema.automigrate(function (err) { + test.ifError(err); + + AppliesTo.create({Title:"custom key", Identifier:"ck", Editable:false}, function(err, data) { + test.ifError(err); + test.notStrictEqual(typeof data.AppliesToID, 'undefined'); + test.done(); + }); + }); + }); \ No newline at end of file From fc5f5026d78fe84cdce6fc112d4df43a0eb0a044 Mon Sep 17 00:00:00 2001 From: Scott Vickers Date: Sun, 31 Mar 2013 17:27:28 -0700 Subject: [PATCH 029/239] upgrade version to 0.1.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48876e2..fde900e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jugglingdb-mssql", - "version": "0.1.4", + "version": "0.1.5", "description": "MsSQL adapter for JugglingDB", "main": "index.js", "dependencies": { From de012033ad07b4062e104b9a72c4d562bcb94fb7 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 21 Jan 2014 21:38:58 -0800 Subject: [PATCH 030/239] Update package.json --- package.json | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index fde900e..bbb5a1b 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,21 @@ { - "name": "jugglingdb-mssql", - "version": "0.1.5", - "description": "MsSQL adapter for JugglingDB", + "name": "loopback-connector-mssql", + "version": "1.0.0", + "description": "MsSQL connector for LoopBack", "main": "index.js", "dependencies": { - "jugglingdb": "~0.2.0-22", - "util":"~0.4.9", + "util": "~0.10.2", "msnodesql":"~0.2.1" }, + "peerDependencies": { + "loopback-datasource-juggler":"1.2.x" + }, "devDependencies": { - "nodeunit":"~0.7.4" + "nodeunit": "~0.8.2" }, "repository": { "type": "git", - "url": "https://github.com/code-vicar/jugglingdb-mssql.git" + "url": "https://github.com/strongloop/loopback-connector-mssql.git" }, - "author": "Scott Vickers", "license": "MIT" -} \ No newline at end of file +} From f0755106c94ddc3ba9e98d0049e58667690b35db Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 12 Feb 2014 17:13:15 -0800 Subject: [PATCH 031/239] Reformat the code and use mssql as the dep --- lib/mssql.js | 325 ++++++++++++++++++++++++++------------------------- package.json | 4 +- 2 files changed, 171 insertions(+), 158 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 8dee79f..66c9d3e 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -1,34 +1,37 @@ /* Module dependencies */ -var mssql = require("msnodesql"); -var jdb = require("jugglingdb"); +var mssql = require("mssql"); +var jdb = require("loopback-datasource-juggler"); var util = require("util"); var name = "mssql"; exports.name = name; -exports.initialize = function initializeSchema(schema, callback) { - //need msnodesql installed, and a host server and a database - if (!mssql || !schema.settings.host || !schema.settings.database){ return; } - - var conn_str = "Driver={SQL Server Native Client 11.0};Server="+schema.settings.host+";"; - var trusted_str = "Trusted_Connection={Yes};Database={"+schema.settings.database+"};"; +exports.initialize = function initializeSchema(dataSource, callback) { + if (!mssql || !dataSource.settings.host || !dataSource.settings.database) { + return; + } + + var conn_str = "Driver={SQL Server Native Client 11.0};Server=" + dataSource.settings.host + ";"; + var trusted_str = "Trusted_Connection={Yes};Database={" + dataSource.settings.database + "};"; //if we have a username and password then we use a credential connection string - if (schema.settings.username && schema.settings.password) { - conn_str += "UID="+schema.settings.username+";PWD="+schema.settings.password+";Database={"+schema.settings.database+"};" + if (dataSource.settings.username && dataSource.settings.password) { + conn_str += "UID=" + dataSource.settings.username + ";PWD=" + + dataSource.settings.password + ";Database={" + + dataSource.settings.database + "};"; } else { - conn_str += trusted_str + conn_str += trusted_str; } - mssql.open(conn_str, function(err, conn){ + mssql.open(conn_str, function (err, conn) { if (err) - throw err + throw err; //debugger; - schema.client = conn; - schema.adapter = new MsSQL(schema.client); - schema.adapter.schema = schema; - schema.adapter.tableNameID = schema.settings.tableNameID; + dataSource.client = conn; + dataSource.connector = new MsSQL(dataSource.client); + dataSource.connector.dataSource = dataSource; + dataSource.connector.tableNameID = dataSource.settings.tableNameID; callback(); }); }; @@ -60,8 +63,8 @@ MsSQL.prototype.query = function (sql, optionsOrCallback, Callback) { // console.log(options); // console.log(sql); } - if (!this.schema.connected) { - return this.schema.on('connected', function () { + if (!this.dataSource.connected) { + return this.dataSource.on('connected', function () { if (hasOptions) { this.query(sql, options, cb); } else { @@ -86,7 +89,7 @@ MsSQL.prototype.query = function (sql, optionsOrCallback, Callback) { } else { this.client.query(sql, innerCB); } - + }; MsSQL.prototype.disconnect = function disconnect() { @@ -110,12 +113,12 @@ MsSQL.prototype.define = function (descr) { //default pkid is "id" var id = "id"; - //override the default with another convention, 'TableName'ID, if defined in the adapter settings + //override the default with another convention, 'TableName'ID, if defined in the connector settings if (this.tableNameID) { id = descr.model.modelName + "ID"; } //override both defaults if a primaryKey is specified in a property - Object.keys(descr.properties).forEach(function(propName) { + Object.keys(descr.properties).forEach(function (propName) { var propVal = descr.properties[propName]; if (typeof propVal === "object" && propVal.primaryKey) { return id = propName; @@ -139,14 +142,16 @@ MsSQL.prototype.create = function (model, data, callback) { var fieldsAndData = this.buildInsert(model, data); var tblName = this.tableEscaped(model); var sql = "INSERT INTO [dbo].[" + tblName + "] (" + fieldsAndData.fields + ")" + MsSQL.newline; - sql += "VALUES (" + fieldsAndData.paramPlaceholders + ");" + MsSQL.newline; - sql += "SELECT IDENT_CURRENT('" + tblName + "') AS insertId;"; - + sql += "VALUES (" + fieldsAndData.paramPlaceholders + ");" + MsSQL.newline; + sql += "SELECT IDENT_CURRENT('" + tblName + "') AS insertId;"; + // console.log(sql); // console.log(fieldsAndData.params); this.query(sql, fieldsAndData.params, function (err, results) { //console.log(err); - if (err) { return callback(err); } + if (err) { + return callback(err); + } //console.log(results); //msnodesql will execute the callback for each statement that get's executed, we're only interested in the one that returns with the insertId if (results.length > 0 && results[0].insertId) { @@ -182,8 +187,10 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { }); var sql = ""; if (id > 0) { - self.exists(model, id, function(err, yn) { - if (err) { return callback(err); } + self.exists(model, id, function (err, yn) { + if (err) { + return callback(err); + } if (yn) { //update sql = "UPDATE [dbo].[" + tblName + "]" + MsSQL.newline; @@ -209,9 +216,11 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { doQuery(sql, fieldValues); } - var doQuery = function(sql, fieldValues) { + var doQuery = function (sql, fieldValues) { self.query(sql, fieldValues, function (err, results) { - if (err) { return callback(err); } + if (err) { + return callback(err); + } //msnodesql will execute the callback for each statement that get's executed, we're only interested in the one that returns with the pkid if (results.length > 0 && results[0].pkid) { data[modelPKID] = results[0].pkid; @@ -234,13 +243,13 @@ MsSQL.prototype.updateAttributes = function (model, id, data, cb) { var modelPKID = this._pkids[model]; //jugglingdb abstract class may have sent up a null value for this id if we aren't using the standard "id" name for the pkid. // if that is the case then set the id to the correct value from the data using the actual pkid name. - if (id === null) { + if (id === null) { id = data[modelPKID]; } else { data[modelPKID] = id; } //console.log(id); - this.exists(model, id, function(err, yn) { + this.exists(model, id, function (err, yn) { if (err) { console.log(err); return cb("An error occurred when checking for the existance of this record"); @@ -249,7 +258,7 @@ MsSQL.prototype.updateAttributes = function (model, id, data, cb) { //only call this after verifying that the record exists, we don't want to create it if it doesn't. return self.updateOrCreate(model, data, cb); } - return cb("A " + tblName + " doesn't exist with a " + modelPKID + " of " + id , id); + return cb("A " + tblName + " doesn't exist with a " + modelPKID + " of " + id, id); }); }; @@ -259,8 +268,8 @@ MsSQL.prototype.exists = function (model, id, callback) { var sql = "SELECT COUNT(*) cnt FROM [dbo].[" + tblName + "] WHERE [" + modelPKID + "] = (?)" //console.log(sql); this.query(sql, [id], function (err, results) { - if (err) return callback(err); - callback(null, results[0].cnt >= 1); + if (err) return callback(err); + callback(null, results[0].cnt >= 1); }); }; @@ -273,27 +282,33 @@ MsSQL.prototype.count = function (model, cb, where) { } this.query(sql, function (err, data) { - if (err) { return cb(err); } + if (err) { + return cb(err); + } cb(null, data[0].cnt); }); return sql; }; -MsSQL.prototype.destroyAll = function(model, cb) { +MsSQL.prototype.destroyAll = function (model, cb) { var sql = "DELETE FROM [dbo].[" + this.tableEscaped(model) + "]"; - this.query(sql, function(err, data) { + this.query(sql, function (err, data) { //don't bother returning data, it's a delete statement - if (err) { return cb(err); } + if (err) { + return cb(err); + } cb(null); }); }; -MsSQL.prototype.destroy = function(model, id, cb) { +MsSQL.prototype.destroy = function (model, id, cb) { var sql = "DELETE FROM [dbo].[" + this.tableEscaped(model) + "]" + MsSQL.newline; sql += "WHERE [" + this._pkids[model] + "] = (?)"; - this.query(sql, [id], function(err, data) { - if (err) { return cb(err); } + this.query(sql, [id], function (err, data) { + if (err) { + return cb(err); + } cb(null); }); }; @@ -328,10 +343,10 @@ MsSQL.prototype.buildInsert = function (model, data) { } }.bind(this)); - return { fields:insertIntoFields.join(), paramPlaceholders:paramPlaceholders.join(), params:params }; + return { fields: insertIntoFields.join(), paramPlaceholders: paramPlaceholders.join(), params: params }; } -//unchanged from MySql adapter, credit to dgsan +//unchanged from MySql connector, credit to dgsan function dateToMsSql(val) { return (val.getUTCMonth() + 1) + '-' + val.getUTCDate() + '-' + @@ -364,8 +379,8 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { //the between operator is never used for insert/updates // therefore always pass the wrap=true parameter when formatting the values return this.toDatabase(prop, val[0], true) + - ' AND ' + - this.toDatabase(prop, val[1], true); + ' AND ' + + this.toDatabase(prop, val[1], true); } else if (operator == 'inq' || operator == 'nin') { //always wrap inq/nin values in single quotes when they are string types, it's never used for insert/updates if (!(val.propertyIsEnumerable('length')) && typeof val === 'object' && typeof val.length === 'number') { //if value is array @@ -375,8 +390,7 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { } return val.join(','); } else { - if (typeof val === 'string') - { + if (typeof val === 'string') { val = "'" + val + "'"; } return val; @@ -491,7 +505,7 @@ MsSQL.prototype.buildOrderBy = function (order) { return 'ORDER BY ' + order.join(','); }; -MsSQL.prototype.buildWhere = function(conds, props) { +MsSQL.prototype.buildWhere = function (conds, props) { // debugger; var self = this; var cs = []; @@ -520,29 +534,29 @@ MsSQL.prototype.buildWhere = function(conds, props) { } switch (condType) { case 'gt': - sqlCond += ' > '; - break; + sqlCond += ' > '; + break; case 'gte': - sqlCond += ' >= '; - break; + sqlCond += ' >= '; + break; case 'lt': - sqlCond += ' < '; - break; + sqlCond += ' < '; + break; case 'lte': - sqlCond += ' <= '; - break; + sqlCond += ' <= '; + break; case 'between': - sqlCond += ' BETWEEN '; - break; + sqlCond += ' BETWEEN '; + break; case 'inq': - sqlCond += ' IN '; - break; + sqlCond += ' IN '; + break; case 'nin': - sqlCond += ' NOT IN '; - break; + sqlCond += ' NOT IN '; + break; case 'neq': - sqlCond += ' != '; - break; + sqlCond += ' != '; + break; } sqlCond += (condType == 'inq' || condType == 'nin') ? '(' + val + ')' : val; cs.push(sqlCond); @@ -606,90 +620,89 @@ MsSQL.prototype.buildWhere = function(conds, props) { // } // }; - //not working yet MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, checkOnly) { var self = this; var m = this._models[model]; var propNames = Object.keys(m.properties).filter(function (name) { - return !!m.properties[name]; + return !!m.properties[name]; }); var indexNames = m.settings.indexes ? Object.keys(m.settings.indexes).filter(function (name) { - return !!m.settings.indexes[name]; + return !!m.settings.indexes[name]; }) : []; var sql = []; var ai = {}; if (actualIndexes) { - actualIndexes.forEach(function (i) { - var name = i.Key_name; - if (!ai[name]) { - ai[name] = { - info: i, - columns: [] - }; - } - ai[name].columns[i.Seq_in_index - 1] = i.Column_name; - }); + actualIndexes.forEach(function (i) { + var name = i.Key_name; + if (!ai[name]) { + ai[name] = { + info: i, + columns: [] + }; + } + ai[name].columns[i.Seq_in_index - 1] = i.Column_name; + }); } var aiNames = Object.keys(ai); // change/add new fields propNames.forEach(function (propName) { - if (propName === 'id') return; - var found; - actualFields.forEach(function (f) { - if (f.Field === propName) { - found = f; - } - }); - - if (found) { - actualize(propName, found); - } else { - sql.push('ADD COLUMN `' + propName + '` ' + self.propertySettingsSQL(model, propName)); + if (propName === 'id') return; + var found; + actualFields.forEach(function (f) { + if (f.Field === propName) { + found = f; } + }); + + if (found) { + actualize(propName, found); + } else { + sql.push('ADD COLUMN `' + propName + '` ' + self.propertySettingsSQL(model, propName)); + } }); // drop columns actualFields.forEach(function (f) { - var notFound = !~propNames.indexOf(f.Field); - if (f.Field === 'id') return; - if (notFound || !m.properties[f.Field]) { - sql.push('DROP COLUMN `' + f.Field + '`'); - } + var notFound = !~propNames.indexOf(f.Field); + if (f.Field === 'id') return; + if (notFound || !m.properties[f.Field]) { + sql.push('DROP COLUMN `' + f.Field + '`'); + } }); // remove indexes aiNames.forEach(function (indexName) { - if (indexName === 'id' || indexName === 'PRIMARY') return; - if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || m.properties[indexName] && !m.properties[indexName].index) { - sql.push('DROP INDEX `' + indexName + '`'); - } else { - // first: check single (only type and kind) - if (m.properties[indexName] && !m.properties[indexName].index) { - // TODO - return; - } - // second: check multiple indexes - var orderMatched = true; - if (indexNames.indexOf(indexName) !== -1) { - m.settings.indexes[indexName].columns.split(/,\s*/).forEach(function (columnName, i) { - if (ai[indexName].columns[i] !== columnName) orderMatched = false; - }); - } - if (!orderMatched) { - sql.push('DROP INDEX `' + indexName + '`'); - delete ai[indexName]; - } + if (indexName === 'id' || indexName === 'PRIMARY') return; + if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || m.properties[indexName] && !m.properties[indexName].index) { + sql.push('DROP INDEX `' + indexName + '`'); + } else { + // first: check single (only type and kind) + if (m.properties[indexName] && !m.properties[indexName].index) { + // TODO + return; + } + // second: check multiple indexes + var orderMatched = true; + if (indexNames.indexOf(indexName) !== -1) { + m.settings.indexes[indexName].columns.split(/,\s*/).forEach(function (columnName, i) { + if (ai[indexName].columns[i] !== columnName) orderMatched = false; + }); } + if (!orderMatched) { + sql.push('DROP INDEX `' + indexName + '`'); + delete ai[indexName]; + } + } }); // add single-column indexes propNames.forEach(function (propName) { var i = m.properties[propName].index; if (!i) { - return; + return; } var found = ai[propName] && ai[propName].info; if (!found) { @@ -765,7 +778,7 @@ MsSQL.prototype.propertiesSQL = function (model) { var sql = ["[" + modelPKID + "] [int] IDENTITY(1,1) NOT NULL"]; Object.keys(this._models[model].properties).forEach(function (prop) { if (prop === modelPKID) { - return; + return; } sql.push("[" + prop + "] " + self.propertySettingsSQL(model, prop)); }); @@ -773,7 +786,7 @@ MsSQL.prototype.propertiesSQL = function (model) { var cmd = "PRIMARY KEY CLUSTERED" + MsSQL.newline + "(" + MsSQL.newline; cmd += " [" + modelPKID + "] ASC" + MsSQL.newline; cmd += ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]" - + joinedSql += "," + MsSQL.newline + cmd; return joinedSql; @@ -801,9 +814,9 @@ MsSQL.prototype.singleIndexSettingsSQL = function (model, prop, add) { } this._idxNames[model].push[name]; var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON [dbo].[" + tblName + "]" + MsSQL.newline; - cmd += "(" + MsSQL.newline; - cmd += " [" + prop + "] " + type; - cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];" + MsSQL.newline; + cmd += "(" + MsSQL.newline; + cmd += " [" + prop + "] " + type; + cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];" + MsSQL.newline; return cmd; }; @@ -826,27 +839,27 @@ MsSQL.prototype.indexSettingsSQL = function (model, prop) { var splitcolumns = i.columns.split(","); var columns = []; var name = ""; - splitcolumns.forEach(function(elem, ind) { + splitcolumns.forEach(function (elem, ind) { var trimmed = elem.trim(); name += trimmed + "_"; trimmed = "[" + trimmed + "] " + type; columns.push(trimmed); }); - + name += kind + "_" + type + "_idx" this._idxNames[model].push[name]; var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON [dbo].[" + tblName + "]" + MsSQL.newline; - cmd += "(" + MsSQL.newline; - cmd += columns.join("," + MsSQL.newline); - cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];" + MsSQL.newline; + cmd += "(" + MsSQL.newline; + cmd += columns.join("," + MsSQL.newline); + cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];" + MsSQL.newline; return cmd; }; MsSQL.prototype.propertySettingsSQL = function (model, prop) { var p = this._models[model].properties[prop]; return datatype(p) + ' ' + - (p.allowNull === false || p['null'] === false ? 'NOT NULL' : 'NULL'); + (p.allowNull === false || p['null'] === false ? 'NOT NULL' : 'NULL'); }; MsSQL.prototype.automigrate = function (cb) { @@ -868,14 +881,14 @@ MsSQL.prototype.automigrate = function (cb) { function done() { if (--wait === 0 && cb) { - cb(); + cb(); } } }; MsSQL.prototype.dropTable = function (model, cb) { var tblName = this.tableEscaped(model); - var cmd ="IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[" + tblName + "]') AND type in (N'U'))"; + var cmd = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[" + tblName + "]') AND type in (N'U'))"; cmd += MsSQL.newline + "BEGIN" + MsSQL.newline; cmd += " DROP TABLE [dbo].[" + tblName + "]"; cmd += MsSQL.newline + "END"; @@ -895,7 +908,7 @@ MsSQL.prototype.createTable = function (model, cb) { this.command(cmd, cb); }; -MsSQL.prototype.createIndexes = function(model) { +MsSQL.prototype.createIndexes = function (model) { var self = this; var sql = []; // Declared in model index property indexes. @@ -908,8 +921,8 @@ MsSQL.prototype.createIndexes = function(model) { // Settings might not have an indexes property. var dxs = this._models[model].settings.indexes; - if(dxs) { - Object.keys(this._models[model].settings.indexes).forEach(function(prop){ + if (dxs) { + Object.keys(this._models[model].settings.indexes).forEach(function (prop) { sql.push(self.indexSettingsSQL(model, prop)); }); } @@ -918,28 +931,28 @@ MsSQL.prototype.createIndexes = function(model) { } function datatype(p) { - var dt = ''; - switch (p.type.name) { - default: - case 'String': - case 'JSON': - dt = '[varchar](' + (p.limit || 255) + ')'; - break; - case 'Text': - dt = '[text]'; - break; - case 'Number': - dt = '[int]'; - break; - case 'Date': - dt = '[datetime]'; - break; - case 'Boolean': - dt = '[bit]'; - break; - case 'Point': - dt = '[float]'; - break; - } - return dt; + var dt = ''; + switch (p.type.name) { + default: + case 'String': + case 'JSON': + dt = '[varchar](' + (p.limit || 255) + ')'; + break; + case 'Text': + dt = '[text]'; + break; + case 'Number': + dt = '[int]'; + break; + case 'Date': + dt = '[datetime]'; + break; + case 'Boolean': + dt = '[bit]'; + break; + case 'Point': + dt = '[float]'; + break; + } + return dt; } \ No newline at end of file diff --git a/package.json b/package.json index bbb5a1b..fbe53fa 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,10 @@ "main": "index.js", "dependencies": { "util": "~0.10.2", - "msnodesql":"~0.2.1" + "mssql": "~0.5.0" }, "peerDependencies": { - "loopback-datasource-juggler":"1.2.x" + "loopback-datasource-juggler":"1.x" }, "devDependencies": { "nodeunit": "~0.8.2" From d90a83b17d24bddaae1bb9f25006beae0fffac72 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 11 Apr 2014 14:41:22 -0700 Subject: [PATCH 032/239] Bring up the MS SQL Server connector --- LICENSE | 288 ++++++++++++++++++++++++++++++++ lib/discovery.js | 380 ++++++++++++++++++++++++++++++++++++++++++ lib/mssql.js | 340 +++++++++++++++++++++++-------------- package.json | 23 ++- test/commontests.js | 14 +- test/imported.test.js | 10 ++ test/init.js | 42 +++++ test/tests.js | 4 - 8 files changed, 962 insertions(+), 139 deletions(-) create mode 100644 LICENSE create mode 100644 lib/discovery.js create mode 100644 test/imported.test.js create mode 100644 test/init.js delete mode 100644 test/tests.js diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6374e5d --- /dev/null +++ b/LICENSE @@ -0,0 +1,288 @@ +Copyright (c) 2013-2014 StrongLoop, Inc. + +StrongLoop License + +STRONGLOOP SUBSCRIPTION AGREEMENT +PLEASE READ THIS AGREEMENT CAREFULLY BEFORE YOU AGREE TO THESE TERMS. IF YOU +ARE ACTING ON BEHALF OF AN ENTITY, THEN YOU REPRESENT THAT YOU HAVE THE +AUTHORITY TO ENTER INTO THIS AGREEMENT ON BEHALF OF THAT ENTITY. IF YOU DO NOT +AGREE TO THESE TERMS, YOU SHOULD NOT AGREE TO THE TERMS OF THIS AGREEMENT OR +INSTALL OR USE THE SOFTWARE. +This StrongLoop Subscription Agreement ("Agreement") is made by and between +StrongLoop, Inc. ("StrongLoop") with its principal place of business at 107 S. +B St, Suite 220, San Mateo, CA 94401 and the person or entity entering into this +Agreement ("Customer"). The effective date ("Effective Date") of this Agreement +is the date Customer agrees to these terms or installs or uses the Software (as +defined below). This Agreement applies to Customer's use of the Software but it +shall be superseded by any signed agreement between you and StrongLoop +concerning the Software. +1. Subscriptions and Licenses. +1.1 Subscriptions. StrongLoop offers five different subscription levels to its +customers, each as more particularly described on StrongLoop's website located +at www.strongloop.com (the "StrongLoop Site"): (1) Free; (2) Developer; (3) +Professional; (4) Gold; and (5) Platinum. The actual subscription level +applicable to Customer (the "Subscription") will be specified in the purchase +order that Customer issues to StrongLoop. This Agreement applies to Customer +regardless of the level of the Subscription selected by Customer and whether or +not Customer upgrades or downgrades its Subscription. StrongLoop hereby agrees +to provide the services as described on the StrongLoop Site for each +Subscription level during the term for which Customer has purchased the +applicable Subscription, subject to Customer paying the fees applicable to the +Subscription level purchased, if any (the "Subscription Fees"). StrongLoop may +modify the services to be provided under any Subscription upon notice to +Customer. +1.2 License Grant. Subject to the terms and conditions of this Agreement, +StrongLoop grants to Customer, during the Subscription Term (as defined in +Section 7.1 (Term and Termination) of this Agreement, a limited, non-exclusive, +non-transferable right and license, to install and use the StrongLoop Suite +software (the "Software") and the documentation made available electronically as +part of the Software (the "Documentation"), either of which may be modified +during the Term (as defined in Section 7.1 below), solely for development, +production and commercial purposes so long as Customer is using the Software to +run only one process on a given operating system at a time. This Agreement, +including but not limited to the license and restrictions contained herein, +apply to Customer regardless of whether Customer accesses the Software via +download from the StrongLoop Site or through a third-party website or service, +even if Customer acquired the Software prior to agreeing to this Agreement. +1.3 License Restrictions. Customer shall not itself, or through any parent, +subsidiary, affiliate, agent or other third party: + 1.3.1 sell, lease, license, distribute, sublicense or otherwise transfer + in whole or in part, any Software or the Documentation to a third party; + or + 1.3.2 decompile, disassemble, translate, reverse engineer or otherwise + attempt to derive source code from the Software, in whole or in part, nor + shall Customer use any mechanical, electronic or other method to trace, + decompile, disassemble, or identify the source code of the Software or + encourage others to do so, except to the limited extent, if any, that + applicable law permits such acts notwithstanding any contractual + prohibitions, provided, however, before Customer exercises any rights that + Customer believes to be entitled to based on mandatory law, Customer shall + provide StrongLoop with thirty (30) days prior written notice and provide + all reasonably requested information to allow StrongLoop to assess + Customer's claim and, at StrongLoop's sole discretion, to provide + alternatives that reduce any adverse impact on StrongLoop's intellectual + property or other rights; or + 1.3.3 allow access or permit use of the Software by any users other than + Customer's employees or authorized third-party contractors who are + providing services to Customer and agree in writing to abide by the terms + of this Agreement, provided further that Customer shall be liable for any + failure by such employees and third-party contractors to comply with the + terms of this Agreement and no usage restrictions, if any, shall be + exceeded; or + 1.3.4 create, develop, license, install, use, or deploy any third party + software or services to circumvent or provide access, permissions or + rights which violate the license keys embedded within the Software; or + 1.3.5 modify or create derivative works based upon the Software or + Documentation; or disclose the results of any benchmark test of the + Software to any third party without StrongLoop's prior written approval; + or + 1.3.6 change any proprietary rights notices which appear in the Software + or Documentation; or + 1.3.7 use the Software as part of a time sharing or service bureau + purposes or in any other resale capacity. +1.4 Third-Party Software. The Software may include individual certain software +that is owned by third parties, including individual open source software +components (the "Third-Party Software"), each of which has its own copyright and +its own applicable license conditions. Such third-party software is licensed to +Customer under the terms of the applicable third-party licenses and/or copyright +notices that can be found in the LICENSES file, the Documentation or other +materials accompanying the Software, except that Sections 5 (Warranty +Disclaimer) and 6 (Limitation of Liability) also govern Customer's use of the +third-party software. Customer agrees to comply with the terms and conditions +of the relevant third-party software licenses. +2. Support Services. StrongLoop has no obligation to provide any support for +the Software other than the support services specifically described on the +StrongLoop Site for the Subscription level procured by Customer. However, +StrongLoop has endeavored to establish a community of users of the Software who +have provided their own feedback, hints and advice regarding their experiences +in using the Software. You can find that community and user feedback on the +StrongLoop Site. The use of any information, content or other materials from, +contained in or on the StrongLoop Site are subject to the StrongLoop website +terms of use located here http://www.strongloop.com/terms-of-service. +3. Confidentiality. For purposes of this Agreement, "Confidential Information" +means any and all information or proprietary materials (in every form and media) +not generally known in the relevant trade or industry and which has been or is +hereafter disclosed or made available by StrongLoop to Customer in connection +with the transactions contemplated under this Agreement, including (i) all trade +secrets, (ii) existing or contemplated Software, services, designs, technology, +processes, technical data, engineering, techniques, methodologies and concepts +and any related information, and (iii) information relating to business plans, +sales or marketing methods and customer lists or requirements. For a period of +five (5) years from the date of disclosure of the applicable Confidential +Information, Customer shall (i) hold the Confidential Information in trust and +confidence and avoid the disclosure or release thereof to any other person or +entity by using the same degree of care as it uses to avoid unauthorized use, +disclosure, or dissemination of its own Confidential Information of a similar +nature, but not less than reasonable care, and (ii) not use the Confidential +Information for any purpose whatsoever except as expressly contemplated under +this Agreement; provided that, to the extent the Confidential Information +constitutes a trade secret under law, Customer agrees to protect such +information for so long as it qualifies as a trade secret under applicable law. +Customer shall disclose the Confidential Information only to those of its +employees and contractors having a need to know such Confidential Information +and shall take all reasonable precautions to ensure that such employees and +contractors comply with the provisions of this Section. The obligations of +Customer under this Section shall not apply to information that Customer can +demonstrate (i) was in its possession at the time of disclosure and without +restriction as to confidentiality, (ii) at the time of disclosure is generally +available to the public or after disclosure becomes generally available to the +public through no breach of agreement or other wrongful act by Customer, (iii) +has been received from a third party without restriction on disclosure and +without breach of agreement by Customer, or (iv) is independently developed by +Customer without regard to the Confidential Information. In addition, Customer +may disclose Confidential Information as required to comply with binding orders +of governmental entities that have jurisdiction over it; provided that Customer +gives StrongLoop reasonable written notice to allow StrongLoop to seek a +protective order or other appropriate remedy, discloses only such Confidential +Information as is required by the governmental entity, and uses commercially +reasonable efforts to obtain confidential treatment for any Confidential +Information disclosed. Notwithstanding the above, Customer agrees that +StrongLoop, its employees and agents shall be free to use and employ their +general skills, know-how, and expertise, and to use, disclose, and employ any +generalized ideas, concepts, know-how, methods, techniques or skills gained or +learned during the Term or thereafter. +4. Ownership. StrongLoop shall retain all intellectual property and proprietary +rights in the Software, Documentation, and related works, including but not +limited to any derivative work of the foregoing and StrongLoop's licensors shall +retain all intellectual property and proprietary rights in any Third-Party +Software that may be provided with or as a part of the Software. Customer shall +do nothing inconsistent with StrongLoop's or its licensors' title to the +Software and the intellectual property rights embodied therein, including, but +not limited to, transferring, loaning, selling, assigning, pledging, or +otherwise disposing, encumbering, or suffering a lien or encumbrance upon or +against any interest in the Software. The Software (including any Third-Party +Software) contain copyrighted material, trade secrets and other proprietary +material of StrongLoop and/or its licensors. +5. Warranty Disclaimer. THE SOFTWARE (INCLUDING ANY THIRD-PARTY SOFTWARE) AND +DOCUMENTATION MADE AVAILABLE TO CUSTOMER ARE PROVIDED "AS-IS" AND STRONGLOOP, +ON BEHALF OF ITSELF AND ITS LICENSORS, EXPRESSLY DISCLAIMS ALL WARRANTIES OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, TITLE, +PERFORMANCE, AND ACCURACY AND ANY IMPLIED WARRANTIES ARISING FROM STATUTE, +COURSE OF DEALING, COURSE OF PERFORMANCE, OR USAGE OF TRADE. STRONGLOOP DOES +NOT WARRANT THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR +ERROR-FREE, THAT DEFECTS IN THE SOFTWARE WILL BE CORRECTED OR THAT THE SOFTWARE +WILL PROVIDE OR ENSURE ANY PARTICULAR RESULTS OR OUTCOME. NO ORAL OR WRITTEN +INFORMATION OR ADVICE GIVEN BY STRONGLOOP OR ITS AUTHORIZED REPRESENTATIVES +SHALL CREATE A WARRANTY OR IN ANY WAY INCREASE THE SCOPE OF THIS WARRANTY. +STRONGLOOP IS NOT OBLIGATED TO PROVIDE CUSTOMER WITH UPGRADES TO THE SOFTWARE, +BUT MAY ELECT TO DO SO IN ITS SOLE DISCRETION. SOME JURISDICTIONS DO NOT ALLOW +THE EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT APPLY TO +CUSTOMER.WITHOUT LIMITING THE GENERALITY OF THE FOREGOING DISCLAIMER, THE +SOFTWARE AND DOCUMENTATION ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE IN +THE PLANNING, CONSTRUCTION, MAINTENANCE, CONTROL, OR DIRECT OPERATION OF NUCLEAR +FACILITIES, AIRCRAFT NAVIGATION, CONTROL OR COMMUNICATION SYSTEMS, WEAPONS +SYSTEMS, OR DIRECT LIFE SUPPORT SYSTEMS. +6. Limitation of Liability. + 6.1 Exclusion of Liability. IN NO EVENT WILL STRONGLOOP OR ITS LICENSORS + BE LIABLE UNDER THIS AGREEMENT FOR ANY INDIRECT, RELIANCE, PUNITIVE, + CONSEQUENTIAL, SPECIAL, EXEMPLARY, OR INCIDENTAL DAMAGES OF ANY KIND AND + HOWEVER CAUSED (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF + BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION AND + THE LIKE), EVEN IF STRONGLOOP HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + DAMAGES. CUSTOMER BEARS FULL RESPONSIBILITY FOR USE OF THE SOFTWARE AND + THE SUBSCRIPTION AND STRONGLOOP DOES NOT GUARANTEE THAT THE USE OF THE + SOFTWARE AND SUBSCRIPTION WILL ENSURE THAT CUSTOMER'S NETWORK WILL BE + AVAILABLE, SECURE, MONITORED OR PROTECTED AGAINST ANY DOWNTIME, DENIAL OF + SERVICE ATTACKS, SECUITY BREACHES, HACKERS AND THE LIKE. IN NO EVENT WILL + STRONGLOOP'S CUMULATIVE LIABILITY FOR ANY DAMAGES, LOSSES AND CAUSES OF + ACTION (WHETHER IN CONTRACT, TORT, INCLUDING NEGLIGENCE, OR OTHERWISE) + ARISING OUT OF OR RELATED TO THIS AGREEMENT EXCEED THE GREATER OF ONE + HUNDRED DOLLARS (US$100) OR THE TOTAL SUBSCRIPTION FEES PAID BY CUSTOMER + TO STRONGLOOP IN THE TWELVE (12) MONTHS PRECEDING THE DATE THE CLAIM + ARISES. + 6.2 Limitation of Damages. IN NO EVENT WILL STRONGLOOP'S LICENSORS HAVE + ANY LIABILITY FOR ANY CLAIM ARISING IN CONNECTION WITH THIS AGREEMENT. + THE PROVISIONS OF THIS SECTION 6 ALLOCATE RISKS UNDER THIS AGREEMENT + BETWEEN CUSTOMER, STRONGLOOP AND STRONGLOOP'S SUPPLIERS. THE FOREGOING + LIMITATIONS, EXCLUSIONS AND DISCLAIMERS APPLY TO THE MAXIMUM EXTENT + PERMITTED BY APPLICABLE LAW, EVEN IF ANY REMEDY FAILS IN ITS ESSENTIAL + PURPOSE. + 6.3 Failure of Essential Purpose. THE PARTIES AGREE THAT THESE + LIMITATIONS SHALL APPLY EVEN IF THIS AGREEMENT OR ANY LIMITED REMEDY + SPECIFIED HEREIN IS FOUND TO HAVE FAILED OF ITS ESSENTIAL PURPOSE. + 6.4 Allocation of Risk. The sections on limitation of liability and + disclaimer of warranties allocate the risks in the Agreement between the + parties. This allocation is an essential element of the basis of the + bargain between the parties. +7. Term and Termination. +7.1 This Agreement shall commence on the Effective Date and continue for so long +as Customer has a valid Subscription and is current on the payment of any +Subscription Fees required to be paid for that Subscription (the "Subscription +Term"). Either party may terminate this Agreement immediately upon written +notice to the other party, and the Subscription and licenses granted hereunder +automatically terminate upon the termination of this Agreement. This Agreement +will terminate immediately without notice from StrongLoop if Customer fails to +comply with or otherwise breaches any provision of this Agreement. +7.2 All Sections other than Section 1.1 (Subscriptions) and 1.2 (Licenses) shall +survive the expiration or termination of this Agreement. +8. Subscription Fees and Payments. StrongLoop, Customer agrees to pay +StrongLoop the Subscription Fees as described on the StrongLoop Site for the +Subscription purchased unless a different amount has been agreed to in a +separate agreement between Customer and StrongLoop. In addition, Customer shall +pay all sales, use, value added, withholding, excise taxes and other tax, duty, +custom and similar fees levied upon the delivery or use of the Software and the +Subscriptions described in this Agreement. Fees shall be invoiced in full upon +StrongLoop's acceptance of Customer's purchase order for the Subscription. All +invoices shall be paid in US dollars and are due upon receipt and shall be paid +within thirty (30) days. Payments shall be made without right of set-off or +chargeback. If Customer does not pay the invoices when due, StrongLoop may +charge interest at one percent (1%) per month or the highest rate permitted by +law, whichever is lower, on the unpaid balance from the original due date. If +Customer fails to pay fees in accordance with this Section, StrongLoop may +suspend fulfilling its obligations under this Agreement (including but not +limited to suspending the services under the Subscription) until payment is +received by StrongLoop. If any applicable law requires Customer to withhold +amounts from any payments to StrongLoop under this Agreement, (a) Customer shall +effect such withholding, remit such amounts to the appropriate taxing +authorities and promptly furnish StrongLoop with tax receipts evidencing the +payments of such amounts and (b) the sum payable by Customer upon which the +deduction or withholding is based shall be increased to the extent necessary to +ensure that, after such deduction or withholding, StrongLoop receives and +retains, free from liability for such deduction or withholding, a net amount +equal to the amount StrongLoop would have received and retained absent the +required deduction or withholding. +9. General. +9.1 Compliance with Laws. Customer shall abide by all local, state, federal and +international laws, rules, regulations and orders applying to Customer's use of +the Software, including, without limitation, the laws and regulations of the +United States that may restrict the export and re-export of certain commodities +and technical data of United States origin, including the Software. Customer +agrees that it will not export or re-export the Software without the appropriate +United States or foreign government licenses. +9.2 Entire Agreement. This Agreement constitutes the entire agreement between +the parties concerning the subject matter hereof. This Agreement supersedes all +prior or contemporaneous discussions, proposals and agreements between the +parties relating to the subject matter hereof. No amendment, modification or +waiver of any provision of this Agreement shall be effective unless in writing +and signed by both parties. Any additional or different terms on any purchase +orders issued by Customer to StrongLoop shall not be binding on either party, +are hereby rejected by StrongLoop and void. +9.3 Severability. If any provision of this Agreement is held to be invalid or +unenforceable, the remaining portions shall remain in full force and effect and +such provision shall be enforced to the maximum extent possible so as to effect +the intent of the parties and shall be reformed to the extent necessary to make +such provision valid and enforceable. +9.4 Waiver. No waiver of rights by either party may be implied from any actions +or failures to enforce rights under this Agreement. +9.5 Force Majeure. Neither party shall be liable to the other for any delay or +failure to perform due to causes beyond its reasonable control (excluding +payment of monies due). +9.6 No Third Party Beneficiaries. Unless otherwise specifically stated, the +terms of this Agreement are intended to be and are solely for the benefit of +StrongLoop and Customer and do not create any right in favor of any third party. +9.7 Governing Law and Jurisdiction. This Agreement shall be governed by the +laws of the State of California, without reference to the principles of +conflicts of law. The provisions of the Uniform Computerized Information +Transaction Act and United Nations Convention on Contracts for the International +Sale of Goods shall not apply to this Agreement. The parties shall attempt to +resolve any dispute related to this Agreement informally, initially through +their respective management, and then by non-binding mediation in San Francisco +County, California. Any litigation related to this Agreement shall be brought +in the state or federal courts located in San Francisco County, California, and +only in those courts and each party irrevocably waives any objections to such +venue. +9.8 Notices. All notices must be in writing and shall be effective three (3) +days after the date sent to the other party's headquarters, Attention Chief +Financial Officer. + diff --git a/lib/discovery.js b/lib/discovery.js new file mode 100644 index 0000000..741c41a --- /dev/null +++ b/lib/discovery.js @@ -0,0 +1,380 @@ +module.exports = mixinDiscovery; + +function mixinDiscovery(PostgreSQL) { + var async = require('async'); + + function paginateSQL(sql, orderBy, options) { + options = options || {}; + var limit = ''; + if (options.offset || options.skip || options.limit) { + limit = ' OFFSET ' + (options.offset || options.skip || 0); // Offset starts from 0 + if (options.limit) { + limit = limit + ' LIMIT ' + options.limit; + } + } + if (!orderBy) { + sql += ' ORDER BY ' + orderBy; + } + return sql + limit; + } + + /*! + * Build sql for listing tables + * @param options {all: for all owners, owner: for a given owner} + * @returns {string} The sql statement + */ + function queryTables(options) { + var sqlTables = null; + var owner = options.owner || options.schema; + + if (options.all && !owner) { + sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' + + ' FROM information_schema.tables', 'table_schema, table_name', options); + } else if (owner) { + sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' + + ' FROM information_schema.tables WHERE table_schema=\'' + owner + '\'', 'table_schema, table_name', options); + } else { + sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name",' + + ' table_schema AS "owner" FROM information_schema.tables WHERE table_schema=current_schema()', + 'table_name', options); + } + return sqlTables; + } + + /*! + * Build sql for listing views + * @param options {all: for all owners, owner: for a given owner} + * @returns {string} The sql statement + */ + function queryViews(options) { + var sqlViews = null; + if (options.views) { + + var owner = options.owner || options.schema; + + if (options.all && !owner) { + sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + + ' table_schema AS "owner" FROM information_schema.views', + 'table_schema, table_name', options); + } else if (owner) { + sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + + ' table_schema AS "owner" FROM information_schema.views WHERE table_schema=\'' + owner + '\'', + 'table_schema, table_name', options); + } else { + sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + + ' current_schema() AS "owner" FROM information_schema.views', + 'table_name', options); + } + } + return sqlViews; + } + + /** + * Discover model definitions + * + * @param {Object} options Options for discovery + * @param {Function} [cb] The callback function + */ + PostgreSQL.prototype.discoverModelDefinitions = function (options, cb) { + if (!cb && typeof options === 'function') { + cb = options; + options = {}; + } + options = options || {}; + + var self = this; + var calls = [function (callback) { + self.query(queryTables(options), callback); + }]; + + if (options.views) { + calls.push(function (callback) { + self.query(queryViews(options), callback); + }); + } + async.parallel(calls, function (err, data) { + if (err) { + cb(err, data); + } else { + var merged = []; + merged = merged.concat(data.shift()); + if (data.length) { + merged = merged.concat(data.shift()); + } + cb(err, merged); + } + }); + }; + + /*! + * Normalize the arguments + * @param table string, required + * @param options object, optional + * @param cb function, optional + */ + function getArgs(table, options, cb) { + if ('string' !== typeof table || !table) { + throw new Error('table is a required string argument: ' + table); + } + options = options || {}; + if (!cb && 'function' === typeof options) { + cb = options; + options = {}; + } + if (typeof options !== 'object') { + throw new Error('options must be an object: ' + options); + } + return { + owner: options.owner || options.schema, + table: table, + options: options, + cb: cb + }; + } + + /*! + * Build the sql statement to query columns for a given table + * @param owner + * @param table + * @returns {String} The sql statement + */ + function queryColumns(owner, table) { + var sql = null; + if (owner) { + sql = paginateSQL('SELECT table_schema AS "owner", table_name AS "tableName", column_name AS "columnName", data_type AS "dataType",' + + ' character_maximum_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"' + + ' FROM information_schema.columns' + + ' WHERE table_schema=\'' + owner + '\'' + + (table ? ' AND table_name=\'' + table + '\'' : ''), + 'table_name, ordinal_position', {}); + } else { + sql = paginateSQL('SELECT current_schema() AS "owner", table_name AS "tableName", column_name AS "columnName", data_type AS "dataType",' + + ' character_maximum_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"' + + ' FROM information_schema.columns' + + (table ? ' WHERE table_name=\'' + table + '\'' : ''), + 'table_name, ordinal_position', {}); + } + return sql; + } + + /** + * Discover model properties from a table + * @param {String} table The table name + * @param {Object} options The options for discovery + * @param {Function} [cb] The callback function + * + */ + PostgreSQL.prototype.discoverModelProperties = function (table, options, cb) { + var args = getArgs(table, options, cb); + var owner = args.owner; + table = args.table; + options = args.options; + cb = args.cb; + + var sql = queryColumns(owner, table); + var callback = function (err, results) { + if (err) { + cb(err, results); + } else { + results.map(function (r) { + r.type = mysqlDataTypeToJSONType(r.dataType, r.dataLength); + }); + cb(err, results); + } + }; + this.query(sql, callback); + }; + + /*! + * Build the sql statement for querying primary keys of a given table + * @param owner + * @param table + * @returns {string} + */ +// http://docs.oracle.com/javase/6/docs/api/java/sql/DatabaseMetaData.html#getPrimaryKeys(java.lang.String, java.lang.String, java.lang.String) + + /* + select tc.table_schema, tc.table_name, kc.column_name + from + information_schema.table_constraints tc + join information_schema.key_column_usage kc + on kc.table_name = tc.table_name and kc.table_schema = tc.table_schema + where + tc.constraint_type = 'PRIMARY KEY' + and kc.position_in_unique_constraint is not null + order by tc.table_schema, + tc.table_name, + kc.position_in_unique_constraint; + */ + + function queryForPrimaryKeys(owner, table) { + var sql = 'SELECT kc.table_schema AS "owner", ' + + 'kc.table_name AS "tableName", kc.column_name AS "columnName", kc.ordinal_position AS "keySeq", kc.constraint_name AS "pkName" FROM' + + ' information_schema.key_column_usage kc' + + ' JOIN information_schema.table_constraints tc' + + ' ON kc.table_name = tc.table_name AND kc.table_schema = tc.table_schema' + + ' WHERE tc.constraint_type=\'PRIMARY KEY\' AND kc.position_in_unique_constraint IS NOT NULL'; + + if (owner) { + sql += ' AND kc.table_schema=\'' + owner + '\''; + } + if (table) { + sql += ' AND kc.table_name=\'' + table + '\''; + } + sql += ' ORDER BY kc.table_schema, kc.table_name, kc.ordinal_position'; + return sql; + } + + /** + * Discover primary keys for a given table + * @param {String} table The table name + * @param {Object} options The options for discovery + * @param {Function} [cb] The callback function + */ + PostgreSQL.prototype.discoverPrimaryKeys = function (table, options, cb) { + var args = getArgs(table, options, cb); + var owner = args.owner; + table = args.table; + options = args.options; + cb = args.cb; + + var sql = queryForPrimaryKeys(owner, table); + this.query(sql, cb); + }; + + /*! + * Build the sql statement for querying foreign keys of a given table + * @param owner + * @param table + * @returns {string} + */ + /* + SELECT + tc.constraint_name, tc.table_name, kcu.column_name, + ccu.table_name AS foreign_table_name, + ccu.column_name AS foreign_column_name + FROM + information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu + ON tc.constraint_name = kcu.constraint_name + JOIN information_schema.constraint_column_usage AS ccu + ON ccu.constraint_name = tc.constraint_name + WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name='mytable'; + + */ + function queryForeignKeys(owner, table) { + var sql = + 'SELECT tc.table_schema AS "fkOwner", tc.constraint_name AS "fkName", tc.table_name AS "fkTableName",' + + ' kcu.column_name AS "fkColumnName", kcu.ordinal_position AS "keySeq",' + + ' ccu.table_schema AS "pkOwner", \'PK\' AS "pkName", ' + + ' ccu.table_name AS "pkTableName", ccu.column_name AS "pkColumnName"' + + ' FROM information_schema.table_constraints tc' + + ' JOIN information_schema.key_column_usage AS kcu' + + ' ON tc.constraint_schema = kcu.constraint_schema AND tc.constraint_name = kcu.constraint_name' + + ' JOIN information_schema.constraint_column_usage ccu' + + ' ON ccu.constraint_schema = tc.constraint_schema AND ccu.constraint_name = tc.constraint_name' + + ' WHERE tc.constraint_type = \'FOREIGN KEY\''; + if (owner) { + sql += ' AND tc.table_schema=\'' + owner + '\''; + } + if (table) { + sql += ' AND tc.table_name=\'' + table + '\''; + } + return sql; + } + + /** + * Discover foreign keys for a given table + * @param {String} table The table name + * @param {Object} options The options for discovery + * @param {Function} [cb] The callback function + */ + PostgreSQL.prototype.discoverForeignKeys = function (table, options, cb) { + var args = getArgs(table, options, cb); + var owner = args.owner; + table = args.table; + options = args.options; + cb = args.cb; + + var sql = queryForeignKeys(owner, table); + this.query(sql, cb); + }; + + /*! + * Retrieves a description of the foreign key columns that reference the given table's primary key columns (the foreign keys exported by a table). + * They are ordered by fkTableOwner, fkTableName, and keySeq. + * @param owner + * @param table + * @returns {string} + */ + function queryExportedForeignKeys(owner, table) { + var sql = 'SELECT kcu.constraint_name AS "fkName", kcu.table_schema AS "fkOwner", kcu.table_name AS "fkTableName",' + + ' kcu.column_name AS "fkColumnName", kcu.ordinal_position AS "keySeq",' + + ' \'PK\' AS "pkName", ccu.table_schema AS "pkOwner",' + + ' ccu.table_name AS "pkTableName", ccu.column_name AS "pkColumnName"' + + ' FROM' + + ' information_schema.constraint_column_usage ccu' + + ' JOIN information_schema.key_column_usage kcu' + + ' ON ccu.constraint_schema = kcu.constraint_schema AND ccu.constraint_name = kcu.constraint_name' + + ' WHERE kcu.position_in_unique_constraint IS NOT NULL'; + if (owner) { + sql += ' and ccu.table_schema=\'' + owner + '\''; + } + if (table) { + sql += ' and ccu.table_name=\'' + table + '\''; + } + sql += ' order by kcu.table_schema, kcu.table_name, kcu.ordinal_position'; + + return sql; + } + + /** + * Discover foreign keys that reference to the primary key of this table + * @param {String} table The table name + * @param {Object} options The options for discovery + * @param {Function} [cb] The callback function + */ + PostgreSQL.prototype.discoverExportedForeignKeys = function (table, options, cb) { + var args = getArgs(table, options, cb); + var owner = args.owner; + table = args.table; + options = args.options; + cb = args.cb; + + var sql = queryExportedForeignKeys(owner, table); + this.query(sql, cb); + }; + + function mysqlDataTypeToJSONType(mysqlType, dataLength) { + var type = mysqlType.toUpperCase(); + switch (type) { + case 'BOOLEAN': + return 'Boolean'; + case 'CHARACTER VARYING': + case 'VARCHAR': + case 'CHARACTER': + case 'TEXT': + return 'String'; + case 'BYTEA': + return 'Binary'; + case 'SMALLINT': + case 'INTEGER': + case 'BIGINT': + case 'DECIMAL': + case 'NUMERIC': + case 'REAL': + case 'DOUBLE PRECISION': + case 'SERIAL': + case 'BIGSERIAL': + return 'Number'; + case 'DATE': + case 'TIMESTAMP': + case 'TIME': + return 'Date'; + case 'POINT': + return 'GeoPoint'; + default: + return 'String'; + } + } +} diff --git a/lib/mssql.js b/lib/mssql.js index 66c9d3e..1f76eb4 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -2,53 +2,72 @@ var mssql = require("mssql"); var jdb = require("loopback-datasource-juggler"); var util = require("util"); +var debug = require('debug')('loopback:connector:mssql'); var name = "mssql"; exports.name = name; exports.initialize = function initializeSchema(dataSource, callback) { - if (!mssql || !dataSource.settings.host || !dataSource.settings.database) { - return; - } - - var conn_str = "Driver={SQL Server Native Client 11.0};Server=" + dataSource.settings.host + ";"; - var trusted_str = "Trusted_Connection={Yes};Database={" + dataSource.settings.database + "};"; - - //if we have a username and password then we use a credential connection string - if (dataSource.settings.username && dataSource.settings.password) { - conn_str += "UID=" + dataSource.settings.username + ";PWD=" - + dataSource.settings.password + ";Database={" - + dataSource.settings.database + "};"; - } else { - conn_str += trusted_str; - } - mssql.open(conn_str, function (err, conn) { - if (err) - throw err; - - //debugger; - dataSource.client = conn; - dataSource.connector = new MsSQL(dataSource.client); + var settings = dataSource.settings || {}; + debug('Settings: %j', settings); + var driver = new MsSQL(settings); + dataSource.connector = driver; + driver.connect(function(err, connection) { + dataSource.client = connection; dataSource.connector.dataSource = dataSource; dataSource.connector.tableNameID = dataSource.settings.tableNameID; - callback(); + callback && callback(err, connection); }); }; -function MsSQL(client) { - this.name = name; +function MsSQL(settings) { + this.constructor.super_.call(this, 'postgresql', settings); + // this.name = name; + // this.settings = settings || {}; + this.settings.server = this.settings.host; this._models = {}; this._pkids = {}; this._idxNames = {}; - this.client = client; } util.inherits(MsSQL, jdb.BaseSQL); MsSQL.newline = "\r\n"; -MsSQL.prototype.query = function (sql, optionsOrCallback, Callback) { +function format(sql, params) { + if (Array.isArray(params)) { + var count = 0; + var index = -1; + while (count < params.length) { + index = sql.indexOf('(?)'); + if (index === -1) { + return sql; + } + sql = sql.substring(0, index) + params[count] + sql.substring(index + 3); + count++; + } + } + return sql; +} + +MsSQL.prototype.connect = function(callback) { + var self = this; + if(self.client) { + return process.nextTick(callback); + } + var connection = new mssql.Connection(this.settings, function (err) { + if (err) { + debug('Connection error: ', err); + return callback(err); + } + debug('Connection established: ', self.settings.server); + self.client = connection; + callback(err, connection); + }); +}; + +MsSQL.prototype.query = function (sql, optionsOrCallback, callback) { //debugger; var hasOptions = true; var options = null; @@ -56,23 +75,19 @@ MsSQL.prototype.query = function (sql, optionsOrCallback, Callback) { if (typeof optionsOrCallback === "function") { hasOptions = false; cb = optionsOrCallback; - // console.log(sql); } else { options = optionsOrCallback; - cb = Callback; - // console.log(options); - // console.log(sql); + cb = callback; + } + if(hasOptions) { + sql = format(sql, options); } + debug('SQL: %s %j', sql, options); if (!this.dataSource.connected) { - return this.dataSource.on('connected', function () { - if (hasOptions) { - this.query(sql, options, cb); - } else { - this.query(sql, cb); - } + return this.dataSource.once('connected', function () { + this.query(sql, cb); }.bind(this)); } - var client = this.client; var time = Date.now(); var log = this.log; if (typeof cb !== 'function') { @@ -80,16 +95,16 @@ MsSQL.prototype.query = function (sql, optionsOrCallback, Callback) { } var innerCB = function (err, data) { - if (log) log(sql, time); - cb(err, data); + debug('Result: %j %j', err, data); + if (log) { + log(sql, time); + } + cb && cb(err, data); }; - if (hasOptions) { - this.client.query(sql, options, innerCB); - } else { - this.client.query(sql, innerCB); - } - + var request = new mssql.Request(this.client); + // request.verbose = true; + request.query(sql, innerCB); }; MsSQL.prototype.disconnect = function disconnect() { @@ -106,28 +121,30 @@ MsSQL.prototype.disconnect = function disconnect() { // properties: ... // settings: ... // } -MsSQL.prototype.define = function (descr) { - if (!descr.settings) descr.settings = {}; +MsSQL.prototype.define = function (modelDefinition) { + if (!modelDefinition.settings) { + modelDefinition.settings = {}; + } - this._models[descr.model.modelName] = descr; + this._models[modelDefinition.model.modelName] = modelDefinition; //default pkid is "id" var id = "id"; //override the default with another convention, 'TableName'ID, if defined in the connector settings if (this.tableNameID) { - id = descr.model.modelName + "ID"; + id = modelDefinition.model.modelName + "ID"; } //override both defaults if a primaryKey is specified in a property - Object.keys(descr.properties).forEach(function (propName) { - var propVal = descr.properties[propName]; + Object.keys(modelDefinition.properties).forEach(function (propName) { + var propVal = modelDefinition.properties[propName]; if (typeof propVal === "object" && propVal.primaryKey) { return id = propName; } }); - this._pkids[descr.model.modelName] = id; + this._pkids[modelDefinition.model.modelName] = id; //track database index names for this model - this._idxNames[descr.model.modelName] = []; + this._idxNames[modelDefinition.model.modelName] = []; }; // MsSQL.prototype.defineProperty = function (model, prop, params) { @@ -141,29 +158,22 @@ MsSQL.prototype.create = function (model, data, callback) { //debugger; var fieldsAndData = this.buildInsert(model, data); var tblName = this.tableEscaped(model); - var sql = "INSERT INTO [dbo].[" + tblName + "] (" + fieldsAndData.fields + ")" + MsSQL.newline; + var sql = "INSERT INTO " + tblName + " (" + fieldsAndData.fields + ")" + MsSQL.newline; sql += "VALUES (" + fieldsAndData.paramPlaceholders + ");" + MsSQL.newline; sql += "SELECT IDENT_CURRENT('" + tblName + "') AS insertId;"; - // console.log(sql); - // console.log(fieldsAndData.params); this.query(sql, fieldsAndData.params, function (err, results) { - //console.log(err); if (err) { return callback(err); } - //console.log(results); //msnodesql will execute the callback for each statement that get's executed, we're only interested in the one that returns with the insertId if (results.length > 0 && results[0].insertId) { - //console.log('new id: ' + results[0].insertId); callback(null, results[0].insertId); } }); }; MsSQL.prototype.updateOrCreate = function (model, data, callback) { - //debugger; - //console.log('updateOrCreate'); var self = this; var props = this._models[model].properties; var tblName = this.tableEscaped(model); @@ -179,7 +189,7 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { //check for the "id" key also, for backwards compatibility with the jugglingdb hardcoded id system if (key !== "id" && key !== modelPKID) { fieldNames.push("[" + key + "]"); - fieldValues.push(self.toDatabase(props[key], data[key])); + fieldValues.push(self.toDatabase(props[key], data[key], true)); fieldValuesPlaceholders.push("(?)"); combined.push(key + "=(?)"); } @@ -193,24 +203,24 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { } if (yn) { //update - sql = "UPDATE [dbo].[" + tblName + "]" + MsSQL.newline; + sql = "UPDATE " + tblName + MsSQL.newline; sql += "SET " + combined.join() + MsSQL.newline; sql += "WHERE [" + modelPKID + "] = (?);" + MsSQL.newline; sql += "SELECT " + id + " AS pkid;"; fieldValues.push(id); } else { //insert with identity_insert - sql = "SET IDENTITY_INSERT [dbo].[" + tblName + "] ON;" + MsSQL.newline; - sql += "INSERT INTO [dbo].[" + tblName + "] ([" + modelPKID + "]," + fieldNames.join() + ")" + MsSQL.newline; + sql = "SET IDENTITY_INSERT " + tblName + " ON;" + MsSQL.newline; + sql += "INSERT INTO " + tblName + " ([" + modelPKID + "]," + fieldNames.join() + ")" + MsSQL.newline; sql += "VALUES (" + id + "," + fieldValuesPlaceholders.join() + ");" + MsSQL.newline; - sql += "SET IDENTITY_INSERT [dbo].[" + tblName + "] OFF;" + MsSQL.newline; + sql += "SET IDENTITY_INSERT " + tblName + " OFF;" + MsSQL.newline; sql += "SELECT " + id + " AS pkid;"; } doQuery(sql, fieldValues); }); } else { //insert - sql = "INSERT INTO [dbo].[" + tblName + "] (" + fieldNames.join() + ")" + MsSQL.newline; + sql = "INSERT INTO " + tblName + " (" + fieldNames.join() + ")" + MsSQL.newline; sql += "VALUES (" + fieldValuesPlaceholders.join() + ");" + MsSQL.newline; sql += "SELECT IDENT_CURRENT('" + tblName + "') AS pkid;"; doQuery(sql, fieldValues); @@ -248,25 +258,23 @@ MsSQL.prototype.updateAttributes = function (model, id, data, cb) { } else { data[modelPKID] = id; } - //console.log(id); this.exists(model, id, function (err, yn) { if (err) { - console.log(err); - return cb("An error occurred when checking for the existance of this record"); + debug(err); + return cb && cb("An error occurred when checking for the existance of this record"); } if (yn) { //only call this after verifying that the record exists, we don't want to create it if it doesn't. return self.updateOrCreate(model, data, cb); } - return cb("A " + tblName + " doesn't exist with a " + modelPKID + " of " + id, id); + return cb && cb("A " + tblName + " doesn't exist with a " + modelPKID + " of " + id, id); }); }; MsSQL.prototype.exists = function (model, id, callback) { var tblName = this.tableEscaped(model); var modelPKID = this._pkids[model]; - var sql = "SELECT COUNT(*) cnt FROM [dbo].[" + tblName + "] WHERE [" + modelPKID + "] = (?)" - //console.log(sql); + var sql = "SELECT COUNT(*) cnt FROM " + tblName + " WHERE [" + modelPKID + "] = (?)" this.query(sql, [id], function (err, results) { if (err) return callback(err); callback(null, results[0].cnt >= 1); @@ -274,7 +282,7 @@ MsSQL.prototype.exists = function (model, id, callback) { }; MsSQL.prototype.count = function (model, cb, where) { - var sql = "SELECT COUNT(*) cnt FROM [dbo].[" + this.tableEscaped(model) + "]" + MsSQL.newline; + var sql = "SELECT COUNT(*) cnt FROM " + this.tableEscaped(model) + MsSQL.newline; var props = this._models[model].properties; if (where !== null) { @@ -283,33 +291,37 @@ MsSQL.prototype.count = function (model, cb, where) { this.query(sql, function (err, data) { if (err) { - return cb(err); + return cb && cb(err); } - cb(null, data[0].cnt); + cb && cb(null, data[0].cnt); }); return sql; }; -MsSQL.prototype.destroyAll = function (model, cb) { - var sql = "DELETE FROM [dbo].[" + this.tableEscaped(model) + "]"; - this.query(sql, function (err, data) { - //don't bother returning data, it's a delete statement - if (err) { - return cb(err); - } - cb(null); - }); + +MsSQL.prototype.destroyAll = function (model, where, cb) { + if (!cb && 'function' === typeof where) { + cb = where; + where = undefined; + } + var props = this._models[model].properties; + this.query('DELETE FROM ' + + this.tableEscaped(model) + ' ' + this.buildWhere(where || {}, props), + function (err, data) { + cb && cb(err, data); + }.bind(this)); + }; MsSQL.prototype.destroy = function (model, id, cb) { - var sql = "DELETE FROM [dbo].[" + this.tableEscaped(model) + "]" + MsSQL.newline; + var sql = "DELETE FROM " + this.tableEscaped(model) + MsSQL.newline; sql += "WHERE [" + this._pkids[model] + "] = (?)"; this.query(sql, [id], function (err, data) { if (err) { - return cb(err); + return cb && cb(err); } - cb(null); + cb && cb(null); }); }; @@ -317,10 +329,11 @@ MsSQL.prototype.find = function (model, id, callback) { //debugger; var tblName = this.tableEscaped(model); var modelPKID = this._pkids[model]; - var sql = "SELECT * FROM [dbo].[" + tblName + "] WHERE [" + modelPKID + "] = (?)"; - //console.log(sql); + var sql = "SELECT * FROM " + tblName + " WHERE [" + modelPKID + "] = (?)"; this.query(sql, [id], function (err, results) { - if (err) return callback(err); + if (err) { + return callback(err); + } callback(null, this.fromDatabase(model, results[0])); }.bind(this)); }; @@ -339,21 +352,32 @@ MsSQL.prototype.buildInsert = function (model, data) { if (props[key]) { insertIntoFields.push("[" + key + "]"); paramPlaceholders.push("(?)"); - params.push(this.toDatabase(props[key], data[key])); + params.push(this.toDatabase(props[key], data[key], true)); } }.bind(this)); return { fields: insertIntoFields.join(), paramPlaceholders: paramPlaceholders.join(), params: params }; } -//unchanged from MySql connector, credit to dgsan +// Convert to ISO8601 format YYYY-MM-DDThh:mm:ss[.mmm] function dateToMsSql(val) { - return (val.getUTCMonth() + 1) + '-' + - val.getUTCDate() + '-' + - val.getUTCFullYear() + ' ' + - fillZeros(val.getUTCHours()) + ':' + + + var dateStr = val.getUTCFullYear() + '-' + + fillZeros(val.getUTCMonth() + 1) + '-' + + fillZeros(val.getUTCDate()) + + 'T' + fillZeros(val.getUTCHours()) + ':' + fillZeros(val.getUTCMinutes()) + ':' + - fillZeros(val.getUTCSeconds()) + '.00'; + fillZeros(val.getUTCSeconds()) + '.'; + + var ms = val.getUTCMilliseconds(); + if (ms < 10) { + ms = '00' + ms; + } else if (ms < 100) { + ms = '0' + ms; + } else { + ms = '' + ms; + } + return dateStr + ms; function fillZeros(v) { return v < 10 ? '0' + v : v; @@ -381,7 +405,7 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { return this.toDatabase(prop, val[0], true) + ' AND ' + this.toDatabase(prop, val[1], true); - } else if (operator == 'inq' || operator == 'nin') { + } else if (operator === 'inq' || operator === 'nin') { //always wrap inq/nin values in single quotes when they are string types, it's never used for insert/updates if (!(val.propertyIsEnumerable('length')) && typeof val === 'object' && typeof val.length === 'number') { //if value is array //check if it is an array of string, because in that cause we need to wrap them in single quotes @@ -416,8 +440,6 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { if (!val.toUTCString) { val = new Date(val); } - //return val; - //console.log('\'' + dateToMsSql(val) + '\''); val = dateToMsSql(val); if (wrap) { val = "'" + val + "'"; @@ -449,7 +471,9 @@ MsSQL.prototype.fromDatabase = function (model, data) { val = (true && val); //convert to a boolean type from number } if (props[key].type.name === 'Date' && val !== null) { - val = new Date(val.toString().replace(/GMT.*$/, 'GMT')); + if(!(val instanceof Date)) { + val = new Date(val.toString()); + } } } data[key] = val; @@ -461,23 +485,76 @@ MsSQL.prototype.escapeName = function (name) { return name.replace(/\./g, '_'); }; +MsSQL.prototype.columnEscaped = function (model, property) { + return '[' + this.escapeName(this.column(model, property)) +']'; +}; + +MsSQL.prototype.tableEscaped = function (model) { + return '[dbo].[' + this.escapeName(this.table(model)) +']'; +}; + + MsSQL.prototype.escapeKey = function (key) { return key; }; +MsSQL.prototype.getColumns = function (model, props) { + var cols = this._models[model].properties; + if (!cols) { + return '*'; + } + var self = this; + var keys = Object.keys(cols); + if (Array.isArray(props) && props.length > 0) { + // No empty array, including all the fields + keys = props; + } else if ('object' === typeof props && Object.keys(props).length > 0) { + // { field1: boolean, field2: boolean ... } + var included = []; + var excluded = []; + keys.forEach(function (k) { + if (props[k]) { + included.push(k); + } else if ((k in props) && !props[k]) { + excluded.push(k); + } + }); + if (included.length > 0) { + keys = included; + } else if (excluded.length > 0) { + excluded.forEach(function (e) { + var index = keys.indexOf(e); + keys.splice(index, 1); + }); + } + } + var names = keys.map(function (c) { + return self.columnEscaped(model, c); + }); + return names.join(', '); +}; + MsSQL.prototype.all = function (model, filter, callback) { - var sql = "SELECT * FROM [dbo].[" + this.tableEscaped(model) + "]" + MsSQL.newline; + var sql = "SELECT " + this.getColumns(model, filter.fields) + + " FROM " + this.tableEscaped(model) + MsSQL.newline; var self = this; var props = this._models[model].properties; if (filter) { if (filter.where) { sql += this.buildWhere(filter.where, props) + MsSQL.newline; - //console.log(sql); } if (filter.order) { - sql += this.buildOrderBy(filter.order) + MsSQL.newline; + sql += this.buildOrderBy(model, filter.order) + MsSQL.newline; + } + + if (filter.limit || filter.offset || filter.skip) { + if (!filter.order) { + var idName = self.idName(model); + sql += 'ORDER BY ' + self.columnEscaped(model, idName) + MsSQL.newline; + } + sql += buildLimit(filter.limit, filter.offset || filter.skip); } } @@ -498,13 +575,31 @@ MsSQL.prototype.all = function (model, filter, callback) { return sql; }; -MsSQL.prototype.buildOrderBy = function (order) { +MsSQL.prototype.buildOrderBy = function(model, order) { + var self = this; if (typeof order === 'string') { order = [order]; } - return 'ORDER BY ' + order.join(','); + return 'ORDER BY ' + order.map(function (o) { + var t = o.split(/[\s,]+/); + if (t.length === 1) { + return self.columnEscaped(model, o); + } + return self.columnEscaped(model, t[0]) + ' ' + t[1]; + }).join(', '); }; +function buildLimit(limit, offset) { + if (isNaN(offset)) { + offset = 0; + } + var sql = 'OFFSET ' + offset + ' ROWS'; + if (limit) { + sql += ' FETCH NEXT ' + limit + ' ROWS ONLY'; + } + return sql; +} + MsSQL.prototype.buildWhere = function (conds, props) { // debugger; var self = this; @@ -813,7 +908,7 @@ MsSQL.prototype.singleIndexSettingsSQL = function (model, prop, add) { name = i.name; } this._idxNames[model].push[name]; - var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON [dbo].[" + tblName + "]" + MsSQL.newline; + var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON " + tblName + MsSQL.newline; cmd += "(" + MsSQL.newline; cmd += " [" + prop + "] " + type; cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];" + MsSQL.newline; @@ -849,7 +944,7 @@ MsSQL.prototype.indexSettingsSQL = function (model, prop) { name += kind + "_" + type + "_idx" this._idxNames[model].push[name]; - var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON [dbo].[" + tblName + "]" + MsSQL.newline; + var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON " + tblName + MsSQL.newline; cmd += "(" + MsSQL.newline; cmd += columns.join("," + MsSQL.newline); cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];" + MsSQL.newline; @@ -868,16 +963,19 @@ MsSQL.prototype.automigrate = function (cb) { Object.keys(this._models).forEach(function (model) { wait += 1; self.dropTable(model, function (err) { - // console.log('drop', model); - if (err) console.log(err); + if (err) { + debug(err); + } self.createTable(model, function (err) { // console.log('create', model); - if (err) console.log(err); + if (err) { + debug(err); + } done(); }); }); }); - if (wait === 0) cb(); + if (wait === 0) cb && cb(); function done() { if (--wait === 0 && cb) { @@ -888,22 +986,20 @@ MsSQL.prototype.automigrate = function (cb) { MsSQL.prototype.dropTable = function (model, cb) { var tblName = this.tableEscaped(model); - var cmd = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[" + tblName + "]') AND type in (N'U'))"; + var cmd = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" + tblName + "') AND type in (N'U'))"; cmd += MsSQL.newline + "BEGIN" + MsSQL.newline; - cmd += " DROP TABLE [dbo].[" + tblName + "]"; + cmd += " DROP TABLE " + tblName; cmd += MsSQL.newline + "END"; - //console.log(cmd); this.command(cmd, cb); }; MsSQL.prototype.createTable = function (model, cb) { var tblName = this.tableEscaped(model); var cmd = "SET ANSI_NULLS ON;" + MsSQL.newline + "SET QUOTED_IDENTIFIER ON;" + MsSQL.newline + "SET ANSI_PADDING ON;" + MsSQL.newline; - cmd += "IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[" + tblName + "]') AND type in (N'U'))" + MsSQL.newline + "BEGIN" + MsSQL.newline; - cmd += "CREATE TABLE [dbo].[" + this.tableEscaped(model) + "] ("; + cmd += "IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" + tblName + "') AND type in (N'U'))" + MsSQL.newline + "BEGIN" + MsSQL.newline; + cmd += "CREATE TABLE " + this.tableEscaped(model) + " ("; cmd += MsSQL.newline + " " + this.propertiesSQL(model) + MsSQL.newline; cmd += ") ON [PRIMARY]" + MsSQL.newline + "END;" + MsSQL.newline; - //console.log(cmd); cmd += this.createIndexes(model); this.command(cmd, cb); }; @@ -955,4 +1051,6 @@ function datatype(p) { break; } return dt; -} \ No newline at end of file +} + +require('./discovery')(MsSQL); \ No newline at end of file diff --git a/package.json b/package.json index fbe53fa..b8d8ced 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,32 @@ { "name": "loopback-connector-mssql", "version": "1.0.0", - "description": "MsSQL connector for LoopBack", + "description": "Microsoft SQL Server connector for LoopBack", "main": "index.js", "dependencies": { - "util": "~0.10.2", - "mssql": "~0.5.0" + "util": "~0.10.3", + "mssql": "~0.5.3", + "debug": "~0.8.0", + "tds": "~0.1.0", + "async": "~0.7.0" }, "peerDependencies": { - "loopback-datasource-juggler":"1.x" + "loopback-datasource-juggler": "1.x" }, "devDependencies": { - "nodeunit": "~0.8.2" + "loopback-datasource-juggler": "1.x", + "mocha": "~1.18.2", + "should": "~1.2.2" + }, + "scripts": { + "test": "./node_modules/.bin/mocha --timeout 5000 -R spec" }, "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-connector-mssql.git" }, - "license": "MIT" + "license": { + "name": "StrongLoop", + "url": "https://github.com/strongloop/loopback-connector-mssql/blob/master/LICENSE" + } } diff --git a/test/commontests.js b/test/commontests.js index 5adb11b..5245d26 100644 --- a/test/commontests.js +++ b/test/commontests.js @@ -1,11 +1,9 @@ -var jdb = require('jugglingdb'), - Schema = jdb.Schema, - commonTest = jdb.test, - db = require("../db/dbconfig"); - -var adapter = require("../"); -var schemaSettings = { host:db.server, database:db.db, username:db.user, password:db.pwd }; -var schema = new Schema(adapter, schemaSettings); +var jdb = require('loopback-datasource-juggler'), + commonTest = jdb.test; + +require('./init'); + +var schema = getSchema(); //run the tests exposed by jugglingdb commonTest(module.exports, schema); diff --git a/test/imported.test.js b/test/imported.test.js new file mode 100644 index 0000000..1c55e2d --- /dev/null +++ b/test/imported.test.js @@ -0,0 +1,10 @@ +describe('mssql imported features', function () { + + before(function () { + require('./init.js'); + }); + + require('loopback-datasource-juggler/test/common.batch.js'); + require('loopback-datasource-juggler/test/include.test.js'); + +}); diff --git a/test/init.js b/test/init.js new file mode 100644 index 0000000..e2f15c8 --- /dev/null +++ b/test/init.js @@ -0,0 +1,42 @@ +module.exports = require('should'); + +var DataSource = require('loopback-datasource-juggler').DataSource; + +var config = {}; +try { + config = require('rc')('loopback', {test: {mssql: {}}}).test.mssql; +} catch(err) { + config = { + user: 'demo', + password: 'L00pBack', + host: 'localhost', + database: 'demo' + }; +} + +global.getConfig = function (options) { + + var dbConf = { + host: config.host || config.hostname || config.server || 'localhost', + port: config.port || 1433, + database: config.database || 'test', + user: config.user || config.username, + password: config.password, + createDatabase: true + }; + + if (options) { + for (var el in options) { + dbConf[el] = options[el]; + } + } + + return dbConf; +}; + +global.getDataSource = global.getSchema = function (options) { + var db = new DataSource(require('../'), getConfig(options)); + return db; +}; + + diff --git a/test/tests.js b/test/tests.js deleted file mode 100644 index 218906e..0000000 --- a/test/tests.js +++ /dev/null @@ -1,4 +0,0 @@ -var reporter = require('nodeunit').reporters.nested; -process.chdir(__dirname); - -reporter.run(['commontests.js']); \ No newline at end of file From 2a6f8302881e29e3902b34723f8759d2575b6c15 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 11 Apr 2014 15:30:05 -0700 Subject: [PATCH 033/239] Removed unused dep --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index b8d8ced..c01ed8c 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ "util": "~0.10.3", "mssql": "~0.5.3", "debug": "~0.8.0", - "tds": "~0.1.0", "async": "~0.7.0" }, "peerDependencies": { From 6ebce74cb6072c904aec8d886b7c8bf24a3afa4a Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 14 Apr 2014 08:44:47 -0700 Subject: [PATCH 034/239] Use idName() for PKs --- lib/mssql.js | 46 +++++++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 1f76eb4..95e9d1a 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -27,7 +27,6 @@ function MsSQL(settings) { // this.settings = settings || {}; this.settings.server = this.settings.host; this._models = {}; - this._pkids = {}; this._idxNames = {}; } @@ -35,6 +34,13 @@ util.inherits(MsSQL, jdb.BaseSQL); MsSQL.newline = "\r\n"; +/*! + * This is a workaround to the limitation that 'msssql' driver doesn't support + * parameterized SQL execution + * @param {String} sql The SQL string with parameters as (?) + * @param {*[]) params An array of parameter values + * @returns {*} The fulfilled SQL string + */ function format(sql, params) { if (Array.isArray(params)) { var count = 0; @@ -128,20 +134,6 @@ MsSQL.prototype.define = function (modelDefinition) { this._models[modelDefinition.model.modelName] = modelDefinition; - //default pkid is "id" - var id = "id"; - //override the default with another convention, 'TableName'ID, if defined in the connector settings - if (this.tableNameID) { - id = modelDefinition.model.modelName + "ID"; - } - //override both defaults if a primaryKey is specified in a property - Object.keys(modelDefinition.properties).forEach(function (propName) { - var propVal = modelDefinition.properties[propName]; - if (typeof propVal === "object" && propVal.primaryKey) { - return id = propName; - } - }); - this._pkids[modelDefinition.model.modelName] = id; //track database index names for this model this._idxNames[modelDefinition.model.modelName] = []; @@ -177,7 +169,7 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { var self = this; var props = this._models[model].properties; var tblName = this.tableEscaped(model); - var modelPKID = this._pkids[model]; + var modelPKID = this.idName(model); //get the correct id of the item using the pkid that they specified var id = data[modelPKID]; var fieldNames = []; @@ -250,7 +242,7 @@ MsSQL.prototype.save = function (model, data, callback) { MsSQL.prototype.updateAttributes = function (model, id, data, cb) { var self = this; var tblName = this.tableEscaped(model); - var modelPKID = this._pkids[model]; + var modelPKID = this.idName(model); //jugglingdb abstract class may have sent up a null value for this id if we aren't using the standard "id" name for the pkid. // if that is the case then set the id to the correct value from the data using the actual pkid name. if (id === null) { @@ -273,7 +265,7 @@ MsSQL.prototype.updateAttributes = function (model, id, data, cb) { MsSQL.prototype.exists = function (model, id, callback) { var tblName = this.tableEscaped(model); - var modelPKID = this._pkids[model]; + var modelPKID = this.idName(model); var sql = "SELECT COUNT(*) cnt FROM " + tblName + " WHERE [" + modelPKID + "] = (?)" this.query(sql, [id], function (err, results) { if (err) return callback(err); @@ -316,7 +308,7 @@ MsSQL.prototype.destroyAll = function (model, where, cb) { MsSQL.prototype.destroy = function (model, id, cb) { var sql = "DELETE FROM " + this.tableEscaped(model) + MsSQL.newline; - sql += "WHERE [" + this._pkids[model] + "] = (?)"; + sql += "WHERE [" + this.idName(model) + "] = (?)"; this.query(sql, [id], function (err, data) { if (err) { return cb && cb(err); @@ -328,7 +320,7 @@ MsSQL.prototype.destroy = function (model, id, cb) { MsSQL.prototype.find = function (model, id, callback) { //debugger; var tblName = this.tableEscaped(model); - var modelPKID = this._pkids[model]; + var modelPKID = this.idName(model); var sql = "SELECT * FROM " + tblName + " WHERE [" + modelPKID + "] = (?)"; this.query(sql, [id], function (err, results) { if (err) { @@ -343,7 +335,7 @@ MsSQL.prototype.buildInsert = function (model, data) { var paramPlaceholders = []; var params = []; var props = this._models[model].properties; - var modelPKID = this._pkids[model]; + var modelPKID = this.idName(model); //remove the pkid column if it's in the data, since we're going to insert a new record, not update an existing one. delete data[modelPKID]; //delete the hardcoded id property that jugglindb automatically creates @@ -368,7 +360,7 @@ function dateToMsSql(val) { + 'T' + fillZeros(val.getUTCHours()) + ':' + fillZeros(val.getUTCMinutes()) + ':' + fillZeros(val.getUTCSeconds()) + '.'; - + var ms = val.getUTCMilliseconds(); if (ms < 10) { ms = '00' + ms; @@ -461,7 +453,7 @@ MsSQL.prototype.fromDatabase = function (model, data) { return null; } //create an "id" property in the data for backwards compatibility with juggling-db - data.id = data[this._pkids[model]]; + data.id = data[this.idName(model)]; var props = this._models[model].properties; //look for date values in the data, convert them from the database to a javascript date object Object.keys(data).forEach(function (key) { @@ -868,10 +860,10 @@ MsSQL.prototype.propertiesSQL = function (model) { // debugger; var self = this; var objModel = this._models[model]; - var modelPKID = this._pkids[model]; + var modelPKID = this.idName(model); var sql = ["[" + modelPKID + "] [int] IDENTITY(1,1) NOT NULL"]; - Object.keys(this._models[model].properties).forEach(function (prop) { + Object.keys(objModel.properties).forEach(function (prop) { if (prop === modelPKID) { return; } @@ -907,7 +899,7 @@ MsSQL.prototype.singleIndexSettingsSQL = function (model, prop, add) { if (i.name) { name = i.name; } - this._idxNames[model].push[name]; + this._idxNames[model].push(name); var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON " + tblName + MsSQL.newline; cmd += "(" + MsSQL.newline; cmd += " [" + prop + "] " + type; @@ -942,7 +934,7 @@ MsSQL.prototype.indexSettingsSQL = function (model, prop) { }); name += kind + "_" + type + "_idx" - this._idxNames[model].push[name]; + this._idxNames[model].push(name); var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON " + tblName + MsSQL.newline; cmd += "(" + MsSQL.newline; From fa06ff865ee843a8822b035214f9bbf56a2db621 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 14 Apr 2014 09:03:39 -0700 Subject: [PATCH 035/239] More clean up --- lib/mssql.js | 130 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 90 insertions(+), 40 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 95e9d1a..6fedc1a 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -22,7 +22,7 @@ exports.initialize = function initializeSchema(dataSource, callback) { }; function MsSQL(settings) { - this.constructor.super_.call(this, 'postgresql', settings); + this.constructor.super_.call(this, name, settings); // this.name = name; // this.settings = settings || {}; this.settings.server = this.settings.host; @@ -203,7 +203,8 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { } else { //insert with identity_insert sql = "SET IDENTITY_INSERT " + tblName + " ON;" + MsSQL.newline; - sql += "INSERT INTO " + tblName + " ([" + modelPKID + "]," + fieldNames.join() + ")" + MsSQL.newline; + sql += "INSERT INTO " + tblName + " ([" + modelPKID + "]," + + fieldNames.join() + ")" + MsSQL.newline; sql += "VALUES (" + id + "," + fieldValuesPlaceholders.join() + ");" + MsSQL.newline; sql += "SET IDENTITY_INSERT " + tblName + " OFF;" + MsSQL.newline; sql += "SELECT " + id + " AS pkid;"; @@ -223,18 +224,21 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { if (err) { return callback(err); } - //msnodesql will execute the callback for each statement that get's executed, we're only interested in the one that returns with the pkid + //msnodesql will execute the callback for each statement that get's + // executed, we're only interested in the one that returns with the pkid if (results.length > 0 && results[0].pkid) { data[modelPKID] = results[0].pkid; //#jdb id compatibility# - data.id = results[0].pkid; //set the id property also, to play nice with the jugglingdb abstract class implementation. + data.id = results[0].pkid; //set the id property also, to play nice + // with the jugglingdb abstract class implementation. callback(err, data); } }); } }; -//redundant, same functionality as "updateOrCreate" right now. Maybe in the future some validation will happen here. +//redundant, same functionality as "updateOrCreate" right now. Maybe in the +// future some validation will happen here. MsSQL.prototype.save = function (model, data, callback) { this.updateOrCreate(model, data, callback); }; @@ -243,8 +247,10 @@ MsSQL.prototype.updateAttributes = function (model, id, data, cb) { var self = this; var tblName = this.tableEscaped(model); var modelPKID = this.idName(model); - //jugglingdb abstract class may have sent up a null value for this id if we aren't using the standard "id" name for the pkid. - // if that is the case then set the id to the correct value from the data using the actual pkid name. + //jugglingdb abstract class may have sent up a null value for this id if we + // aren't using the standard "id" name for the pkid. + // if that is the case then set the id to the correct value from the data + // using the actual pkid name. if (id === null) { id = data[modelPKID]; } else { @@ -259,7 +265,8 @@ MsSQL.prototype.updateAttributes = function (model, id, data, cb) { //only call this after verifying that the record exists, we don't want to create it if it doesn't. return self.updateOrCreate(model, data, cb); } - return cb && cb("A " + tblName + " doesn't exist with a " + modelPKID + " of " + id, id); + return cb && cb("A " + tblName + " doesn't exist with a " + modelPKID + + " of " + id, id); }); }; @@ -336,7 +343,8 @@ MsSQL.prototype.buildInsert = function (model, data) { var params = []; var props = this._models[model].properties; var modelPKID = this.idName(model); - //remove the pkid column if it's in the data, since we're going to insert a new record, not update an existing one. + //remove the pkid column if it's in the data, since we're going to insert a + // new record, not update an existing one. delete data[modelPKID]; //delete the hardcoded id property that jugglindb automatically creates delete data.id @@ -348,7 +356,9 @@ MsSQL.prototype.buildInsert = function (model, data) { } }.bind(this)); - return { fields: insertIntoFields.join(), paramPlaceholders: paramPlaceholders.join(), params: params }; + return { fields: insertIntoFields.join(), + paramPlaceholders: paramPlaceholders.join(), + params: params }; } // Convert to ISO8601 format YYYY-MM-DDThh:mm:ss[.mmm] @@ -382,7 +392,8 @@ function dateToMsSql(val) { // because we are using the msnodesql driver's parameterization feature // in the case of building a where clause we want to wrap strings in single quotes // so they can be concatenated into the sql statement -// use the 'wrap' parameter to tell the function which case it's handling (false=raw, true=single quotes) +// use the 'wrap' parameter to tell the function which case it's handling +// (false=raw, true=single quotes) MsSQL.prototype.toDatabase = function (prop, val, wrap) { if (val === null || typeof val === 'undefined') { // return 'NULL'; @@ -398,9 +409,12 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { ' AND ' + this.toDatabase(prop, val[1], true); } else if (operator === 'inq' || operator === 'nin') { - //always wrap inq/nin values in single quotes when they are string types, it's never used for insert/updates - if (!(val.propertyIsEnumerable('length')) && typeof val === 'object' && typeof val.length === 'number') { //if value is array - //check if it is an array of string, because in that cause we need to wrap them in single quotes + //always wrap inq/nin values in single quotes when they are string types, + // it's never used for insert/updates + if (!(val.propertyIsEnumerable('length')) && typeof val === 'object' + && typeof val.length === 'number') { //if value is array + //check if it is an array of string, because in that cause we need to + // wrap them in single quotes if (typeof val[0] === 'string') { return "'" + val.join("','") + "'"; } @@ -482,7 +496,10 @@ MsSQL.prototype.columnEscaped = function (model, property) { }; MsSQL.prototype.tableEscaped = function (model) { - return '[dbo].[' + this.escapeName(this.table(model)) +']'; + // Check if there is a 'schema' property for mssql + var mssqlMeta = this._models[model].settings && this._models[model].settings.mssql; + var schemaName = (mssqlMeta && (mssqlMeta.schema || mssqlMeta.schemaName)) || 'dbo'; + return '[' + schemaName + '].[' + this.escapeName(this.table(model)) + ']'; }; @@ -604,8 +621,8 @@ MsSQL.prototype.buildWhere = function (conds, props) { } else if (conds[key].constructor.name === 'Object') { var condType = Object.keys(conds[key])[0]; var sqlCond = keyEscaped; - if ((condType == 'inq' || condType == 'nin') && val.length == 0) { - cs.push(condType == 'inq' ? 0 : 1); + if ((condType === 'inq' || condType === 'nin') && val.length === 0) { + cs.push(condType === 'inq' ? 0 : 1); return true; } if (condType === "max") { @@ -645,7 +662,7 @@ MsSQL.prototype.buildWhere = function (conds, props) { sqlCond += ' != '; break; } - sqlCond += (condType == 'inq' || condType == 'nin') ? '(' + val + ')' : val; + sqlCond += (condType === 'inq' || condType === 'nin') ? '(' + val + ')' : val; cs.push(sqlCond); } else { cs.push(keyEscaped + ' = ' + val); @@ -711,6 +728,8 @@ MsSQL.prototype.buildWhere = function (conds, props) { MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, checkOnly) { var self = this; var m = this._models[model]; + var idName = this.idName(model); + var propNames = Object.keys(m.properties).filter(function (name) { return !!m.properties[name]; }); @@ -736,7 +755,7 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, // change/add new fields propNames.forEach(function (propName) { - if (propName === 'id') return; + if (propName === idName) return; var found; actualFields.forEach(function (f) { if (f.Field === propName) { @@ -754,7 +773,7 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, // drop columns actualFields.forEach(function (f) { var notFound = !~propNames.indexOf(f.Field); - if (f.Field === 'id') return; + if (f.Field === idName) return; if (notFound || !m.properties[f.Field]) { sql.push('DROP COLUMN `' + f.Field + '`'); } @@ -762,8 +781,11 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, // remove indexes aiNames.forEach(function (indexName) { - if (indexName === 'id' || indexName === 'PRIMARY') return; - if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || m.properties[indexName] && !m.properties[indexName].index) { + if (indexName === idName || indexName === 'PRIMARY') { + return; + } + if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] + || m.properties[indexName] && !m.properties[indexName].index) { sql.push('DROP INDEX `' + indexName + '`'); } else { // first: check single (only type and kind) @@ -775,7 +797,9 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, var orderMatched = true; if (indexNames.indexOf(indexName) !== -1) { m.settings.indexes[indexName].columns.split(/,\s*/).forEach(function (columnName, i) { - if (ai[indexName].columns[i] !== columnName) orderMatched = false; + if (ai[indexName].columns[i] !== columnName) { + orderMatched = false; + } }); } if (!orderMatched) { @@ -802,9 +826,11 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, // kind = i.kind; } if (kind && type) { - sql.push('ADD ' + kind + ' INDEX `' + propName + '` (`' + propName + '`) ' + type); + sql.push('ADD ' + kind + ' INDEX `' + propName + + '` (`' + propName + '`) ' + type); } else { - sql.push('ADD ' + kind + ' INDEX `' + propName + '` ' + type + ' (`' + propName + '`) '); + sql.push('ADD ' + kind + ' INDEX `' + propName + '` ' + + type + ' (`' + propName + '`) '); } } }); @@ -823,15 +849,18 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, kind = i.kind; } if (kind && type) { - sql.push('ADD ' + kind + ' INDEX `' + indexName + '` (' + i.columns + ') ' + type); + sql.push('ADD ' + kind + ' INDEX `' + indexName + '` (' + + i.columns + ') ' + type); } else { - sql.push('ADD ' + kind + ' INDEX ' + type + ' `' + indexName + '` (' + i.columns + ')'); + sql.push('ADD ' + kind + ' INDEX ' + type + ' `' + indexName + + '` (' + i.columns + ')'); } } }); if (sql.length) { - var query = 'ALTER TABLE ' + self.tableEscaped(model) + ' ' + sql.join(',' + MsSQL.newline); + var query = 'ALTER TABLE ' + self.tableEscaped(model) + ' ' + sql.join(',' + + MsSQL.newline); if (checkOnly) { done(null, true, {statements: sql, query: query}); } else { @@ -844,14 +873,23 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, function actualize(propName, oldSettings) { var newSettings = m.properties[propName]; if (newSettings && changed(newSettings, oldSettings)) { - sql.push('CHANGE COLUMN `' + propName + '` `' + propName + '` ' + self.propertySettingsSQL(model, propName)); + sql.push('CHANGE COLUMN `' + propName + '` `' + propName + '` ' + + self.propertySettingsSQL(model, propName)); } } function changed(newSettings, oldSettings) { - if (oldSettings.Null === 'YES' && (newSettings.allowNull === false || newSettings.null === false)) return true; - if (oldSettings.Null === 'NO' && !(newSettings.allowNull === false || newSettings.null === false)) return true; - if (oldSettings.Type.toUpperCase() !== datatype(newSettings)) return true; + if (oldSettings.Null === 'YES' + && (newSettings.allowNull === false || newSettings.null === false)) { + return true; + } + if (oldSettings.Null === 'NO' && !(newSettings.allowNull === false + || newSettings.null === false)) { + return true; + } + if (oldSettings.Type.toUpperCase() !== datatype(newSettings)) { + return true; + } return false; } }; @@ -872,7 +910,8 @@ MsSQL.prototype.propertiesSQL = function (model) { var joinedSql = sql.join("," + MsSQL.newline + " "); var cmd = "PRIMARY KEY CLUSTERED" + MsSQL.newline + "(" + MsSQL.newline; cmd += " [" + modelPKID + "] ASC" + MsSQL.newline; - cmd += ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]" + cmd += ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, " + + "IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]" joinedSql += "," + MsSQL.newline + cmd; @@ -900,10 +939,14 @@ MsSQL.prototype.singleIndexSettingsSQL = function (model, prop, add) { name = i.name; } this._idxNames[model].push(name); - var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON " + tblName + MsSQL.newline; + var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON " + + tblName + MsSQL.newline; cmd += "(" + MsSQL.newline; cmd += " [" + prop + "] " + type; - cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];" + MsSQL.newline; + cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF," + + " SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, " + + "ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];" + + MsSQL.newline; return cmd; }; @@ -936,10 +979,14 @@ MsSQL.prototype.indexSettingsSQL = function (model, prop) { name += kind + "_" + type + "_idx" this._idxNames[model].push(name); - var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON " + tblName + MsSQL.newline; + var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON " + + tblName + MsSQL.newline; cmd += "(" + MsSQL.newline; cmd += columns.join("," + MsSQL.newline); - cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];" + MsSQL.newline; + cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, " + + "SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, " + + "ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];" + + MsSQL.newline; return cmd; }; @@ -978,7 +1025,8 @@ MsSQL.prototype.automigrate = function (cb) { MsSQL.prototype.dropTable = function (model, cb) { var tblName = this.tableEscaped(model); - var cmd = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" + tblName + "') AND type in (N'U'))"; + var cmd = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" + + tblName + "') AND type in (N'U'))"; cmd += MsSQL.newline + "BEGIN" + MsSQL.newline; cmd += " DROP TABLE " + tblName; cmd += MsSQL.newline + "END"; @@ -987,8 +1035,10 @@ MsSQL.prototype.dropTable = function (model, cb) { MsSQL.prototype.createTable = function (model, cb) { var tblName = this.tableEscaped(model); - var cmd = "SET ANSI_NULLS ON;" + MsSQL.newline + "SET QUOTED_IDENTIFIER ON;" + MsSQL.newline + "SET ANSI_PADDING ON;" + MsSQL.newline; - cmd += "IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" + tblName + "') AND type in (N'U'))" + MsSQL.newline + "BEGIN" + MsSQL.newline; + var cmd = "SET ANSI_NULLS ON;" + MsSQL.newline + "SET QUOTED_IDENTIFIER ON;" + + MsSQL.newline + "SET ANSI_PADDING ON;" + MsSQL.newline; + cmd += "IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" + + tblName + "') AND type in (N'U'))" + MsSQL.newline + "BEGIN" + MsSQL.newline; cmd += "CREATE TABLE " + this.tableEscaped(model) + " ("; cmd += MsSQL.newline + " " + this.propertiesSQL(model) + MsSQL.newline; cmd += ") ON [PRIMARY]" + MsSQL.newline + "END;" + MsSQL.newline; From 10d8a9c8aa32a2e00027d648eabb0609110f0639 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 14 Apr 2014 14:01:20 -0700 Subject: [PATCH 036/239] Add a flag to use OFFSET FETCH clause --- lib/mssql.js | 70 ++++++++++++++++++++++++++++++++++++++++++++++------ test/init.js | 6 ++--- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 6fedc1a..8dc0935 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -544,26 +544,65 @@ MsSQL.prototype.getColumns = function (model, props) { }; MsSQL.prototype.all = function (model, filter, callback) { - var sql = "SELECT " + this.getColumns(model, filter.fields) + - " FROM " + this.tableEscaped(model) + MsSQL.newline; var self = this; var props = this._models[model].properties; + var sql = "SELECT " + this.getColumns(model, filter.fields) + + " FROM " + this.tableEscaped(model) + MsSQL.newline; + if (filter) { if (filter.where) { sql += this.buildWhere(filter.where, props) + MsSQL.newline; } + var orderBy = null; if (filter.order) { - sql += this.buildOrderBy(model, filter.order) + MsSQL.newline; + orderBy = this.buildOrderBy(model, filter.order); + sql += orderBy + MsSQL.newline; } if (filter.limit || filter.offset || filter.skip) { if (!filter.order) { var idName = self.idName(model); - sql += 'ORDER BY ' + self.columnEscaped(model, idName) + MsSQL.newline; + orderBy = 'ORDER BY ' + self.columnEscaped(model, idName) + sql += orderBy + MsSQL.newline; + } + + var offset = filter.offset || filter.skip; + if (isNaN(offset)) { + offset = 0; + } + var limit = filter.limit; + if (isNaN(limit)) { + limit = -1; + } + + if (this.supportsOffsetFetch) { + // SQL 2012 or later + // http://technet.microsoft.com/en-us/library/gg699618.aspx + sql += buildLimit(limit, offset); + } else { + + // SQL 2005/2008 + // http://blog.sqlauthority.com/2013/04/14/sql-server-tricks-for-row-offset-and-paging-in-various-versions-of-sql-server/ + + var paginatedSQL = 'SELECT ' + this.getColumns(model, filter.fields) + MsSQL.newline + + ' FROM (' + MsSQL.newline + + ' SELECT ' + this.getColumns(model, filter.fields) + ', ROW_NUMBER() OVER' + + ' (' + orderBy + ') AS RowNum' + MsSQL.newline + + ' FROM ' + this.tableEscaped(model) + MsSQL.newline; + if (filter.where) { + paginatedSQL += this.buildWhere(filter.where, props) + MsSQL.newline; + } + paginatedSQL += ') AS S' + MsSQL.newline + + ' WHERE S.RowNum > ' + offset; + + if (limit !== -1) { + paginatedSQL += ' AND S.RowNum <= ' + (offset + limit); + } + + sql = paginatedSQL + MsSQL.newline; } - sql += buildLimit(filter.limit, filter.offset || filter.skip); } } @@ -603,7 +642,7 @@ function buildLimit(limit, offset) { offset = 0; } var sql = 'OFFSET ' + offset + ' ROWS'; - if (limit) { + if (limit >= 0) { sql += ' FETCH NEXT ' + limit + ' ROWS ONLY'; } return sql; @@ -992,7 +1031,7 @@ MsSQL.prototype.indexSettingsSQL = function (model, prop) { MsSQL.prototype.propertySettingsSQL = function (model, prop) { var p = this._models[model].properties[prop]; - return datatype(p) + ' ' + + return this.columnDataType(model, prop) + ' ' + (p.allowNull === false || p['null'] === false ? 'NOT NULL' : 'NULL'); }; @@ -1068,6 +1107,23 @@ MsSQL.prototype.createIndexes = function (model) { return sql.join(MsSQL.newline); } +MsSQL.prototype.columnDataType = function (model, property) { + var columnMetadata = this.columnMetadata(model, property); + var colType = columnMetadata && columnMetadata.dataType; + if (colType) { + colType = colType.toUpperCase(); + } + var prop = this._models[model].properties[property]; + if (!prop) { + return null; + } + var colLength = columnMetadata && columnMetadata.dataLength || prop.length; + if (colType) { + return colType + (colLength ? '(' + colLength + ')' : ''); + } + return datatype(prop); +}; + function datatype(p) { var dt = ''; switch (p.type.name) { diff --git a/test/init.js b/test/init.js index e2f15c8..00d72d5 100644 --- a/test/init.js +++ b/test/init.js @@ -10,7 +10,8 @@ try { user: 'demo', password: 'L00pBack', host: 'localhost', - database: 'demo' + database: 'demo', + supportsOffSetFetch: Math.random() > 0.5 }; } @@ -21,8 +22,7 @@ global.getConfig = function (options) { port: config.port || 1433, database: config.database || 'test', user: config.user || config.username, - password: config.password, - createDatabase: true + password: config.password }; if (options) { From debd65cef4d658df95936099ec8ca5d79e202740 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 22 Apr 2014 09:35:43 -0700 Subject: [PATCH 037/239] Add NOTICE file --- NOTICE | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 NOTICE diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..5b6210a --- /dev/null +++ b/NOTICE @@ -0,0 +1,9 @@ +The project contains code from https://github.com/code-vicar/jugglingdb-mssql under the MIT license: + +MIT License +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + From 9cbb5c541d614e7ee9ec13a98ac97a3e5f6cbd98 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 22 Apr 2014 09:54:09 -0700 Subject: [PATCH 038/239] Update README --- README.md | 239 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 165 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 3ca823f..be93ed3 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,172 @@ -jugglingdb-mssql -================ +## loopback-connector-mssql -MsSQL adapter for the jugglingdb ORM +The Microsoft SQL Server (MSSQL) Connector module for for [loopback-datasource-juggler](http://docs.strongloop.com/loopback-datasource-juggler/). -Now passing all tests exposed by the jugglingdb framework! -Usage ---- -To use it you need
jugglingdb@0.2.x
and msnodesql +## Connector settings -1. Setup dependencies in package.json: -
-    
+The connector can be configured using the following settings from the data source.
+
+* host or hostname (default to 'localhost'): The host name or ip address of the Microsoft SQL Server
+* port (default to 1433): The port number of the Microsoft SQL Server
+* username or user: The user name to connect to the Microsoft SQL Server
+* password: The password
+* database: The Microsoft SQL Server database name
+* schema: The database schema, default to 'dbo'
+
+**NOTE**: By default, the 'dbo' schema is used for all tables.
+
+The MSSQL connector uses [node-mssql](https://github.com/patriksimek/node-mssql) as the driver. See more
+information about configuration parameters, check [https://github.com/patriksimek/node-mssql#configuration-1](https://github.com/patriksimek/node-mssql#configuration-1).
+
+**NOTE**: The connector can also be configured using a single 'url' property,
+for example:
+```json
     {
-      ...
-      "dependencies":{
-        "msnodesql":"~0.2.1",
-        "jugglingdb": "~0.2.0",
-        "jugglingdb-mssql":"latest"
-      }
-      ...
+        "url": "mssql://test:mypassword@localhost:1433/demo?schema=dbo"
     }
-    
-  
-2. Use: -
-    
-    var Schema = require("jugglingdb").Schema;
-    var schema = new Schema("mssql", {host:"YourSqlServer", database:"YourDatabase"});
-    ...
-    
-  
-3. Primary Keys: - using anything other than the default 'id' as the primary key - will cause the hasMany and belongsTo relationships in jugglingdb to - not work, and possibly other oddities as well (so you probably - shouldn't use it until it's officially supported). - - to specify a custom primary key name for a model use -
-    
-      var AppliesTo = schema.define("AppliesTo", {
-        AppliesToID: {
-          type:Number,
-          primaryKey:true
-        },
-        Title: {
-          type:String,
-          limit:100
-        },
-        Identifier: {
-          type:String,
-          limit:100
-        },
-        Editable: {
-          type:Number
+```
+
+## Discovering Models
+
+Microsoft SQL Server data sources allow you to discover model definition information from existing mssql databases. See the following APIs:
+
+ - [dataSource.discoverModelDefinitions([username], fn)](https://github.com/strongloop/loopback#datasourcediscovermodeldefinitionsusername-fn)
+ - [dataSource.discoverSchema([owner], name, fn)](https://github.com/strongloop/loopback#datasourcediscoverschemaowner-name-fn)
+
+
+## Model definition for Microsoft SQL Server
+
+The model definition consists of the following properties:
+
+* name: Name of the model, by default, it's the camel case of the table
+* options: Model level operations and mapping to Microsoft SQL Server schema/table
+* properties: Property definitions, including mapping to Microsoft SQL Server columns
+
+```json
+
+    {"name": "Inventory", "options": {
+      "idInjection": false,
+      "mssql": {
+        "schema": "strongloop",
+        "table": "inventory"
+      }
+    }, "properties": {
+      "id": {
+        "type": "String",
+        "required": false,
+        "length": 64,
+        "precision": null,
+        "scale": null,
+        "mssql": {
+          "columnName": "id",
+          "dataType": "character varying",
+          "dataLength": 64,
+          "dataPrecision": null,
+          "dataScale": null,
+          "nullable": "NO"
+        }
+      },
+      "productId": {
+        "type": "String",
+        "required": false,
+        "length": 20,
+        "precision": null,
+        "scale": null,
+        "id": 1,
+        "mssql": {
+          "columnName": "product_id",
+          "dataType": "character varying",
+          "dataLength": 20,
+          "dataPrecision": null,
+          "dataScale": null,
+          "nullable": "YES"
+        }
+      },
+      "locationId": {
+        "type": "String",
+        "required": false,
+        "length": 20,
+        "precision": null,
+        "scale": null,
+        "id": 1,
+        "mssql": {
+          "columnName": "location_id",
+          "dataType": "character varying",
+          "dataLength": 20,
+          "dataPrecision": null,
+          "dataScale": null,
+          "nullable": "YES"
+        }
+      },
+      "available": {
+        "type": "Number",
+        "required": false,
+        "length": null,
+        "precision": 32,
+        "scale": 0,
+        "mssql": {
+          "columnName": "available",
+          "dataType": "integer",
+          "dataLength": null,
+          "dataPrecision": 32,
+          "dataScale": 0,
+          "nullable": "YES"
         }
-      });
-    
-  
- -MIT License ---- -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file + }, + "total": { + "type": "Number", + "required": false, + "length": null, + "precision": 32, + "scale": 0, + "mssql": { + "columnName": "total", + "dataType": "integer", + "dataLength": null, + "dataPrecision": 32, + "dataScale": 0, + "nullable": "YES" + } + } + }} + +``` + +## Type Mapping + + - Number + - Boolean + - String + - Object + - Date + - Array + - Buffer + +### JSON to Microsoft SQL Server Types + + +### Microsoft SQL Server Types to JSON + + +## Destroying Models + +Destroying models may result in errors due to foreign key integrity. Make sure +to delete any related models first before calling delete on model's with +relationships. + +## Auto Migrate / Auto Update + +After making changes to your model properties you must call `Model.automigrate()` +or `Model.autoupdate()`. Only call `Model.automigrate()` on new models +as it will drop existing tables. + +LoopBack MSSQL connector creates the following schema objects for a given +model: + +* A table, for example, PRODUCT under the 'dbo' schema within the database + + +## Running tests + + npm test From e50d3ae43b4255182711c048ae0e7b7e7a729013 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 22 Apr 2014 09:54:46 -0700 Subject: [PATCH 039/239] Allow hostname --- lib/mssql.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mssql.js b/lib/mssql.js index 8dc0935..dde74f0 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -25,7 +25,7 @@ function MsSQL(settings) { this.constructor.super_.call(this, name, settings); // this.name = name; // this.settings = settings || {}; - this.settings.server = this.settings.host; + this.settings.server = this.settings.host || this.settings.hostname; this._models = {}; this._idxNames = {}; } From 1e10140ff610a9686a9f380559787aa904256a24 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 22 Apr 2014 15:35:17 -0700 Subject: [PATCH 040/239] Add discovery capability --- lib/discovery.js | 62 ++-- test/discover.test.js | 190 +++++++++++ test/tables.sql | 715 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 946 insertions(+), 21 deletions(-) create mode 100644 test/discover.test.js create mode 100644 test/tables.sql diff --git a/lib/discovery.js b/lib/discovery.js index 741c41a..bcbff66 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -7,12 +7,15 @@ function mixinDiscovery(PostgreSQL) { options = options || {}; var limit = ''; if (options.offset || options.skip || options.limit) { - limit = ' OFFSET ' + (options.offset || options.skip || 0); // Offset starts from 0 + limit = ' OFFSET ' + (options.offset || options.skip || 0) + ' ROWS'; // Offset starts from 0 if (options.limit) { - limit = limit + ' LIMIT ' + options.limit; + limit = limit + ' FETCH NEXT ' + options.limit + 'ROWS ONLY'; + } + if(!orderBy) { + sql += ' ORDER BY 1'; } } - if (!orderBy) { + if (orderBy) { sql += ' ORDER BY ' + orderBy; } return sql + limit; @@ -35,7 +38,7 @@ function mixinDiscovery(PostgreSQL) { + ' FROM information_schema.tables WHERE table_schema=\'' + owner + '\'', 'table_schema, table_name', options); } else { sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name",' - + ' table_schema AS "owner" FROM information_schema.tables WHERE table_schema=current_schema()', + + ' table_schema AS "owner" FROM information_schema.tables WHERE table_schema=schema_name()', 'table_name', options); } return sqlTables; @@ -62,7 +65,7 @@ function mixinDiscovery(PostgreSQL) { 'table_schema, table_name', options); } else { sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' - + ' current_schema() AS "owner" FROM information_schema.views', + + ' schema_name() AS "owner" FROM information_schema.views', 'table_name', options); } } @@ -148,7 +151,7 @@ function mixinDiscovery(PostgreSQL) { + (table ? ' AND table_name=\'' + table + '\'' : ''), 'table_name, ordinal_position', {}); } else { - sql = paginateSQL('SELECT current_schema() AS "owner", table_name AS "tableName", column_name AS "columnName", data_type AS "dataType",' + sql = paginateSQL('SELECT schema_name() AS "owner", table_name AS "tableName", column_name AS "columnName", data_type AS "dataType",' + ' character_maximum_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"' + ' FROM information_schema.columns' + (table ? ' WHERE table_name=\'' + table + '\'' : ''), @@ -201,10 +204,10 @@ function mixinDiscovery(PostgreSQL) { on kc.table_name = tc.table_name and kc.table_schema = tc.table_schema where tc.constraint_type = 'PRIMARY KEY' - and kc.position_in_unique_constraint is not null + and kc.ordinal_position is not null order by tc.table_schema, tc.table_name, - kc.position_in_unique_constraint; + kc.ordinal_position; */ function queryForPrimaryKeys(owner, table) { @@ -213,7 +216,7 @@ function mixinDiscovery(PostgreSQL) { + ' information_schema.key_column_usage kc' + ' JOIN information_schema.table_constraints tc' + ' ON kc.table_name = tc.table_name AND kc.table_schema = tc.table_schema' - + ' WHERE tc.constraint_type=\'PRIMARY KEY\' AND kc.position_in_unique_constraint IS NOT NULL'; + + ' WHERE tc.constraint_type=\'PRIMARY KEY\' AND kc.ordinal_position IS NOT NULL'; if (owner) { sql += ' AND kc.table_schema=\'' + owner + '\''; @@ -316,7 +319,7 @@ function mixinDiscovery(PostgreSQL) { + ' information_schema.constraint_column_usage ccu' + ' JOIN information_schema.key_column_usage kcu' + ' ON ccu.constraint_schema = kcu.constraint_schema AND ccu.constraint_name = kcu.constraint_name' - + ' WHERE kcu.position_in_unique_constraint IS NOT NULL'; + + ' WHERE kcu.ordinal_position IS NOT NULL'; if (owner) { sql += ' and ccu.table_schema=\'' + owner + '\''; } @@ -348,29 +351,46 @@ function mixinDiscovery(PostgreSQL) { function mysqlDataTypeToJSONType(mysqlType, dataLength) { var type = mysqlType.toUpperCase(); switch (type) { - case 'BOOLEAN': + case 'BIT': return 'Boolean'; - case 'CHARACTER VARYING': + + case 'CHAR': case 'VARCHAR': - case 'CHARACTER': case 'TEXT': + + case 'NCHAR': + case 'NVARCHAR': + case 'NTEXT': + + case 'CHARACTER VARYING': + case 'CHARACTER': return 'String'; - case 'BYTEA': + + case 'BINARY': + case 'VARBINARY': + case 'IMAGE': return 'Binary'; - case 'SMALLINT': - case 'INTEGER': + case 'BIGINT': - case 'DECIMAL': case 'NUMERIC': + case 'SMALLINT': + case 'DECIMAL': + case 'SMALLMONEY': + case 'INT': + case 'TINYINT': + case 'MONEY': + case 'FLOAT': case 'REAL': - case 'DOUBLE PRECISION': - case 'SERIAL': - case 'BIGSERIAL': return 'Number'; + case 'DATE': - case 'TIMESTAMP': + case 'DATETIMEOFFSET': + case 'DATETIME2': + case 'SMALLDATETIME': + case 'DATETIME': case 'TIME': return 'Date'; + case 'POINT': return 'GeoPoint'; default: diff --git a/test/discover.test.js b/test/discover.test.js new file mode 100644 index 0000000..ffa9937 --- /dev/null +++ b/test/discover.test.js @@ -0,0 +1,190 @@ +process.env.NODE_ENV = 'test'; +require('./init.js'); +require('should'); + +var assert = require('assert'); + +var db = getDataSource(); + +describe('discoverModels', function () { + describe('Discover models including views', function () { + it('should return an array of tables and views', function (done) { + + db.discoverModelDefinitions({ + views: true, + limit: 3 + }, function (err, models) { + if (err) { + console.error(err); + done(err); + } else { + var views = false; + models.forEach(function (m) { + // console.dir(m); + if (m.type === 'view') { + views = true; + } + }); + assert(views, 'Should have views'); + done(null, models); + } + }); + }); + }); + + describe('Discover models excluding views', function () { + it('should return an array of only tables', function (done) { + + db.discoverModelDefinitions({ + views: false, + limit: 3 + }, function (err, models) { + if (err) { + console.error(err); + done(err); + } else { + var views = false; + models.forEach(function (m) { + // console.dir(m); + if (m.type === 'view') { + views = true; + } + }); + models.should.have.length(3); + assert(!views, 'Should not have views'); + done(null, models); + } + }); + }); + }); +}); + +describe('Discover models including other users', function () { + it('should return an array of all tables and views', function (done) { + + db.discoverModelDefinitions({ + all: true, + limit: 50 + }, function (err, models) { + if (err) { + console.error(err); + done(err); + } else { + var others = false; + models.forEach(function (m) { + // console.dir(m); + if (m.owner !== 'dbo') { + others = true; + } + }); + assert(others, 'Should have tables/views owned by others'); + done(err, models); + } + }); + }); +}); + +describe('Discover model properties', function () { + describe('Discover a named model', function () { + it('should return an array of columns for product', function (done) { + db.discoverModelProperties('product', function (err, models) { + if (err) { + console.error(err); + done(err); + } else { + models.forEach(function (m) { + // console.dir(m); + assert(m.tableName === 'product'); + }); + done(null, models); + } + }); + }); + }); + +}); + +describe('Discover model primary keys', function () { + it('should return an array of primary keys for product', function (done) { + db.discoverPrimaryKeys('product', function (err, models) { + if (err) { + console.error(err); + done(err); + } else { + models.forEach(function (m) { + // console.dir(m); + assert(m.tableName === 'product'); + }); + done(null, models); + } + }); + }); + + it('should return an array of primary keys for dbo.product', function (done) { + db.discoverPrimaryKeys('product', {owner: 'dbo'}, function (err, models) { + if (err) { + console.error(err); + done(err); + } else { + models.forEach(function (m) { + // console.dir(m); + assert(m.tableName === 'product'); + }); + done(null, models); + } + }); + }); +}); + +describe('Discover model foreign keys', function () { + it('should return an array of foreign keys for inventory', function (done) { + db.discoverForeignKeys('inventory', function (err, models) { + if (err) { + console.error(err); + done(err); + } else { + models.forEach(function (m) { + // console.dir(m); + assert(m.fkTableName === 'inventory'); + }); + done(null, models); + } + }); + }); + it('should return an array of foreign keys for dbo.inventory', function (done) { + db.discoverForeignKeys('inventory', {owner: 'dbo'}, function (err, models) { + if (err) { + console.error(err); + done(err); + } else { + models.forEach(function (m) { + // console.dir(m); + assert(m.fkTableName === 'inventory'); + }); + done(null, models); + } + }); + }); +}); + +describe('Discover adl schema from a table', function () { + it('should return an adl schema for inventory', function (done) { + db.discoverSchema('inventory', {owner: 'dbo'}, function (err, schema) { + // console.log('%j', schema); + assert(schema.name === 'Inventory'); + assert(schema.options.mssql.schema === 'dbo'); + assert(schema.options.mssql.table === 'inventory'); + assert(schema.properties.productId); + assert(schema.properties.productId.type === 'String'); + assert(schema.properties.productId.mssql.columnName === 'product_id'); + assert(schema.properties.locationId); + assert(schema.properties.locationId.type === 'String'); + assert(schema.properties.locationId.mssql.columnName === 'location_id'); + assert(schema.properties.available); + assert(schema.properties.available.type === 'Number'); + assert(schema.properties.total); + assert(schema.properties.total.type === 'Number'); + done(null, schema); + }); + }); +}); diff --git a/test/tables.sql b/test/tables.sql new file mode 100644 index 0000000..d7a47e9 --- /dev/null +++ b/test/tables.sql @@ -0,0 +1,715 @@ +drop table customer; +drop table location; +drop table product; +drop table inventory; +drop table reservation; +drop table session; + +go + + create table customer + ( id varchar(64) not null, + username varchar(1024), + email varchar(1024), + password varchar(1024), + name varchar(40), + military_agency varchar(20), + realm varchar(1024), + emailverified char(1), + verificationtoken varchar(1024), + credentials varchar(1024), + challenges varchar(1024), + status varchar(1024), + created date, + lastupdated date + ) ; + + create table inventory + ( id varchar(64) not null, + product_id varchar(64), + location_id varchar(64), + available integer, + total integer + ) ; + + create table location + ( id varchar(64) not null, + street varchar(64), + city varchar(64), + zipcode varchar(16), + name varchar(32), + geo varchar(32) + ) ; + + create table product + ( id varchar(64) not null, + name varchar(64), + audible_range integer, + effective_range integer, + rounds integer, + extras varchar(64), + fire_modes varchar(64) + ) ; + + create table reservation + ( id varchar(64) not null, + product_id varchar(64), + location_id varchar(64), + customer_id varchar(64), + qty integer, + status varchar(20), + reserve_date date, + pickup_date date, + return_date date + ) ; + + create table session + ( id varchar(64) not null, + uid varchar(1024), + ttl integer + ) ; + +insert into customer (id,[username],email,password,name,military_agency,realm,emailverified,verificationtoken,credentials,challenges,status,created,lastupdated) values ('612','bat','bat@bar.com','$2a$10$beg18wcyqn7trkfic59eb.vmnsewqjwmlym4dng73izb.mka1rjac',null,null,null,null,null,']',']',null,null,null); +insert into customer (id,username,email,password,name,military_agency,realm,emailverified,verificationtoken,credentials,challenges,status,created,lastupdated) values ('613','baz','baz@bar.com','$2a$10$jksyf2glmdi4cwvqh8astos0b24ldu9p8jccnmri/0rvhtwsicm9c',null,null,null,null,null,']',']',null,null,null); +insert into customer (id,username,email,password,name,military_agency,realm,emailverified,verificationtoken,credentials,challenges,status,created,lastupdated) values ('610','foo','foo@bar.com','$2a$10$tn1hn7xv6x74ccb7tvfwkeaajtd4/6q4rbcmzgmajewe40xqrrsui',null,null,null,null,null,']',']',null,null,null); +insert into customer (id,username,email,password,name,military_agency,realm,emailverified,verificationtoken,credentials,challenges,status,created,lastupdated) values ('611','bar','bar@bar.com','$2a$10$a8mcol6d5vqxm6vubqxl8e5v66steg6e8vzjqqppoyk95vm3smpik',null,null,null,null,null,']',']',null,null,null); + +insert into inventory (id,product_id,location_id,available,total) values ('441','6','91',8,19); +insert into inventory (id,product_id,location_id,available,total) values ('442','7','91',21,23); +insert into inventory (id,product_id,location_id,available,total) values ('443','8','91',35,63); +insert into inventory (id,product_id,location_id,available,total) values ('444','9','91',0,7); +insert into inventory (id,product_id,location_id,available,total) values ('445','10','91',0,2); +insert into inventory (id,product_id,location_id,available,total) values ('446','11','91',1,6); +insert into inventory (id,product_id,location_id,available,total) values ('447','12','91',67,77); +insert into inventory (id,product_id,location_id,available,total) values ('448','13','91',7,51); +insert into inventory (id,product_id,location_id,available,total) values ('449','14','91',39,96); +insert into inventory (id,product_id,location_id,available,total) values ('450','15','91',36,74); +insert into inventory (id,product_id,location_id,available,total) values ('451','16','91',15,73); +insert into inventory (id,product_id,location_id,available,total) values ('453','18','91',0,19); +insert into inventory (id,product_id,location_id,available,total) values ('452','17','91',36,63); +insert into inventory (id,product_id,location_id,available,total) values ('454','19','91',24,94); +insert into inventory (id,product_id,location_id,available,total) values ('455','20','91',8,38); +insert into inventory (id,product_id,location_id,available,total) values ('456','21','91',41,58); +insert into inventory (id,product_id,location_id,available,total) values ('457','22','91',18,22); +insert into inventory (id,product_id,location_id,available,total) values ('458','23','91',25,37); +insert into inventory (id,product_id,location_id,available,total) values ('459','24','91',39,60); +insert into inventory (id,product_id,location_id,available,total) values ('460','25','91',30,55); +insert into inventory (id,product_id,location_id,available,total) values ('461','26','91',4,4); +insert into inventory (id,product_id,location_id,available,total) values ('462','27','91',6,17); +insert into inventory (id,product_id,location_id,available,total) values ('463','28','91',63,82); +insert into inventory (id,product_id,location_id,available,total) values ('464','29','91',30,76); +insert into inventory (id,product_id,location_id,available,total) values ('465','30','91',13,31); +insert into inventory (id,product_id,location_id,available,total) values ('466','31','91',10,59); +insert into inventory (id,product_id,location_id,available,total) values ('467','32','91',39,80); +insert into inventory (id,product_id,location_id,available,total) values ('468','33','91',69,89); +insert into inventory (id,product_id,location_id,available,total) values ('469','34','91',62,93); +insert into inventory (id,product_id,location_id,available,total) values ('470','35','91',13,27); +insert into inventory (id,product_id,location_id,available,total) values ('471','36','91',8,22); +insert into inventory (id,product_id,location_id,available,total) values ('472','37','91',0,31); +insert into inventory (id,product_id,location_id,available,total) values ('473','38','91',9,79); +insert into inventory (id,product_id,location_id,available,total) values ('474','39','91',6,49); +insert into inventory (id,product_id,location_id,available,total) values ('475','40','91',39,40); +insert into inventory (id,product_id,location_id,available,total) values ('476','41','91',1,22); +insert into inventory (id,product_id,location_id,available,total) values ('477','42','91',12,82); +insert into inventory (id,product_id,location_id,available,total) values ('478','43','91',1,7); +insert into inventory (id,product_id,location_id,available,total) values ('479','44','91',15,26); +insert into inventory (id,product_id,location_id,available,total) values ('480','45','91',22,31); +insert into inventory (id,product_id,location_id,available,total) values ('481','46','91',64,65); +insert into inventory (id,product_id,location_id,available,total) values ('482','47','91',10,99); +insert into inventory (id,product_id,location_id,available,total) values ('483','48','91',26,56); +insert into inventory (id,product_id,location_id,available,total) values ('484','49','91',14,19); +insert into inventory (id,product_id,location_id,available,total) values ('485','50','91',51,55); +insert into inventory (id,product_id,location_id,available,total) values ('486','51','91',25,29); +insert into inventory (id,product_id,location_id,available,total) values ('487','52','91',31,37); +insert into inventory (id,product_id,location_id,available,total) values ('488','53','91',35,71); +insert into inventory (id,product_id,location_id,available,total) values ('489','54','91',5,61); +insert into inventory (id,product_id,location_id,available,total) values ('490','55','91',4,26); +insert into inventory (id,product_id,location_id,available,total) values ('491','56','91',29,50); +insert into inventory (id,product_id,location_id,available,total) values ('492','57','91',15,34); +insert into inventory (id,product_id,location_id,available,total) values ('493','58','91',30,38); +insert into inventory (id,product_id,location_id,available,total) values ('494','59','91',54,71); +insert into inventory (id,product_id,location_id,available,total) values ('495','60','91',6,43); +insert into inventory (id,product_id,location_id,available,total) values ('496','61','91',40,80); +insert into inventory (id,product_id,location_id,available,total) values ('497','62','91',32,33); +insert into inventory (id,product_id,location_id,available,total) values ('498','63','91',44,53); +insert into inventory (id,product_id,location_id,available,total) values ('499','64','91',10,68); +insert into inventory (id,product_id,location_id,available,total) values ('500','65','91',11,13); +insert into inventory (id,product_id,location_id,available,total) values ('501','66','91',7,40); +insert into inventory (id,product_id,location_id,available,total) values ('502','67','91',5,20); +insert into inventory (id,product_id,location_id,available,total) values ('503','68','91',30,40); +insert into inventory (id,product_id,location_id,available,total) values ('504','69','91',6,48); +insert into inventory (id,product_id,location_id,available,total) values ('505','70','91',7,53); +insert into inventory (id,product_id,location_id,available,total) values ('506','71','91',2,21); +insert into inventory (id,product_id,location_id,available,total) values ('507','72','91',25,56); +insert into inventory (id,product_id,location_id,available,total) values ('508','73','91',13,85); +insert into inventory (id,product_id,location_id,available,total) values ('509','74','91',63,67); +insert into inventory (id,product_id,location_id,available,total) values ('510','75','91',9,11); +insert into inventory (id,product_id,location_id,available,total) values ('511','76','91',18,46); +insert into inventory (id,product_id,location_id,available,total) values ('512','77','91',7,88); +insert into inventory (id,product_id,location_id,available,total) values ('513','78','91',36,55); +insert into inventory (id,product_id,location_id,available,total) values ('514','79','91',8,33); +insert into inventory (id,product_id,location_id,available,total) values ('515','80','91',63,73); +insert into inventory (id,product_id,location_id,available,total) values ('517','82','91',2,5); +insert into inventory (id,product_id,location_id,available,total) values ('516','81','91',36,71); +insert into inventory (id,product_id,location_id,available,total) values ('518','83','91',11,11); +insert into inventory (id,product_id,location_id,available,total) values ('519','84','91',21,39); +insert into inventory (id,product_id,location_id,available,total) values ('520','85','91',90,91); +insert into inventory (id,product_id,location_id,available,total) values ('521','86','91',1,2); +insert into inventory (id,product_id,location_id,available,total) values ('522','87','91',36,47); +insert into inventory (id,product_id,location_id,available,total) values ('523','2','92',6,7); +insert into inventory (id,product_id,location_id,available,total) values ('524','3','92',15,23); +insert into inventory (id,product_id,location_id,available,total) values ('525','4','92',1,1); +insert into inventory (id,product_id,location_id,available,total) values ('527','6','92',22,24); +insert into inventory (id,product_id,location_id,available,total) values ('526','5','92',37,42); +insert into inventory (id,product_id,location_id,available,total) values ('528','7','92',12,13); +insert into inventory (id,product_id,location_id,available,total) values ('529','8','92',4,25); +insert into inventory (id,product_id,location_id,available,total) values ('531','10','92',9,31); +insert into inventory (id,product_id,location_id,available,total) values ('530','9','92',32,87); +insert into inventory (id,product_id,location_id,available,total) values ('532','11','92',2,38); +insert into inventory (id,product_id,location_id,available,total) values ('533','12','92',66,88); +insert into inventory (id,product_id,location_id,available,total) values ('534','13','92',4,15); +insert into inventory (id,product_id,location_id,available,total) values ('535','14','92',9,88); +insert into inventory (id,product_id,location_id,available,total) values ('536','15','92',18,72); +insert into inventory (id,product_id,location_id,available,total) values ('537','16','92',13,26); +insert into inventory (id,product_id,location_id,available,total) values ('538','17','92',20,55); +insert into inventory (id,product_id,location_id,available,total) values ('539','18','92',17,76); +insert into inventory (id,product_id,location_id,available,total) values ('540','19','92',28,58); +insert into inventory (id,product_id,location_id,available,total) values ('542','21','92',7,12); +insert into inventory (id,product_id,location_id,available,total) values ('541','20','92',78,99); +insert into inventory (id,product_id,location_id,available,total) values ('543','22','92',4,13); +insert into inventory (id,product_id,location_id,available,total) values ('544','23','92',12,96); +insert into inventory (id,product_id,location_id,available,total) values ('545','24','92',82,84); +insert into inventory (id,product_id,location_id,available,total) values ('546','25','92',29,64); +insert into inventory (id,product_id,location_id,available,total) values ('547','26','92',5,7); +insert into inventory (id,product_id,location_id,available,total) values ('548','27','92',3,35); +insert into inventory (id,product_id,location_id,available,total) values ('549','28','92',23,46); +insert into inventory (id,product_id,location_id,available,total) values ('550','29','92',21,39); +insert into inventory (id,product_id,location_id,available,total) values ('551','30','92',19,21); +insert into inventory (id,product_id,location_id,available,total) values ('552','31','92',24,73); +insert into inventory (id,product_id,location_id,available,total) values ('553','32','92',51,89); +insert into inventory (id,product_id,location_id,available,total) values ('554','33','92',22,32); +insert into inventory (id,product_id,location_id,available,total) values ('555','34','92',56,95); +insert into inventory (id,product_id,location_id,available,total) values ('556','35','92',47,95); +insert into inventory (id,product_id,location_id,available,total) values ('557','36','92',17,24); +insert into inventory (id,product_id,location_id,available,total) values ('558','37','92',0,0); +insert into inventory (id,product_id,location_id,available,total) values ('559','38','92',14,53); +insert into inventory (id,product_id,location_id,available,total) values ('560','39','92',65,67); +insert into inventory (id,product_id,location_id,available,total) values ('561','40','92',64,95); +insert into inventory (id,product_id,location_id,available,total) values ('562','41','92',5,5); +insert into inventory (id,product_id,location_id,available,total) values ('563','42','92',7,10); +insert into inventory (id,product_id,location_id,available,total) values ('564','43','92',34,45); +insert into inventory (id,product_id,location_id,available,total) values ('565','44','92',0,3); +insert into inventory (id,product_id,location_id,available,total) values ('566','45','92',20,67); +insert into inventory (id,product_id,location_id,available,total) values ('567','46','92',58,92); +insert into inventory (id,product_id,location_id,available,total) values ('568','47','92',21,70); +insert into inventory (id,product_id,location_id,available,total) values ('569','48','92',56,62); +insert into inventory (id,product_id,location_id,available,total) values ('570','49','92',0,5); +insert into inventory (id,product_id,location_id,available,total) values ('571','50','92',16,97); +insert into inventory (id,product_id,location_id,available,total) values ('572','51','92',6,46); +insert into inventory (id,product_id,location_id,available,total) values ('573','52','92',58,84); +insert into inventory (id,product_id,location_id,available,total) values ('574','53','92',25,42); +insert into inventory (id,product_id,location_id,available,total) values ('575','54','92',13,40); +insert into inventory (id,product_id,location_id,available,total) values ('576','55','92',18,34); +insert into inventory (id,product_id,location_id,available,total) values ('577','56','92',44,92); +insert into inventory (id,product_id,location_id,available,total) values ('578','57','92',0,19); +insert into inventory (id,product_id,location_id,available,total) values ('579','58','92',13,67); +insert into inventory (id,product_id,location_id,available,total) values ('580','59','92',18,38); +insert into inventory (id,product_id,location_id,available,total) values ('581','60','92',7,7); +insert into inventory (id,product_id,location_id,available,total) values ('582','61','92',6,53); +insert into inventory (id,product_id,location_id,available,total) values ('583','62','92',4,25); +insert into inventory (id,product_id,location_id,available,total) values ('584','63','92',31,59); +insert into inventory (id,product_id,location_id,available,total) values ('585','64','92',25,40); +insert into inventory (id,product_id,location_id,available,total) values ('586','65','92',2,81); +insert into inventory (id,product_id,location_id,available,total) values ('587','66','92',23,81); +insert into inventory (id,product_id,location_id,available,total) values ('588','67','92',9,33); +insert into inventory (id,product_id,location_id,available,total) values ('589','68','92',2,37); +insert into inventory (id,product_id,location_id,available,total) values ('590','69','92',53,64); +insert into inventory (id,product_id,location_id,available,total) values ('591','70','92',21,22); +insert into inventory (id,product_id,location_id,available,total) values ('592','71','92',7,45); +insert into inventory (id,product_id,location_id,available,total) values ('593','72','92',9,25); +insert into inventory (id,product_id,location_id,available,total) values ('594','73','92',0,40); +insert into inventory (id,product_id,location_id,available,total) values ('595','74','92',21,34); +insert into inventory (id,product_id,location_id,available,total) values ('596','75','92',33,87); +insert into inventory (id,product_id,location_id,available,total) values ('597','76','92',44,48); +insert into inventory (id,product_id,location_id,available,total) values ('598','77','92',64,69); +insert into inventory (id,product_id,location_id,available,total) values ('599','78','92',31,56); +insert into inventory (id,product_id,location_id,available,total) values ('600','79','92',11,12); +insert into inventory (id,product_id,location_id,available,total) values ('601','80','92',3,7); +insert into inventory (id,product_id,location_id,available,total) values ('602','81','92',26,74); +insert into inventory (id,product_id,location_id,available,total) values ('603','82','92',29,46); +insert into inventory (id,product_id,location_id,available,total) values ('604','83','92',1,5); +insert into inventory (id,product_id,location_id,available,total) values ('605','84','92',35,37); +insert into inventory (id,product_id,location_id,available,total) values ('606','85','92',12,100); +insert into inventory (id,product_id,location_id,available,total) values ('607','86','92',9,18); +insert into inventory (id,product_id,location_id,available,total) values ('608','87','92',49,64); +insert into inventory (id,product_id,location_id,available,total) values ('95','4','87',18,30); +insert into inventory (id,product_id,location_id,available,total) values ('97','6','87',10,21); +insert into inventory (id,product_id,location_id,available,total) values ('96','5','87',3,38); +insert into inventory (id,product_id,location_id,available,total) values ('98','7','87',43,58); +insert into inventory (id,product_id,location_id,available,total) values ('99','8','87',6,12); +insert into inventory (id,product_id,location_id,available,total) values ('100','9','87',0,3); +insert into inventory (id,product_id,location_id,available,total) values ('101','10','87',0,31); +insert into inventory (id,product_id,location_id,available,total) values ('102','11','87',73,93); +insert into inventory (id,product_id,location_id,available,total) values ('103','12','87',22,25); +insert into inventory (id,product_id,location_id,available,total) values ('104','13','87',44,70); +insert into inventory (id,product_id,location_id,available,total) values ('105','14','87',26,50); +insert into inventory (id,product_id,location_id,available,total) values ('106','15','87',36,83); +insert into inventory (id,product_id,location_id,available,total) values ('107','16','87',20,59); +insert into inventory (id,product_id,location_id,available,total) values ('108','17','87',28,44); +insert into inventory (id,product_id,location_id,available,total) values ('109','18','87',5,50); +insert into inventory (id,product_id,location_id,available,total) values ('110','19','87',2,29); +insert into inventory (id,product_id,location_id,available,total) values ('111','20','87',38,54); +insert into inventory (id,product_id,location_id,available,total) values ('112','21','87',4,29); +insert into inventory (id,product_id,location_id,available,total) values ('113','22','87',1,59); +insert into inventory (id,product_id,location_id,available,total) values ('114','23','87',20,36); +insert into inventory (id,product_id,location_id,available,total) values ('115','24','87',10,10); +insert into inventory (id,product_id,location_id,available,total) values ('116','25','87',58,60); +insert into inventory (id,product_id,location_id,available,total) values ('117','26','87',0,18); +insert into inventory (id,product_id,location_id,available,total) values ('118','27','87',29,50); +insert into inventory (id,product_id,location_id,available,total) values ('119','28','87',24,34); +insert into inventory (id,product_id,location_id,available,total) values ('120','29','87',36,43); +insert into inventory (id,product_id,location_id,available,total) values ('121','30','87',43,64); +insert into inventory (id,product_id,location_id,available,total) values ('122','31','87',79,90); +insert into inventory (id,product_id,location_id,available,total) values ('123','32','87',13,13); +insert into inventory (id,product_id,location_id,available,total) values ('124','33','87',9,60); +insert into inventory (id,product_id,location_id,available,total) values ('125','34','87',7,13); +insert into inventory (id,product_id,location_id,available,total) values ('126','35','87',43,54); +insert into inventory (id,product_id,location_id,available,total) values ('127','36','87',67,69); +insert into inventory (id,product_id,location_id,available,total) values ('128','37','87',1,15); +insert into inventory (id,product_id,location_id,available,total) values ('129','38','87',36,44); +insert into inventory (id,product_id,location_id,available,total) values ('130','39','87',1,17); +insert into inventory (id,product_id,location_id,available,total) values ('131','40','87',13,16); +insert into inventory (id,product_id,location_id,available,total) values ('132','41','87',24,64); +insert into inventory (id,product_id,location_id,available,total) values ('133','42','87',87,99); +insert into inventory (id,product_id,location_id,available,total) values ('134','43','87',27,99); +insert into inventory (id,product_id,location_id,available,total) values ('135','44','87',71,71); +insert into inventory (id,product_id,location_id,available,total) values ('136','45','87',9,20); +insert into inventory (id,product_id,location_id,available,total) values ('137','46','87',9,67); +insert into inventory (id,product_id,location_id,available,total) values ('138','47','87',19,21); +insert into inventory (id,product_id,location_id,available,total) values ('139','48','87',5,5); +insert into inventory (id,product_id,location_id,available,total) values ('140','49','87',82,91); +insert into inventory (id,product_id,location_id,available,total) values ('141','50','87',27,42); +insert into inventory (id,product_id,location_id,available,total) values ('142','51','87',51,60); +insert into inventory (id,product_id,location_id,available,total) values ('143','52','87',8,72); +insert into inventory (id,product_id,location_id,available,total) values ('145','54','87',3,71); +insert into inventory (id,product_id,location_id,available,total) values ('144','53','87',5,13); +insert into inventory (id,product_id,location_id,available,total) values ('146','55','87',55,56); +insert into inventory (id,product_id,location_id,available,total) values ('147','56','87',9,90); +insert into inventory (id,product_id,location_id,available,total) values ('148','57','87',3,18); +insert into inventory (id,product_id,location_id,available,total) values ('149','58','87',2,14); +insert into inventory (id,product_id,location_id,available,total) values ('150','59','87',54,95); +insert into inventory (id,product_id,location_id,available,total) values ('151','60','87',62,70); +insert into inventory (id,product_id,location_id,available,total) values ('152','61','87',18,50); +insert into inventory (id,product_id,location_id,available,total) values ('153','62','87',60,78); +insert into inventory (id,product_id,location_id,available,total) values ('154','63','87',23,59); +insert into inventory (id,product_id,location_id,available,total) values ('155','64','87',14,23); +insert into inventory (id,product_id,location_id,available,total) values ('156','65','87',2,97); +insert into inventory (id,product_id,location_id,available,total) values ('157','66','87',49,50); +insert into inventory (id,product_id,location_id,available,total) values ('158','67','87',47,93); +insert into inventory (id,product_id,location_id,available,total) values ('159','68','87',34,42); +insert into inventory (id,product_id,location_id,available,total) values ('160','69','87',3,18); +insert into inventory (id,product_id,location_id,available,total) values ('161','70','87',37,84); +insert into inventory (id,product_id,location_id,available,total) values ('162','71','87',22,40); +insert into inventory (id,product_id,location_id,available,total) values ('163','72','87',8,61); +insert into inventory (id,product_id,location_id,available,total) values ('164','73','87',2,3); +insert into inventory (id,product_id,location_id,available,total) values ('165','74','87',10,16); +insert into inventory (id,product_id,location_id,available,total) values ('166','75','87',53,89); +insert into inventory (id,product_id,location_id,available,total) values ('167','76','87',35,60); +insert into inventory (id,product_id,location_id,available,total) values ('168','77','87',57,80); +insert into inventory (id,product_id,location_id,available,total) values ('169','78','87',53,81); +insert into inventory (id,product_id,location_id,available,total) values ('170','79','87',32,54); +insert into inventory (id,product_id,location_id,available,total) values ('171','80','87',1,4); +insert into inventory (id,product_id,location_id,available,total) values ('172','81','87',78,86); +insert into inventory (id,product_id,location_id,available,total) values ('173','82','87',11,21); +insert into inventory (id,product_id,location_id,available,total) values ('174','83','87',28,81); +insert into inventory (id,product_id,location_id,available,total) values ('175','84','87',2,57); +insert into inventory (id,product_id,location_id,available,total) values ('176','85','87',30,37); +insert into inventory (id,product_id,location_id,available,total) values ('177','86','87',17,80); +insert into inventory (id,product_id,location_id,available,total) values ('179','2','88',10,10); +insert into inventory (id,product_id,location_id,available,total) values ('178','87','87',1,9); +insert into inventory (id,product_id,location_id,available,total) values ('180','3','88',1,1); +insert into inventory (id,product_id,location_id,available,total) values ('181','4','88',8,27); +insert into inventory (id,product_id,location_id,available,total) values ('182','5','88',3,38); +insert into inventory (id,product_id,location_id,available,total) values ('183','6','88',28,76); +insert into inventory (id,product_id,location_id,available,total) values ('184','7','88',40,83); +insert into inventory (id,product_id,location_id,available,total) values ('185','8','88',1,4); +insert into inventory (id,product_id,location_id,available,total) values ('186','9','88',87,95); +insert into inventory (id,product_id,location_id,available,total) values ('187','10','88',29,35); +insert into inventory (id,product_id,location_id,available,total) values ('188','11','88',10,69); +insert into inventory (id,product_id,location_id,available,total) values ('189','12','88',32,86); +insert into inventory (id,product_id,location_id,available,total) values ('190','13','88',27,28); +insert into inventory (id,product_id,location_id,available,total) values ('191','14','88',59,66); +insert into inventory (id,product_id,location_id,available,total) values ('192','15','88',59,70); +insert into inventory (id,product_id,location_id,available,total) values ('193','16','88',43,70); +insert into inventory (id,product_id,location_id,available,total) values ('194','17','88',50,63); +insert into inventory (id,product_id,location_id,available,total) values ('195','18','88',8,20); +insert into inventory (id,product_id,location_id,available,total) values ('196','19','88',20,29); +insert into inventory (id,product_id,location_id,available,total) values ('197','20','88',36,50); +insert into inventory (id,product_id,location_id,available,total) values ('198','21','88',40,63); +insert into inventory (id,product_id,location_id,available,total) values ('199','22','88',4,96); +insert into inventory (id,product_id,location_id,available,total) values ('200','23','88',70,98); +insert into inventory (id,product_id,location_id,available,total) values ('201','24','88',1,1); +insert into inventory (id,product_id,location_id,available,total) values ('202','25','88',17,45); +insert into inventory (id,product_id,location_id,available,total) values ('203','26','88',52,97); +insert into inventory (id,product_id,location_id,available,total) values ('204','27','88',0,0); +insert into inventory (id,product_id,location_id,available,total) values ('205','28','88',97,98); +insert into inventory (id,product_id,location_id,available,total) values ('206','29','88',26,80); +insert into inventory (id,product_id,location_id,available,total) values ('207','30','88',11,33); +insert into inventory (id,product_id,location_id,available,total) values ('208','31','88',10,21); +insert into inventory (id,product_id,location_id,available,total) values ('209','32','88',14,36); +insert into inventory (id,product_id,location_id,available,total) values ('210','33','88',71,86); +insert into inventory (id,product_id,location_id,available,total) values ('211','34','88',85,100); +insert into inventory (id,product_id,location_id,available,total) values ('212','35','88',3,45); +insert into inventory (id,product_id,location_id,available,total) values ('213','36','88',0,3); +insert into inventory (id,product_id,location_id,available,total) values ('214','37','88',17,71); +insert into inventory (id,product_id,location_id,available,total) values ('215','38','88',41,75); +insert into inventory (id,product_id,location_id,available,total) values ('216','39','88',37,41); +insert into inventory (id,product_id,location_id,available,total) values ('217','40','88',37,49); +insert into inventory (id,product_id,location_id,available,total) values ('218','41','88',1,2); +insert into inventory (id,product_id,location_id,available,total) values ('219','42','88',49,72); +insert into inventory (id,product_id,location_id,available,total) values ('220','43','88',24,38); +insert into inventory (id,product_id,location_id,available,total) values ('221','44','88',6,66); +insert into inventory (id,product_id,location_id,available,total) values ('222','45','88',31,49); +insert into inventory (id,product_id,location_id,available,total) values ('223','46','88',9,10); +insert into inventory (id,product_id,location_id,available,total) values ('224','47','88',57,72); +insert into inventory (id,product_id,location_id,available,total) values ('225','48','88',17,24); +insert into inventory (id,product_id,location_id,available,total) values ('226','49','88',41,61); +insert into inventory (id,product_id,location_id,available,total) values ('227','50','88',33,87); +insert into inventory (id,product_id,location_id,available,total) values ('228','51','88',11,25); +insert into inventory (id,product_id,location_id,available,total) values ('229','52','88',1,8); +insert into inventory (id,product_id,location_id,available,total) values ('230','53','88',14,64); +insert into inventory (id,product_id,location_id,available,total) values ('231','54','88',50,89); +insert into inventory (id,product_id,location_id,available,total) values ('232','55','88',16,66); +insert into inventory (id,product_id,location_id,available,total) values ('233','56','88',0,6); +insert into inventory (id,product_id,location_id,available,total) values ('234','57','88',18,32); +insert into inventory (id,product_id,location_id,available,total) values ('235','58','88',6,6); +insert into inventory (id,product_id,location_id,available,total) values ('236','59','88',68,84); +insert into inventory (id,product_id,location_id,available,total) values ('237','60','88',50,74); +insert into inventory (id,product_id,location_id,available,total) values ('238','61','88',7,18); +insert into inventory (id,product_id,location_id,available,total) values ('239','62','88',14,49); +insert into inventory (id,product_id,location_id,available,total) values ('240','63','88',3,3); +insert into inventory (id,product_id,location_id,available,total) values ('241','64','88',21,83); +insert into inventory (id,product_id,location_id,available,total) values ('242','65','88',48,90); +insert into inventory (id,product_id,location_id,available,total) values ('243','66','88',11,65); +insert into inventory (id,product_id,location_id,available,total) values ('244','67','88',29,90); +insert into inventory (id,product_id,location_id,available,total) values ('245','68','88',44,45); +insert into inventory (id,product_id,location_id,available,total) values ('246','69','88',23,30); +insert into inventory (id,product_id,location_id,available,total) values ('247','70','88',53,71); +insert into inventory (id,product_id,location_id,available,total) values ('248','71','88',50,76); +insert into inventory (id,product_id,location_id,available,total) values ('249','72','88',13,20); +insert into inventory (id,product_id,location_id,available,total) values ('250','73','88',6,8); +insert into inventory (id,product_id,location_id,available,total) values ('251','74','88',7,11); +insert into inventory (id,product_id,location_id,available,total) values ('252','75','88',0,3); +insert into inventory (id,product_id,location_id,available,total) values ('253','76','88',49,51); +insert into inventory (id,product_id,location_id,available,total) values ('254','77','88',37,61); +insert into inventory (id,product_id,location_id,available,total) values ('255','78','88',4,78); +insert into inventory (id,product_id,location_id,available,total) values ('257','80','88',23,29); +insert into inventory (id,product_id,location_id,available,total) values ('256','79','88',1,5); +insert into inventory (id,product_id,location_id,available,total) values ('259','82','88',1,2); +insert into inventory (id,product_id,location_id,available,total) values ('258','81','88',3,52); +insert into inventory (id,product_id,location_id,available,total) values ('260','83','88',65,67); +insert into inventory (id,product_id,location_id,available,total) values ('261','84','88',41,87); +insert into inventory (id,product_id,location_id,available,total) values ('262','85','88',20,21); +insert into inventory (id,product_id,location_id,available,total) values ('93','2','87',43,56); +insert into inventory (id,product_id,location_id,available,total) values ('94','3','87',27,85); +insert into inventory (id,product_id,location_id,available,total) values ('263','86','88',46,94); +insert into inventory (id,product_id,location_id,available,total) values ('264','87','88',64,68); +insert into inventory (id,product_id,location_id,available,total) values ('265','2','89',5,78); +insert into inventory (id,product_id,location_id,available,total) values ('266','3','89',29,41); +insert into inventory (id,product_id,location_id,available,total) values ('267','4','89',2,39); +insert into inventory (id,product_id,location_id,available,total) values ('268','5','89',57,67); +insert into inventory (id,product_id,location_id,available,total) values ('269','6','89',1,2); +insert into inventory (id,product_id,location_id,available,total) values ('270','7','89',68,80); +insert into inventory (id,product_id,location_id,available,total) values ('271','8','89',22,81); +insert into inventory (id,product_id,location_id,available,total) values ('272','9','89',9,52); +insert into inventory (id,product_id,location_id,available,total) values ('273','10','89',26,42); +insert into inventory (id,product_id,location_id,available,total) values ('274','11','89',42,91); +insert into inventory (id,product_id,location_id,available,total) values ('275','12','89',23,35); +insert into inventory (id,product_id,location_id,available,total) values ('276','13','89',38,59); +insert into inventory (id,product_id,location_id,available,total) values ('277','14','89',43,51); +insert into inventory (id,product_id,location_id,available,total) values ('278','15','89',19,29); +insert into inventory (id,product_id,location_id,available,total) values ('279','16','89',21,29); +insert into inventory (id,product_id,location_id,available,total) values ('280','17','89',18,47); +insert into inventory (id,product_id,location_id,available,total) values ('281','18','89',26,52); +insert into inventory (id,product_id,location_id,available,total) values ('282','19','89',18,61); +insert into inventory (id,product_id,location_id,available,total) values ('283','20','89',33,97); +insert into inventory (id,product_id,location_id,available,total) values ('284','21','89',1,35); +insert into inventory (id,product_id,location_id,available,total) values ('285','22','89',41,65); +insert into inventory (id,product_id,location_id,available,total) values ('286','23','89',16,41); +insert into inventory (id,product_id,location_id,available,total) values ('287','24','89',26,32); +insert into inventory (id,product_id,location_id,available,total) values ('288','25','89',0,11); +insert into inventory (id,product_id,location_id,available,total) values ('289','26','89',30,52); +insert into inventory (id,product_id,location_id,available,total) values ('290','27','89',29,73); +insert into inventory (id,product_id,location_id,available,total) values ('291','28','89',26,86); +insert into inventory (id,product_id,location_id,available,total) values ('292','29','89',48,48); +insert into inventory (id,product_id,location_id,available,total) values ('293','30','89',0,68); +insert into inventory (id,product_id,location_id,available,total) values ('294','31','89',25,32); +insert into inventory (id,product_id,location_id,available,total) values ('295','32','89',37,80); +insert into inventory (id,product_id,location_id,available,total) values ('296','33','89',12,43); +insert into inventory (id,product_id,location_id,available,total) values ('297','34','89',34,89); +insert into inventory (id,product_id,location_id,available,total) values ('298','35','89',54,97); +insert into inventory (id,product_id,location_id,available,total) values ('299','36','89',2,18); +insert into inventory (id,product_id,location_id,available,total) values ('300','37','89',13,16); +insert into inventory (id,product_id,location_id,available,total) values ('301','38','89',19,54); +insert into inventory (id,product_id,location_id,available,total) values ('302','39','89',16,40); +insert into inventory (id,product_id,location_id,available,total) values ('303','40','89',10,93); +insert into inventory (id,product_id,location_id,available,total) values ('304','41','89',35,39); +insert into inventory (id,product_id,location_id,available,total) values ('305','42','89',24,25); +insert into inventory (id,product_id,location_id,available,total) values ('306','43','89',5,55); +insert into inventory (id,product_id,location_id,available,total) values ('307','44','89',9,43); +insert into inventory (id,product_id,location_id,available,total) values ('308','45','89',36,82); +insert into inventory (id,product_id,location_id,available,total) values ('309','46','89',5,8); +insert into inventory (id,product_id,location_id,available,total) values ('310','47','89',18,20); +insert into inventory (id,product_id,location_id,available,total) values ('311','48','89',2,9); +insert into inventory (id,product_id,location_id,available,total) values ('312','49','89',34,91); +insert into inventory (id,product_id,location_id,available,total) values ('313','50','89',27,55); +insert into inventory (id,product_id,location_id,available,total) values ('314','51','89',11,72); +insert into inventory (id,product_id,location_id,available,total) values ('315','52','89',8,13); +insert into inventory (id,product_id,location_id,available,total) values ('316','53','89',4,31); +insert into inventory (id,product_id,location_id,available,total) values ('317','54','89',1,1); +insert into inventory (id,product_id,location_id,available,total) values ('318','55','89',7,19); +insert into inventory (id,product_id,location_id,available,total) values ('319','56','89',3,35); +insert into inventory (id,product_id,location_id,available,total) values ('320','57','89',58,73); +insert into inventory (id,product_id,location_id,available,total) values ('321','58','89',2,32); +insert into inventory (id,product_id,location_id,available,total) values ('322','59','89',51,64); +insert into inventory (id,product_id,location_id,available,total) values ('323','60','89',34,79); +insert into inventory (id,product_id,location_id,available,total) values ('324','61','89',44,66); +insert into inventory (id,product_id,location_id,available,total) values ('325','62','89',37,46); +insert into inventory (id,product_id,location_id,available,total) values ('326','63','89',10,11); +insert into inventory (id,product_id,location_id,available,total) values ('327','64','89',15,74); +insert into inventory (id,product_id,location_id,available,total) values ('328','65','89',8,19); +insert into inventory (id,product_id,location_id,available,total) values ('329','66','89',13,26); +insert into inventory (id,product_id,location_id,available,total) values ('330','67','89',0,1); +insert into inventory (id,product_id,location_id,available,total) values ('331','68','89',5,17); +insert into inventory (id,product_id,location_id,available,total) values ('332','69','89',0,0); +insert into inventory (id,product_id,location_id,available,total) values ('333','70','89',1,48); +insert into inventory (id,product_id,location_id,available,total) values ('334','71','89',13,70); +insert into inventory (id,product_id,location_id,available,total) values ('335','72','89',24,68); +insert into inventory (id,product_id,location_id,available,total) values ('336','73','89',21,48); +insert into inventory (id,product_id,location_id,available,total) values ('337','74','89',17,68); +insert into inventory (id,product_id,location_id,available,total) values ('338','75','89',72,72); +insert into inventory (id,product_id,location_id,available,total) values ('339','76','89',6,24); +insert into inventory (id,product_id,location_id,available,total) values ('340','77','89',18,22); +insert into inventory (id,product_id,location_id,available,total) values ('341','78','89',8,24); +insert into inventory (id,product_id,location_id,available,total) values ('342','79','89',26,31); +insert into inventory (id,product_id,location_id,available,total) values ('343','80','89',14,19); +insert into inventory (id,product_id,location_id,available,total) values ('344','81','89',10,31); +insert into inventory (id,product_id,location_id,available,total) values ('345','82','89',88,92); +insert into inventory (id,product_id,location_id,available,total) values ('346','83','89',5,11); +insert into inventory (id,product_id,location_id,available,total) values ('347','84','89',13,72); +insert into inventory (id,product_id,location_id,available,total) values ('348','85','89',18,37); +insert into inventory (id,product_id,location_id,available,total) values ('349','86','89',6,12); +insert into inventory (id,product_id,location_id,available,total) values ('350','87','89',79,99); +insert into inventory (id,product_id,location_id,available,total) values ('351','2','90',10,19); +insert into inventory (id,product_id,location_id,available,total) values ('353','4','90',8,38); +insert into inventory (id,product_id,location_id,available,total) values ('352','3','90',3,6); +insert into inventory (id,product_id,location_id,available,total) values ('354','5','90',26,54); +insert into inventory (id,product_id,location_id,available,total) values ('355','6','90',20,73); +insert into inventory (id,product_id,location_id,available,total) values ('356','7','90',30,95); +insert into inventory (id,product_id,location_id,available,total) values ('357','8','90',32,93); +insert into inventory (id,product_id,location_id,available,total) values ('358','9','90',4,18); +insert into inventory (id,product_id,location_id,available,total) values ('359','10','90',32,94); +insert into inventory (id,product_id,location_id,available,total) values ('360','11','90',57,80); +insert into inventory (id,product_id,location_id,available,total) values ('361','12','90',3,6); +insert into inventory (id,product_id,location_id,available,total) values ('362','13','90',40,58); +insert into inventory (id,product_id,location_id,available,total) values ('363','14','90',54,91); +insert into inventory (id,product_id,location_id,available,total) values ('364','15','90',10,11); +insert into inventory (id,product_id,location_id,available,total) values ('365','16','90',34,58); +insert into inventory (id,product_id,location_id,available,total) values ('366','17','90',34,99); +insert into inventory (id,product_id,location_id,available,total) values ('367','18','90',72,90); +insert into inventory (id,product_id,location_id,available,total) values ('368','19','90',13,76); +insert into inventory (id,product_id,location_id,available,total) values ('369','20','90',37,71); +insert into inventory (id,product_id,location_id,available,total) values ('370','21','90',21,39); +insert into inventory (id,product_id,location_id,available,total) values ('371','22','90',4,20); +insert into inventory (id,product_id,location_id,available,total) values ('372','23','90',11,73); +insert into inventory (id,product_id,location_id,available,total) values ('373','24','90',18,100); +insert into inventory (id,product_id,location_id,available,total) values ('375','26','90',0,1); +insert into inventory (id,product_id,location_id,available,total) values ('374','25','90',26,62); +insert into inventory (id,product_id,location_id,available,total) values ('376','27','90',10,28); +insert into inventory (id,product_id,location_id,available,total) values ('377','28','90',68,78); +insert into inventory (id,product_id,location_id,available,total) values ('378','29','90',10,73); +insert into inventory (id,product_id,location_id,available,total) values ('379','30','90',73,96); +insert into inventory (id,product_id,location_id,available,total) values ('380','31','90',35,75); +insert into inventory (id,product_id,location_id,available,total) values ('381','32','90',58,88); +insert into inventory (id,product_id,location_id,available,total) values ('382','33','90',14,26); +insert into inventory (id,product_id,location_id,available,total) values ('383','34','90',22,24); +insert into inventory (id,product_id,location_id,available,total) values ('384','35','90',23,72); +insert into inventory (id,product_id,location_id,available,total) values ('385','36','90',23,59); +insert into inventory (id,product_id,location_id,available,total) values ('387','38','90',51,71); +insert into inventory (id,product_id,location_id,available,total) values ('386','37','90',3,6); +insert into inventory (id,product_id,location_id,available,total) values ('388','39','90',48,60); +insert into inventory (id,product_id,location_id,available,total) values ('389','40','90',44,56); +insert into inventory (id,product_id,location_id,available,total) values ('390','41','90',25,36); +insert into inventory (id,product_id,location_id,available,total) values ('391','42','90',32,83); +insert into inventory (id,product_id,location_id,available,total) values ('392','43','90',77,92); +insert into inventory (id,product_id,location_id,available,total) values ('393','44','90',30,38); +insert into inventory (id,product_id,location_id,available,total) values ('394','45','90',43,49); +insert into inventory (id,product_id,location_id,available,total) values ('395','46','90',23,27); +insert into inventory (id,product_id,location_id,available,total) values ('396','47','90',78,84); +insert into inventory (id,product_id,location_id,available,total) values ('397','48','90',26,48); +insert into inventory (id,product_id,location_id,available,total) values ('398','49','90',15,52); +insert into inventory (id,product_id,location_id,available,total) values ('399','50','90',4,45); +insert into inventory (id,product_id,location_id,available,total) values ('400','51','90',53,77); +insert into inventory (id,product_id,location_id,available,total) values ('401','52','90',5,6); +insert into inventory (id,product_id,location_id,available,total) values ('402','53','90',17,30); +insert into inventory (id,product_id,location_id,available,total) values ('403','54','90',4,44); +insert into inventory (id,product_id,location_id,available,total) values ('404','55','90',12,20); +insert into inventory (id,product_id,location_id,available,total) values ('405','56','90',15,25); +insert into inventory (id,product_id,location_id,available,total) values ('406','57','90',1,33); +insert into inventory (id,product_id,location_id,available,total) values ('407','58','90',22,34); +insert into inventory (id,product_id,location_id,available,total) values ('408','59','90',6,12); +insert into inventory (id,product_id,location_id,available,total) values ('409','60','90',3,9); +insert into inventory (id,product_id,location_id,available,total) values ('410','61','90',41,59); +insert into inventory (id,product_id,location_id,available,total) values ('411','62','90',16,32); +insert into inventory (id,product_id,location_id,available,total) values ('412','63','90',7,15); +insert into inventory (id,product_id,location_id,available,total) values ('413','64','90',49,95); +insert into inventory (id,product_id,location_id,available,total) values ('414','65','90',41,45); +insert into inventory (id,product_id,location_id,available,total) values ('416','67','90',11,39); +insert into inventory (id,product_id,location_id,available,total) values ('415','66','90',18,45); +insert into inventory (id,product_id,location_id,available,total) values ('417','68','90',26,84); +insert into inventory (id,product_id,location_id,available,total) values ('418','69','90',3,4); +insert into inventory (id,product_id,location_id,available,total) values ('419','70','90',72,98); +insert into inventory (id,product_id,location_id,available,total) values ('420','71','90',26,28); +insert into inventory (id,product_id,location_id,available,total) values ('421','72','90',2,2); +insert into inventory (id,product_id,location_id,available,total) values ('422','73','90',57,90); +insert into inventory (id,product_id,location_id,available,total) values ('423','74','90',12,75); +insert into inventory (id,product_id,location_id,available,total) values ('424','75','90',23,37); +insert into inventory (id,product_id,location_id,available,total) values ('425','76','90',22,22); +insert into inventory (id,product_id,location_id,available,total) values ('426','77','90',30,86); +insert into inventory (id,product_id,location_id,available,total) values ('427','78','90',44,82); +insert into inventory (id,product_id,location_id,available,total) values ('428','79','90',13,17); +insert into inventory (id,product_id,location_id,available,total) values ('429','80','90',38,45); +insert into inventory (id,product_id,location_id,available,total) values ('430','81','90',26,91); +insert into inventory (id,product_id,location_id,available,total) values ('431','82','90',34,41); +insert into inventory (id,product_id,location_id,available,total) values ('432','83','90',19,43); +insert into inventory (id,product_id,location_id,available,total) values ('433','84','90',43,43); +insert into inventory (id,product_id,location_id,available,total) values ('434','85','90',34,69); +insert into inventory (id,product_id,location_id,available,total) values ('435','86','90',10,25); +insert into inventory (id,product_id,location_id,available,total) values ('436','87','90',18,34); +insert into inventory (id,product_id,location_id,available,total) values ('437','2','91',25,98); +insert into inventory (id,product_id,location_id,available,total) values ('438','3','91',15,28); +insert into inventory (id,product_id,location_id,available,total) values ('439','4','91',56,97); +insert into inventory (id,product_id,location_id,available,total) values ('440','5','91',20,30); + +insert into location (id,street,city,zipcode,name,geo) values ('87','7153 east thomas road','scottsdale','85251','phoenix equipment rentals','-111.9271738,33.48034450000001'); +insert into location (id,street,city,zipcode,name,geo) values ('91','2799 broadway','new york','10025','cascabel armory','-73.9676965,40.8029807'); +insert into location (id,street,city,zipcode,name,geo) values ('89','1850 el camino real','menlo park','94027','military weaponry','-122.194253,37.459525'); +insert into location (id,street,city,zipcode,name,geo) values ('92','32/66-70 marine parade','coolangatta','4225','marine parade','153.536972,-28.167598'); +insert into location (id,street,city,zipcode,name,geo) values ('90','tolstraat 200','amsterdam','1074','munitions shopmore','4.907475499999999,52.3530638'); +insert into location (id,street,city,zipcode,name,geo) values ('88','390 lang road','burlingame','94010','bay area firearms','-122.3381437,37.5874391'); + +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('4','m9',53,75,15,null,'single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('3','m1911',53,50,7,null,'single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('6','makarov sd',0,50,8,null,'single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('7','pdw',53,75,30,null,'single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('8','makarov pm',53,50,8,null,'single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('9','double-barreled shotgun',90,null,2,null,'single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('10','saiga 12k',90,250,8,null,'single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('11','remington 870 (flashlight)',90,null,8,'flashlight','single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('12','revolver',53,100,6,null,'single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('13','winchester 1866',125,150,15,null,'single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('14','bizon pp-19 sd',0,100,64,'silenced','single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('15','mp5sd6',0,100,30,'silenced','single,burst,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('16','mp5a5',53,100,30,null,'single,burst,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('17','ak-107',80,400,30,'kobra sight','single,burst,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('18','ak-107 gl',80,null,30,'kobra sight','single,burst,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('19','ak-107 gl pso',80,400,30,'scope,gp-25 launcher]','single,burst,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('20','ak-107 pso',80,600,30,'scope','single,burst,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('21','ak-74',80,300,30,null,'single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('22','akm',149,400,30,null,'single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('23','aks',149,200,30,null,'single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('24','aks (gold)',149,200,30,null,'single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('25','m1014',90,null,8,null,'single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('26','aks-74 kobra',80,300,30,'kobra sight','single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('27','aks-74 pso',80,400,30,'scope','single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('28','aks-74u',80,200,30,null,'single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('29','aks-74un kobra',0,300,30,'kobra sight,silenced]','single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('30','ak-74 gp-25',80,300,30,'gp-25 launcher','single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('31','fn fal an/pvs-4',180,400,20,'nv scope','single,burst]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('32','g36',80,400,30,'scope,aimpoint sight]','single,burst,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('33','fn fal',180,400,20,null,'single,burst]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('34','g36 c',80,300,30,null,'single,burst,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('35','g36-c sd (camo)',0,300,30,'aimpoint sight,silenced]','single,burst,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('36','g36a (camo)',80,400,30,'scope,aimpoint sight]','single,burst,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('37','g36c (camo)',80,300,30,null,'single,burst,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('38','g36 k',80,400,30,'scope,aimpoint sight]','single,burst,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('39','g36c-sd',0,300,30,'aimpoint sight','single,burst,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('40','g36k (camo)',80,400,30,'scope,aimpoint sight]','single,burst,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('41','l85a2 acog gl',80,600,30,'acog scope,m203 launcher]','single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('42','l85a2 susat',80,300,30,'susat optical scope','single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('43','m16a2',80,400,30,null,'single,burst]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('44','l85a2 aws',80,300,30,'thermal scope,nv scope,laser sight,variable zoom]','single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('45','l85a2 holo',80,300,30,'holographic sight','single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('46','lee enfield',162,400,10,null,'single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('47','m16a4 acog',80,600,30,'acog scope','single,burst]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('49','m16a2 m203',80,400,30,'m203 launcher','single,burst]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('48','m4a1',80,300,30,null,'single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('50','m4a1 holo',80,300,30,'holographic sight,m203 launcher]','single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('51','m4a1 cco',80,300,30,'aimpoint sight','single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('52','m4a1 cco sd',0,200,30,'aimpoint sight,silenced]','single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('53','m4a1 m203 rco',80,600,30,'acog sight,m203 launcher]','single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('54','m4a3 cco',80,300,30,'aimpoint sight,flashlight]','single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('55','rpk',80,400,30,null,'single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('56','sa-58 cco',149,300,30,'aimpoint sight','single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('57','sa-58p',149,400,30,null,'single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('58','sa-58v',149,200,30,null,'single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('59','sa-58v acog',149,400,30,'acog sight','single,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('60','er7 rfw',180,2000,25,'scope,aimpoint sight]','single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('61','as50',455,1600,5,'scope','single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('62','ksvk',455,800,5,'scope','single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('63','cz550',180,800,5,'scope','single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('64','dmr',180,800,20,'scope','single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('65','m107',455,1200,10,'scope','single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('66','m24',180,800,5,'scope','single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('67','m40a3',180,800,5,'scope,camo]','single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('68','m14 aim',180,500,20,'aimpoint sight','single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('69','m240',180,400,100,null,'full auto'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('70','mg36',80,400,100,'aimpoint sight','single,burst,full auto]'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('72','pkm',180,400,100,null,'full auto'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('71','svd camo',180,1200,10,'scope,camo]','single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('73','mk 48 mod 0',180,400,100,'aimpoint sight','full auto'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('74','m249 saw',80,300,200,null,'full auto'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('75','crowbar',2,1,null,null,'single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('76','hatchet',2,1,null,null,'single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('77','pkp',180,600,100,'scope','full auto'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('78','machete',2,1,null,null,'single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('79','m67 frag grenade',null,null,null,null,null); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('80','compound crossbow',3,30,1,null,'single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('81','smoke grenade',null,null,null,null,null); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('82','m136 launcher',160,1000,1,null,'single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('83','30rnd. ak sd',0,null,30,null,null); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('84','30rnd g36 sd',0,null,30,null,null); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('85','g36 mag.',80,null,30,null,null); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('86','flashlight',null,null,null,null,null); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('87','nv goggles',null,null,null,null,null); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('2','g17',53,75,15,'flashlight','single'); +insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('5','m9 sd',0,75,15,'silenced','single'); + + + + alter table customer add primary key (id); + alter table inventory add primary key (id); + alter table location add primary key (id); + alter table product add primary key (id); + alter table session add primary key (id); + alter table inventory add constraint location_fk foreign key (location_id) references location (id); + alter table inventory add constraint product_fk foreign key (product_id) + references product (id); + alter table reservation add constraint reservation_customer_fk foreign key (customer_id) references customer (id); + alter table reservation add constraint reservation_location_fk foreign key (location_id) references location (id); + alter table reservation add constraint reservation_product_fk foreign key (product_id) references product (id); + + go + + create view inventory_view + as + select p.name as product, + l.name as location, + i.available + from inventory i, + product p, + location l + where p.id = i.product_id + and l.id = i.location_id; + + From d24ff7d1db607fcf0dfedb0fddc59718cccb5355 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 23 Apr 2014 16:53:59 -0700 Subject: [PATCH 041/239] Bring up autoupdate --- lib/mssql.js | 199 +++++++++++++++++++++++++--------------- test/autoupdate.test.js | 121 ++++++++++++++++++++++++ 2 files changed, 248 insertions(+), 72 deletions(-) create mode 100644 test/autoupdate.test.js diff --git a/lib/mssql.js b/lib/mssql.js index dde74f0..357aa8f 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -2,6 +2,7 @@ var mssql = require("mssql"); var jdb = require("loopback-datasource-juggler"); var util = require("util"); +var async = require('async'); var debug = require('debug')('loopback:connector:mssql'); var name = "mssql"; @@ -495,11 +496,15 @@ MsSQL.prototype.columnEscaped = function (model, property) { return '[' + this.escapeName(this.column(model, property)) +']'; }; -MsSQL.prototype.tableEscaped = function (model) { +MsSQL.prototype.schemaName = function (model) { // Check if there is a 'schema' property for mssql var mssqlMeta = this._models[model].settings && this._models[model].settings.mssql; var schemaName = (mssqlMeta && (mssqlMeta.schema || mssqlMeta.schemaName)) || 'dbo'; - return '[' + schemaName + '].[' + this.escapeName(this.table(model)) + ']'; + return schemaName; +}; + +MsSQL.prototype.tableEscaped = function (model) { + return '[' + this.schemaName(model) + '].[' + this.escapeName(this.table(model)) + ']'; }; @@ -713,57 +718,89 @@ MsSQL.prototype.buildWhere = function (conds, props) { return 'WHERE ' + cs.join(' AND '); }; -// MsSQL.prototype.autoupdate = function (cb) { -// var self = this; -// var wait = 0; -// Object.keys(this._models).forEach(function (model) { -// wait += 1; -// self.query('SHOW FIELDS FROM ' + self.tableEscaped(model), function (err, fields) { -// self.query('SHOW INDEXES FROM ' + self.tableEscaped(model), function (err, indexes) { -// if (!err && fields.length) { -// self.alterTable(model, fields, indexes, done); -// } else { -// self.createTable(model, done); -// } -// }); -// }); -// }); - -// function done(err) { -// if (err) { -// console.log(err); -// } -// if (--wait === 0 && cb) { -// cb(); -// } -// } -// }; +MsSQL.prototype.showFields = function (model, cb) { + var sql = 'select [COLUMN_NAME] as [Field], ' + + ' [IS_NULLABLE] as [Null], [DATA_TYPE] as [Type],' + + ' [CHARACTER_MAXIMUM_LENGTH] as [Length],' + + ' [NUMERIC_PRECISION] as [Precision], NUMERIC_SCALE as [Scale]' + + ' from INFORMATION_SCHEMA.COLUMNS' + + ' where [TABLE_SCHEMA] = \'' + this.schemaName(model) + '\'' + + ' and [TABLE_NAME] = \'' + this.escapeName(this.table(model)) + '\'' + + ' order by [ORDINAL_POSITION]' + this.query(sql, function (err, fields) { + if (err) { + return cb && cb(err); + } else { + if (Array.isArray(fields)) { + fields.forEach(function (f) { + if (f.Length) { + f.Type = f.Type + '(' + f.Length + ')'; + } else if (f.Precision) { + f.Type = f.Type + '(' + f.Precision, +',' + f.Scale + ')'; + } + }); + } + cb && cb(err, fields); + } + }); +}; -// MsSQL.prototype.isActual = function (cb) { -// var ok = false; -// var self = this; -// var wait = 0; -// Object.keys(this._models).forEach(function (model) { -// wait += 1; -// self.query('SHOW FIELDS FROM ' + model, function (err, fields) { -// self.query('SHOW INDEXES FROM ' + model, function (err, indexes) { -// self.alterTable(model, fields, indexes, done, true); -// }); -// }); -// }); - -// function done(err, needAlter) { -// if (err) { -// console.log(err); -// } -// ok = ok || needAlter; -// if (--wait === 0 && cb) { -// cb(null, !ok); -// } -// } -// }; +MsSQL.prototype.showIndexes = function (model, cb) { + process.nextTick(function () { + cb && cb(null, []); + }); +}; + +MsSQL.prototype.autoupdate = function (cb) { + var self = this; + var wait = 0; + Object.keys(this._models).forEach(function (model) { + wait += 1; + self.showFields(model, function (err, fields) { + self.showIndexes(model, function (err, indexes) { + if (!err && fields.length) { + self.alterTable(model, fields, indexes, done); + } else { + self.createTable(model, done); + } + }); + }); + }); + + function done(err) { + if (err) { + console.log(err); + } + if (--wait === 0 && cb) { + cb(); + } + } +}; + +MsSQL.prototype.isActual = function (cb) { + var ok = false; + var self = this; + var wait = 0; + Object.keys(this._models).forEach(function (model) { + wait += 1; + self.showFields(model, function (err, fields) { + self.showIndexes(model, function (err, indexes) { + self.alterTable(model, fields, indexes, done, true); + }); + }); + }); + + function done(err, needAlter) { + if (err) { + console.log(err); + } + ok = ok || needAlter; + if (--wait === 0 && cb) { + cb(null, !ok); + } + } +}; -//not working yet MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, checkOnly) { var self = this; var m = this._models[model]; @@ -792,6 +829,10 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, } var aiNames = Object.keys(ai); + var columnsToAdd = []; + var columnsToDrop = []; + var columnsToAlter = []; + // change/add new fields propNames.forEach(function (propName) { if (propName === idName) return; @@ -805,7 +846,7 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, if (found) { actualize(propName, found); } else { - sql.push('ADD COLUMN `' + propName + '` ' + self.propertySettingsSQL(model, propName)); + columnsToAdd.push('[' + propName + '] ' + self.propertySettingsSQL(model, propName)); } }); @@ -814,7 +855,7 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, var notFound = !~propNames.indexOf(f.Field); if (f.Field === idName) return; if (notFound || !m.properties[f.Field]) { - sql.push('DROP COLUMN `' + f.Field + '`'); + columnsToDrop.push('[' + f.Field + ']'); } }); @@ -825,7 +866,7 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, } if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || m.properties[indexName] && !m.properties[indexName].index) { - sql.push('DROP INDEX `' + indexName + '`'); + sql.push('DROP INDEX [' + indexName + ']'); } else { // first: check single (only type and kind) if (m.properties[indexName] && !m.properties[indexName].index) { @@ -842,7 +883,7 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, }); } if (!orderMatched) { - sql.push('DROP INDEX `' + indexName + '`'); + sql.push('DROP INDEX [' + indexName + ']'); delete ai[indexName]; } } @@ -865,11 +906,11 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, // kind = i.kind; } if (kind && type) { - sql.push('ADD ' + kind + ' INDEX `' + propName - + '` (`' + propName + '`) ' + type); + sql.push('ADD ' + kind + ' INDEX [' + propName + + '] ([' + propName + ']) ' + type); } else { - sql.push('ADD ' + kind + ' INDEX `' + propName + '` ' - + type + ' (`' + propName + '`) '); + sql.push('ADD ' + kind + ' INDEX [' + propName + '] ' + + type + ' ([' + propName + ']) '); } } }); @@ -888,31 +929,45 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, kind = i.kind; } if (kind && type) { - sql.push('ADD ' + kind + ' INDEX `' + indexName + '` (' + sql.push('ADD ' + kind + ' INDEX [' + indexName + '] (' + i.columns + ') ' + type); } else { - sql.push('ADD ' + kind + ' INDEX ' + type + ' `' + indexName - + '` (' + i.columns + ')'); + sql.push('ADD ' + kind + ' INDEX ' + type + ' [' + indexName + + '] (' + i.columns + ')'); } } }); - if (sql.length) { - var query = 'ALTER TABLE ' + self.tableEscaped(model) + ' ' + sql.join(',' - + MsSQL.newline); + var statements = []; + if (columnsToAdd.length) { + statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' ADD ' + + columnsToAdd.join(',' + MsSQL.newline)); + } + + if (columnsToAlter.length) { + statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' ALTER COLUMN ' + + columnsToAlter.join(',' + MsSQL.newline)); + } + + if (columnsToDrop.length) { + statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' DROP COLUMN' + + columnsToDrop.join(',' + MsSQL.newline)); + } + + async.each(statements, function(query, fn) { if (checkOnly) { - done(null, true, {statements: sql, query: query}); + fn(null, true, {statements: statements, query: query}); } else { - this.query(query, done); + self.query(query, fn); } - } else { - done(); - } + }, function(err, results) { + done && done(err, results); + }); function actualize(propName, oldSettings) { var newSettings = m.properties[propName]; if (newSettings && changed(newSettings, oldSettings)) { - sql.push('CHANGE COLUMN `' + propName + '` `' + propName + '` ' + columnsToAlter.push('[' + propName + '] ' + self.propertySettingsSQL(model, propName)); } } @@ -1130,7 +1185,7 @@ function datatype(p) { default: case 'String': case 'JSON': - dt = '[varchar](' + (p.limit || 255) + ')'; + dt = '[nvarchar](' + (p.length || p.limit || 255) + ')'; break; case 'Text': dt = '[text]'; diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js new file mode 100644 index 0000000..35cfae4 --- /dev/null +++ b/test/autoupdate.test.js @@ -0,0 +1,121 @@ +require('./init.js'); +var assert = require('assert'); +var ds; + +before(function () { + ds = getDataSource(); +}); + +describe('MS SQL server connector', function () { + it('should auto migrate/update tables', function (done) { + + var schema_v1 = + { + "name": "CustomerTest", + "options": { + "idInjection": false, + "mssql": { + "schema": "dbo", + "table": "CUSTOMER_TEST" + } + }, + "properties": { + "id": { + "type": "String", + "length": 20, + "id": 1 + }, + "name": { + "type": "String", + "required": false, + "length": 40 + }, + "email": { + "type": "String", + "required": false, + "length": 40 + }, + "age": { + "type": "Number", + "required": false + } + } + } + + var schema_v2 = + { + "name": "CustomerTest", + "options": { + "idInjection": false, + "mssql": { + "schema": "dbo", + "table": "CUSTOMER_TEST" + } + }, + "properties": { + "id": { + "type": "String", + "length": 20, + "id": 1 + }, + "email": { + "type": "String", + "required": false, + "length": 60, + "mssql": { + "columnName": "EMAIL", + "dataType": "nvarchar", + "dataLength": 60, + "nullable": "Y" + } + }, + "firstName": { + "type": "String", + "required": false, + "length": 40 + }, + "lastName": { + "type": "String", + "required": false, + "length": 40 + } + } + } + + ds.createModel(schema_v1.name, schema_v1.properties, schema_v1.options); + + ds.automigrate(function (err) { + assert(!err); + ds.discoverModelProperties('CUSTOMER_TEST', function (err, props) { + console.error(err); + assert(!err); + assert.equal(props.length, 4); + var names = props.map(function (p) { + return p.columnName; + }); + assert.equal(names[0], 'id'); + assert.equal(names[1], 'name'); + assert.equal(names[2], 'email'); + assert.equal(names[3], 'age'); + + ds.createModel(schema_v2.name, schema_v2.properties, schema_v2.options); + + ds.autoupdate(function (err, result) { + ds.discoverModelProperties('CUSTOMER_TEST', function (err, props) { + assert.equal(props.length, 4); + var names = props.map(function (p) { + return p.columnName; + }); + assert.equal(names[0], 'id'); + assert.equal(names[1], 'email'); + assert.equal(names[2], 'firstName'); + assert.equal(names[3], 'lastName'); + // console.log(err, result); + done(err, result); + }); + }); + }); + }); + }); +}); + From 222bc9983f547ec760d7538f68e163de4dcb7cd8 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 24 Apr 2014 20:47:00 -0700 Subject: [PATCH 042/239] Fix alter columns --- lib/mssql.js | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 357aa8f..491ac64 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -27,6 +27,7 @@ function MsSQL(settings) { // this.name = name; // this.settings = settings || {}; this.settings.server = this.settings.host || this.settings.hostname; + this.settings.user = this.settings.user || this.settings.username; this._models = {}; this._idxNames = {}; } @@ -467,8 +468,8 @@ MsSQL.prototype.fromDatabase = function (model, data) { if (!data) { return null; } - //create an "id" property in the data for backwards compatibility with juggling-db - data.id = data[this.idName(model)]; + // create an "id" property in the data for backwards compatibility with juggling-db + // data.id = data[this.idName(model)]; var props = this._models[model].properties; //look for date values in the data, convert them from the database to a javascript date object Object.keys(data).forEach(function (key) { @@ -751,10 +752,20 @@ MsSQL.prototype.showIndexes = function (model, cb) { }); }; -MsSQL.prototype.autoupdate = function (cb) { +MsSQL.prototype.autoupdate = function (models, cb) { var self = this; var wait = 0; - Object.keys(this._models).forEach(function (model) { + if ((!cb) && ('function' === typeof models)) { + cb = models; + models = undefined; + } + // First argument is a model name + if ('string' === typeof models) { + models = [models]; + } + + models = models || Object.keys(this._models); + models.forEach(function (model) { wait += 1; self.showFields(model, function (err, fields) { self.showIndexes(model, function (err, indexes) { @@ -769,7 +780,7 @@ MsSQL.prototype.autoupdate = function (cb) { function done(err) { if (err) { - console.log(err); + debug(err); } if (--wait === 0 && cb) { cb(); @@ -792,7 +803,7 @@ MsSQL.prototype.isActual = function (cb) { function done(err, needAlter) { if (err) { - console.log(err); + debug(err); } ok = ok || needAlter; if (--wait === 0 && cb) { @@ -945,8 +956,11 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, } if (columnsToAlter.length) { - statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' ALTER COLUMN ' - + columnsToAlter.join(',' + MsSQL.newline)); + // SQL Server doesn't allow multiple columns to be altered in one statement + columnsToAlter.forEach(function (c) { + statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' ALTER COLUMN ' + + c); + }); } if (columnsToDrop.length) { @@ -1090,10 +1104,20 @@ MsSQL.prototype.propertySettingsSQL = function (model, prop) { (p.allowNull === false || p['null'] === false ? 'NOT NULL' : 'NULL'); }; -MsSQL.prototype.automigrate = function (cb) { +MsSQL.prototype.automigrate = function (models, cb) { var self = this; var wait = 0; - Object.keys(this._models).forEach(function (model) { + if ((!cb) && ('function' === typeof models)) { + cb = models; + models = undefined; + } + // First argument is a model name + if ('string' === typeof models) { + models = [models]; + } + + models = models || Object.keys(this._models); + models.forEach(function (model) { wait += 1; self.dropTable(model, function (err) { if (err) { From 31cd2d65a374d403974aa056a5f7a8c7c5c74560 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 28 Apr 2014 11:40:04 -0700 Subject: [PATCH 043/239] Use a compatible pagination for mssql prior to 2012 --- NOTICE | 21 ++++++++++++--- lib/discovery.js | 70 ++++++++++++++++++++++++++++++++++++++++-------- package.json | 3 ++- 3 files changed, 78 insertions(+), 16 deletions(-) diff --git a/NOTICE b/NOTICE index 5b6210a..3a5202d 100644 --- a/NOTICE +++ b/NOTICE @@ -1,9 +1,22 @@ -The project contains code from https://github.com/code-vicar/jugglingdb-mssql under the MIT license: +The project contains code from [https://github.com/code-vicar/jugglingdb-mssql](https://github.com/code-vicar/jugglingdb-mssql) +under the MIT license: MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/discovery.js b/lib/discovery.js index bcbff66..9e99f68 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -1,15 +1,27 @@ module.exports = mixinDiscovery; -function mixinDiscovery(PostgreSQL) { +function mixinDiscovery(MsSQL) { var async = require('async'); - function paginateSQL(sql, orderBy, options) { + function paginateSQL2012(sql, orderBy, options) { options = options || {}; - var limit = ''; + var offset = options.offset || options.skip; + if (isNaN(offset)) { + offset = 0; + } + var limit = options.limit; + if (isNaN(limit)) { + limit = -1; + } + if (offset === 0 && limit === -1) { + return sql; + } + + var fetch = ''; if (options.offset || options.skip || options.limit) { - limit = ' OFFSET ' + (options.offset || options.skip || 0) + ' ROWS'; // Offset starts from 0 + fetch = ' OFFSET ' + offset + ' ROWS'; // Offset starts from 0 if (options.limit) { - limit = limit + ' FETCH NEXT ' + options.limit + 'ROWS ONLY'; + fetch = fetch + ' FETCH NEXT ' + options.limit + 'ROWS ONLY'; } if(!orderBy) { sql += ' ORDER BY 1'; @@ -18,7 +30,43 @@ function mixinDiscovery(PostgreSQL) { if (orderBy) { sql += ' ORDER BY ' + orderBy; } - return sql + limit; + return sql + fetch; + } + + function paginateSQL(sql, orderBy, options) { + options = options || {}; + var offset = options.offset || options.skip; + if (isNaN(offset)) { + offset = 0; + } + var limit = options.limit; + if (isNaN(limit)) { + limit = -1; + } + if (offset === 0 && limit === -1) { + return sql; + } + var index = sql.indexOf(' FROM'); + var select = sql.substring(0, index); + var from = sql.substring(index); + if (orderBy) { + orderBy = 'ORDER BY ' + orderBy; + } else { + orderBy = 'ORDER BY 1'; + } + var paginatedSQL = 'SELECT *' + MsSQL.newline + + 'FROM (' + MsSQL.newline + + select + ', ROW_NUMBER() OVER' + + ' (' + orderBy + ') AS rowNum' + MsSQL.newline + + from + MsSQL.newline; + paginatedSQL += ') AS S' + MsSQL.newline + + 'WHERE S.rowNum > ' + offset; + + if (limit !== -1) { + paginatedSQL += ' AND S.rowNum <= ' + (offset + limit); + } + + return paginatedSQL + MsSQL.newline; } /*! @@ -78,7 +126,7 @@ function mixinDiscovery(PostgreSQL) { * @param {Object} options Options for discovery * @param {Function} [cb] The callback function */ - PostgreSQL.prototype.discoverModelDefinitions = function (options, cb) { + MsSQL.prototype.discoverModelDefinitions = function (options, cb) { if (!cb && typeof options === 'function') { cb = options; options = {}; @@ -167,7 +215,7 @@ function mixinDiscovery(PostgreSQL) { * @param {Function} [cb] The callback function * */ - PostgreSQL.prototype.discoverModelProperties = function (table, options, cb) { + MsSQL.prototype.discoverModelProperties = function (table, options, cb) { var args = getArgs(table, options, cb); var owner = args.owner; table = args.table; @@ -234,7 +282,7 @@ function mixinDiscovery(PostgreSQL) { * @param {Object} options The options for discovery * @param {Function} [cb] The callback function */ - PostgreSQL.prototype.discoverPrimaryKeys = function (table, options, cb) { + MsSQL.prototype.discoverPrimaryKeys = function (table, options, cb) { var args = getArgs(table, options, cb); var owner = args.owner; table = args.table; @@ -292,7 +340,7 @@ function mixinDiscovery(PostgreSQL) { * @param {Object} options The options for discovery * @param {Function} [cb] The callback function */ - PostgreSQL.prototype.discoverForeignKeys = function (table, options, cb) { + MsSQL.prototype.discoverForeignKeys = function (table, options, cb) { var args = getArgs(table, options, cb); var owner = args.owner; table = args.table; @@ -337,7 +385,7 @@ function mixinDiscovery(PostgreSQL) { * @param {Object} options The options for discovery * @param {Function} [cb] The callback function */ - PostgreSQL.prototype.discoverExportedForeignKeys = function (table, options, cb) { + MsSQL.prototype.discoverExportedForeignKeys = function (table, options, cb) { var args = getArgs(table, options, cb); var owner = args.owner; table = args.table; diff --git a/package.json b/package.json index c01ed8c..7942724 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "devDependencies": { "loopback-datasource-juggler": "1.x", "mocha": "~1.18.2", - "should": "~1.2.2" + "should": "~1.2.2", + "rc": "~0.3.5" }, "scripts": { "test": "./node_modules/.bin/mocha --timeout 5000 -R spec" From 22f2f3f68ee39b754552e34239034a65f3306e50 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 28 Apr 2014 13:10:46 -0700 Subject: [PATCH 044/239] Add table schema into orderBy --- lib/discovery.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/discovery.js b/lib/discovery.js index 9e99f68..0a7569d 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -197,7 +197,7 @@ function mixinDiscovery(MsSQL) { + ' FROM information_schema.columns' + ' WHERE table_schema=\'' + owner + '\'' + (table ? ' AND table_name=\'' + table + '\'' : ''), - 'table_name, ordinal_position', {}); + 'table_schema, table_name, ordinal_position', {}); } else { sql = paginateSQL('SELECT schema_name() AS "owner", table_name AS "tableName", column_name AS "columnName", data_type AS "dataType",' + ' character_maximum_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"' From c923fa3b63ca0182b54b048a5905475fe02b6371 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 28 Apr 2014 13:16:58 -0700 Subject: [PATCH 045/239] Fix the ref to supportsOffsetFetch --- README.md | 26 +++++++++++++------------- lib/mssql.js | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index be93ed3..1ffb2c4 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ The model definition consists of the following properties: "scale": null, "mssql": { "columnName": "id", - "dataType": "character varying", + "dataType": "varchar", "dataLength": 64, "dataPrecision": null, "dataScale": null, @@ -70,14 +70,14 @@ The model definition consists of the following properties: "productId": { "type": "String", "required": false, - "length": 20, + "length": 64, "precision": null, "scale": null, "id": 1, "mssql": { "columnName": "product_id", - "dataType": "character varying", - "dataLength": 20, + "dataType": "varchar", + "dataLength": 64, "dataPrecision": null, "dataScale": null, "nullable": "YES" @@ -86,14 +86,14 @@ The model definition consists of the following properties: "locationId": { "type": "String", "required": false, - "length": 20, + "length": 64, "precision": null, "scale": null, "id": 1, "mssql": { "columnName": "location_id", - "dataType": "character varying", - "dataLength": 20, + "dataType": "varchar", + "dataLength": 64, "dataPrecision": null, "dataScale": null, "nullable": "YES" @@ -103,13 +103,13 @@ The model definition consists of the following properties: "type": "Number", "required": false, "length": null, - "precision": 32, + "precision": 10, "scale": 0, "mssql": { "columnName": "available", - "dataType": "integer", + "dataType": "int", "dataLength": null, - "dataPrecision": 32, + "dataPrecision": 10, "dataScale": 0, "nullable": "YES" } @@ -118,13 +118,13 @@ The model definition consists of the following properties: "type": "Number", "required": false, "length": null, - "precision": 32, + "precision": 10, "scale": 0, "mssql": { "columnName": "total", - "dataType": "integer", + "dataType": "int", "dataLength": null, - "dataPrecision": 32, + "dataPrecision": 10, "dataScale": 0, "nullable": "YES" } diff --git a/lib/mssql.js b/lib/mssql.js index 491ac64..289207c 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -454,7 +454,7 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { } return val; } - if (prop.type.name == "Boolean") { + if (prop.type.name === "Boolean") { return val ? 1 : 0; } @@ -583,7 +583,7 @@ MsSQL.prototype.all = function (model, filter, callback) { limit = -1; } - if (this.supportsOffsetFetch) { + if (this.settings.supportsOffsetFetch) { // SQL 2012 or later // http://technet.microsoft.com/en-us/library/gg699618.aspx sql += buildLimit(limit, offset); From 685bec66974f4f693368549304976d992393c397 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 28 Apr 2014 13:20:57 -0700 Subject: [PATCH 046/239] Clean up package.json --- package.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 7942724..067d761 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,17 @@ "name": "loopback-connector-mssql", "version": "1.0.0", "description": "Microsoft SQL Server connector for LoopBack", + "keywords": [ + "StrongLoop", + "LoopBack", + "Microsoft SQL Server", + "MSSQL", + "Database", + "DataSource", + "Connector" + ], "main": "index.js", "dependencies": { - "util": "~0.10.3", "mssql": "~0.5.3", "debug": "~0.8.0", "async": "~0.7.0" From c8c05ab65aae615e06d37462d6392dd326a9c7e0 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 29 Apr 2014 09:56:30 -0700 Subject: [PATCH 047/239] Add code to show indexes --- lib/mssql.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/mssql.js b/lib/mssql.js index 289207c..0abb45e 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -747,6 +747,28 @@ MsSQL.prototype.showFields = function (model, cb) { }; MsSQL.prototype.showIndexes = function (model, cb) { + // TODO: [rfeng] Implement SHOW INDEXES + /* + var schema = "'" + this.schemaName(model) +"'"; + var table = "'" + this.escapeName(this.table(model)) +"'"; + var sql = "SELECT OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) AS [table_schema]," + + " T.[name] AS [table_name], I.[name] AS [index_name], AC.[name] AS [column_name]," + + " I.[type_desc], I.[is_unique], I.[data_space_id], I.[ignore_dup_key], I.[is_primary_key]," + + " I.[is_unique_constraint], I.[fill_factor], I.[is_padded], I.[is_disabled], I.[is_hypothetical]," + + " I.[allow_row_locks], I.[allow_page_locks], IC.[is_descending_key], IC.[is_included_column]" + + " FROM sys.[tables] AS T" + + " INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]" + + " INNER JOIN sys.[index_columns] IC ON I.[object_id] = IC.[object_id]" + + " INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] AND IC.[column_id] = AC.[column_id]" + + " WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP'" + + " AND OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) = " + schema + " AND T.[name] = " + table + + " ORDER BY T.[name], I.[index_id], IC.[key_ordinal]"; + + this.query(sql, function (err, fields) { + cb && cb(err, fields); + }); + */ + process.nextTick(function () { cb && cb(null, []); }); From c245b397e5859497b7b6bf41d2007367da4a13b0 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 30 Apr 2014 22:29:45 -0700 Subject: [PATCH 048/239] Honor the default schema name at data source level --- lib/mssql.js | 3 ++- test/autoupdate.test.js | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 0abb45e..7899876 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -500,7 +500,8 @@ MsSQL.prototype.columnEscaped = function (model, property) { MsSQL.prototype.schemaName = function (model) { // Check if there is a 'schema' property for mssql var mssqlMeta = this._models[model].settings && this._models[model].settings.mssql; - var schemaName = (mssqlMeta && (mssqlMeta.schema || mssqlMeta.schemaName)) || 'dbo'; + var schemaName = (mssqlMeta && (mssqlMeta.schema || mssqlMeta.schemaName)) + || this.settings.schema || 'dbo'; return schemaName; }; diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js index 35cfae4..5005107 100644 --- a/test/autoupdate.test.js +++ b/test/autoupdate.test.js @@ -87,7 +87,6 @@ describe('MS SQL server connector', function () { ds.automigrate(function (err) { assert(!err); ds.discoverModelProperties('CUSTOMER_TEST', function (err, props) { - console.error(err); assert(!err); assert.equal(props.length, 4); var names = props.map(function (p) { From a229211e380792cc6b390f8cb67ec0db54137b67 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 14 May 2014 21:22:16 -0700 Subject: [PATCH 049/239] Add support for logical operators (AND/OR) --- lib/mssql.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/mssql.js b/lib/mssql.js index 7899876..e033a0f 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -660,6 +660,16 @@ MsSQL.prototype.buildWhere = function (conds, props) { var self = this; var cs = []; Object.keys(conds).forEach(function (key) { + if (key === 'and' || key === 'or') { + var clauses = conds[key]; + if (Array.isArray(clauses)) { + clauses = clauses.map(function (c) { + return '(' + self.buildWhere(c, props) + ')'; + }); + return clauses.join(' ' + key.toUpperCase() + ' '); + } + // The value is not an array, fall back to regular fields + } var keyEscaped = self.escapeKey(key); var val = self.toDatabase(props[key], conds[key], true); if (conds[key] === null) { From 48b89c0c2298c78d9620fd3b30b2bf087d110808 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 15 May 2014 10:27:39 -0700 Subject: [PATCH 050/239] Fix buildWhere --- lib/mssql.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index e033a0f..bdb62bf 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -655,7 +655,7 @@ function buildLimit(limit, offset) { return sql; } -MsSQL.prototype.buildWhere = function (conds, props) { +MsSQL.prototype._buildWhere = function (conds, props) { // debugger; var self = this; var cs = []; @@ -664,9 +664,10 @@ MsSQL.prototype.buildWhere = function (conds, props) { var clauses = conds[key]; if (Array.isArray(clauses)) { clauses = clauses.map(function (c) { - return '(' + self.buildWhere(c, props) + ')'; + return '(' + self._buildWhere(c, props) + ')'; }); - return clauses.join(' ' + key.toUpperCase() + ' '); + cs.push(clauses.join(' ' + key.toUpperCase() + ' ')); + return; } // The value is not an array, fall back to regular fields } @@ -686,7 +687,7 @@ MsSQL.prototype.buildWhere = function (conds, props) { var subClause = conds[key].max.where; sqlCond += " = (SELECT MAX(" + val + ") FROM " + tbl; if (subClause) { - sqlCond += " " + self.buildWhere(subClause, props); + sqlCond += " " + self._buildWhere(subClause, props); } sqlCond += ")"; cs.push(sqlCond); @@ -727,7 +728,12 @@ MsSQL.prototype.buildWhere = function (conds, props) { if (cs.length === 0) { return ''; } - return 'WHERE ' + cs.join(' AND '); + return cs.join(' AND '); +}; + +MsSQL.prototype.buildWhere = function (conds, props) { + var where = this._buildWhere(conds, props); + return where? 'WHERE ' + where : ''; }; MsSQL.prototype.showFields = function (model, cb) { From cf95ab4afc4830dc964778ef16718737e03ea85a Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 16 May 2014 09:49:33 -0700 Subject: [PATCH 051/239] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 067d761..c47b468 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "1.0.0", + "version": "1.0.1", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", From 2ff6df0c2093dfe50026d5a93b4b9ef1ffa06db6 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 16 May 2014 10:01:35 -0700 Subject: [PATCH 052/239] Update async version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c47b468..145df70 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "dependencies": { "mssql": "~0.5.3", "debug": "~0.8.0", - "async": "~0.7.0" + "async": "~0.9.0" }, "peerDependencies": { "loopback-datasource-juggler": "1.x" From 41b7e0bae2e0b554e084933bc6c33d190a4f51b1 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Fri, 23 May 2014 11:47:05 -0700 Subject: [PATCH 053/239] Create docs.json --- docs.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docs.json diff --git a/docs.json b/docs.json new file mode 100644 index 0000000..c0d1d67 --- /dev/null +++ b/docs.json @@ -0,0 +1,15 @@ +{ + "content": [ + { + "title": "LoopBack SQL Server Connector API", + "depth": 2 + }, + "lib/mssql.js", + { + "title": "SQL Server Discovery API", + "depth": 2 + }, + "lib/discovery.js" + ], + "codeSectionDepth": 3 +} From 417c6adbc159a769f9bc867a13299a9860d0d9cc Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Fri, 23 May 2014 13:20:10 -0700 Subject: [PATCH 054/239] Replace old README with link to docs and basic info. --- README.md | 186 ++++++++---------------------------------------------- 1 file changed, 28 insertions(+), 158 deletions(-) diff --git a/README.md b/README.md index 1ffb2c4..f93bca9 100644 --- a/README.md +++ b/README.md @@ -1,172 +1,42 @@ ## loopback-connector-mssql -The Microsoft SQL Server (MSSQL) Connector module for for [loopback-datasource-juggler](http://docs.strongloop.com/loopback-datasource-juggler/). +`loopback-connector-mssql` is the Microsoft SQL Server connector module for [loopback-datasource-juggler](https://github.com/strongloop/loopback-datasource-juggler/). +For complete documentation, see [StrongLoop Documentation | SQL Server Connector](http://docs.strongloop.com/display/DOC/SQL+Server+connector). -## Connector settings +## Installation -The connector can be configured using the following settings from the data source. +````sh +npm install loopback-connector-mssql --save +```` -* host or hostname (default to 'localhost'): The host name or ip address of the Microsoft SQL Server -* port (default to 1433): The port number of the Microsoft SQL Server -* username or user: The user name to connect to the Microsoft SQL Server -* password: The password -* database: The Microsoft SQL Server database name -* schema: The database schema, default to 'dbo' +## Basic use -**NOTE**: By default, the 'dbo' schema is used for all tables. +To use it you need `loopback-datasource-juggler`. -The MSSQL connector uses [node-mssql](https://github.com/patriksimek/node-mssql) as the driver. See more -information about configuration parameters, check [https://github.com/patriksimek/node-mssql#configuration-1](https://github.com/patriksimek/node-mssql#configuration-1). +1. Setup dependencies in `package.json`: -**NOTE**: The connector can also be configured using a single 'url' property, -for example: -```json + ```json { - "url": "mssql://test:mypassword@localhost:1433/demo?schema=dbo" - } -``` - -## Discovering Models - -Microsoft SQL Server data sources allow you to discover model definition information from existing mssql databases. See the following APIs: - - - [dataSource.discoverModelDefinitions([username], fn)](https://github.com/strongloop/loopback#datasourcediscovermodeldefinitionsusername-fn) - - [dataSource.discoverSchema([owner], name, fn)](https://github.com/strongloop/loopback#datasourcediscoverschemaowner-name-fn) - - -## Model definition for Microsoft SQL Server - -The model definition consists of the following properties: - -* name: Name of the model, by default, it's the camel case of the table -* options: Model level operations and mapping to Microsoft SQL Server schema/table -* properties: Property definitions, including mapping to Microsoft SQL Server columns - -```json - - {"name": "Inventory", "options": { - "idInjection": false, - "mssql": { - "schema": "strongloop", - "table": "inventory" - } - }, "properties": { - "id": { - "type": "String", - "required": false, - "length": 64, - "precision": null, - "scale": null, - "mssql": { - "columnName": "id", - "dataType": "varchar", - "dataLength": 64, - "dataPrecision": null, - "dataScale": null, - "nullable": "NO" - } - }, - "productId": { - "type": "String", - "required": false, - "length": 64, - "precision": null, - "scale": null, - "id": 1, - "mssql": { - "columnName": "product_id", - "dataType": "varchar", - "dataLength": 64, - "dataPrecision": null, - "dataScale": null, - "nullable": "YES" - } + ... + "dependencies": { + "loopback-datasource-juggler": "latest", + "loopback-connector-mssql": "latest" }, - "locationId": { - "type": "String", - "required": false, - "length": 64, - "precision": null, - "scale": null, - "id": 1, - "mssql": { - "columnName": "location_id", - "dataType": "varchar", - "dataLength": 64, - "dataPrecision": null, - "dataScale": null, - "nullable": "YES" - } - }, - "available": { - "type": "Number", - "required": false, - "length": null, - "precision": 10, - "scale": 0, - "mssql": { - "columnName": "available", - "dataType": "int", - "dataLength": null, - "dataPrecision": 10, - "dataScale": 0, - "nullable": "YES" - } - }, - "total": { - "type": "Number", - "required": false, - "length": null, - "precision": 10, - "scale": 0, - "mssql": { - "columnName": "total", - "dataType": "int", - "dataLength": null, - "dataPrecision": 10, - "dataScale": 0, - "nullable": "YES" - } - } - }} - -``` - -## Type Mapping - - - Number - - Boolean - - String - - Object - - Date - - Array - - Buffer - -### JSON to Microsoft SQL Server Types - - -### Microsoft SQL Server Types to JSON - - -## Destroying Models - -Destroying models may result in errors due to foreign key integrity. Make sure -to delete any related models first before calling delete on model's with -relationships. - -## Auto Migrate / Auto Update - -After making changes to your model properties you must call `Model.automigrate()` -or `Model.autoupdate()`. Only call `Model.automigrate()` on new models -as it will drop existing tables. - -LoopBack MSSQL connector creates the following schema objects for a given -model: - -* A table, for example, PRODUCT under the 'dbo' schema within the database + ... + } + ``` +2. Use: -## Running tests + ```javascript + var DataSource = require('loopback-datasource-juggler').DataSource; + var dataSource = new DataSource('mssql', { + host: 'demo.strongloop.com', + port: 3306, + database: 'mydb', + username: 'myuser', + password: 'mypass' + }); + ``` - npm test From ba04ebd9a9ab1514325fb97100546281a0380dcc Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Fri, 23 May 2014 13:29:53 -0700 Subject: [PATCH 055/239] Remove 'module deps' from JSDocs --- lib/mssql.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index bdb62bf..ef00ecb 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -1,4 +1,4 @@ -/* Module dependencies */ +/*! Module dependencies */ var mssql = require("mssql"); var jdb = require("loopback-datasource-juggler"); var util = require("util"); @@ -1269,4 +1269,4 @@ function datatype(p) { return dt; } -require('./discovery')(MsSQL); \ No newline at end of file +require('./discovery')(MsSQL); From 4f6b67596de6d88aab161e59e583f4d681bf54bf Mon Sep 17 00:00:00 2001 From: Jason Douglas Date: Wed, 4 Jun 2014 08:15:29 -0700 Subject: [PATCH 056/239] Update mssql.js to properly escape ' chars Fixes a minor error that occurs when passing data that contains a ' character. Should properly escape the value when being wrapped. --- lib/mssql.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mssql.js b/lib/mssql.js index ef00ecb..f1e36d4 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -459,7 +459,7 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { } if (wrap) { - return "'" + val.toString() + "'"; + return "'" + val.toString().replace("'", "''") + "'"; } return val.toString(); }; From 0cf8ffe7a88215d0918762cc7dd42a7c6aac63ad Mon Sep 17 00:00:00 2001 From: Jason Douglas Date: Wed, 4 Jun 2014 08:25:50 -0700 Subject: [PATCH 057/239] Updated to allow global replacement --- lib/mssql.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mssql.js b/lib/mssql.js index f1e36d4..4071561 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -459,7 +459,7 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { } if (wrap) { - return "'" + val.toString().replace("'", "''") + "'"; + return "'" + val.toString().replace(/'/g, "''") + "'"; } return val.toString(); }; From 6b0ed3d9560f945cb344c7a12f484dcc88c96255 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 10 Jun 2014 16:09:45 -0700 Subject: [PATCH 058/239] Fix comparison for null/boolean values --- lib/mssql.js | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index ef00ecb..8485938 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -284,10 +284,9 @@ MsSQL.prototype.exists = function (model, id, callback) { MsSQL.prototype.count = function (model, cb, where) { var sql = "SELECT COUNT(*) cnt FROM " + this.tableEscaped(model) + MsSQL.newline; - var props = this._models[model].properties; if (where !== null) { - sql += this.buildWhere(where, props) + MsSQL.newline; + sql += this.buildWhere(model, where) + MsSQL.newline; } this.query(sql, function (err, data) { @@ -306,9 +305,8 @@ MsSQL.prototype.destroyAll = function (model, where, cb) { cb = where; where = undefined; } - var props = this._models[model].properties; this.query('DELETE FROM ' - + this.tableEscaped(model) + ' ' + this.buildWhere(where || {}, props), + + this.tableEscaped(model) + ' ' + this.buildWhere(model, where || {}), function (err, data) { cb && cb(err, data); }.bind(this)); @@ -397,7 +395,7 @@ function dateToMsSql(val) { // use the 'wrap' parameter to tell the function which case it's handling // (false=raw, true=single quotes) MsSQL.prototype.toDatabase = function (prop, val, wrap) { - if (val === null || typeof val === 'undefined') { + if (val === null || val === undefined) { // return 'NULL'; return null; } @@ -458,6 +456,10 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { return val ? 1 : 0; } + if (val === null || val === undefined) { + return val; + } + if (wrap) { return "'" + val.toString() + "'"; } @@ -552,14 +554,13 @@ MsSQL.prototype.getColumns = function (model, props) { MsSQL.prototype.all = function (model, filter, callback) { var self = this; - var props = this._models[model].properties; var sql = "SELECT " + this.getColumns(model, filter.fields) + " FROM " + this.tableEscaped(model) + MsSQL.newline; if (filter) { if (filter.where) { - sql += this.buildWhere(filter.where, props) + MsSQL.newline; + sql += this.buildWhere(model, filter.where) + MsSQL.newline; } var orderBy = null; @@ -599,7 +600,7 @@ MsSQL.prototype.all = function (model, filter, callback) { + ' (' + orderBy + ') AS RowNum' + MsSQL.newline + ' FROM ' + this.tableEscaped(model) + MsSQL.newline; if (filter.where) { - paginatedSQL += this.buildWhere(filter.where, props) + MsSQL.newline; + paginatedSQL += this.buildWhere(model, filter.where) + MsSQL.newline; } paginatedSQL += ') AS S' + MsSQL.newline + ' WHERE S.RowNum > ' + offset; @@ -655,16 +656,17 @@ function buildLimit(limit, offset) { return sql; } -MsSQL.prototype._buildWhere = function (conds, props) { +MsSQL.prototype._buildWhere = function (model, conds) { // debugger; var self = this; + var props = this._models[model].properties; var cs = []; Object.keys(conds).forEach(function (key) { if (key === 'and' || key === 'or') { var clauses = conds[key]; if (Array.isArray(clauses)) { clauses = clauses.map(function (c) { - return '(' + self._buildWhere(c, props) + ')'; + return '(' + self._buildWhere(model, c) + ')'; }); cs.push(clauses.join(' ' + key.toUpperCase() + ' ')); return; @@ -672,6 +674,9 @@ MsSQL.prototype._buildWhere = function (conds, props) { // The value is not an array, fall back to regular fields } var keyEscaped = self.escapeKey(key); + if (props[key]) { + keyEscaped = self.columnEscaped(model, key); + } var val = self.toDatabase(props[key], conds[key], true); if (conds[key] === null) { cs.push(keyEscaped + ' IS NULL'); @@ -687,7 +692,7 @@ MsSQL.prototype._buildWhere = function (conds, props) { var subClause = conds[key].max.where; sqlCond += " = (SELECT MAX(" + val + ") FROM " + tbl; if (subClause) { - sqlCond += " " + self._buildWhere(subClause, props); + sqlCond += " " + self._buildWhere(model, subClause); } sqlCond += ")"; cs.push(sqlCond); @@ -718,6 +723,12 @@ MsSQL.prototype._buildWhere = function (conds, props) { case 'neq': sqlCond += ' != '; break; + case 'like': + sqlCond += ' LIKE '; + break; + case 'nlike': + sqlCond += ' NOT LIKE '; + break; } sqlCond += (condType === 'inq' || condType === 'nin') ? '(' + val + ')' : val; cs.push(sqlCond); @@ -731,8 +742,8 @@ MsSQL.prototype._buildWhere = function (conds, props) { return cs.join(' AND '); }; -MsSQL.prototype.buildWhere = function (conds, props) { - var where = this._buildWhere(conds, props); +MsSQL.prototype.buildWhere = function (model, conds) { + var where = this._buildWhere(model, conds); return where? 'WHERE ' + where : ''; }; From 114864911839bb1be29b6b6aeb067ce1ca4668ef Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 20 Jun 2014 21:54:04 -0700 Subject: [PATCH 059/239] Use base connector and add update support --- lib/mssql.js | 49 ++++++++++++++++++++++++++++++++++++++++++++----- package.json | 8 +++----- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 8d7ac94..f157165 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -1,6 +1,6 @@ /*! Module dependencies */ var mssql = require("mssql"); -var jdb = require("loopback-datasource-juggler"); +var SqlConnector = require('loopback-connector').SqlConnector; var util = require("util"); var async = require('async'); var debug = require('debug')('loopback:connector:mssql'); @@ -23,7 +23,7 @@ exports.initialize = function initializeSchema(dataSource, callback) { }; function MsSQL(settings) { - this.constructor.super_.call(this, name, settings); + MsSQL.super_.call(this, name, settings); // this.name = name; // this.settings = settings || {}; this.settings.server = this.settings.host || this.settings.hostname; @@ -32,7 +32,7 @@ function MsSQL(settings) { this._idxNames = {}; } -util.inherits(MsSQL, jdb.BaseSQL); +util.inherits(MsSQL, SqlConnector); MsSQL.newline = "\r\n"; @@ -236,7 +236,7 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { callback(err, data); } }); - } + }; }; //redundant, same functionality as "updateOrCreate" right now. Maybe in the @@ -324,6 +324,41 @@ MsSQL.prototype.destroy = function (model, id, cb) { }); }; +MsSQL.prototype.update = MsSQL.prototype.updateAll = function (model, where, data, cb) { + var self = this; + var props = this._models[model].properties; + var tblName = this.tableEscaped(model); + var modelPKID = this.idName(model); + //get the correct id of the item using the pkid that they specified + var id = data[modelPKID]; + var fieldNames = []; + var fieldValues = []; + var fieldValuesPlaceholders = []; + var combined = []; + Object.keys(data).forEach(function (key) { + if (props[key]) { + //check for the "id" key also, for backwards compatibility with the jugglingdb hardcoded id system + if (key !== "id" && key !== modelPKID) { + fieldNames.push("[" + key + "]"); + fieldValues.push(self.toDatabase(props[key], data[key], true)); + fieldValuesPlaceholders.push("(?)"); + combined.push(key + "=(?)"); + } + } + }); + var sql = ""; + + //update + sql = "UPDATE " + tblName + MsSQL.newline; + sql += "SET " + combined.join() + MsSQL.newline; + sql += self.buildWhere(model, where) + MsSQL.newline; + fieldValues.push(id); + + self.query(sql, fieldValues, cb); + +}; + + MsSQL.prototype.find = function (model, id, callback) { //debugger; var tblName = this.tableEscaped(model); @@ -657,7 +692,9 @@ function buildLimit(limit, offset) { } MsSQL.prototype._buildWhere = function (model, conds) { - // debugger; + if (!conds) { + return null; + } var self = this; var props = this._models[model].properties; var cs = []; @@ -725,9 +762,11 @@ MsSQL.prototype._buildWhere = function (model, conds) { break; case 'like': sqlCond += ' LIKE '; + val += " ESCAPE '\\'"; break; case 'nlike': sqlCond += ' NOT LIKE '; + val += " ESCAPE '\\'"; break; } sqlCond += (condType === 'inq' || condType === 'nin') ? '(' + val + ')' : val; diff --git a/package.json b/package.json index 145df70..120760d 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,10 @@ ], "main": "index.js", "dependencies": { - "mssql": "~0.5.3", + "async": "~0.9.0", "debug": "~0.8.0", - "async": "~0.9.0" - }, - "peerDependencies": { - "loopback-datasource-juggler": "1.x" + "loopback-connector": "1.x", + "mssql": "~0.5.3" }, "devDependencies": { "loopback-datasource-juggler": "1.x", From b10f87813bfaa1527293f86370d24c5489a0973c Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 23 Jun 2014 10:22:45 -0700 Subject: [PATCH 060/239] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 120760d..8bd1d57 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "1.0.1", + "version": "1.1.0", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", From 010fd12297d8347dd72c33fc8c3a9e8346b064e8 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Tue, 24 Jun 2014 16:21:02 -0700 Subject: [PATCH 061/239] Update link to doc --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f93bca9..e0840dc 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ `loopback-connector-mssql` is the Microsoft SQL Server connector module for [loopback-datasource-juggler](https://github.com/strongloop/loopback-datasource-juggler/). -For complete documentation, see [StrongLoop Documentation | SQL Server Connector](http://docs.strongloop.com/display/DOC/SQL+Server+connector). +For complete documentation, see [StrongLoop Documentation | SQL Server Connector](http://docs.strongloop.com/display/LB/SQL+Server+connector). ## Installation From 5c9bae3befe8c327cd225b1f5cb3bc30cfb733d6 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 26 Jun 2014 23:44:12 -0700 Subject: [PATCH 062/239] Tidy up filter.order parsing --- lib/mssql.js | 89 ++++++++++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index f157165..7615288 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -593,59 +593,58 @@ MsSQL.prototype.all = function (model, filter, callback) { var sql = "SELECT " + this.getColumns(model, filter.fields) + " FROM " + this.tableEscaped(model) + MsSQL.newline; - if (filter) { - if (filter.where) { - sql += this.buildWhere(model, filter.where) + MsSQL.newline; - } + filter = filter || {}; + if (filter.where) { + sql += this.buildWhere(model, filter.where) + MsSQL.newline; + } - var orderBy = null; - if (filter.order) { - orderBy = this.buildOrderBy(model, filter.order); - sql += orderBy + MsSQL.newline; + var orderBy = null; + if (!filter.order) { + var idNames = this.idNames(model); + if (idNames && idNames.length) { + filter.order = idNames; } + } + if (filter.order) { + orderBy = this.buildOrderBy(model, filter.order); + sql += orderBy + MsSQL.newline; + } + if (filter.limit || filter.offset || filter.skip) { - if (filter.limit || filter.offset || filter.skip) { - if (!filter.order) { - var idName = self.idName(model); - orderBy = 'ORDER BY ' + self.columnEscaped(model, idName) - sql += orderBy + MsSQL.newline; - } - - var offset = filter.offset || filter.skip; - if (isNaN(offset)) { - offset = 0; - } - var limit = filter.limit; - if (isNaN(limit)) { - limit = -1; - } - - if (this.settings.supportsOffsetFetch) { - // SQL 2012 or later - // http://technet.microsoft.com/en-us/library/gg699618.aspx - sql += buildLimit(limit, offset); - } else { + var offset = filter.offset || filter.skip; + if (isNaN(offset)) { + offset = 0; + } + var limit = filter.limit; + if (isNaN(limit)) { + limit = -1; + } - // SQL 2005/2008 - // http://blog.sqlauthority.com/2013/04/14/sql-server-tricks-for-row-offset-and-paging-in-various-versions-of-sql-server/ + if (this.settings.supportsOffsetFetch) { + // SQL 2012 or later + // http://technet.microsoft.com/en-us/library/gg699618.aspx + sql += buildLimit(limit, offset); + } else { - var paginatedSQL = 'SELECT ' + this.getColumns(model, filter.fields) + MsSQL.newline - + ' FROM (' + MsSQL.newline - + ' SELECT ' + this.getColumns(model, filter.fields) + ', ROW_NUMBER() OVER' - + ' (' + orderBy + ') AS RowNum' + MsSQL.newline - + ' FROM ' + this.tableEscaped(model) + MsSQL.newline; - if (filter.where) { - paginatedSQL += this.buildWhere(model, filter.where) + MsSQL.newline; - } - paginatedSQL += ') AS S' + MsSQL.newline - + ' WHERE S.RowNum > ' + offset; + // SQL 2005/2008 + // http://blog.sqlauthority.com/2013/04/14/sql-server-tricks-for-row-offset-and-paging-in-various-versions-of-sql-server/ - if (limit !== -1) { - paginatedSQL += ' AND S.RowNum <= ' + (offset + limit); - } + var paginatedSQL = 'SELECT ' + this.getColumns(model, filter.fields) + MsSQL.newline + + ' FROM (' + MsSQL.newline + + ' SELECT ' + this.getColumns(model, filter.fields) + ', ROW_NUMBER() OVER' + + ' (' + orderBy + ') AS RowNum' + MsSQL.newline + + ' FROM ' + this.tableEscaped(model) + MsSQL.newline; + if (filter.where) { + paginatedSQL += this.buildWhere(model, filter.where) + MsSQL.newline; + } + paginatedSQL += ') AS S' + MsSQL.newline + + ' WHERE S.RowNum > ' + offset; - sql = paginatedSQL + MsSQL.newline; + if (limit !== -1) { + paginatedSQL += ' AND S.RowNum <= ' + (offset + limit); } + + sql = paginatedSQL + MsSQL.newline; } } From b69aaa09c381663638927364a36ff30af2c30847 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 27 Jun 2014 10:33:37 -0700 Subject: [PATCH 063/239] Bump versions --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8bd1d57..5cc4ca0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "1.1.0", + "version": "1.1.1", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -14,15 +14,15 @@ "main": "index.js", "dependencies": { "async": "~0.9.0", - "debug": "~0.8.0", + "debug": "~1.0.2", "loopback-connector": "1.x", - "mssql": "~0.5.3" + "mssql": "~0.5.5" }, "devDependencies": { "loopback-datasource-juggler": "1.x", - "mocha": "~1.18.2", + "mocha": "~1.20.1", "should": "~1.2.2", - "rc": "~0.3.5" + "rc": "~0.4.0" }, "scripts": { "test": "./node_modules/.bin/mocha --timeout 5000 -R spec" From e9ea53dfa260a37ce99cadb10ac389da213d6d84 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 20 Aug 2014 16:14:33 -0700 Subject: [PATCH 064/239] Add ping() --- lib/mssql.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/mssql.js b/lib/mssql.js index 7615288..b17435a 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -1318,4 +1318,8 @@ function datatype(p) { return dt; } +MsSQL.prototype.ping = function(cb) { + this.query('SELECT 1 AS result', cb); +}; + require('./discovery')(MsSQL); From eb9a9e792295d6e377c7a231f5ecc92e14ff9849 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 20 Aug 2014 16:43:38 -0700 Subject: [PATCH 065/239] Bump version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5cc4ca0..053a590 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "1.1.1", + "version": "1.1.2", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -16,7 +16,7 @@ "async": "~0.9.0", "debug": "~1.0.2", "loopback-connector": "1.x", - "mssql": "~0.5.5" + "mssql": "~1.1.1" }, "devDependencies": { "loopback-datasource-juggler": "1.x", From eacd5091f4abd42b465aa5ce95eb4de459bbc3bd Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 25 Aug 2014 21:45:37 -0700 Subject: [PATCH 066/239] Remove ON[PRIMARY] option See https://github.com/strongloop/loopback-connector-mssql/issues/10 --- lib/mssql.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index b17435a..1940d74 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -434,6 +434,9 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { // return 'NULL'; return null; } + if (prop.type && prop.type.modelName) { + return "'" + JSON.stringify(val) + "'"; + } if (val.constructor && val.constructor.name === 'Object') { var operator = Object.keys(val)[0] val = val[operator]; @@ -1107,7 +1110,7 @@ MsSQL.prototype.propertiesSQL = function (model) { var cmd = "PRIMARY KEY CLUSTERED" + MsSQL.newline + "(" + MsSQL.newline; cmd += " [" + modelPKID + "] ASC" + MsSQL.newline; cmd += ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, " + - "IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]" + "IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)" joinedSql += "," + MsSQL.newline + cmd; @@ -1141,7 +1144,7 @@ MsSQL.prototype.singleIndexSettingsSQL = function (model, prop, add) { cmd += " [" + prop + "] " + type; cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF," + " SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, " + - "ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];" + "ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);" + MsSQL.newline; return cmd; }; @@ -1181,7 +1184,7 @@ MsSQL.prototype.indexSettingsSQL = function (model, prop) { cmd += columns.join("," + MsSQL.newline); cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, " + "SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, " + - "ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];" + "ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);" + MsSQL.newline; return cmd; }; @@ -1247,7 +1250,7 @@ MsSQL.prototype.createTable = function (model, cb) { + tblName + "') AND type in (N'U'))" + MsSQL.newline + "BEGIN" + MsSQL.newline; cmd += "CREATE TABLE " + this.tableEscaped(model) + " ("; cmd += MsSQL.newline + " " + this.propertiesSQL(model) + MsSQL.newline; - cmd += ") ON [PRIMARY]" + MsSQL.newline + "END;" + MsSQL.newline; + cmd += ")" + MsSQL.newline + "END;" + MsSQL.newline; cmd += this.createIndexes(model); this.command(cmd, cb); }; From b9404e13c0ee9294ae2e70f5ccffd77db9c1253a Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 25 Aug 2014 21:46:16 -0700 Subject: [PATCH 067/239] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 053a590..0cd1633 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "1.1.2", + "version": "1.1.3", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", From 70469aafbbaeccafcd2958d92dec499e5c695746 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 11 Sep 2014 12:17:27 -0700 Subject: [PATCH 068/239] Make sure errors are reported for automigrate/autoupdate --- lib/mssql.js | 84 +++++++++++++++++------------------------ test/autoupdate.test.js | 12 ++++++ 2 files changed, 47 insertions(+), 49 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 1940d74..11bb49a 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -843,9 +843,8 @@ MsSQL.prototype.showIndexes = function (model, cb) { }); }; -MsSQL.prototype.autoupdate = function (models, cb) { +MsSQL.prototype.autoupdate = function(models, cb) { var self = this; - var wait = 0; if ((!cb) && ('function' === typeof models)) { cb = models; models = undefined; @@ -856,10 +855,14 @@ MsSQL.prototype.autoupdate = function (models, cb) { } models = models || Object.keys(this._models); - models.forEach(function (model) { - wait += 1; - self.showFields(model, function (err, fields) { - self.showIndexes(model, function (err, indexes) { + async.each(models, function(model, done) { + if (!(model in self._models)) { + return process.nextTick(function() { + done(new Error('Model not found: ' + model)); + }); + } + self.showFields(model, function(err, fields) { + self.showIndexes(model, function(err, indexes) { if (!err && fields.length) { self.alterTable(model, fields, indexes, done); } else { @@ -867,40 +870,31 @@ MsSQL.prototype.autoupdate = function (models, cb) { } }); }); - }); - - function done(err) { - if (err) { - debug(err); - } - if (--wait === 0 && cb) { - cb(); - } - } + }, cb); }; MsSQL.prototype.isActual = function (cb) { var ok = false; var self = this; - var wait = 0; - Object.keys(this._models).forEach(function (model) { - wait += 1; + async.each(Object.keys(this._models), function(model, done) { self.showFields(model, function (err, fields) { self.showIndexes(model, function (err, indexes) { - self.alterTable(model, fields, indexes, done, true); + self.alterTable(model, fields, indexes, function(err, needAlter) { + if (err) { + return done(err); + } else { + ok = ok || needAlter; + done(err); + } + }, true); }); }); - }); - - function done(err, needAlter) { + }, function(err) { if (err) { - debug(err); - } - ok = ok || needAlter; - if (--wait === 0 && cb) { - cb(null, !ok); + return err; } - } + cb(null, !ok); + }); }; MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, checkOnly) { @@ -1195,9 +1189,8 @@ MsSQL.prototype.propertySettingsSQL = function (model, prop) { (p.allowNull === false || p['null'] === false ? 'NOT NULL' : 'NULL'); }; -MsSQL.prototype.automigrate = function (models, cb) { +MsSQL.prototype.automigrate = function(models, cb) { var self = this; - var wait = 0; if ((!cb) && ('function' === typeof models)) { cb = models; models = undefined; @@ -1208,28 +1201,21 @@ MsSQL.prototype.automigrate = function (models, cb) { } models = models || Object.keys(this._models); - models.forEach(function (model) { - wait += 1; - self.dropTable(model, function (err) { + async.each(models, function(model, done) { + if (!(model in self._models)) { + return process.nextTick(function() { + done(new Error('Model not found: ' + model)); + }); + } + self.dropTable(model, function(err) { if (err) { - debug(err); + return done(err); } - self.createTable(model, function (err) { - // console.log('create', model); - if (err) { - debug(err); - } - done(); - }); + self.createTable(model, done); }); + }, function(err) { + cb && cb(err); }); - if (wait === 0) cb && cb(); - - function done() { - if (--wait === 0 && cb) { - cb(); - } - } }; MsSQL.prototype.dropTable = function (model, cb) { diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js index 5005107..44d9980 100644 --- a/test/autoupdate.test.js +++ b/test/autoupdate.test.js @@ -116,5 +116,17 @@ describe('MS SQL server connector', function () { }); }); }); + + it('should report errors for automigrate', function() { + ds.automigrate('XYZ', function(err) { + assert(err); + }); + }); + + it('should report errors for autoupdate', function() { + ds.autoupdate('XYZ', function(err) { + assert(err); + }); + }); }); From bd21c95d18e2c0e5a16dcdae747ca8d633cc08c6 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 11 Sep 2014 12:18:46 -0700 Subject: [PATCH 069/239] Bump versions --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0cd1633..bc75d96 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,9 @@ "main": "index.js", "dependencies": { "async": "~0.9.0", - "debug": "~1.0.2", + "debug": "~2.0.0", "loopback-connector": "1.x", - "mssql": "~1.1.1" + "mssql": "~1.2.0" }, "devDependencies": { "loopback-datasource-juggler": "1.x", From f50732a73932eb92772b96debdfcf72fa031c96c Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 11 Sep 2014 12:19:13 -0700 Subject: [PATCH 070/239] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bc75d96..a53b6c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "1.1.3", + "version": "1.1.4", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", From f65ddd16dfd99b6b3550808316942b0be5a50280 Mon Sep 17 00:00:00 2001 From: Ryan Graham Date: Wed, 1 Oct 2014 17:55:03 -0700 Subject: [PATCH 071/239] Add contribution guidelines Uses https://cla.strongloop.com --- CONTRIBUTING.md | 151 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..33d2d8b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,151 @@ +### Contributing ### + +Thank you for your interest in `loopback-connector-mssql`, an open source project +administered by StrongLoop. + +Contributing to `loopback-connector-mssql` is easy. In a few simple steps: + + * Ensure that your effort is aligned with the project's roadmap by + talking to the maintainers, especially if you are going to spend a + lot of time on it. + + * Make something better or fix a bug. + + * Adhere to code style outlined in the [Google C++ Style Guide][] and + [Google Javascript Style Guide][]. + + * Sign the [Contributor License Agreement](https://cla.strongloop.com/strongloop/loopback-connector-mssql) + + * Submit a pull request through Github. + + +### Contributor License Agreement ### + +``` + Individual Contributor License Agreement + + By signing this Individual Contributor License Agreement + ("Agreement"), and making a Contribution (as defined below) to + StrongLoop, Inc. ("StrongLoop"), You (as defined below) accept and + agree to the following terms and conditions for Your present and + future Contributions submitted to StrongLoop. Except for the license + granted in this Agreement to StrongLoop and recipients of software + distributed by StrongLoop, You reserve all right, title, and interest + in and to Your Contributions. + + 1. Definitions + + "You" or "Your" shall mean the copyright owner or the individual + authorized by the copyright owner that is entering into this + Agreement with StrongLoop. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to StrongLoop for inclusion in, + or documentation of, any of the products owned or managed by + StrongLoop ("Work"). For purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication + sent to StrongLoop or its representatives, including but not + limited to communication or electronic mailing lists, source code + control systems, and issue tracking systems that are managed by, + or on behalf of, StrongLoop for the purpose of discussing and + improving the Work, but excluding communication that is + conspicuously marked or otherwise designated in writing by You as + "Not a Contribution." + + 2. You Grant a Copyright License to StrongLoop + + Subject to the terms and conditions of this Agreement, You hereby + grant to StrongLoop and recipients of software distributed by + StrongLoop, a perpetual, worldwide, non-exclusive, no-charge, + royalty-free, irrevocable copyright license to reproduce, prepare + derivative works of, publicly display, publicly perform, + sublicense, and distribute Your Contributions and such derivative + works under any license and without any restrictions. + + 3. You Grant a Patent License to StrongLoop + + Subject to the terms and conditions of this Agreement, You hereby + grant to StrongLoop and to recipients of software distributed by + StrongLoop a perpetual, worldwide, non-exclusive, no-charge, + royalty-free, irrevocable (except as stated in this Section) + patent license to make, have made, use, offer to sell, sell, + import, and otherwise transfer the Work under any license and + without any restrictions. The patent license You grant to + StrongLoop under this Section applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contributions(s) alone or by combination of Your Contributions(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes a patent litigation against You or any other + entity (including a cross-claim or counterclaim in a lawsuit) + alleging that Your Contribution, or the Work to which You have + contributed, constitutes direct or contributory patent + infringement, any patent licenses granted to that entity under + this Agreement for that Contribution or Work shall terminate as + of the date such litigation is filed. + + 4. You Have the Right to Grant Licenses to StrongLoop + + You represent that You are legally entitled to grant the licenses + in this Agreement. + + If Your employer(s) has rights to intellectual property that You + create, You represent that You have received permission to make + the Contributions on behalf of that employer, that Your employer + has waived such rights for Your Contributions, or that Your + employer has executed a separate Corporate Contributor License + Agreement with StrongLoop. + + 5. The Contributions Are Your Original Work + + You represent that each of Your Contributions are Your original + works of authorship (see Section 8 (Submissions on Behalf of + Others) for submission on behalf of others). You represent that to + Your knowledge, no other person claims, or has the right to claim, + any right in any intellectual property right related to Your + Contributions. + + You also represent that You are not legally obligated, whether by + entering into an agreement or otherwise, in any way that conflicts + with the terms of this Agreement. + + You represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which You + are personally aware and which are associated with any part of + Your Contributions. + + 6. You Don't Have an Obligation to Provide Support for Your Contributions + + You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. + + 6. No Warranties or Conditions + + StrongLoop acknowledges that unless required by applicable law or + agreed to in writing, You provide Your Contributions on an "AS IS" + BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER + EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES + OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR + FITNESS FOR A PARTICULAR PURPOSE. + + 7. Submission on Behalf of Others + + If You wish to submit work that is not Your original creation, You + may submit it to StrongLoop separately from any Contribution, + identifying the complete details of its source and of any license + or other restriction (including, but not limited to, related + patents, trademarks, and license agreements) of which You are + personally aware, and conspicuously marking the work as + "Submitted on Behalf of a Third-Party: [named here]". + + 8. Agree to Notify of Change of Circumstances + + You agree to notify StrongLoop of any facts or circumstances of + which You become aware that would make these representations + inaccurate in any respect. Email us at callback@strongloop.com. +``` + +[Google C++ Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml +[Google Javascript Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml From 4460cc8278f95116083a7ee1e4103c0ed6523983 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 26 Nov 2014 15:00:06 -0800 Subject: [PATCH 072/239] Update README.md Per Sumitha: Input from user: "I think: `port: 3306` is wrong, because this is the default MySQL port. MSSQL uses the default port 1433" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0840dc..62f2ff3 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ To use it you need `loopback-datasource-juggler`. var DataSource = require('loopback-datasource-juggler').DataSource; var dataSource = new DataSource('mssql', { host: 'demo.strongloop.com', - port: 3306, + port: 1433, database: 'mydb', username: 'myuser', password: 'mypass' From 7567df919f9ddcc13d4de08626cb1795fd46b90d Mon Sep 17 00:00:00 2001 From: Ryan Graham Date: Thu, 27 Nov 2014 11:43:08 -0800 Subject: [PATCH 073/239] v1.1.5 --- CHANGES.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 5 +++- 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 CHANGES.md diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..064038b --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,68 @@ +2014-11-27, Version 1.1.5 +========================= + + * Update README.md (Rand McKinney) + + * Add contribution guidelines (Ryan Graham) + + +2014-09-11, Version 1.1.4 +========================= + + * Bump version (Raymond Feng) + + * Bump versions (Raymond Feng) + + * Make sure errors are reported for automigrate/autoupdate (Raymond Feng) + + +2014-08-25, Version 1.1.3 +========================= + + * Bump version (Raymond Feng) + + * Remove ON[PRIMARY] option (Raymond Feng) + + +2014-08-20, Version 1.1.2 +========================= + + * Bump version (Raymond Feng) + + * Add ping() (Raymond Feng) + + +2014-06-27, Version 1.1.1 +========================= + + * Bump versions (Raymond Feng) + + * Tidy up filter.order parsing (Raymond Feng) + + * Update link to doc (Rand McKinney) + + * Bump version (Raymond Feng) + + +2014-06-23, Version 1.1.0 +========================= + + * Use base connector and add update support (Raymond Feng) + + * Fix comparison for null/boolean values (Raymond Feng) + + * Updated to allow global replacement (Jason Douglas) + + * Update mssql.js to properly escape ' chars (Jason Douglas) + + * Remove 'module deps' from JSDocs (Rand McKinney) + + * Replace old README with link to docs and basic info. (Rand McKinney) + + * Create docs.json (Rand McKinney) + + +2014-05-16, Version 1.0.1 +========================= + + * First release! diff --git a/package.json b/package.json index a53b6c3..7374d98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "1.1.4", + "version": "1.1.5", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -34,5 +34,8 @@ "license": { "name": "StrongLoop", "url": "https://github.com/strongloop/loopback-connector-mssql/blob/master/LICENSE" + }, + "optionalDependencies": { + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@1.1.5" } } From ddd05024828d1e3b74aa6ebcd4b93e737448e2bb Mon Sep 17 00:00:00 2001 From: bitmage Date: Mon, 1 Dec 2014 20:59:07 -0700 Subject: [PATCH 074/239] handle precision and scale --- lib/mssql.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/mssql.js b/lib/mssql.js index 11bb49a..0cc1294 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -1275,6 +1275,11 @@ MsSQL.prototype.columnDataType = function (model, property) { } var colLength = columnMetadata && columnMetadata.dataLength || prop.length; if (colType) { + var dataPrecision = columnMetadata.dataPrecision; + var dataScale = columnMetadata.dataScale; + if (dataPrecision && dataScale) { + return colType + '(' + dataPrecision + ', ' + dataScale + ')'; + } return colType + (colLength ? '(' + colLength + ')' : ''); } return datatype(prop); From 0e85f5975c032b9b646109435f9bcdb61432461c Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 4 Dec 2014 13:35:08 -0800 Subject: [PATCH 075/239] Map required/id properties to NOT NULL See https://github.com/strongloop/strong-arc/issues/600 --- lib/mssql.js | 7 ++++++- test/autoupdate.test.js | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 11bb49a..04e0139 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -1183,10 +1183,15 @@ MsSQL.prototype.indexSettingsSQL = function (model, prop) { return cmd; }; +function isNullable(p) { + return !(p.required || p.id || p.nullable === false || + p.allowNull === false || p['null'] === false); +} + MsSQL.prototype.propertySettingsSQL = function (model, prop) { var p = this._models[model].properties[prop]; return this.columnDataType(model, prop) + ' ' + - (p.allowNull === false || p['null'] === false ? 'NOT NULL' : 'NULL'); + (isNullable(p) ? 'NULL' : 'NOT NULL'); }; MsSQL.prototype.automigrate = function(models, cb) { diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js index 44d9980..f327b7d 100644 --- a/test/autoupdate.test.js +++ b/test/autoupdate.test.js @@ -32,7 +32,7 @@ describe('MS SQL server connector', function () { }, "email": { "type": "String", - "required": false, + "required": true, "length": 40 }, "age": { @@ -66,7 +66,7 @@ describe('MS SQL server connector', function () { "columnName": "EMAIL", "dataType": "nvarchar", "dataLength": 60, - "nullable": "Y" + "nullable": "YES" } }, "firstName": { @@ -92,6 +92,11 @@ describe('MS SQL server connector', function () { var names = props.map(function (p) { return p.columnName; }); + assert.equal(props[0].nullable, 'NO'); + assert.equal(props[1].nullable, 'YES'); + assert.equal(props[2].nullable, 'NO'); + assert.equal(props[3].nullable, 'YES'); + assert.equal(names[0], 'id'); assert.equal(names[1], 'name'); assert.equal(names[2], 'email'); From e59919e622bfab7abaf201f991c34c0e2c350ef9 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 5 Dec 2014 11:40:21 -0800 Subject: [PATCH 076/239] Update deps --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7374d98..2648c24 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,9 @@ "main": "index.js", "dependencies": { "async": "~0.9.0", - "debug": "~2.0.0", + "debug": "^2.0.0", "loopback-connector": "1.x", - "mssql": "~1.2.0" + "mssql": "^1.2.0" }, "devDependencies": { "loopback-datasource-juggler": "1.x", From 33455f62392db3f79559267338e5e0a821dd6fd4 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 5 Dec 2014 11:40:41 -0800 Subject: [PATCH 077/239] v1.1.6 --- CHANGES.md | 8 ++++++++ package.json | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 064038b..8676341 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,11 @@ +2014-12-05, Version 1.1.6 +========================= + + * Update deps (Raymond Feng) + + * Map required/id properties to NOT NULL (Raymond Feng) + + 2014-11-27, Version 1.1.5 ========================= diff --git a/package.json b/package.json index 2648c24..0bb52ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "1.1.5", + "version": "1.1.6", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -36,6 +36,6 @@ "url": "https://github.com/strongloop/loopback-connector-mssql/blob/master/LICENSE" }, "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@1.1.5" + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@1.1.6" } } From 0a6e65cb2caf61ffdb45a6d8248c01897269fd91 Mon Sep 17 00:00:00 2001 From: bitmage Date: Mon, 8 Dec 2014 13:48:59 -0700 Subject: [PATCH 078/239] fixed race condition causing incorrect IDs to be reported on INSERT --- lib/mssql.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 04e0139..f79ea60 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -153,8 +153,8 @@ MsSQL.prototype.create = function (model, data, callback) { var fieldsAndData = this.buildInsert(model, data); var tblName = this.tableEscaped(model); var sql = "INSERT INTO " + tblName + " (" + fieldsAndData.fields + ")" + MsSQL.newline; - sql += "VALUES (" + fieldsAndData.paramPlaceholders + ");" + MsSQL.newline; - sql += "SELECT IDENT_CURRENT('" + tblName + "') AS insertId;"; + sql += "OUTPUT INSERTED." + modelPKID + " AS insertId" + MsSQL.newline; + sql += "VALUES (" + fieldsAndData.paramPlaceholders + ");"; this.query(sql, fieldsAndData.params, function (err, results) { if (err) { From da2a02964b12cb3165ecef1d960a9d9a901c7909 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 8 Dec 2014 16:14:12 -0800 Subject: [PATCH 079/239] Fix the missing var --- lib/mssql.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/mssql.js b/lib/mssql.js index da3a21d..f9825ba 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -152,6 +152,8 @@ MsSQL.prototype.create = function (model, data, callback) { //debugger; var fieldsAndData = this.buildInsert(model, data); var tblName = this.tableEscaped(model); + var modelPKID = this.idName(model); + var sql = "INSERT INTO " + tblName + " (" + fieldsAndData.fields + ")" + MsSQL.newline; sql += "OUTPUT INSERTED." + modelPKID + " AS insertId" + MsSQL.newline; sql += "VALUES (" + fieldsAndData.paramPlaceholders + ");"; From b999ddd085194a18384841d802af7f204cefcd50 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 8 Dec 2014 16:15:31 -0800 Subject: [PATCH 080/239] Update test dep --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0bb52ed..8ffb44f 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "mssql": "^1.2.0" }, "devDependencies": { - "loopback-datasource-juggler": "1.x", + "loopback-datasource-juggler": "2.x", "mocha": "~1.20.1", "should": "~1.2.2", "rc": "~0.4.0" From 31bdbd3a7c66d4ad38c038d87a7c0291aabb49da Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 8 Dec 2014 16:15:55 -0800 Subject: [PATCH 081/239] v1.2.0 --- CHANGES.md | 12 ++++++++++++ package.json | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8676341..ced1c31 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,15 @@ +2014-12-09, Version 1.2.0 +========================= + + * Update test dep (Raymond Feng) + + * Fix the missing var (Raymond Feng) + + * fixed race condition causing incorrect IDs to be reported on INSERT (bitmage) + + * handle precision and scale (bitmage) + + 2014-12-05, Version 1.1.6 ========================= diff --git a/package.json b/package.json index 8ffb44f..1b5c06d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "1.1.6", + "version": "1.2.0", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -36,6 +36,6 @@ "url": "https://github.com/strongloop/loopback-connector-mssql/blob/master/LICENSE" }, "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@1.1.6" + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@1.2.0" } } From b680af048ad848febad238aceb5b2aa89691f1e7 Mon Sep 17 00:00:00 2001 From: Ryan Graham Date: Mon, 29 Dec 2014 08:27:46 -0800 Subject: [PATCH 082/239] Fix bad CLA URL in CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 33d2d8b..6cece43 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ Contributing to `loopback-connector-mssql` is easy. In a few simple steps: * Adhere to code style outlined in the [Google C++ Style Guide][] and [Google Javascript Style Guide][]. - * Sign the [Contributor License Agreement](https://cla.strongloop.com/strongloop/loopback-connector-mssql) + * Sign the [Contributor License Agreement](https://cla.strongloop.com/agreements/strongloop/loopback-connector-mssql) * Submit a pull request through Github. From a8eef61061b273779bebbf8d97841322d6bd3ebe Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 9 Jan 2015 00:08:01 -0800 Subject: [PATCH 083/239] Fix SQL injection --- lib/mssql.js | 58 ++++++++++++++++--- test/mssql.test.js | 137 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 test/mssql.test.js diff --git a/lib/mssql.js b/lib/mssql.js index f9825ba..49a63b7 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -423,6 +423,50 @@ function dateToMsSql(val) { } } +function escape(val) { + if (val === undefined || val === null) { + return 'NULL'; + } + + switch (typeof val) { + case 'boolean': + return (val) ? 1 : 0; + case 'number': + return val + ''; + } + + if (typeof val === 'object') { + val = (typeof val.toISOString === 'function') + ? val.toISOString() + : val.toString(); + } + + 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 "''"; // For sql server, double quote + case "\"": + return s; // For oracle + default: + return "\\" + s; + } + }); + // return "q'#"+val+"#'"; + return "'" + val + "'"; +} + //toDatabase is used for formatting data when inserting/updating records // it is also used when building a where clause for filtering selects // in the case of update/insert we want the data to be returned raw @@ -437,7 +481,7 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { return null; } if (prop.type && prop.type.modelName) { - return "'" + JSON.stringify(val) + "'"; + return escape(JSON.stringify(val)); } if (val.constructor && val.constructor.name === 'Object') { var operator = Object.keys(val)[0] @@ -456,12 +500,12 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { //check if it is an array of string, because in that cause we need to // wrap them in single quotes if (typeof val[0] === 'string') { - return "'" + val.join("','") + "'"; + return escape(val.join("','")); } return val.join(','); } else { if (typeof val === 'string') { - val = "'" + val + "'"; + val = escape(val); } return val; } @@ -471,12 +515,12 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { } if (!prop) { if (typeof val === 'string' && wrap) { - val = "'" + val + "'"; + val = escape(val); } return val; } if (prop.type.name === 'Number') { - return val; + return escape(val); } if (prop.type.name === 'Date') { if (!val) { @@ -488,7 +532,7 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { } val = dateToMsSql(val); if (wrap) { - val = "'" + val + "'"; + val = escape(val); } return val; } @@ -501,7 +545,7 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { } if (wrap) { - return "'" + val.toString().replace(/'/g, "''") + "'"; + return escape(val.toString()); } return val.toString(); }; diff --git a/test/mssql.test.js b/test/mssql.test.js new file mode 100644 index 0000000..480dcca --- /dev/null +++ b/test/mssql.test.js @@ -0,0 +1,137 @@ +require('./init'); + +var should = require('should'); + +var Post, db; + +describe('mssql connector', function () { + + before(function () { + db = getDataSource(); + + Post = db.define('PostWithBoolean', { + title: { type: String, length: 255, index: true }, + content: { type: String }, + approved: Boolean + }); + }); + + it('should run migration', function (done) { + db.automigrate('PostWithBoolean', function () { + done(); + }); + }); + + var post; + it('should support boolean types with true value', function(done) { + Post.create({title: 'T1', content: 'C1', approved: true}, function(err, p) { + should.not.exists(err); + post = p; + Post.findById(p.id, function(err, p) { + should.not.exists(err); + p.should.have.property('approved', true); + done(); + }); + }); + }); + + it('should support updating boolean types with false value', function(done) { + Post.update({id: post.id}, {approved: false}, function(err) { + should.not.exists(err); + Post.findById(post.id, function(err, p) { + should.not.exists(err); + p.should.have.property('approved', false); + done(); + }); + }); + }); + + + it('should support boolean types with false value', function(done) { + Post.create({title: 'T2', content: 'C2', approved: false}, function(err, p) { + should.not.exists(err); + post = p; + Post.findById(p.id, function(err, p) { + should.not.exists(err); + p.should.have.property('approved', false); + done(); + }); + }); + }); + + it('should single quote escape', function(done) { + Post.create({title: "T2", content: "C,D", approved: false}, function(err, p) { + should.not.exists(err); + post = p; + Post.findById(p.id, function(err, p) { + should.not.exists(err); + p.should.have.property('content', "C,D"); + done(); + }); + }); + }); + + it('should return the model instance for upsert', function(done) { + Post.upsert({id: post.id, title: 'T2_new', content: 'C2_new', + approved: true}, function(err, p) { + p.should.have.property('id', post.id); + p.should.have.property('title', 'T2_new'); + p.should.have.property('content', 'C2_new'); + p.should.have.property('approved', true); + done(); + }); + }); + + it('should return the model instance for upsert when id is not present', + function(done) { + Post.upsert({title: 'T2_new', content: 'C2_new', approved: true}, + function(err, p) { + p.should.have.property('id'); + p.should.have.property('title', 'T2_new'); + p.should.have.property('content', 'C2_new'); + p.should.have.property('approved', true); + done(); + }); + }); + + it('should escape number values to defect SQL injection in findById', + function(done) { + Post.findById('(SELECT 1+1)', function(err, p) { + should.exists(err); + done(); + }); + }); + + it('should escape number values to defect SQL injection in find', + function(done) { + Post.find({where: {id: '(SELECT 1+1)'}}, function(err, p) { + should.exists(err); + done(); + }); + }); + + it('should escape number values to defect SQL injection in find with gt', + function(done) { + Post.find({where: {id: {gt: '(SELECT 1+1)'}}}, function(err, p) { + should.exists(err); + done(); + }); + }); + + it('should escape number values to defect SQL injection in find', + function(done) { + Post.find({limit: '(SELECT 1+1)'}, function(err, p) { + should.exists(err); + done(); + }); + }); + + it('should escape number values to defect SQL injection in find with inq', + function(done) { + Post.find({where: {id: {inq: ['(SELECT 1+1)']}}}, function(err, p) { + should.exists(err); + done(); + }); + }); + +}); From f389acf6b6a1d12cee0b00ff490a3d3e5a3a1f7d Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 9 Jan 2015 00:16:23 -0800 Subject: [PATCH 084/239] v1.3.0 --- CHANGES.md | 10 +++++++++- package.json | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ced1c31..c08fa89 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,12 @@ -2014-12-09, Version 1.2.0 +2015-01-09, Version 1.3.0 +========================= + + * Fix SQL injection (Raymond Feng) + + * Fix bad CLA URL in CONTRIBUTING.md (Ryan Graham) + + +2014-12-08, Version 1.2.0 ========================= * Update test dep (Raymond Feng) diff --git a/package.json b/package.json index 1b5c06d..8968502 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "1.2.0", + "version": "1.3.0", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -36,6 +36,6 @@ "url": "https://github.com/strongloop/loopback-connector-mssql/blob/master/LICENSE" }, "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@1.2.0" + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@1.3.0" } } From d7de4dd5eea8835c9107320765c66a1cf6e61293 Mon Sep 17 00:00:00 2001 From: bitmage Date: Sat, 10 Jan 2015 12:02:32 -0700 Subject: [PATCH 085/239] Fix: empty inq/nin function correctly --- lib/mssql.js | 2 +- test/mssql.test.js | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/mssql.js b/lib/mssql.js index 49a63b7..758e87e 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -769,7 +769,7 @@ MsSQL.prototype._buildWhere = function (model, conds) { var condType = Object.keys(conds[key])[0]; var sqlCond = keyEscaped; if ((condType === 'inq' || condType === 'nin') && val.length === 0) { - cs.push(condType === 'inq' ? 0 : 1); + cs.push(condType === 'inq' ? '0 = 1' : '1 = 1'); return true; } if (condType === "max") { diff --git a/test/mssql.test.js b/test/mssql.test.js index 480dcca..d96bae7 100644 --- a/test/mssql.test.js +++ b/test/mssql.test.js @@ -134,4 +134,24 @@ describe('mssql connector', function () { }); }); + it('should perform an empty inq', + function(done) { + Post.find({where: {id: {inq: []}}}, function(err, p) { + should.not.exist(err); + should.exist(p); + p.should.have.length(0); + done(); + }); + }); + + it('should perform an empty nin', + function(done) { + Post.find({where: {id: {nin: []}}}, function(err, p) { + should.not.exist(err); + should.exist(p); + p.should.have.length(4); + done(); + }); + }); + }); From 9d121badb570b37821e13f2c232fa9874001de2a Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 26 Jan 2015 16:25:05 -0800 Subject: [PATCH 086/239] Enhance id to pk mapping --- lib/mssql.js | 41 +++++++++++++++++++---------- test/mssql.test.js | 64 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 87 insertions(+), 18 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 758e87e..78dbaaf 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -278,7 +278,7 @@ MsSQL.prototype.exists = function (model, id, callback) { var tblName = this.tableEscaped(model); var modelPKID = this.idName(model); var sql = "SELECT COUNT(*) cnt FROM " + tblName + " WHERE [" + modelPKID + "] = (?)" - this.query(sql, [id], function (err, results) { + this.query(sql, [escape(id)], function (err, results) { if (err) return callback(err); callback(null, results[0].cnt >= 1); }); @@ -318,7 +318,7 @@ MsSQL.prototype.destroyAll = function (model, where, cb) { MsSQL.prototype.destroy = function (model, id, cb) { var sql = "DELETE FROM " + this.tableEscaped(model) + MsSQL.newline; sql += "WHERE [" + this.idName(model) + "] = (?)"; - this.query(sql, [id], function (err, data) { + this.query(sql, [escape(id)], function (err, data) { if (err) { return cb && cb(err); } @@ -362,16 +362,16 @@ MsSQL.prototype.update = MsSQL.prototype.updateAll = function (model, where, dat MsSQL.prototype.find = function (model, id, callback) { - //debugger; + var self = this; var tblName = this.tableEscaped(model); var modelPKID = this.idName(model); var sql = "SELECT * FROM " + tblName + " WHERE [" + modelPKID + "] = (?)"; - this.query(sql, [id], function (err, results) { + this.query(sql, [escape(id)], function (err, results) { if (err) { return callback(err); } - callback(null, this.fromDatabase(model, results[0])); - }.bind(this)); + callback(null, self.fromDatabase(model, results[0])); + }); }; MsSQL.prototype.buildInsert = function (model, data) { @@ -499,8 +499,8 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { && typeof val.length === 'number') { //if value is array //check if it is an array of string, because in that cause we need to // wrap them in single quotes - if (typeof val[0] === 'string') { - return escape(val.join("','")); + for (var i = 0, n = val.length; i < n; i++) { + val[i] = escape(val[i]); } return val.join(','); } else { @@ -1133,19 +1133,34 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, } }; -MsSQL.prototype.propertiesSQL = function (model) { +MsSQL.prototype.propertiesSQL = function(model) { // debugger; var self = this; var objModel = this._models[model]; var modelPKID = this.idName(model); - var sql = ["[" + modelPKID + "] [int] IDENTITY(1,1) NOT NULL"]; - Object.keys(objModel.properties).forEach(function (prop) { + var sql = []; + var props = Object.keys(objModel.properties); + for (var i = 0, n = props.length; i < n; i++) { + var prop = props[i]; if (prop === modelPKID) { - return; + var idProp = objModel.properties[modelPKID]; + if (idProp.type === Number) { + sql.push("[" + modelPKID + "] [int] IDENTITY(1,1) NOT NULL"); + continue; + } else if (idProp.type === String) { + if(idProp.generated) { + sql.push("[" + modelPKID + + "] [uniqueidentifier] DEFAULT newid() NOT NULL"); + } else { + sql.push("[" + modelPKID + "] " + + self.propertySettingsSQL(model, prop) + " DEFAULT newid()"); + } + continue; + } } sql.push("[" + prop + "] " + self.propertySettingsSQL(model, prop)); - }); + } var joinedSql = sql.join("," + MsSQL.newline + " "); var cmd = "PRIMARY KEY CLUSTERED" + MsSQL.newline + "(" + MsSQL.newline; cmd += " [" + modelPKID + "] ASC" + MsSQL.newline; diff --git a/test/mssql.test.js b/test/mssql.test.js index d96bae7..ed134ab 100644 --- a/test/mssql.test.js +++ b/test/mssql.test.js @@ -2,7 +2,7 @@ require('./init'); var should = require('should'); -var Post, db; +var Post, PostWithUUID, PostWithStringId, db; describe('mssql connector', function () { @@ -14,12 +14,28 @@ describe('mssql connector', function () { content: { type: String }, approved: Boolean }); - }); - it('should run migration', function (done) { - db.automigrate('PostWithBoolean', function () { - done(); + PostWithUUID = db.define('PostWithUUID', { + id: {type: String, generated: true, id: true}, + title: { type: String, length: 255, index: true }, + content: { type: String }, + approved: Boolean }); + + PostWithStringId = db.define('PostWithStringId', { + id: {type: String, id: true, generated: false}, + title: { type: String, length: 255, index: true }, + content: { type: String }, + approved: Boolean + }); + + }); + + it('should run migration', function(done) { + db.automigrate(['PostWithBoolean', 'PostWithUUID', 'PostWithStringId'], + function(err) { + done(err); + }); }); var post; @@ -134,6 +150,16 @@ describe('mssql connector', function () { }); }); + it('should allow string array for inq', + function(done) { + Post.find({where: {content: {inq: ['C1', 'C2']}}}, function(err, p) { + should.not.exist(err); + should.exist(p); + p.should.have.length(2); + done(); + }); + }); + it('should perform an empty inq', function(done) { Post.find({where: {id: {inq: []}}}, function(err, p) { @@ -154,4 +180,32 @@ describe('mssql connector', function () { }); }); + it('should support uuid', function(done) { + PostWithUUID.create({title: 'T1', content: 'C1', approved: true}, + function(err, p) { + should.not.exists(err); + p.should.have.property('id'); + // p.id.should.be.a.string(); + PostWithUUID.findById(p.id, function(err, p) { + should.not.exists(err); + p.should.have.property('title', 'T1'); + done(); + }); + }); + }); + + it('should support string id', function(done) { + PostWithStringId.create({title: 'T1', content: 'C1', approved: true}, + function(err, p) { + should.not.exists(err); + p.should.have.property('id'); + p.id.should.be.a.string; + PostWithStringId.findById(p.id, function(err, p) { + should.not.exists(err); + p.should.have.property('title', 'T1'); + done(); + }); + }); + }); + }); From d1f49b82abe3975186843feab4d64e09b4c714f2 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 27 Jan 2015 14:38:53 -0800 Subject: [PATCH 087/239] Increase the limit to make sure other owners are selected --- test/discover.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/discover.test.js b/test/discover.test.js index ffa9937..954e23a 100644 --- a/test/discover.test.js +++ b/test/discover.test.js @@ -64,7 +64,7 @@ describe('Discover models including other users', function () { db.discoverModelDefinitions({ all: true, - limit: 50 + limit: 100 }, function (err, models) { if (err) { console.error(err); From 91366384826df19d77a4af22655ecb7496877dc0 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 27 Jan 2015 15:36:45 -0800 Subject: [PATCH 088/239] Fix the empty column list --- lib/mssql.js | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 78dbaaf..0ef5f81 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -154,9 +154,12 @@ MsSQL.prototype.create = function (model, data, callback) { var tblName = this.tableEscaped(model); var modelPKID = this.idName(model); - var sql = "INSERT INTO " + tblName + " (" + fieldsAndData.fields + ")" + MsSQL.newline; + var columns = fieldsAndData.fields ? " (" + fieldsAndData.fields + ")" : ""; + var sql = "INSERT INTO " + tblName + columns + MsSQL.newline; sql += "OUTPUT INSERTED." + modelPKID + " AS insertId" + MsSQL.newline; - sql += "VALUES (" + fieldsAndData.paramPlaceholders + ");"; + sql += (fieldsAndData.paramPlaceholders ? + "VALUES (" + fieldsAndData.paramPlaceholders + ");" : + "DEFAULT VALUES;"); this.query(sql, fieldsAndData.params, function (err, results) { if (err) { @@ -207,8 +210,9 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { } else { //insert with identity_insert sql = "SET IDENTITY_INSERT " + tblName + " ON;" + MsSQL.newline; - sql += "INSERT INTO " + tblName + " ([" + modelPKID + "]," - + fieldNames.join() + ")" + MsSQL.newline; + var columns = fieldNames.length ? "," + fieldNames.join() + ")" : ")"; + sql += "INSERT INTO " + tblName + " ([" + modelPKID + "]" + + columns + MsSQL.newline; sql += "VALUES (" + id + "," + fieldValuesPlaceholders.join() + ");" + MsSQL.newline; sql += "SET IDENTITY_INSERT " + tblName + " OFF;" + MsSQL.newline; sql += "SELECT " + id + " AS pkid;"; @@ -216,9 +220,13 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { doQuery(sql, fieldValues); }); } else { + var columns = fieldNames.length ? " (" + fieldNames.join() + ")" : ""; //insert - sql = "INSERT INTO " + tblName + " (" + fieldNames.join() + ")" + MsSQL.newline; - sql += "VALUES (" + fieldValuesPlaceholders.join() + ");" + MsSQL.newline; + sql = "INSERT INTO " + tblName + columns + MsSQL.newline; + var values = fieldValuesPlaceholders.length ? + "VALUES (" + fieldValuesPlaceholders.join() + ");" : + "DEFAULT VALUES;"; + sql += values + MsSQL.newline; sql += "SELECT IDENT_CURRENT('" + tblName + "') AS pkid;"; doQuery(sql, fieldValues); } @@ -375,6 +383,7 @@ MsSQL.prototype.find = function (model, id, callback) { }; MsSQL.prototype.buildInsert = function (model, data) { + var self = this; var insertIntoFields = []; var paramPlaceholders = []; var params = []; @@ -384,14 +393,14 @@ MsSQL.prototype.buildInsert = function (model, data) { // new record, not update an existing one. delete data[modelPKID]; //delete the hardcoded id property that jugglindb automatically creates - delete data.id + delete data.id; Object.keys(data).forEach(function (key) { if (props[key]) { insertIntoFields.push("[" + key + "]"); paramPlaceholders.push("(?)"); - params.push(this.toDatabase(props[key], data[key], true)); + params.push(self.toDatabase(props[key], data[key], true)); } - }.bind(this)); + }); return { fields: insertIntoFields.join(), paramPlaceholders: paramPlaceholders.join(), From ede01be1ffa846dcb9611960821f4ee9b0049906 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 27 Jan 2015 15:37:58 -0800 Subject: [PATCH 089/239] v1.4.0 --- CHANGES.md | 12 ++++++++++++ package.json | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c08fa89..34cb6cb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,15 @@ +2015-01-27, Version 1.4.0 +========================= + + * Fix the empty column list (Raymond Feng) + + * Increase the limit to make sure other owners are selected (Raymond Feng) + + * Enhance id to pk mapping (Raymond Feng) + + * Fix: empty inq/nin function correctly (bitmage) + + 2015-01-09, Version 1.3.0 ========================= diff --git a/package.json b/package.json index 8968502..092420b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "1.3.0", + "version": "1.4.0", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -36,6 +36,6 @@ "url": "https://github.com/strongloop/loopback-connector-mssql/blob/master/LICENSE" }, "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@1.3.0" + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@1.4.0" } } From 30e5ee178076edb16def1aec59821a1854f0022d Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 20 Feb 2015 16:12:39 -0800 Subject: [PATCH 090/239] Add support for custom column mapping --- lib/mssql.js | 115 +++++++++++++++++++++++------------------- package.json | 15 +++--- test/mapping.test.js | 116 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 58 deletions(-) create mode 100644 test/mapping.test.js diff --git a/lib/mssql.js b/lib/mssql.js index 0ef5f81..c3fdc2b 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -156,7 +156,8 @@ MsSQL.prototype.create = function (model, data, callback) { var columns = fieldsAndData.fields ? " (" + fieldsAndData.fields + ")" : ""; var sql = "INSERT INTO " + tblName + columns + MsSQL.newline; - sql += "OUTPUT INSERTED." + modelPKID + " AS insertId" + MsSQL.newline; + sql += "OUTPUT INSERTED." + this.columnEscaped(model, modelPKID) + + " AS insertId" + MsSQL.newline; sql += (fieldsAndData.paramPlaceholders ? "VALUES (" + fieldsAndData.paramPlaceholders + ");" : "DEFAULT VALUES;"); @@ -183,14 +184,33 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { var fieldValues = []; var fieldValuesPlaceholders = []; var combined = []; + + var doQuery = function (sql, fieldValues) { + self.query(sql, fieldValues, function (err, results) { + if (err) { + return callback(err); + } + //msnodesql will execute the callback for each statement that get's + // executed, we're only interested in the one that returns with the pkid + if (results.length > 0 && results[0].pkid) { + data[modelPKID] = results[0].pkid; + //#jdb id compatibility# + data.id = results[0].pkid; //set the id property also, to play nice + // with the jugglingdb abstract class implementation. + callback(err, data); + } + }); + }; + Object.keys(data).forEach(function (key) { if (props[key]) { //check for the "id" key also, for backwards compatibility with the jugglingdb hardcoded id system if (key !== "id" && key !== modelPKID) { - fieldNames.push("[" + key + "]"); + var columnName = self.columnEscaped(model, key); + fieldNames.push(columnName); fieldValues.push(self.toDatabase(props[key], data[key], true)); fieldValuesPlaceholders.push("(?)"); - combined.push(key + "=(?)"); + combined.push(columnName + "=(?)"); } } }); @@ -204,18 +224,18 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { //update sql = "UPDATE " + tblName + MsSQL.newline; sql += "SET " + combined.join() + MsSQL.newline; - sql += "WHERE [" + modelPKID + "] = (?);" + MsSQL.newline; - sql += "SELECT " + id + " AS pkid;"; + sql += "WHERE " + self.columnEscaped(model, modelPKID) + " = (?);" + MsSQL.newline; + sql += "SELECT " + escape(id) + " AS pkid;"; fieldValues.push(id); } else { //insert with identity_insert sql = "SET IDENTITY_INSERT " + tblName + " ON;" + MsSQL.newline; var columns = fieldNames.length ? "," + fieldNames.join() + ")" : ")"; - sql += "INSERT INTO " + tblName + " ([" + modelPKID + "]" + sql += "INSERT INTO " + tblName + " (" + self.columnEscaped(model, modelPKID) + columns + MsSQL.newline; - sql += "VALUES (" + id + "," + fieldValuesPlaceholders.join() + ");" + MsSQL.newline; + sql += "VALUES (" + escape(id) + "," + fieldValuesPlaceholders.join() + ");" + MsSQL.newline; sql += "SET IDENTITY_INSERT " + tblName + " OFF;" + MsSQL.newline; - sql += "SELECT " + id + " AS pkid;"; + sql += "SELECT " + escape(id) + " AS pkid;"; } doQuery(sql, fieldValues); }); @@ -230,23 +250,6 @@ MsSQL.prototype.updateOrCreate = function (model, data, callback) { sql += "SELECT IDENT_CURRENT('" + tblName + "') AS pkid;"; doQuery(sql, fieldValues); } - - var doQuery = function (sql, fieldValues) { - self.query(sql, fieldValues, function (err, results) { - if (err) { - return callback(err); - } - //msnodesql will execute the callback for each statement that get's - // executed, we're only interested in the one that returns with the pkid - if (results.length > 0 && results[0].pkid) { - data[modelPKID] = results[0].pkid; - //#jdb id compatibility# - data.id = results[0].pkid; //set the id property also, to play nice - // with the jugglingdb abstract class implementation. - callback(err, data); - } - }); - }; }; //redundant, same functionality as "updateOrCreate" right now. Maybe in the @@ -349,7 +352,7 @@ MsSQL.prototype.update = MsSQL.prototype.updateAll = function (model, where, dat if (props[key]) { //check for the "id" key also, for backwards compatibility with the jugglingdb hardcoded id system if (key !== "id" && key !== modelPKID) { - fieldNames.push("[" + key + "]"); + fieldNames.push(self.columnEscaped(model, key)); fieldValues.push(self.toDatabase(props[key], data[key], true)); fieldValuesPlaceholders.push("(?)"); combined.push(key + "=(?)"); @@ -396,7 +399,7 @@ MsSQL.prototype.buildInsert = function (model, data) { delete data.id; Object.keys(data).forEach(function (key) { if (props[key]) { - insertIntoFields.push("[" + key + "]"); + insertIntoFields.push(self.columnEscaped(model, key)); paramPlaceholders.push("(?)"); params.push(self.toDatabase(props[key], data[key], true)); } @@ -560,28 +563,33 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) { }; MsSQL.prototype.fromDatabase = function (model, data) { + var self = this; if (!data) { return null; } // create an "id" property in the data for backwards compatibility with juggling-db // data.id = data[this.idName(model)]; var props = this._models[model].properties; + var result = {}; //look for date values in the data, convert them from the database to a javascript date object - Object.keys(data).forEach(function (key) { + Object.keys(props).forEach(function (p) { + var key = self.column(model, p); var val = data[key]; - if (props[key]) { - if (props[key].type.name === 'Boolean' && val !== null) { + if (val !== undefined) { + if (props[p].type.name === 'Boolean' && val !== null) { val = (true && val); //convert to a boolean type from number } - if (props[key].type.name === 'Date' && val !== null) { + if (props[p].type.name === 'Date' && val !== null) { if(!(val instanceof Date)) { val = new Date(val.toString()); } } } - data[key] = val; + if (val !== undefined) { + result[p] = val; + } }); - return data; + return result; }; MsSQL.prototype.escapeName = function (name) { @@ -710,15 +718,17 @@ MsSQL.prototype.all = function (model, filter, callback) { if (err) return callback(err); //convert database types to js types - data = self.fromDatabase(model, data); + data = data.map(function (obj) { + return self.fromDatabase(model, obj); + }); //check for eager loading relationships if (filter && filter.include) { - this._models[model].model.include(data, filter.include, callback); + self._models[model].model.include(data, filter.include, callback); } else { callback(null, data); } - }.bind(this)); + }); return sql; }; @@ -997,7 +1007,8 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, if (found) { actualize(propName, found); } else { - columnsToAdd.push('[' + propName + '] ' + self.propertySettingsSQL(model, propName)); + columnsToAdd.push(self.columnEscaped(model, propName) + + ' ' + self.propertySettingsSQL(model, propName)); } }); @@ -1006,7 +1017,7 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, var notFound = !~propNames.indexOf(f.Field); if (f.Field === idName) return; if (notFound || !m.properties[f.Field]) { - columnsToDrop.push('[' + f.Field + ']'); + columnsToDrop.push(self.columnEscaped(model, f.Field)); } }); @@ -1017,7 +1028,7 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, } if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || m.properties[indexName] && !m.properties[indexName].index) { - sql.push('DROP INDEX [' + indexName + ']'); + sql.push('DROP INDEX ' + self.columnEscaped(model, indexName)); } else { // first: check single (only type and kind) if (m.properties[indexName] && !m.properties[indexName].index) { @@ -1034,7 +1045,7 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, }); } if (!orderMatched) { - sql.push('DROP INDEX [' + indexName + ']'); + sql.push('DROP INDEX [' + self.columnEscaped(model, indexName) + ']'); delete ai[indexName]; } } @@ -1047,6 +1058,7 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, return; } var found = ai[propName] && ai[propName].info; + var columnName = self.columnEscaped(model, propName); if (!found) { var type = ''; var kind = ''; @@ -1057,8 +1069,8 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, // kind = i.kind; } if (kind && type) { - sql.push('ADD ' + kind + ' INDEX [' + propName - + '] ([' + propName + ']) ' + type); + sql.push('ADD ' + kind + ' INDEX ' + columnName + + ' (' + columnName + ') ' + type); } else { sql.push('ADD ' + kind + ' INDEX [' + propName + '] ' + type + ' ([' + propName + ']) '); @@ -1083,8 +1095,8 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, sql.push('ADD ' + kind + ' INDEX [' + indexName + '] (' + i.columns + ') ' + type); } else { - sql.push('ADD ' + kind + ' INDEX ' + type + ' [' + indexName - + '] (' + i.columns + ')'); + sql.push('ADD ' + kind + ' INDEX [' + indexName + '] (' + + i.columns + ') '); } } }); @@ -1121,7 +1133,7 @@ MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, function actualize(propName, oldSettings) { var newSettings = m.properties[propName]; if (newSettings && changed(newSettings, oldSettings)) { - columnsToAlter.push('[' + propName + '] ' + columnsToAlter.push(self.columnEscaped(model, propName) + ' ' + self.propertySettingsSQL(model, propName)); } } @@ -1155,24 +1167,25 @@ MsSQL.prototype.propertiesSQL = function(model) { if (prop === modelPKID) { var idProp = objModel.properties[modelPKID]; if (idProp.type === Number) { - sql.push("[" + modelPKID + "] [int] IDENTITY(1,1) NOT NULL"); + sql.push(self.columnEscaped(model, modelPKID) + + " [int] IDENTITY(1,1) NOT NULL"); continue; } else if (idProp.type === String) { if(idProp.generated) { - sql.push("[" + modelPKID - + "] [uniqueidentifier] DEFAULT newid() NOT NULL"); + sql.push(self.columnEscaped(model, modelPKID) + + " [uniqueidentifier] DEFAULT newid() NOT NULL"); } else { - sql.push("[" + modelPKID + "] " + + sql.push(self.columnEscaped(model, modelPKID) + " " + self.propertySettingsSQL(model, prop) + " DEFAULT newid()"); } continue; } } - sql.push("[" + prop + "] " + self.propertySettingsSQL(model, prop)); + sql.push(self.columnEscaped(model, prop) + " " + self.propertySettingsSQL(model, prop)); } var joinedSql = sql.join("," + MsSQL.newline + " "); var cmd = "PRIMARY KEY CLUSTERED" + MsSQL.newline + "(" + MsSQL.newline; - cmd += " [" + modelPKID + "] ASC" + MsSQL.newline; + cmd += " " + self.columnEscaped(model, modelPKID) + " ASC" + MsSQL.newline; cmd += ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, " + "IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)" diff --git a/package.json b/package.json index 092420b..7b4f357 100644 --- a/package.json +++ b/package.json @@ -13,16 +13,17 @@ ], "main": "index.js", "dependencies": { - "async": "~0.9.0", - "debug": "^2.0.0", + "async": "^0.9.0", + "debug": "^2.1.1", "loopback-connector": "1.x", - "mssql": "^1.2.0" + "mssql": "^2.1.0" }, "devDependencies": { + "bluebird": "^2.9.12", "loopback-datasource-juggler": "2.x", - "mocha": "~1.20.1", - "should": "~1.2.2", - "rc": "~0.4.0" + "mocha": "^2.1.0", + "rc": "^0.6.0", + "should": "^5.0.0" }, "scripts": { "test": "./node_modules/.bin/mocha --timeout 5000 -R spec" @@ -36,6 +37,6 @@ "url": "https://github.com/strongloop/loopback-connector-mssql/blob/master/LICENSE" }, "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@1.4.0" + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@1.0.0" } } diff --git a/test/mapping.test.js b/test/mapping.test.js new file mode 100644 index 0000000..9230a81 --- /dev/null +++ b/test/mapping.test.js @@ -0,0 +1,116 @@ +var should = require('should'); +require('./init'); + +var async = require('async'); + +var db; + +before(function() { + db = getDataSource(); +}); + +describe('Mapping models', function() { + it('should honor the mssql settings for table/column', function(done) { + + var schema = { + name: 'TestInventory', + options: { + idInjection: false, + mssql: { + schema: 'dbo', table: 'INVENTORYTEST' + } + }, + properties: { + productId: { + type: 'Number', + id: true, + generated: true, + mssql: { + columnName: 'PRODUCT_ID', + dataType: 'uniqueidentifier', + nullable: 'N' + } + }, + locationId: { + type: 'String', + required: true, + length: 20, + mssql: { + columnName: 'LOCATION_ID', + dataType: 'nvarchar', + nullable: 'N' + } + }, + available: { + type: 'Number', + required: false, + mssql: { + columnName: 'AVAILABLE', + dataType: 'int', + nullable: 'Y' + } + }, + total: { + type: 'Number', + required: false, + mssql: { + columnName: 'TOTAL', + dataType: 'int', + nullable: 'Y' + } + } + } + }; + var models = db.modelBuilder.buildModels(schema); + var Model = models.TestInventory; + Model.attachTo(db); + + db.automigrate(function(err, data) { + async.series([ + function(callback) { + Model.destroyAll(callback); + }, + function(callback) { + Model.create({locationId: 'l001', available: 10, total: 50}, + callback); + }, + function(callback) { + Model.create({locationId: 'l002', available: 30, total: 40}, + callback); + }, + function(callback) { + Model.create({locationId: 'l001', available: 15, total: 30}, + callback); + }, + function(callback) { + Model.find({fields: ['productId', 'locationId', 'available']}, + function(err, results) { + // console.log(results); + results.should.have.lengthOf(3); + results.forEach(function(r) { + r.should.have.property('productId'); + r.should.have.property('locationId'); + r.should.have.property('available'); + should.equal(r.total, undefined); + }); + callback(null, results); + }); + }, + function(callback) { + Model.find({fields: {'total': false}}, function(err, results) { + // console.log(results); + results.should.have.lengthOf(3); + results.forEach(function(r) { + r.should.have.property('productId'); + r.should.have.property('locationId'); + r.should.have.property('available'); + should.equal(r.total, undefined); + }); + callback(null, results); + }); + } + ], done); + }); + + }); +}); From 6f6041708022748b603cec1971f21242f379fda3 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 20 Feb 2015 17:09:24 -0800 Subject: [PATCH 091/239] 1.5.0 * Add support for custom column mapping (Raymond Feng) --- CHANGES.md | 6 ++++++ package.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 34cb6cb..5ad04b9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,9 @@ +2015-02-21, Version 1.5.0 +========================= + + * Add support for custom column mapping (Raymond Feng) + + 2015-01-27, Version 1.4.0 ========================= diff --git a/package.json b/package.json index 7b4f357..c914085 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "1.4.0", + "version": "1.5.0", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -37,6 +37,6 @@ "url": "https://github.com/strongloop/loopback-connector-mssql/blob/master/LICENSE" }, "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@1.0.0" + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@1.5.0" } } From 87543e7b48068d74aa166463186c6b78dc8ccc00 Mon Sep 17 00:00:00 2001 From: Ido Shamun Date: Mon, 2 Mar 2015 01:33:42 +0200 Subject: [PATCH 092/239] Add support for custom column mapping in primary key column Add support for idInjection Closes #1 --- lib/mssql.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index c3fdc2b..ee7037c 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -392,11 +392,13 @@ MsSQL.prototype.buildInsert = function (model, data) { var params = []; var props = this._models[model].properties; var modelPKID = this.idName(model); - //remove the pkid column if it's in the data, since we're going to insert a - // new record, not update an existing one. - delete data[modelPKID]; - //delete the hardcoded id property that jugglindb automatically creates - delete data.id; + if (props[modelPKID].idInjection !== false) { + //remove the pkid column if it's in the data, since we're going to insert a + // new record, not update an existing one. + delete data[modelPKID]; + //delete the hardcoded id property that jugglindb automatically creates + delete data.id; + } Object.keys(data).forEach(function (key) { if (props[key]) { insertIntoFields.push(self.columnEscaped(model, key)); @@ -1167,8 +1169,14 @@ MsSQL.prototype.propertiesSQL = function(model) { if (prop === modelPKID) { var idProp = objModel.properties[modelPKID]; if (idProp.type === Number) { - sql.push(self.columnEscaped(model, modelPKID) + - " [int] IDENTITY(1,1) NOT NULL"); + if(idProp.generated !== false) { + sql.push(self.columnEscaped(model, modelPKID) + + " " + self.columnDataType(model, modelPKID) + " IDENTITY(1,1) NOT NULL"); + } + else { + sql.push(self.columnEscaped(model, modelPKID) + + " " + self.columnDataType(model, modelPKID) + " NOT NULL"); + } continue; } else if (idProp.type === String) { if(idProp.generated) { From 60b3aa55711bdc278cc022f24ecfbb74d7330cb6 Mon Sep 17 00:00:00 2001 From: Ido Shamun Date: Mon, 2 Mar 2015 23:36:27 +0200 Subject: [PATCH 093/239] add test case for id manipulation --- lib/mssql.js | 2 +- test/id.test.js | 183 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 test/id.test.js diff --git a/lib/mssql.js b/lib/mssql.js index ee7037c..2692534 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -392,7 +392,7 @@ MsSQL.prototype.buildInsert = function (model, data) { var params = []; var props = this._models[model].properties; var modelPKID = this.idName(model); - if (props[modelPKID].idInjection !== false) { + if (this._models[model].settings.idInjection !== false) { //remove the pkid column if it's in the data, since we're going to insert a // new record, not update an existing one. delete data[modelPKID]; diff --git a/test/id.test.js b/test/id.test.js new file mode 100644 index 0000000..ba814ab --- /dev/null +++ b/test/id.test.js @@ -0,0 +1,183 @@ +require('./init.js'); +var should = require('should'); +var assert = require('assert'); +var async = require('async'); +var ds; + +before(function () { + ds = getDataSource(); +}); + +describe('Manipulating id column', function () { + it('should auto generate id', function (done) { + + var schema = + { + "name": "WarehouseTest", + "options": { + "mssql": { + "schema": "dbo", + "table": "WAREHOUSE_TEST" + } + }, + "properties": { + "id": { + "type": "Number", + "id": true + }, + "name": { + "type": "String", + "required": false, + "length": 40 + } + } + } + + var models = ds.modelBuilder.buildModels(schema); + var Model = models.WarehouseTest; + Model.attachTo(ds); + + ds.automigrate(function (err) { + assert(!err); + async.series([ + function(callback) { + Model.destroyAll(callback); + }, + function(callback) { + Model.create({name: 'w1'}, + callback); + }, + function(callback) { + Model.create({name: 'w2'}, + callback); + }, + function(callback) { + Model.create({name: 'w3'}, + callback); + }, + function(callback) { + Model.find({order: 'id asc'}, + function(err, results) { + assert(!err); + results.should.have.lengthOf(3); + for (var i = 0; i < results.length; i++) { + should.equal(results[i].id, i + 1); + } + callback(); + }); + } + ], done); + }); + }); + + it('should use manual id', function (done) { + + var schema = + { + "name": "WarehouseTest", + "options": { + "idInjection": false, + "mssql": { + "schema": "dbo", + "table": "WAREHOUSE_TEST" + } + }, + "properties": { + "id": { + "type": "Number", + "id": true, + "generated": false + }, + "name": { + "type": "String", + "required": false, + "length": 40 + } + } + } + + var models = ds.modelBuilder.buildModels(schema); + var Model = models.WarehouseTest; + Model.attachTo(ds); + + ds.automigrate(function (err) { + assert(!err); + async.series([ + function(callback) { + Model.destroyAll(callback); + }, + function(callback) { + Model.create({id: 501, name: 'w1'}, + callback); + }, + function(callback) { + Model.find({order: 'id asc'}, + function(err, results) { + assert(!err); + results.should.have.lengthOf(1); + should.equal(results[0].id, 501); + callback(); + }); + } + ], done); + }); + }); + + it('should use bigint id', function (done) { + + var schema = + { + "name": "WarehouseTest", + "options": { + "idInjection": false, + "mssql": { + "schema": "dbo", + "table": "WAREHOUSE_TEST", + } + }, + "properties": { + "id": { + "type": "Number", + "id": true, + "generated": false, + "mssql": { + "dataType": "bigint", + "dataPrecision": 20, + "dataScale": 0 + } + }, + "name": { + "type": "String", + "required": false, + "length": 40 + } + } + } + + var models = ds.modelBuilder.buildModels(schema); + var Model = models.WarehouseTest; + Model.attachTo(ds); + + ds.automigrate(function (err) { + assert(!err); + async.series([ + function(callback) { + Model.destroyAll(callback); + }, + function(callback) { + Model.create({id: 962744456683738, name: 'w1'}, + callback); + }, + function(callback) { + Model.find({order: 'id asc'}, + function(err, results) { + assert(!err); + results.should.have.lengthOf(1); + should.equal(results[0].id, 962744456683738); + callback(); + }); + } + ], done); + }); + }); +}); From 6d3c9e50cbae7e176f229c7b780be7c306bc637d Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 2 Mar 2015 22:49:37 -0800 Subject: [PATCH 094/239] Test if the id is generated --- lib/mssql.js | 4 ++-- test/mapping.test.js | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 2692534..aac16a7 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -392,7 +392,7 @@ MsSQL.prototype.buildInsert = function (model, data) { var params = []; var props = this._models[model].properties; var modelPKID = this.idName(model); - if (this._models[model].settings.idInjection !== false) { + if (props[modelPKID].generated !== false) { //remove the pkid column if it's in the data, since we're going to insert a // new record, not update an existing one. delete data[modelPKID]; @@ -1179,7 +1179,7 @@ MsSQL.prototype.propertiesSQL = function(model) { } continue; } else if (idProp.type === String) { - if(idProp.generated) { + if(idProp.generated !== false) { sql.push(self.columnEscaped(model, modelPKID) + " [uniqueidentifier] DEFAULT newid() NOT NULL"); } else { diff --git a/test/mapping.test.js b/test/mapping.test.js index 9230a81..d460c21 100644 --- a/test/mapping.test.js +++ b/test/mapping.test.js @@ -27,7 +27,6 @@ describe('Mapping models', function() { generated: true, mssql: { columnName: 'PRODUCT_ID', - dataType: 'uniqueidentifier', nullable: 'N' } }, From 3c931520d17c2cd1811ae5d3e92ac010c923b74c Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 2 Mar 2015 22:51:34 -0800 Subject: [PATCH 095/239] 1.5.1 * Test if the id is generated (Raymond Feng) * add test case for id manipulation (Ido Shamun) * Add support for custom column mapping in primary key column Add support for idInjection (Ido Shamun) --- CHANGES.md | 12 +++++++++++- package.json | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5ad04b9..6c38a5d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,14 @@ -2015-02-21, Version 1.5.0 +2015-03-03, Version 1.5.1 +========================= + + * Test if the id is generated (Raymond Feng) + + * add test case for id manipulation (Ido Shamun) + + * Add support for custom column mapping in primary key column Add support for idInjection (Ido Shamun) + + +2015-02-20, Version 1.5.0 ========================= * Add support for custom column mapping (Raymond Feng) diff --git a/package.json b/package.json index c914085..9fc69b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "1.5.0", + "version": "1.5.1", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -37,6 +37,6 @@ "url": "https://github.com/strongloop/loopback-connector-mssql/blob/master/LICENSE" }, "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@1.5.0" + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@1.5.1" } } From 0c695fc5f39e923d1dbe3f8b9fd6eabd89229e71 Mon Sep 17 00:00:00 2001 From: Simon Ho Date: Sun, 15 Mar 2015 17:44:42 -0700 Subject: [PATCH 096/239] Add "Running tests" section to readme --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 62f2ff3..40d2abe 100644 --- a/README.md +++ b/README.md @@ -40,3 +40,11 @@ To use it you need `loopback-datasource-juggler`. }); ``` +## Running tests + +The tests in this repository are mainly integration tests, meaning you will need +to run them using our preconfigured test server. + +1. Ask a core developer for instructions on how to set up test server + credentials on your machine +2. `npm test` From 5e892a0f67f50f65ecbe359c53ad24e05a309a35 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 19 Mar 2015 15:36:27 -0700 Subject: [PATCH 097/239] Add strongloop license check --- index.js | 3 ++- package.json | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index a4a1362..94446aa 100644 --- a/index.js +++ b/index.js @@ -1 +1,2 @@ -module.exports = require("./lib/mssql.js"); \ No newline at end of file +require('strongloop-license')('connectors:mssql'); +module.exports = require("./lib/mssql.js"); diff --git a/package.json b/package.json index 9fc69b6..618b997 100644 --- a/package.json +++ b/package.json @@ -16,13 +16,14 @@ "async": "^0.9.0", "debug": "^2.1.1", "loopback-connector": "1.x", - "mssql": "^2.1.0" + "mssql": "^2.1.0", + "strongloop-license": "^1.0.2" }, "devDependencies": { "bluebird": "^2.9.12", "loopback-datasource-juggler": "2.x", "mocha": "^2.1.0", - "rc": "^0.6.0", + "rc": "^1.0.0", "should": "^5.0.0" }, "scripts": { From 5a6695e07e4ce6b093bf2f394d673c5a35826310 Mon Sep 17 00:00:00 2001 From: Simon Ho Date: Wed, 25 Mar 2015 00:00:28 -0700 Subject: [PATCH 098/239] Return count when updating or deleting models --- lib/mssql.js | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index aac16a7..8c67aa9 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -318,12 +318,23 @@ MsSQL.prototype.destroyAll = function (model, where, cb) { cb = where; where = undefined; } - this.query('DELETE FROM ' - + this.tableEscaped(model) + ' ' + this.buildWhere(model, where || {}), - function (err, data) { - cb && cb(err, data); - }.bind(this)); + var sql = 'DELETE FROM ' + this.tableEscaped(model) + ' ' + + this.buildWhere(model, where || {}); + + // attach a secondary query to get the number of affected items + var affectedCountQuery = ';SELECT @@ROWCOUNT as count'; + sql += affectedCountQuery; + + this.query(sql, function(err, info) { + if (err) return cb && cb(err); + + var affectedCountQueryResult = info[0]; + var affectedCount = typeof affectedCountQueryResult.count === 'number' ? + affectedCountQueryResult.count : undefined; + + cb && cb(null, {count: affectedCount}); + }); }; MsSQL.prototype.destroy = function (model, id, cb) { @@ -367,8 +378,19 @@ MsSQL.prototype.update = MsSQL.prototype.updateAll = function (model, where, dat sql += self.buildWhere(model, where) + MsSQL.newline; fieldValues.push(id); - self.query(sql, fieldValues, cb); + // attach a secondary query to get the number of affected items + var affectedCountQuery = ';SELECT @@ROWCOUNT as count'; + sql += affectedCountQuery; + + self.query(sql, fieldValues, function(err, info) { + if (err) return cb && cb(err); + var affectedCountQueryResult = info[0]; + var affectedCount = typeof affectedCountQueryResult.count === 'number' ? + affectedCountQueryResult.count : undefined; + + cb && cb(null, {count: affectedCount}); + }); }; From 100e4b916895251c7d42959714f1ed18a990b5ef Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 2 Apr 2015 11:45:34 -0700 Subject: [PATCH 099/239] Use SET IDENTITY_INSERT option to allow explicit id --- lib/mssql.js | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 8c67aa9..f321608 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -155,18 +155,34 @@ MsSQL.prototype.create = function (model, data, callback) { var modelPKID = this.idName(model); var columns = fieldsAndData.fields ? " (" + fieldsAndData.fields + ")" : ""; - var sql = "INSERT INTO " + tblName + columns + MsSQL.newline; + var sql = ''; + + var props = this._models[model].properties; + var isIdentity = (props[modelPKID].type === Number && + props[modelPKID].generated !== false); + + if (isIdentity && data[modelPKID] != null) { + // insert with identity_insert + sql += "SET IDENTITY_INSERT " + tblName + " ON;" + MsSQL.newline; + } + + sql += "INSERT INTO " + tblName + columns + MsSQL.newline; sql += "OUTPUT INSERTED." + this.columnEscaped(model, modelPKID) + " AS insertId" + MsSQL.newline; sql += (fieldsAndData.paramPlaceholders ? "VALUES (" + fieldsAndData.paramPlaceholders + ");" : "DEFAULT VALUES;"); + if (isIdentity && data[modelPKID] != null) { + sql += "SET IDENTITY_INSERT " + tblName + " OFF;" + MsSQL.newline; + } + this.query(sql, fieldsAndData.params, function (err, results) { if (err) { return callback(err); } - //msnodesql will execute the callback for each statement that get's executed, we're only interested in the one that returns with the insertId + //msnodesql will execute the callback for each statement that get's + // executed, we're only interested in the one that returns with the insertId if (results.length > 0 && results[0].insertId) { callback(null, results[0].insertId); } @@ -414,20 +430,26 @@ MsSQL.prototype.buildInsert = function (model, data) { var params = []; var props = this._models[model].properties; var modelPKID = this.idName(model); - if (props[modelPKID].generated !== false) { + + var isIdentity = (props[modelPKID].type === Number && + props[modelPKID].generated !== false); + + if (isIdentity && data[modelPKID] == null) { //remove the pkid column if it's in the data, since we're going to insert a // new record, not update an existing one. delete data[modelPKID]; //delete the hardcoded id property that jugglindb automatically creates - delete data.id; + // delete data.id; } - Object.keys(data).forEach(function (key) { + var keys = Object.keys(data); + for (var k = 0, n = keys.length; k < n; k++) { + var key = keys[k]; if (props[key]) { insertIntoFields.push(self.columnEscaped(model, key)); paramPlaceholders.push("(?)"); params.push(self.toDatabase(props[key], data[key], true)); } - }); + } return { fields: insertIntoFields.join(), paramPlaceholders: paramPlaceholders.join(), From e5d9f0c13da797cd215bb992d7cca6b7b4eddf5f Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 7 May 2015 12:01:44 -0700 Subject: [PATCH 100/239] Refactor the mssql connector to use base SqlConnector --- lib/discovery.js | 12 +- lib/migration.js | 558 +++++++++++++++++++ lib/mssql.js | 1366 ++++++---------------------------------------- 3 files changed, 730 insertions(+), 1206 deletions(-) create mode 100644 lib/migration.js diff --git a/lib/discovery.js b/lib/discovery.js index 0a7569d..9f2f229 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -135,12 +135,12 @@ function mixinDiscovery(MsSQL) { var self = this; var calls = [function (callback) { - self.query(queryTables(options), callback); + self.execute(queryTables(options), callback); }]; if (options.views) { calls.push(function (callback) { - self.query(queryViews(options), callback); + self.execute(queryViews(options), callback); }); } async.parallel(calls, function (err, data) { @@ -233,7 +233,7 @@ function mixinDiscovery(MsSQL) { cb(err, results); } }; - this.query(sql, callback); + this.execute(sql, callback); }; /*! @@ -290,7 +290,7 @@ function mixinDiscovery(MsSQL) { cb = args.cb; var sql = queryForPrimaryKeys(owner, table); - this.query(sql, cb); + this.execute(sql, cb); }; /*! @@ -348,7 +348,7 @@ function mixinDiscovery(MsSQL) { cb = args.cb; var sql = queryForeignKeys(owner, table); - this.query(sql, cb); + this.execute(sql, cb); }; /*! @@ -393,7 +393,7 @@ function mixinDiscovery(MsSQL) { cb = args.cb; var sql = queryExportedForeignKeys(owner, table); - this.query(sql, cb); + this.execute(sql, cb); }; function mysqlDataTypeToJSONType(mysqlType, dataLength) { diff --git a/lib/migration.js b/lib/migration.js new file mode 100644 index 0000000..3586753 --- /dev/null +++ b/lib/migration.js @@ -0,0 +1,558 @@ +var async = require('async'); + +module.exports = mixinMigration; + +function mixinMigration(MsSQL) { + MsSQL.prototype.showFields = function(model, cb) { + var sql = 'select [COLUMN_NAME] as [Field], ' + + ' [IS_NULLABLE] as [Null], [DATA_TYPE] as [Type],' + + ' [CHARACTER_MAXIMUM_LENGTH] as [Length],' + + ' [NUMERIC_PRECISION] as [Precision], NUMERIC_SCALE as [Scale]' + + ' from INFORMATION_SCHEMA.COLUMNS' + + ' where [TABLE_SCHEMA] = \'' + this.schema(model) + '\'' + + ' and [TABLE_NAME] = \'' + this.table(model) + '\'' + + ' order by [ORDINAL_POSITION]' + this.execute(sql, function(err, fields) { + if (err) { + return cb && cb(err); + } else { + if (Array.isArray(fields)) { + fields.forEach(function(f) { + if (f.Length) { + f.Type = f.Type + '(' + f.Length + ')'; + } else if (f.Precision) { + f.Type = f.Type + '(' + f.Precision, +',' + f.Scale + ')'; + } + }); + } + cb && cb(err, fields); + } + }); + }; + + MsSQL.prototype.showIndexes = function(model, cb) { + // TODO: [rfeng] Implement SHOW INDEXES + /* + var schema = "'" + this.schemaName(model) +"'"; + var table = "'" + this.escapeName(this.table(model)) +"'"; + var sql = "SELECT OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) AS [table_schema]," + + " T.[name] AS [table_name], I.[name] AS [index_name], AC.[name] AS [column_name]," + + " I.[type_desc], I.[is_unique], I.[data_space_id], I.[ignore_dup_key], I.[is_primary_key]," + + " I.[is_unique_constraint], I.[fill_factor], I.[is_padded], I.[is_disabled], I.[is_hypothetical]," + + " I.[allow_row_locks], I.[allow_page_locks], IC.[is_descending_key], IC.[is_included_column]" + + " FROM sys.[tables] AS T" + + " INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]" + + " INNER JOIN sys.[index_columns] IC ON I.[object_id] = IC.[object_id]" + + " INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] AND IC.[column_id] = AC.[column_id]" + + " WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP'" + + " AND OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) = " + schema + " AND T.[name] = " + table + + " ORDER BY T.[name], I.[index_id], IC.[key_ordinal]"; + + this.execute(sql, function (err, fields) { + cb && cb(err, fields); + }); + */ + + process.nextTick(function() { + cb && cb(null, []); + }); + }; + + MsSQL.prototype.autoupdate = function(models, cb) { + var self = this; + if ((!cb) && ('function' === typeof models)) { + cb = models; + models = undefined; + } + // First argument is a model name + if ('string' === typeof models) { + models = [models]; + } + + models = models || Object.keys(this._models); + async.each(models, function(model, done) { + if (!(model in self._models)) { + return process.nextTick(function() { + done(new Error('Model not found: ' + model)); + }); + } + self.showFields(model, function(err, fields) { + self.showIndexes(model, function(err, indexes) { + if (!err && fields.length) { + self.alterTable(model, fields, indexes, done); + } else { + self.createTable(model, done); + } + }); + }); + }, cb); + }; + + MsSQL.prototype.isActual = function(cb) { + var ok = false; + var self = this; + async.each(Object.keys(this._models), function(model, done) { + self.showFields(model, function(err, fields) { + self.showIndexes(model, function(err, indexes) { + self.alterTable(model, fields, indexes, function(err, needAlter) { + if (err) { + return done(err); + } else { + ok = ok || needAlter; + done(err); + } + }, true); + }); + }); + }, function(err) { + if (err) { + return err; + } + cb(null, !ok); + }); + }; + + MsSQL.prototype.alterTable = function(model, actualFields, actualIndexes, done, checkOnly) { + var self = this; + var m = this._models[model]; + var idName = this.idName(model); + + var propNames = Object.keys(m.properties).filter(function(name) { + return !!m.properties[name]; + }); + var indexNames = m.settings.indexes ? Object.keys(m.settings.indexes).filter(function(name) { + return !!m.settings.indexes[name]; + }) : []; + var sql = []; + var ai = {}; + + if (actualIndexes) { + actualIndexes.forEach(function(i) { + var name = i.Key_name; + if (!ai[name]) { + ai[name] = { + info: i, + columns: [] + }; + } + ai[name].columns[i.Seq_in_index - 1] = i.Column_name; + }); + } + var aiNames = Object.keys(ai); + + var columnsToAdd = []; + var columnsToDrop = []; + var columnsToAlter = []; + + // change/add new fields + propNames.forEach(function(propName) { + if (propName === idName) return; + var found; + actualFields.forEach(function(f) { + if (f.Field === propName) { + found = f; + } + }); + + if (found) { + actualize(propName, found); + } else { + columnsToAdd.push(self.columnEscaped(model, propName) + + ' ' + self.propertySettingsSQL(model, propName)); + } + }); + + // drop columns + actualFields.forEach(function(f) { + var notFound = !~propNames.indexOf(f.Field); + if (f.Field === idName) return; + if (notFound || !m.properties[f.Field]) { + columnsToDrop.push(self.columnEscaped(model, f.Field)); + } + }); + + // remove indexes + aiNames.forEach(function(indexName) { + if (indexName === idName || indexName === 'PRIMARY') { + return; + } + if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] + || m.properties[indexName] && !m.properties[indexName].index) { + sql.push('DROP INDEX ' + self.columnEscaped(model, indexName)); + } else { + // first: check single (only type and kind) + if (m.properties[indexName] && !m.properties[indexName].index) { + // TODO + return; + } + // second: check multiple indexes + var orderMatched = true; + if (indexNames.indexOf(indexName) !== -1) { + m.settings.indexes[indexName].columns.split(/,\s*/).forEach(function(columnName, i) { + if (ai[indexName].columns[i] !== columnName) { + orderMatched = false; + } + }); + } + if (!orderMatched) { + sql.push('DROP INDEX [' + self.columnEscaped(model, indexName) + ']'); + delete ai[indexName]; + } + } + }); + + // add single-column indexes + propNames.forEach(function(propName) { + var i = m.properties[propName].index; + if (!i) { + return; + } + var found = ai[propName] && ai[propName].info; + var columnName = self.columnEscaped(model, propName); + if (!found) { + var type = ''; + var kind = ''; + if (i.type) { + type = 'USING ' + i.type; + } + if (i.kind) { + // kind = i.kind; + } + if (kind && type) { + sql.push('ADD ' + kind + ' INDEX ' + columnName + + ' (' + columnName + ') ' + type); + } else { + sql.push('ADD ' + kind + ' INDEX [' + propName + '] ' + + type + ' ([' + propName + ']) '); + } + } + }); + + // add multi-column indexes + indexNames.forEach(function(indexName) { + var i = m.settings.indexes[indexName]; + var found = ai[indexName] && ai[indexName].info; + if (!found) { + var type = ''; + var kind = ''; + if (i.type) { + type = 'USING ' + i.type; + } + if (i.kind) { + kind = i.kind; + } + if (kind && type) { + sql.push('ADD ' + kind + ' INDEX [' + indexName + '] (' + + i.columns + ') ' + type); + } else { + sql.push('ADD ' + kind + ' INDEX [' + indexName + '] (' + + i.columns + ') '); + } + } + }); + + var statements = []; + if (columnsToAdd.length) { + statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' ADD ' + + columnsToAdd.join(',' + MsSQL.newline)); + } + + if (columnsToAlter.length) { + // SQL Server doesn't allow multiple columns to be altered in one statement + columnsToAlter.forEach(function(c) { + statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' ALTER COLUMN ' + + c); + }); + } + + if (columnsToDrop.length) { + statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' DROP COLUMN' + + columnsToDrop.join(',' + MsSQL.newline)); + } + + async.each(statements, function(query, fn) { + if (checkOnly) { + fn(null, true, {statements: statements, query: query}); + } else { + self.execute(query, fn); + } + }, function(err, results) { + done && done(err, results); + }); + + function actualize(propName, oldSettings) { + var newSettings = m.properties[propName]; + if (newSettings && changed(newSettings, oldSettings)) { + columnsToAlter.push(self.columnEscaped(model, propName) + ' ' + + self.propertySettingsSQL(model, propName)); + } + } + + function changed(newSettings, oldSettings) { + if (oldSettings.Null === 'YES' + && (newSettings.allowNull === false || newSettings.null === false)) { + return true; + } + if (oldSettings.Null === 'NO' && !(newSettings.allowNull === false + || newSettings.null === false)) { + return true; + } + if (oldSettings.Type.toUpperCase() !== datatype(newSettings)) { + return true; + } + return false; + } + }; + + MsSQL.prototype.propertiesSQL = function(model) { + // debugger; + var self = this; + var objModel = this._models[model]; + var modelPKID = this.idName(model); + + var sql = []; + var props = Object.keys(objModel.properties); + for (var i = 0, n = props.length; i < n; i++) { + var prop = props[i]; + if (prop === modelPKID) { + var idProp = objModel.properties[modelPKID]; + if (idProp.type === Number) { + if (idProp.generated !== false) { + sql.push(self.columnEscaped(model, modelPKID) + + " " + self.columnDataType(model, modelPKID) + " IDENTITY(1,1) NOT NULL"); + } + else { + sql.push(self.columnEscaped(model, modelPKID) + + " " + self.columnDataType(model, modelPKID) + " NOT NULL"); + } + continue; + } else if (idProp.type === String) { + if (idProp.generated !== false) { + sql.push(self.columnEscaped(model, modelPKID) + + " [uniqueidentifier] DEFAULT newid() NOT NULL"); + } else { + sql.push(self.columnEscaped(model, modelPKID) + " " + + self.propertySettingsSQL(model, prop) + " DEFAULT newid()"); + } + continue; + } + } + sql.push(self.columnEscaped(model, prop) + " " + self.propertySettingsSQL(model, prop)); + } + var joinedSql = sql.join("," + MsSQL.newline + " "); + var cmd = "PRIMARY KEY CLUSTERED" + MsSQL.newline + "(" + MsSQL.newline; + cmd += " " + self.columnEscaped(model, modelPKID) + " ASC" + MsSQL.newline; + cmd += ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, " + + "IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)" + + joinedSql += "," + MsSQL.newline + cmd; + + return joinedSql; + }; + + MsSQL.prototype.singleIndexSettingsSQL = function(model, prop, add) { + // Recycled from alterTable single indexes above, more or less. + var tblName = this.tableEscaped(model); + var i = this._models[model].properties[prop].index; + var type = 'ASC'; + var kind = 'NONCLUSTERED'; + var unique = false; + if (i.type) { + type = i.type; + } + if (i.kind) { + kind = i.kind; + } + if (i.unique) { + unique = true; + } + var name = prop + "_" + kind + "_" + type + "_idx" + if (i.name) { + name = i.name; + } + this._idxNames[model].push(name); + var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON " + + tblName + MsSQL.newline; + cmd += "(" + MsSQL.newline; + cmd += " [" + prop + "] " + type; + cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF," + + " SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, " + + "ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);" + + MsSQL.newline; + return cmd; + }; + + MsSQL.prototype.indexSettingsSQL = function(model, prop) { + // Recycled from alterTable multi-column indexes above, more or less. + var tblName = this.tableEscaped(model); + var i = this._models[model].settings.indexes[prop]; + var type = 'ASC'; + var kind = 'NONCLUSTERED'; + var unique = false; + if (i.type) { + type = i.type; + } + if (i.kind) { + kind = i.kind; + } + if (i.unique) { + unique = true; + } + var splitcolumns = i.columns.split(","); + var columns = []; + var name = ""; + splitcolumns.forEach(function(elem, ind) { + var trimmed = elem.trim(); + name += trimmed + "_"; + trimmed = "[" + trimmed + "] " + type; + columns.push(trimmed); + }); + + name += kind + "_" + type + "_idx" + this._idxNames[model].push(name); + + var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON " + + tblName + MsSQL.newline; + cmd += "(" + MsSQL.newline; + cmd += columns.join("," + MsSQL.newline); + cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, " + + "SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, " + + "ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);" + + MsSQL.newline; + return cmd; + }; + + function isNullable(p) { + return !(p.required || p.id || p.nullable === false || + p.allowNull === false || p['null'] === false); + } + + MsSQL.prototype.propertySettingsSQL = function(model, prop) { + var p = this._models[model].properties[prop]; + return this.columnDataType(model, prop) + ' ' + + (isNullable(p) ? 'NULL' : 'NOT NULL'); + }; + + MsSQL.prototype.automigrate = function(models, cb) { + var self = this; + if ((!cb) && ('function' === typeof models)) { + cb = models; + models = undefined; + } + // First argument is a model name + if ('string' === typeof models) { + models = [models]; + } + + models = models || Object.keys(this._models); + async.each(models, function(model, done) { + if (!(model in self._models)) { + return process.nextTick(function() { + done(new Error('Model not found: ' + model)); + }); + } + self.dropTable(model, function(err) { + if (err) { + return done(err); + } + self.createTable(model, done); + }); + }, function(err) { + cb && cb(err); + }); + }; + + MsSQL.prototype.dropTable = function(model, cb) { + var tblName = this.tableEscaped(model); + var cmd = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" + + tblName + "') AND type in (N'U'))"; + cmd += MsSQL.newline + "BEGIN" + MsSQL.newline; + cmd += " DROP TABLE " + tblName; + cmd += MsSQL.newline + "END"; + this.execute(cmd, cb); + }; + + MsSQL.prototype.createTable = function(model, cb) { + var tblName = this.tableEscaped(model); + var cmd = "SET ANSI_NULLS ON;" + MsSQL.newline + "SET QUOTED_IDENTIFIER ON;" + + MsSQL.newline + "SET ANSI_PADDING ON;" + MsSQL.newline; + cmd += "IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" + + tblName + "') AND type in (N'U'))" + MsSQL.newline + "BEGIN" + MsSQL.newline; + cmd += "CREATE TABLE " + this.tableEscaped(model) + " ("; + cmd += MsSQL.newline + " " + this.propertiesSQL(model) + MsSQL.newline; + cmd += ")" + MsSQL.newline + "END;" + MsSQL.newline; + cmd += this.createIndexes(model); + this.execute(cmd, cb); + }; + + MsSQL.prototype.createIndexes = function(model) { + var self = this; + var sql = []; + // Declared in model index property indexes. + Object.keys(this._models[model].properties).forEach(function(prop) { + var i = self._models[model].properties[prop].index; + if (i) { + sql.push(self.singleIndexSettingsSQL(model, prop)); + } + }); + + // Settings might not have an indexes property. + var dxs = this._models[model].settings.indexes; + if (dxs) { + Object.keys(this._models[model].settings.indexes).forEach(function(prop) { + sql.push(self.indexSettingsSQL(model, prop)); + }); + } + + return sql.join(MsSQL.newline); + } + + MsSQL.prototype.columnDataType = function(model, property) { + var columnMetadata = this.columnMetadata(model, property); + var colType = columnMetadata && columnMetadata.dataType; + if (colType) { + colType = colType.toUpperCase(); + } + var prop = this._models[model].properties[property]; + if (!prop) { + return null; + } + var colLength = columnMetadata && columnMetadata.dataLength || prop.length; + if (colType) { + var dataPrecision = columnMetadata.dataPrecision; + var dataScale = columnMetadata.dataScale; + if (dataPrecision && dataScale) { + return colType + '(' + dataPrecision + ', ' + dataScale + ')'; + } + return colType + (colLength ? '(' + colLength + ')' : ''); + } + return datatype(prop); + }; + + function datatype(p) { + var dt = ''; + switch (p.type.name) { + default: + case 'String': + case 'JSON': + dt = '[nvarchar](' + (p.length || p.limit || 255) + ')'; + break; + case 'Text': + dt = '[text]'; + break; + case 'Number': + dt = '[int]'; + break; + case 'Date': + dt = '[datetime]'; + break; + case 'Boolean': + dt = '[bit]'; + break; + case 'Point': + dt = '[float]'; + break; + } + return dt; + } +} \ No newline at end of file diff --git a/lib/mssql.js b/lib/mssql.js index f321608..d5d1730 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -1,8 +1,8 @@ /*! Module dependencies */ var mssql = require("mssql"); var SqlConnector = require('loopback-connector').SqlConnector; +var ParameterizedSQL = SqlConnector.ParameterizedSQL; var util = require("util"); -var async = require('async'); var debug = require('debug')('loopback:connector:mssql'); var name = "mssql"; @@ -52,7 +52,8 @@ function format(sql, params) { if (index === -1) { return sql; } - sql = sql.substring(0, index) + params[count] + sql.substring(index + 3); + sql = sql.substring(0, index) + escape(params[count]) + + sql.substring(index + 3); count++; } } @@ -61,10 +62,10 @@ function format(sql, params) { MsSQL.prototype.connect = function(callback) { var self = this; - if(self.client) { + if (self.client) { return process.nextTick(callback); } - var connection = new mssql.Connection(this.settings, function (err) { + var connection = new mssql.Connection(this.settings, function(err) { if (err) { debug('Connection error: ', err); return callback(err); @@ -75,39 +76,16 @@ MsSQL.prototype.connect = function(callback) { }); }; -MsSQL.prototype.query = function (sql, optionsOrCallback, callback) { - //debugger; - var hasOptions = true; - var options = null; - var cb = null; - if (typeof optionsOrCallback === "function") { - hasOptions = false; - cb = optionsOrCallback; - } else { - options = optionsOrCallback; - cb = callback; - } - if(hasOptions) { - sql = format(sql, options); - } - debug('SQL: %s %j', sql, options); - if (!this.dataSource.connected) { - return this.dataSource.once('connected', function () { - this.query(sql, cb); - }.bind(this)); - } - var time = Date.now(); - var log = this.log; - if (typeof cb !== 'function') { - throw new Error('callback should be a function'); +MsSQL.prototype.executeSQL = function(sql, params, options, callback) { + debug('SQL: %s Parameters: %j', sql, params); + if (Array.isArray(params) && params.length > 0) { + sql = format(sql, params); + debug('Formatted SQL: %s', sql); } - var innerCB = function (err, data) { + var innerCB = function(err, data) { debug('Result: %j %j', err, data); - if (log) { - log(sql, time); - } - cb && cb(err, data); + callback && callback(err, data); }; var request = new mssql.Request(this.client); @@ -115,12 +93,12 @@ MsSQL.prototype.query = function (sql, optionsOrCallback, callback) { request.query(sql, innerCB); }; -MsSQL.prototype.disconnect = function disconnect() { - this.client.close(); +MsSQL.prototype.disconnect = function disconnect(cb) { + this.client.close(cb); }; // MsSQL.prototype.command = function (sql, callback) { -// return this.query(sql, callback); +// return this.execute(sql, callback); // }; //params @@ -129,332 +107,84 @@ MsSQL.prototype.disconnect = function disconnect() { // properties: ... // settings: ... // } -MsSQL.prototype.define = function (modelDefinition) { +MsSQL.prototype.define = function(modelDefinition) { if (!modelDefinition.settings) { modelDefinition.settings = {}; } this._models[modelDefinition.model.modelName] = modelDefinition; - //track database index names for this model this._idxNames[modelDefinition.model.modelName] = []; }; -// MsSQL.prototype.defineProperty = function (model, prop, params) { -// this._models[model].properties[prop] = params; -// }; - -/** - * Must invoke callback(err, id) - */ -MsSQL.prototype.create = function (model, data, callback) { - //debugger; - var fieldsAndData = this.buildInsert(model, data); - var tblName = this.tableEscaped(model); - var modelPKID = this.idName(model); - - var columns = fieldsAndData.fields ? " (" + fieldsAndData.fields + ")" : ""; - var sql = ''; - - var props = this._models[model].properties; - var isIdentity = (props[modelPKID].type === Number && - props[modelPKID].generated !== false); - - if (isIdentity && data[modelPKID] != null) { - // insert with identity_insert - sql += "SET IDENTITY_INSERT " + tblName + " ON;" + MsSQL.newline; - } - - sql += "INSERT INTO " + tblName + columns + MsSQL.newline; - sql += "OUTPUT INSERTED." + this.columnEscaped(model, modelPKID) - + " AS insertId" + MsSQL.newline; - sql += (fieldsAndData.paramPlaceholders ? - "VALUES (" + fieldsAndData.paramPlaceholders + ");" : - "DEFAULT VALUES;"); - - if (isIdentity && data[modelPKID] != null) { - sql += "SET IDENTITY_INSERT " + tblName + " OFF;" + MsSQL.newline; - } - - this.query(sql, fieldsAndData.params, function (err, results) { - if (err) { - return callback(err); - } - //msnodesql will execute the callback for each statement that get's - // executed, we're only interested in the one that returns with the insertId - if (results.length > 0 && results[0].insertId) { - callback(null, results[0].insertId); - } - }); +MsSQL.prototype.getPlaceholderForValue = function(key) { + return '(?)'; }; -MsSQL.prototype.updateOrCreate = function (model, data, callback) { - var self = this; - var props = this._models[model].properties; - var tblName = this.tableEscaped(model); - var modelPKID = this.idName(model); - //get the correct id of the item using the pkid that they specified - var id = data[modelPKID]; - var fieldNames = []; - var fieldValues = []; - var fieldValuesPlaceholders = []; - var combined = []; - - var doQuery = function (sql, fieldValues) { - self.query(sql, fieldValues, function (err, results) { - if (err) { - return callback(err); - } - //msnodesql will execute the callback for each statement that get's - // executed, we're only interested in the one that returns with the pkid - if (results.length > 0 && results[0].pkid) { - data[modelPKID] = results[0].pkid; - //#jdb id compatibility# - data.id = results[0].pkid; //set the id property also, to play nice - // with the jugglingdb abstract class implementation. - callback(err, data); - } - }); - }; - - Object.keys(data).forEach(function (key) { - if (props[key]) { - //check for the "id" key also, for backwards compatibility with the jugglingdb hardcoded id system - if (key !== "id" && key !== modelPKID) { - var columnName = self.columnEscaped(model, key); - fieldNames.push(columnName); - fieldValues.push(self.toDatabase(props[key], data[key], true)); - fieldValuesPlaceholders.push("(?)"); - combined.push(columnName + "=(?)"); - } - } - }); - var sql = ""; - if (id > 0) { - self.exists(model, id, function (err, yn) { - if (err) { - return callback(err); - } - if (yn) { - //update - sql = "UPDATE " + tblName + MsSQL.newline; - sql += "SET " + combined.join() + MsSQL.newline; - sql += "WHERE " + self.columnEscaped(model, modelPKID) + " = (?);" + MsSQL.newline; - sql += "SELECT " + escape(id) + " AS pkid;"; - fieldValues.push(id); - } else { - //insert with identity_insert - sql = "SET IDENTITY_INSERT " + tblName + " ON;" + MsSQL.newline; - var columns = fieldNames.length ? "," + fieldNames.join() + ")" : ")"; - sql += "INSERT INTO " + tblName + " (" + self.columnEscaped(model, modelPKID) - + columns + MsSQL.newline; - sql += "VALUES (" + escape(id) + "," + fieldValuesPlaceholders.join() + ");" + MsSQL.newline; - sql += "SET IDENTITY_INSERT " + tblName + " OFF;" + MsSQL.newline; - sql += "SELECT " + escape(id) + " AS pkid;"; - } - doQuery(sql, fieldValues); - }); - } else { - var columns = fieldNames.length ? " (" + fieldNames.join() + ")" : ""; - //insert - sql = "INSERT INTO " + tblName + columns + MsSQL.newline; - var values = fieldValuesPlaceholders.length ? - "VALUES (" + fieldValuesPlaceholders.join() + ");" : - "DEFAULT VALUES;"; - sql += values + MsSQL.newline; - sql += "SELECT IDENT_CURRENT('" + tblName + "') AS pkid;"; - doQuery(sql, fieldValues); - } +MsSQL.prototype.buildInsertDefaultValues = function(model, data, options) { + return 'DEFAULT VALUES'; }; -//redundant, same functionality as "updateOrCreate" right now. Maybe in the -// future some validation will happen here. -MsSQL.prototype.save = function (model, data, callback) { - this.updateOrCreate(model, data, callback); +MsSQL.prototype.buildInsertInto = function(model, fields, options) { + var stmt = this.invokeSuper('buildInsertInto', model, fields, options); + var idName = this.idName(model); + stmt.merge(MsSQL.newline + 'OUTPUT INSERTED.' + + this.columnEscaped(model, idName) + ' AS insertId'); + return stmt; }; -MsSQL.prototype.updateAttributes = function (model, id, data, cb) { - var self = this; - var tblName = this.tableEscaped(model); - var modelPKID = this.idName(model); - //jugglingdb abstract class may have sent up a null value for this id if we - // aren't using the standard "id" name for the pkid. - // if that is the case then set the id to the correct value from the data - // using the actual pkid name. - if (id === null) { - id = data[modelPKID]; - } else { - data[modelPKID] = id; +MsSQL.prototype.buildInsert = function(model, data, options) { + var idName = this.idName(model); + var prop = this.getPropertyDefinition(model, idName); + var isIdentity = (prop.type === Number && prop.generated !== false); + if (isIdentity && data[idName] == null) { + //remove the pkid column if it's in the data, since we're going to insert a + // new record, not update an existing one. + delete data[idName]; + //delete the hardcoded id property that jugglindb automatically creates + // delete data.id; } - this.exists(model, id, function (err, yn) { - if (err) { - debug(err); - return cb && cb("An error occurred when checking for the existance of this record"); - } - if (yn) { - //only call this after verifying that the record exists, we don't want to create it if it doesn't. - return self.updateOrCreate(model, data, cb); - } - return cb && cb("A " + tblName + " doesn't exist with a " + modelPKID - + " of " + id, id); - }); -}; -MsSQL.prototype.exists = function (model, id, callback) { + var stmt = this.invokeSuper('buildInsert', model, data, options); var tblName = this.tableEscaped(model); - var modelPKID = this.idName(model); - var sql = "SELECT COUNT(*) cnt FROM " + tblName + " WHERE [" + modelPKID + "] = (?)" - this.query(sql, [escape(id)], function (err, results) { - if (err) return callback(err); - callback(null, results[0].cnt >= 1); - }); -}; -MsSQL.prototype.count = function (model, cb, where) { - var sql = "SELECT COUNT(*) cnt FROM " + this.tableEscaped(model) + MsSQL.newline; - - if (where !== null) { - sql += this.buildWhere(model, where) + MsSQL.newline; + if (isIdentity && data[idName] != null) { + stmt.sql = 'SET IDENTITY_INSERT ' + tblName + ' ON;' + MsSQL.newline + + stmt.sql; } - - this.query(sql, function (err, data) { - if (err) { - return cb && cb(err); - } - cb && cb(null, data[0].cnt); - }); - - return sql; -}; - - -MsSQL.prototype.destroyAll = function (model, where, cb) { - if (!cb && 'function' === typeof where) { - cb = where; - where = undefined; + if (isIdentity && data[idName] != null) { + stmt.sql += MsSQL.newline + 'SET IDENTITY_INSERT ' + tblName + ' OFF;' + + MsSQL.newline; } - - var sql = 'DELETE FROM ' + this.tableEscaped(model) + ' ' + - this.buildWhere(model, where || {}); - - // attach a secondary query to get the number of affected items - var affectedCountQuery = ';SELECT @@ROWCOUNT as count'; - sql += affectedCountQuery; - - this.query(sql, function(err, info) { - if (err) return cb && cb(err); - - var affectedCountQueryResult = info[0]; - var affectedCount = typeof affectedCountQueryResult.count === 'number' ? - affectedCountQueryResult.count : undefined; - - cb && cb(null, {count: affectedCount}); - }); + return stmt; }; -MsSQL.prototype.destroy = function (model, id, cb) { - var sql = "DELETE FROM " + this.tableEscaped(model) + MsSQL.newline; - sql += "WHERE [" + this.idName(model) + "] = (?)"; - this.query(sql, [escape(id)], function (err, data) { - if (err) { - return cb && cb(err); - } - cb && cb(null); - }); +MsSQL.prototype.getInsertedId = function(model, info) { + return info && info.length > 0 && info[0].insertId; }; -MsSQL.prototype.update = MsSQL.prototype.updateAll = function (model, where, data, cb) { - var self = this; - var props = this._models[model].properties; - var tblName = this.tableEscaped(model); - var modelPKID = this.idName(model); - //get the correct id of the item using the pkid that they specified - var id = data[modelPKID]; - var fieldNames = []; - var fieldValues = []; - var fieldValuesPlaceholders = []; - var combined = []; - Object.keys(data).forEach(function (key) { - if (props[key]) { - //check for the "id" key also, for backwards compatibility with the jugglingdb hardcoded id system - if (key !== "id" && key !== modelPKID) { - fieldNames.push(self.columnEscaped(model, key)); - fieldValues.push(self.toDatabase(props[key], data[key], true)); - fieldValuesPlaceholders.push("(?)"); - combined.push(key + "=(?)"); - } - } - }); - var sql = ""; - - //update - sql = "UPDATE " + tblName + MsSQL.newline; - sql += "SET " + combined.join() + MsSQL.newline; - sql += self.buildWhere(model, where) + MsSQL.newline; - fieldValues.push(id); - - // attach a secondary query to get the number of affected items - var affectedCountQuery = ';SELECT @@ROWCOUNT as count'; - sql += affectedCountQuery; - - self.query(sql, fieldValues, function(err, info) { - if (err) return cb && cb(err); - - var affectedCountQueryResult = info[0]; - var affectedCount = typeof affectedCountQueryResult.count === 'number' ? - affectedCountQueryResult.count : undefined; - - cb && cb(null, {count: affectedCount}); - }); +MsSQL.prototype.buildDelete = function(model, where, options) { + var stmt = this.invokeSuper('buildDelete', model, where, options); + stmt.merge(';SELECT @@ROWCOUNT as count', ''); + return stmt; }; - -MsSQL.prototype.find = function (model, id, callback) { - var self = this; - var tblName = this.tableEscaped(model); - var modelPKID = this.idName(model); - var sql = "SELECT * FROM " + tblName + " WHERE [" + modelPKID + "] = (?)"; - this.query(sql, [escape(id)], function (err, results) { - if (err) { - return callback(err); - } - callback(null, self.fromDatabase(model, results[0])); - }); -}; - -MsSQL.prototype.buildInsert = function (model, data) { - var self = this; - var insertIntoFields = []; - var paramPlaceholders = []; - var params = []; - var props = this._models[model].properties; - var modelPKID = this.idName(model); - - var isIdentity = (props[modelPKID].type === Number && - props[modelPKID].generated !== false); - - if (isIdentity && data[modelPKID] == null) { - //remove the pkid column if it's in the data, since we're going to insert a - // new record, not update an existing one. - delete data[modelPKID]; - //delete the hardcoded id property that jugglindb automatically creates - // delete data.id; - } - var keys = Object.keys(data); - for (var k = 0, n = keys.length; k < n; k++) { - var key = keys[k]; - if (props[key]) { - insertIntoFields.push(self.columnEscaped(model, key)); - paramPlaceholders.push("(?)"); - params.push(self.toDatabase(props[key], data[key], true)); - } +MsSQL.prototype.getCountForAffectedRows = function(model, info) { + var affectedCountQueryResult = info && info[0]; + if (!affectedCountQueryResult) { + return undefined; } + var affectedCount = typeof affectedCountQueryResult.count === 'number' ? + affectedCountQueryResult.count : undefined; + return affectedCount; +}; - return { fields: insertIntoFields.join(), - paramPlaceholders: paramPlaceholders.join(), - params: params }; -} +MsSQL.prototype.buildUpdate = function(model, where, data, options) { + var stmt = this.invokeSuper('buildUpdate', model, where, data, options); + stmt.merge(';SELECT @@ROWCOUNT as count', ''); + return stmt; +}; // Convert to ISO8601 format YYYY-MM-DDThh:mm:ss[.mmm] function dateToMsSql(val) { @@ -499,7 +229,7 @@ function escape(val) { : val.toString(); } - val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function (s) { + val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) { switch (s) { case "\0": return "\\0"; @@ -525,272 +255,67 @@ function escape(val) { return "'" + val + "'"; } -//toDatabase is used for formatting data when inserting/updating records -// it is also used when building a where clause for filtering selects -// in the case of update/insert we want the data to be returned raw -// because we are using the msnodesql driver's parameterization feature -// in the case of building a where clause we want to wrap strings in single quotes -// so they can be concatenated into the sql statement -// use the 'wrap' parameter to tell the function which case it's handling -// (false=raw, true=single quotes) -MsSQL.prototype.toDatabase = function (prop, val, wrap) { - if (val === null || val === undefined) { - // return 'NULL'; +MsSQL.prototype.toColumnValue = function(prop, val) { + if (val == null) { return null; } - if (prop.type && prop.type.modelName) { - return escape(JSON.stringify(val)); + if (prop.type === String) { + return String(val); } - if (val.constructor && val.constructor.name === 'Object') { - var operator = Object.keys(val)[0] - val = val[operator]; - if (operator === 'between') { - //the between operator is never used for insert/updates - // therefore always pass the wrap=true parameter when formatting the values - return this.toDatabase(prop, val[0], true) + - ' AND ' + - this.toDatabase(prop, val[1], true); - } else if (operator === 'inq' || operator === 'nin') { - //always wrap inq/nin values in single quotes when they are string types, - // it's never used for insert/updates - if (!(val.propertyIsEnumerable('length')) && typeof val === 'object' - && typeof val.length === 'number') { //if value is array - //check if it is an array of string, because in that cause we need to - // wrap them in single quotes - for (var i = 0, n = val.length; i < n; i++) { - val[i] = escape(val[i]); - } - return val.join(','); - } else { - if (typeof val === 'string') { - val = escape(val); - } - return val; - } - } else if (operator === "max") { - return val.field; - } - } - if (!prop) { - if (typeof val === 'string' && wrap) { - val = escape(val); + if (prop.type === Number) { + if (isNaN(val)) { + // Map NaN to NULL + return val; } return val; } - if (prop.type.name === 'Number') { - return escape(val); - } - if (prop.type.name === 'Date') { - if (!val) { - return null; - // return 'NULL'; - } + + if (prop.type === Date || prop.type.name === 'Timestamp') { if (!val.toUTCString) { val = new Date(val); } val = dateToMsSql(val); - if (wrap) { - val = escape(val); - } - return val; - } - if (prop.type.name === "Boolean") { - return val ? 1 : 0; - } - - if (val === null || val === undefined) { return val; } - if (wrap) { - return escape(val.toString()); - } - return val.toString(); -}; - -MsSQL.prototype.fromDatabase = function (model, data) { - var self = this; - if (!data) { - return null; - } - // create an "id" property in the data for backwards compatibility with juggling-db - // data.id = data[this.idName(model)]; - var props = this._models[model].properties; - var result = {}; - //look for date values in the data, convert them from the database to a javascript date object - Object.keys(props).forEach(function (p) { - var key = self.column(model, p); - var val = data[key]; - if (val !== undefined) { - if (props[p].type.name === 'Boolean' && val !== null) { - val = (true && val); //convert to a boolean type from number - } - if (props[p].type.name === 'Date' && val !== null) { - if(!(val instanceof Date)) { - val = new Date(val.toString()); - } - } - } - if (val !== undefined) { - result[p] = val; - } - }); - return result; -}; - -MsSQL.prototype.escapeName = function (name) { - return name.replace(/\./g, '_'); -}; - -MsSQL.prototype.columnEscaped = function (model, property) { - return '[' + this.escapeName(this.column(model, property)) +']'; -}; - -MsSQL.prototype.schemaName = function (model) { - // Check if there is a 'schema' property for mssql - var mssqlMeta = this._models[model].settings && this._models[model].settings.mssql; - var schemaName = (mssqlMeta && (mssqlMeta.schema || mssqlMeta.schemaName)) - || this.settings.schema || 'dbo'; - return schemaName; -}; - -MsSQL.prototype.tableEscaped = function (model) { - return '[' + this.schemaName(model) + '].[' + this.escapeName(this.table(model)) + ']'; -}; - - -MsSQL.prototype.escapeKey = function (key) { - return key; -}; - -MsSQL.prototype.getColumns = function (model, props) { - var cols = this._models[model].properties; - if (!cols) { - return '*'; - } - var self = this; - var keys = Object.keys(cols); - if (Array.isArray(props) && props.length > 0) { - // No empty array, including all the fields - keys = props; - } else if ('object' === typeof props && Object.keys(props).length > 0) { - // { field1: boolean, field2: boolean ... } - var included = []; - var excluded = []; - keys.forEach(function (k) { - if (props[k]) { - included.push(k); - } else if ((k in props) && !props[k]) { - excluded.push(k); - } - }); - if (included.length > 0) { - keys = included; - } else if (excluded.length > 0) { - excluded.forEach(function (e) { - var index = keys.indexOf(e); - keys.splice(index, 1); - }); + if (prop.type === Boolean) { + if (val) { + return true; + } else { + return false; } } - var names = keys.map(function (c) { - return self.columnEscaped(model, c); - }); - return names.join(', '); -}; -MsSQL.prototype.all = function (model, filter, callback) { - var self = this; - - var sql = "SELECT " + this.getColumns(model, filter.fields) + - " FROM " + this.tableEscaped(model) + MsSQL.newline; - - filter = filter || {}; - if (filter.where) { - sql += this.buildWhere(model, filter.where) + MsSQL.newline; - } + return this.serializeObject(val); +} - var orderBy = null; - if (!filter.order) { - var idNames = this.idNames(model); - if (idNames && idNames.length) { - filter.order = idNames; - } +MsSQL.prototype.fromColumnValue = function(prop, val) { + if (val == null) { + return val; } - if (filter.order) { - orderBy = this.buildOrderBy(model, filter.order); - sql += orderBy + MsSQL.newline; + var type = prop && prop.type; + if (type === Boolean) { + val = !!val; //convert to a boolean type from number } - if (filter.limit || filter.offset || filter.skip) { - - var offset = filter.offset || filter.skip; - if (isNaN(offset)) { - offset = 0; - } - var limit = filter.limit; - if (isNaN(limit)) { - limit = -1; - } - - if (this.settings.supportsOffsetFetch) { - // SQL 2012 or later - // http://technet.microsoft.com/en-us/library/gg699618.aspx - sql += buildLimit(limit, offset); - } else { - - // SQL 2005/2008 - // http://blog.sqlauthority.com/2013/04/14/sql-server-tricks-for-row-offset-and-paging-in-various-versions-of-sql-server/ - - var paginatedSQL = 'SELECT ' + this.getColumns(model, filter.fields) + MsSQL.newline - + ' FROM (' + MsSQL.newline - + ' SELECT ' + this.getColumns(model, filter.fields) + ', ROW_NUMBER() OVER' - + ' (' + orderBy + ') AS RowNum' + MsSQL.newline - + ' FROM ' + this.tableEscaped(model) + MsSQL.newline; - if (filter.where) { - paginatedSQL += this.buildWhere(model, filter.where) + MsSQL.newline; - } - paginatedSQL += ') AS S' + MsSQL.newline - + ' WHERE S.RowNum > ' + offset; - - if (limit !== -1) { - paginatedSQL += ' AND S.RowNum <= ' + (offset + limit); - } - - sql = paginatedSQL + MsSQL.newline; + if (type === Date) { + if (!(val instanceof Date)) { + val = new Date(val.toString()); } } + return val; +}; - this.query(sql, function (err, data) { - if (err) return callback(err); - - //convert database types to js types - data = data.map(function (obj) { - return self.fromDatabase(model, obj); - }); - - //check for eager loading relationships - if (filter && filter.include) { - self._models[model].model.include(data, filter.include, callback); - } else { - callback(null, data); - } - }); +MsSQL.prototype.escapeName = function(name) { + return '[' + name.replace(/\./g, '_') + ']'; +}; - return sql; +MsSQL.prototype.getDefaultSchemaName = function() { + return 'dbo'; }; -MsSQL.prototype.buildOrderBy = function(model, order) { - var self = this; - if (typeof order === 'string') { - order = [order]; - } - return 'ORDER BY ' + order.map(function (o) { - var t = o.split(/[\s,]+/); - if (t.length === 1) { - return self.columnEscaped(model, o); - } - return self.columnEscaped(model, t[0]) + ' ' + t[1]; - }).join(', '); +MsSQL.prototype.tableEscaped = function(model) { + return this.escapeName(this.schema(model)) + '.' + + this.escapeName(this.table(model)); }; function buildLimit(limit, offset) { @@ -804,656 +329,97 @@ function buildLimit(limit, offset) { return sql; } -MsSQL.prototype._buildWhere = function (model, conds) { - if (!conds) { - return null; - } - var self = this; - var props = this._models[model].properties; - var cs = []; - Object.keys(conds).forEach(function (key) { - if (key === 'and' || key === 'or') { - var clauses = conds[key]; - if (Array.isArray(clauses)) { - clauses = clauses.map(function (c) { - return '(' + self._buildWhere(model, c) + ')'; - }); - cs.push(clauses.join(' ' + key.toUpperCase() + ' ')); - return; - } - // The value is not an array, fall back to regular fields - } - var keyEscaped = self.escapeKey(key); - if (props[key]) { - keyEscaped = self.columnEscaped(model, key); - } - var val = self.toDatabase(props[key], conds[key], true); - if (conds[key] === null) { - cs.push(keyEscaped + ' IS NULL'); - } else if (conds[key].constructor.name === 'Object') { - var condType = Object.keys(conds[key])[0]; - var sqlCond = keyEscaped; - if ((condType === 'inq' || condType === 'nin') && val.length === 0) { - cs.push(condType === 'inq' ? '0 = 1' : '1 = 1'); - return true; - } - if (condType === "max") { - var tbl = conds[key].max.from; - var subClause = conds[key].max.where; - sqlCond += " = (SELECT MAX(" + val + ") FROM " + tbl; - if (subClause) { - sqlCond += " " + self._buildWhere(model, subClause); - } - sqlCond += ")"; - cs.push(sqlCond); - return true; - } - switch (condType) { - case 'gt': - sqlCond += ' > '; - break; - case 'gte': - sqlCond += ' >= '; - break; - case 'lt': - sqlCond += ' < '; - break; - case 'lte': - sqlCond += ' <= '; - break; - case 'between': - sqlCond += ' BETWEEN '; - break; - case 'inq': - sqlCond += ' IN '; - break; - case 'nin': - sqlCond += ' NOT IN '; - break; - case 'neq': - sqlCond += ' != '; - break; - case 'like': - sqlCond += ' LIKE '; - val += " ESCAPE '\\'"; - break; - case 'nlike': - sqlCond += ' NOT LIKE '; - val += " ESCAPE '\\'"; - break; - } - sqlCond += (condType === 'inq' || condType === 'nin') ? '(' + val + ')' : val; - cs.push(sqlCond); - } else { - cs.push(keyEscaped + ' = ' + val); - } - }); - if (cs.length === 0) { - return ''; - } - return cs.join(' AND '); -}; - -MsSQL.prototype.buildWhere = function (model, conds) { - var where = this._buildWhere(model, conds); - return where? 'WHERE ' + where : ''; -}; - -MsSQL.prototype.showFields = function (model, cb) { - var sql = 'select [COLUMN_NAME] as [Field], ' - + ' [IS_NULLABLE] as [Null], [DATA_TYPE] as [Type],' - + ' [CHARACTER_MAXIMUM_LENGTH] as [Length],' - + ' [NUMERIC_PRECISION] as [Precision], NUMERIC_SCALE as [Scale]' - + ' from INFORMATION_SCHEMA.COLUMNS' - + ' where [TABLE_SCHEMA] = \'' + this.schemaName(model) + '\'' - + ' and [TABLE_NAME] = \'' + this.escapeName(this.table(model)) + '\'' - + ' order by [ORDINAL_POSITION]' - this.query(sql, function (err, fields) { - if (err) { - return cb && cb(err); - } else { - if (Array.isArray(fields)) { - fields.forEach(function (f) { - if (f.Length) { - f.Type = f.Type + '(' + f.Length + ')'; - } else if (f.Precision) { - f.Type = f.Type + '(' + f.Precision, +',' + f.Scale + ')'; - } - }); - } - cb && cb(err, fields); - } - }); -}; - -MsSQL.prototype.showIndexes = function (model, cb) { - // TODO: [rfeng] Implement SHOW INDEXES - /* - var schema = "'" + this.schemaName(model) +"'"; - var table = "'" + this.escapeName(this.table(model)) +"'"; - var sql = "SELECT OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) AS [table_schema]," - + " T.[name] AS [table_name], I.[name] AS [index_name], AC.[name] AS [column_name]," - + " I.[type_desc], I.[is_unique], I.[data_space_id], I.[ignore_dup_key], I.[is_primary_key]," - + " I.[is_unique_constraint], I.[fill_factor], I.[is_padded], I.[is_disabled], I.[is_hypothetical]," - + " I.[allow_row_locks], I.[allow_page_locks], IC.[is_descending_key], IC.[is_included_column]" - + " FROM sys.[tables] AS T" - + " INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]" - + " INNER JOIN sys.[index_columns] IC ON I.[object_id] = IC.[object_id]" - + " INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] AND IC.[column_id] = AC.[column_id]" - + " WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP'" - + " AND OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) = " + schema + " AND T.[name] = " + table - + " ORDER BY T.[name], I.[index_id], IC.[key_ordinal]"; - - this.query(sql, function (err, fields) { - cb && cb(err, fields); - }); - */ - - process.nextTick(function () { - cb && cb(null, []); - }); -}; - -MsSQL.prototype.autoupdate = function(models, cb) { - var self = this; - if ((!cb) && ('function' === typeof models)) { - cb = models; - models = undefined; - } - // First argument is a model name - if ('string' === typeof models) { - models = [models]; +MsSQL.prototype.buildColumnNames = function(model, filter) { + var columnNames = this.invokeSuper('buildColumnNames', model, filter); + if (filter.limit || filter.offset || filter.skip) { + var orderBy = this.buildOrderBy(model, filter.order); + columnNames += ',ROW_NUMBER() OVER' + ' (' + orderBy + ') AS RowNum'; } - - models = models || Object.keys(this._models); - async.each(models, function(model, done) { - if (!(model in self._models)) { - return process.nextTick(function() { - done(new Error('Model not found: ' + model)); - }); - } - self.showFields(model, function(err, fields) { - self.showIndexes(model, function(err, indexes) { - if (!err && fields.length) { - self.alterTable(model, fields, indexes, done); - } else { - self.createTable(model, done); - } - }); - }); - }, cb); + return columnNames; }; -MsSQL.prototype.isActual = function (cb) { - var ok = false; - var self = this; - async.each(Object.keys(this._models), function(model, done) { - self.showFields(model, function (err, fields) { - self.showIndexes(model, function (err, indexes) { - self.alterTable(model, fields, indexes, function(err, needAlter) { - if (err) { - return done(err); - } else { - ok = ok || needAlter; - done(err); - } - }, true); - }); - }); - }, function(err) { - if (err) { - return err; +MsSQL.prototype.buildSelect = function(model, filter, options) { + if (!filter.order) { + var idNames = this.idNames(model); + if (idNames && idNames.length) { + filter.order = idNames; } - cb(null, !ok); - }); -}; - -MsSQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, checkOnly) { - var self = this; - var m = this._models[model]; - var idName = this.idName(model); - - var propNames = Object.keys(m.properties).filter(function (name) { - return !!m.properties[name]; - }); - var indexNames = m.settings.indexes ? Object.keys(m.settings.indexes).filter(function (name) { - return !!m.settings.indexes[name]; - }) : []; - var sql = []; - var ai = {}; - - if (actualIndexes) { - actualIndexes.forEach(function (i) { - var name = i.Key_name; - if (!ai[name]) { - ai[name] = { - info: i, - columns: [] - }; - } - ai[name].columns[i.Seq_in_index - 1] = i.Column_name; - }); } - var aiNames = Object.keys(ai); - var columnsToAdd = []; - var columnsToDrop = []; - var columnsToAlter = []; + var selectStmt = new ParameterizedSQL('SELECT ' + + this.buildColumnNames(model, filter) + + ' FROM ' + this.tableEscaped(model) + ); - // change/add new fields - propNames.forEach(function (propName) { - if (propName === idName) return; - var found; - actualFields.forEach(function (f) { - if (f.Field === propName) { - found = f; - } - }); + if (filter) { - if (found) { - actualize(propName, found); - } else { - columnsToAdd.push(self.columnEscaped(model, propName) + - ' ' + self.propertySettingsSQL(model, propName)); + if (filter.where) { + var whereStmt = this.buildWhere(model, filter.where); + selectStmt.merge(whereStmt); } - }); - - // drop columns - actualFields.forEach(function (f) { - var notFound = !~propNames.indexOf(f.Field); - if (f.Field === idName) return; - if (notFound || !m.properties[f.Field]) { - columnsToDrop.push(self.columnEscaped(model, f.Field)); - } - }); - // remove indexes - aiNames.forEach(function (indexName) { - if (indexName === idName || indexName === 'PRIMARY') { - return; - } - if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] - || m.properties[indexName] && !m.properties[indexName].index) { - sql.push('DROP INDEX ' + self.columnEscaped(model, indexName)); + if (filter.limit || filter.skip || filter.offset) { + selectStmt = this.applyPagination( + model, selectStmt, filter); } else { - // first: check single (only type and kind) - if (m.properties[indexName] && !m.properties[indexName].index) { - // TODO - return; - } - // second: check multiple indexes - var orderMatched = true; - if (indexNames.indexOf(indexName) !== -1) { - m.settings.indexes[indexName].columns.split(/,\s*/).forEach(function (columnName, i) { - if (ai[indexName].columns[i] !== columnName) { - orderMatched = false; - } - }); - } - if (!orderMatched) { - sql.push('DROP INDEX [' + self.columnEscaped(model, indexName) + ']'); - delete ai[indexName]; + if (filter.order) { + selectStmt.merge(this.buildOrderBy(model, filter.order)); } } - }); - // add single-column indexes - propNames.forEach(function (propName) { - var i = m.properties[propName].index; - if (!i) { - return; - } - var found = ai[propName] && ai[propName].info; - var columnName = self.columnEscaped(model, propName); - if (!found) { - var type = ''; - var kind = ''; - if (i.type) { - type = 'USING ' + i.type; - } - if (i.kind) { - // kind = i.kind; - } - if (kind && type) { - sql.push('ADD ' + kind + ' INDEX ' + columnName - + ' (' + columnName + ') ' + type); - } else { - sql.push('ADD ' + kind + ' INDEX [' + propName + '] ' - + type + ' ([' + propName + ']) '); - } - } - }); - - // add multi-column indexes - indexNames.forEach(function (indexName) { - var i = m.settings.indexes[indexName]; - var found = ai[indexName] && ai[indexName].info; - if (!found) { - var type = ''; - var kind = ''; - if (i.type) { - type = 'USING ' + i.type; - } - if (i.kind) { - kind = i.kind; - } - if (kind && type) { - sql.push('ADD ' + kind + ' INDEX [' + indexName + '] (' - + i.columns + ') ' + type); - } else { - sql.push('ADD ' + kind + ' INDEX [' + indexName + '] (' - + i.columns + ') '); - } - } - }); - - var statements = []; - if (columnsToAdd.length) { - statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' ADD ' - + columnsToAdd.join(',' + MsSQL.newline)); - } - - if (columnsToAlter.length) { - // SQL Server doesn't allow multiple columns to be altered in one statement - columnsToAlter.forEach(function (c) { - statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' ALTER COLUMN ' - + c); - }); - } - - if (columnsToDrop.length) { - statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' DROP COLUMN' - + columnsToDrop.join(',' + MsSQL.newline)); - } - - async.each(statements, function(query, fn) { - if (checkOnly) { - fn(null, true, {statements: statements, query: query}); - } else { - self.query(query, fn); - } - }, function(err, results) { - done && done(err, results); - }); - - function actualize(propName, oldSettings) { - var newSettings = m.properties[propName]; - if (newSettings && changed(newSettings, oldSettings)) { - columnsToAlter.push(self.columnEscaped(model, propName) + ' ' - + self.propertySettingsSQL(model, propName)); - } } - - function changed(newSettings, oldSettings) { - if (oldSettings.Null === 'YES' - && (newSettings.allowNull === false || newSettings.null === false)) { - return true; - } - if (oldSettings.Null === 'NO' && !(newSettings.allowNull === false - || newSettings.null === false)) { - return true; - } - if (oldSettings.Type.toUpperCase() !== datatype(newSettings)) { - return true; - } - return false; - } -}; - -MsSQL.prototype.propertiesSQL = function(model) { - // debugger; - var self = this; - var objModel = this._models[model]; - var modelPKID = this.idName(model); - - var sql = []; - var props = Object.keys(objModel.properties); - for (var i = 0, n = props.length; i < n; i++) { - var prop = props[i]; - if (prop === modelPKID) { - var idProp = objModel.properties[modelPKID]; - if (idProp.type === Number) { - if(idProp.generated !== false) { - sql.push(self.columnEscaped(model, modelPKID) + - " " + self.columnDataType(model, modelPKID) + " IDENTITY(1,1) NOT NULL"); - } - else { - sql.push(self.columnEscaped(model, modelPKID) + - " " + self.columnDataType(model, modelPKID) + " NOT NULL"); - } - continue; - } else if (idProp.type === String) { - if(idProp.generated !== false) { - sql.push(self.columnEscaped(model, modelPKID) + - " [uniqueidentifier] DEFAULT newid() NOT NULL"); - } else { - sql.push(self.columnEscaped(model, modelPKID) + " " + - self.propertySettingsSQL(model, prop) + " DEFAULT newid()"); - } - continue; - } - } - sql.push(self.columnEscaped(model, prop) + " " + self.propertySettingsSQL(model, prop)); - } - var joinedSql = sql.join("," + MsSQL.newline + " "); - var cmd = "PRIMARY KEY CLUSTERED" + MsSQL.newline + "(" + MsSQL.newline; - cmd += " " + self.columnEscaped(model, modelPKID) + " ASC" + MsSQL.newline; - cmd += ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, " + - "IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)" - - joinedSql += "," + MsSQL.newline + cmd; - - return joinedSql; -}; - -MsSQL.prototype.singleIndexSettingsSQL = function (model, prop, add) { - // Recycled from alterTable single indexes above, more or less. - var tblName = this.tableEscaped(model); - var i = this._models[model].properties[prop].index; - var type = 'ASC'; - var kind = 'NONCLUSTERED'; - var unique = false; - if (i.type) { - type = i.type; - } - if (i.kind) { - kind = i.kind; - } - if (i.unique) { - unique = true; - } - var name = prop + "_" + kind + "_" + type + "_idx" - if (i.name) { - name = i.name; - } - this._idxNames[model].push(name); - var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON " - + tblName + MsSQL.newline; - cmd += "(" + MsSQL.newline; - cmd += " [" + prop + "] " + type; - cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF," + - " SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, " + - "ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);" - + MsSQL.newline; - return cmd; -}; - -MsSQL.prototype.indexSettingsSQL = function (model, prop) { - // Recycled from alterTable multi-column indexes above, more or less. - var tblName = this.tableEscaped(model); - var i = this._models[model].settings.indexes[prop]; - var type = 'ASC'; - var kind = 'NONCLUSTERED'; - var unique = false; - if (i.type) { - type = i.type; - } - if (i.kind) { - kind = i.kind; - } - if (i.unique) { - unique = true; - } - var splitcolumns = i.columns.split(","); - var columns = []; - var name = ""; - splitcolumns.forEach(function (elem, ind) { - var trimmed = elem.trim(); - name += trimmed + "_"; - trimmed = "[" + trimmed + "] " + type; - columns.push(trimmed); - }); - - name += kind + "_" + type + "_idx" - this._idxNames[model].push(name); - - var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON " - + tblName + MsSQL.newline; - cmd += "(" + MsSQL.newline; - cmd += columns.join("," + MsSQL.newline); - cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, " + - "SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, " + - "ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);" - + MsSQL.newline; - return cmd; -}; - -function isNullable(p) { - return !(p.required || p.id || p.nullable === false || - p.allowNull === false || p['null'] === false); -} - -MsSQL.prototype.propertySettingsSQL = function (model, prop) { - var p = this._models[model].properties[prop]; - return this.columnDataType(model, prop) + ' ' + - (isNullable(p) ? 'NULL' : 'NOT NULL'); + return this.parameterize(selectStmt); }; -MsSQL.prototype.automigrate = function(models, cb) { - var self = this; - if ((!cb) && ('function' === typeof models)) { - cb = models; - models = undefined; - } - // First argument is a model name - if ('string' === typeof models) { - models = [models]; - } +MsSQL.prototype.applyPagination = + function(model, stmt, filter) { + var offset = filter.offset || filter.skip || 0; + if (this.settings.supportsOffsetFetch) { + // SQL 2012 or later + // http://technet.microsoft.com/en-us/library/gg699618.aspx + var limitClause = buildLimit(filter.limit, filter.offset || filter.skip); + return stmt.merge(limitClause); + } else { + // SQL 2005/2008 + // http://blog.sqlauthority.com/2013/04/14/sql-server-tricks-for-row-offset-and-paging-in-various-versions-of-sql-server/ + var paginatedSQL = 'SELECT * FROM (' + stmt.sql + MsSQL.newline + + ') AS S' + MsSQL.newline + ' WHERE S.RowNum > ' + offset; - models = models || Object.keys(this._models); - async.each(models, function(model, done) { - if (!(model in self._models)) { - return process.nextTick(function() { - done(new Error('Model not found: ' + model)); - }); - } - self.dropTable(model, function(err) { - if (err) { - return done(err); + if (filter.limit !== -1) { + paginatedSQL += ' AND S.RowNum <= ' + (offset + filter.limit); } - self.createTable(model, done); - }); - }, function(err) { - cb && cb(err); - }); -}; -MsSQL.prototype.dropTable = function (model, cb) { - var tblName = this.tableEscaped(model); - var cmd = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" - + tblName + "') AND type in (N'U'))"; - cmd += MsSQL.newline + "BEGIN" + MsSQL.newline; - cmd += " DROP TABLE " + tblName; - cmd += MsSQL.newline + "END"; - this.command(cmd, cb); -}; - -MsSQL.prototype.createTable = function (model, cb) { - var tblName = this.tableEscaped(model); - var cmd = "SET ANSI_NULLS ON;" + MsSQL.newline + "SET QUOTED_IDENTIFIER ON;" - + MsSQL.newline + "SET ANSI_PADDING ON;" + MsSQL.newline; - cmd += "IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" - + tblName + "') AND type in (N'U'))" + MsSQL.newline + "BEGIN" + MsSQL.newline; - cmd += "CREATE TABLE " + this.tableEscaped(model) + " ("; - cmd += MsSQL.newline + " " + this.propertiesSQL(model) + MsSQL.newline; - cmd += ")" + MsSQL.newline + "END;" + MsSQL.newline; - cmd += this.createIndexes(model); - this.command(cmd, cb); -}; - -MsSQL.prototype.createIndexes = function (model) { - var self = this; - var sql = []; - // Declared in model index property indexes. - Object.keys(this._models[model].properties).forEach(function (prop) { - var i = self._models[model].properties[prop].index; - if (i) { - sql.push(self.singleIndexSettingsSQL(model, prop)); + stmt.sql = paginatedSQL + MsSQL.newline; + return stmt; } - }); - - // Settings might not have an indexes property. - var dxs = this._models[model].settings.indexes; - if (dxs) { - Object.keys(this._models[model].settings.indexes).forEach(function (prop) { - sql.push(self.indexSettingsSQL(model, prop)); - }); - } - - return sql.join(MsSQL.newline); -} + }; -MsSQL.prototype.columnDataType = function (model, property) { - var columnMetadata = this.columnMetadata(model, property); - var colType = columnMetadata && columnMetadata.dataType; - if (colType) { - colType = colType.toUpperCase(); - } - var prop = this._models[model].properties[property]; - if (!prop) { - return null; - } - var colLength = columnMetadata && columnMetadata.dataLength || prop.length; - if (colType) { - var dataPrecision = columnMetadata.dataPrecision; - var dataScale = columnMetadata.dataScale; - if (dataPrecision && dataScale) { - return colType + '(' + dataPrecision + ', ' + dataScale + ')'; +MsSQL.prototype.buildExpression = + function(columnName, operator, columnValue, propertyValue) { + switch (operator) { + case 'like': + return new ParameterizedSQL({ + sql: columnName + " LIKE ? ESCAPE '\\'", + params: [columnValue] + }); + case 'nlike': + return new ParameterizedSQL({ + sql: columnName + " NOT LIKE ? ESCAPE '\\'", + params: [columnValue] + }); + case 'max': + // + default: + // Invoke the base implementation of `buildExpression` + var exp = this.invokeSuper('buildExpression', + columnName, operator, columnValue, propertyValue); + return exp; } - return colType + (colLength ? '(' + colLength + ')' : ''); - } - return datatype(prop); -}; - -function datatype(p) { - var dt = ''; - switch (p.type.name) { - default: - case 'String': - case 'JSON': - dt = '[nvarchar](' + (p.length || p.limit || 255) + ')'; - break; - case 'Text': - dt = '[text]'; - break; - case 'Number': - dt = '[int]'; - break; - case 'Date': - dt = '[datetime]'; - break; - case 'Boolean': - dt = '[bit]'; - break; - case 'Point': - dt = '[float]'; - break; - } - return dt; -} + }; MsSQL.prototype.ping = function(cb) { - this.query('SELECT 1 AS result', cb); + this.execute('SELECT 1 AS result', cb); }; require('./discovery')(MsSQL); +require('./migration')(MsSQL); From 21c6a8a86543d6d1a547ea95bcc03bbc13235604 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 13 May 2015 16:23:22 -0700 Subject: [PATCH 101/239] Update deps --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 618b997..62edad7 100644 --- a/package.json +++ b/package.json @@ -15,13 +15,13 @@ "dependencies": { "async": "^0.9.0", "debug": "^2.1.1", - "loopback-connector": "1.x", + "loopback-connector": "^2.0.0", "mssql": "^2.1.0", "strongloop-license": "^1.0.2" }, "devDependencies": { "bluebird": "^2.9.12", - "loopback-datasource-juggler": "2.x", + "loopback-datasource-juggler": "^2.27.0", "mocha": "^2.1.0", "rc": "^1.0.0", "should": "^5.0.0" From fa55226be6fa243507beb3f7af8db72d9f6b00aa Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 13 May 2015 16:23:40 -0700 Subject: [PATCH 102/239] 2.0.0 * Update deps (Raymond Feng) * Refactor the mssql connector to use base SqlConnector (Raymond Feng) * Use SET IDENTITY_INSERT option to allow explicit id (Raymond Feng) * Return count when updating or deleting models (Simon Ho) * Add strongloop license check (Raymond Feng) * Add "Running tests" section to readme (Simon Ho) --- CHANGES.md | 18 +++++++++++++++++- package.json | 4 ++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6c38a5d..b34a28a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,20 @@ -2015-03-03, Version 1.5.1 +2015-05-13, Version 2.0.0 +========================= + + * Update deps (Raymond Feng) + + * Refactor the mssql connector to use base SqlConnector (Raymond Feng) + + * Use SET IDENTITY_INSERT option to allow explicit id (Raymond Feng) + + * Return count when updating or deleting models (Simon Ho) + + * Add strongloop license check (Raymond Feng) + + * Add "Running tests" section to readme (Simon Ho) + + +2015-03-02, Version 1.5.1 ========================= * Test if the id is generated (Raymond Feng) diff --git a/package.json b/package.json index 62edad7..abdae2e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "1.5.1", + "version": "2.0.0", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -38,6 +38,6 @@ "url": "https://github.com/strongloop/loopback-connector-mssql/blob/master/LICENSE" }, "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@1.5.1" + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.0.0" } } From 4e21702684f04f92eecf468447b9153745cb54bd Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 12 May 2015 13:59:07 -0700 Subject: [PATCH 103/239] Add transaction support --- lib/mssql.js | 14 ++++-- lib/transaction.js | 43 ++++++++++++++++ test/init.js | 7 ++- test/transaction.test.js | 103 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 lib/transaction.js create mode 100644 test/transaction.test.js diff --git a/lib/mssql.js b/lib/mssql.js index d5d1730..2003ff1 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -83,12 +83,19 @@ MsSQL.prototype.executeSQL = function(sql, params, options, callback) { debug('Formatted SQL: %s', sql); } + var connection = this.client; + + var transaction = options.transaction; + if (transaction && transaction.connector === this && transaction.connection) { + debug('Execute SQL in a transaction'); + connection = transaction.connection; + } var innerCB = function(err, data) { debug('Result: %j %j', err, data); callback && callback(err, data); }; - var request = new mssql.Request(this.client); + var request = new mssql.Request(connection); // request.verbose = true; request.query(sql, innerCB); }; @@ -421,5 +428,6 @@ MsSQL.prototype.ping = function(cb) { this.execute('SELECT 1 AS result', cb); }; -require('./discovery')(MsSQL); -require('./migration')(MsSQL); +require('./discovery')(MsSQL, mssql); +require('./migration')(MsSQL, mssql); +require('./transaction')(MsSQL, mssql); \ No newline at end of file diff --git a/lib/transaction.js b/lib/transaction.js new file mode 100644 index 0000000..547b376 --- /dev/null +++ b/lib/transaction.js @@ -0,0 +1,43 @@ +var debug = require('debug')('loopback:connector:mssql:transaction'); + +module.exports = mixinTransaction; + +/*! + * @param {MsSQL} MsSQL connector class + */ +function mixinTransaction(MsSQL, mssql) { + + /** + * Begin a new transaction + * @param isolationLevel + * @param cb + */ + MsSQL.prototype.beginTransaction = function(isolationLevel, cb) { + debug('Begin a transaction with isolation level: %s', isolationLevel); + isolationLevel = mssql.ISOLATION_LEVEL[isolationLevel.replace(' ', '_')]; + var transaction = new mssql.Transaction(this.client); + transaction.begin(isolationLevel, function(err) { + cb(err, transaction); + }); + }; + + /** + * + * @param connection + * @param cb + */ + MsSQL.prototype.commit = function(connection, cb) { + debug('Commit a transaction'); + connection.commit(cb); + }; + + /** + * + * @param connection + * @param cb + */ + MsSQL.prototype.rollback = function(connection, cb) { + debug('Rollback a transaction'); + connection.rollback(cb); + }; +} diff --git a/test/init.js b/test/init.js index 00d72d5..f82b90f 100644 --- a/test/init.js +++ b/test/init.js @@ -22,7 +22,12 @@ global.getConfig = function (options) { port: config.port || 1433, database: config.database || 'test', user: config.user || config.username, - password: config.password + password: config.password, + pool: { + max: 10, + min: 0, + idleTimeoutMillis: 30000 + } }; if (options) { diff --git a/test/transaction.test.js b/test/transaction.test.js new file mode 100644 index 0000000..24f6b88 --- /dev/null +++ b/test/transaction.test.js @@ -0,0 +1,103 @@ +require('./init.js'); +require('should'); + +var Transaction = require('loopback-connector').Transaction; + +var db, Post; + +describe('transactions', function() { + + before(function(done) { + db = getDataSource(); + Post = db.define('PostTX', { + title: {type: String, length: 255, index: true}, + content: {type: String} + }); + db.automigrate('PostTX', done); + }); + + var currentTx; + // Return an async function to start a transaction and create a post + function createPostInTx(post) { + return function(done) { + Transaction.begin(db.connector, Transaction.READ_COMMITTED, + function(err, tx) { + if (err) return done(err); + currentTx = tx; + Post.create(post, {transaction: tx}, + function(err, p) { + if (err) { + done(err); + } else { + done(); + } + }); + }); + }; + } + + // Return an async function to find matching posts and assert number of + // records to equal to the count + function expectToFindPosts(where, count, inTx) { + return function(done) { + var options = {}; + if (inTx) { + options.transaction = currentTx; + } + Post.find({where: where}, options, + function(err, posts) { + if (err) return done(err); + posts.length.should.be.eql(count); + done(); + }); + }; + } + + describe('commit', function() { + + var post = {title: 't1', content: 'c1'}; + before(createPostInTx(post)); + + // FIXME: [rfeng] SQL server creates LCK_M_S (Shared Lock on the table + // and it prevents the following test to run as the SELECT will be suspended + // until the transaction releases the lock + it.skip('should not see the uncommitted insert', expectToFindPosts(post, 0)); + + it('should see the uncommitted insert from the same transaction', + expectToFindPosts(post, 1, true)); + + it('should commit a transaction', function(done) { + currentTx.commit(done); + }); + + it('should see the committed insert', expectToFindPosts(post, 1)); + }); + + describe('rollback', function() { + + var post = {title: 't2', content: 'c2'}; + before(createPostInTx(post)); + + // FIXME: [rfeng] SQL server creates LCK_M_S (Shared Lock on the table + // and it prevents the following test to run as the SELECT will be suspended + // until the transaction releases the lock + it.skip('should not see the uncommitted insert', expectToFindPosts(post, 0)); + + it('should see the uncommitted insert from the same transaction', + expectToFindPosts(post, 1, true)); + + it('should rollback a transaction', function(done) { + currentTx.rollback(done); + }); + + it('should not see the rolledback insert', expectToFindPosts(post, 0)); + }); + +}); + + + + + + + From da83e6304ec6bef1eeaf47e3339ef260308a02b1 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 18 May 2015 14:35:18 -0700 Subject: [PATCH 104/239] Update deps --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index abdae2e..b4f48ed 100644 --- a/package.json +++ b/package.json @@ -15,13 +15,13 @@ "dependencies": { "async": "^0.9.0", "debug": "^2.1.1", - "loopback-connector": "^2.0.0", + "loopback-connector": "^2.1.0", "mssql": "^2.1.0", "strongloop-license": "^1.0.2" }, "devDependencies": { "bluebird": "^2.9.12", - "loopback-datasource-juggler": "^2.27.0", + "loopback-datasource-juggler": "^2.28.0", "mocha": "^2.1.0", "rc": "^1.0.0", "should": "^5.0.0" From 5464b456465901d3b2a624f68808ccf590752aef Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 18 May 2015 14:36:00 -0700 Subject: [PATCH 105/239] 2.1.0 * Update deps (Raymond Feng) * Add transaction support (Raymond Feng) --- CHANGES.md | 8 ++++++++ package.json | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b34a28a..93d5777 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,11 @@ +2015-05-18, Version 2.1.0 +========================= + + * Update deps (Raymond Feng) + + * Add transaction support (Raymond Feng) + + 2015-05-13, Version 2.0.0 ========================= diff --git a/package.json b/package.json index b4f48ed..2bd0cac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.0.0", + "version": "2.1.0", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -38,6 +38,6 @@ "url": "https://github.com/strongloop/loopback-connector-mssql/blob/master/LICENSE" }, "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.0.0" + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.1.0" } } From e53f61a4e00f1f8918932b8caae3f178016d15aa Mon Sep 17 00:00:00 2001 From: Simon Ho Date: Tue, 28 Jul 2015 18:31:56 -0700 Subject: [PATCH 106/239] Add support for regex operator --- lib/mssql.js | 42 +++++++++++++++++--------------------- package.json | 3 ++- test/init.js | 2 +- test/mssql.test.js | 51 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 25 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 2003ff1..1c891d5 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -401,28 +401,24 @@ MsSQL.prototype.applyPagination = } }; -MsSQL.prototype.buildExpression = - function(columnName, operator, columnValue, propertyValue) { - switch (operator) { - case 'like': - return new ParameterizedSQL({ - sql: columnName + " LIKE ? ESCAPE '\\'", - params: [columnValue] - }); - case 'nlike': - return new ParameterizedSQL({ - sql: columnName + " NOT LIKE ? ESCAPE '\\'", - params: [columnValue] - }); - case 'max': - // - default: - // Invoke the base implementation of `buildExpression` - var exp = this.invokeSuper('buildExpression', - columnName, operator, columnValue, propertyValue); - return exp; - } - }; +MsSQL.prototype.buildExpression = function(columnName, operator, operatorValue, + propertyDefinition) { + switch (operator) { + case 'like': + return new ParameterizedSQL(columnName + " LIKE ? ESCAPE '\\'", + [operatorValue]); + case 'nlike': + return new ParameterizedSQL(columnName + " NOT LIKE ? ESCAPE '\\'", + [operatorValue]); + case 'regexp': + console.warn('Microsoft SQL Server doe not support the regular ' + + 'expression operator'); + default: + // invoke the base implementation of `buildExpression` + return this.invokeSuper('buildExpression', columnName, operator, + operatorValue, propertyDefinition); + } +}; MsSQL.prototype.ping = function(cb) { this.execute('SELECT 1 AS result', cb); @@ -430,4 +426,4 @@ MsSQL.prototype.ping = function(cb) { require('./discovery')(MsSQL, mssql); require('./migration')(MsSQL, mssql); -require('./transaction')(MsSQL, mssql); \ No newline at end of file +require('./transaction')(MsSQL, mssql); diff --git a/package.json b/package.json index 2bd0cac..9247a87 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "loopback-datasource-juggler": "^2.28.0", "mocha": "^2.1.0", "rc": "^1.0.0", - "should": "^5.0.0" + "should": "^5.0.0", + "sinon": "^1.15.4" }, "scripts": { "test": "./node_modules/.bin/mocha --timeout 5000 -R spec" diff --git a/test/init.js b/test/init.js index f82b90f..7fc619b 100644 --- a/test/init.js +++ b/test/init.js @@ -44,4 +44,4 @@ global.getDataSource = global.getSchema = function (options) { return db; }; - +global.sinon = require('sinon'); diff --git a/test/mssql.test.js b/test/mssql.test.js index ed134ab..581be56 100644 --- a/test/mssql.test.js +++ b/test/mssql.test.js @@ -208,4 +208,55 @@ describe('mssql connector', function () { }); }); + context('regexp operator', function() { + beforeEach(function deleteExistingTestFixtures(done) { + Post.destroyAll(done); + }); + beforeEach(function createTestFixtures(done) { + Post.create([ + {title: 'a', content: 'AAA'}, + {title: 'b', content: 'BBB'} + ], done); + }); + beforeEach(function addSpy() { + sinon.stub(console, 'warn'); + }); + afterEach(function removeSpy() { + console.warn.restore(); + }); + after(function deleteTestFixtures(done) { + Post.destroyAll(done); + }); + + context('with regex strings', function() { + it('should print a warning and return an error', function(done) { + Post.find({where: {content: {regexp: '^A'}}}, function(err, posts) { + console.warn.calledOnce.should.be.ok; + should.exist(err); + done(); + }); + }); + }); + + context('with regex literals', function() { + it('should print a warning and return an error', function(done) { + Post.find({where: {content: {regexp: /^A/}}}, function(err, posts) { + console.warn.calledOnce.should.be.ok; + should.exist(err); + done(); + }); + }); + }); + + context('with regex objects', function() { + it('should print a warning and return an error', function(done) { + Post.find({where: {content: {regexp: new RegExp(/^A/)}}}, function(err, + posts) { + console.warn.calledOnce.should.be.ok; + should.exist(err); + done(); + }); + }); + }); + }); }); From bb87d49a78b7953c3bb0eb3fc1e9b81457a5b19f Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 29 Jul 2015 10:26:43 -0700 Subject: [PATCH 107/239] 2.2.0 * Add support for regex operator (Simon Ho) --- CHANGES.md | 6 ++++++ package.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 93d5777..3a709c1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,9 @@ +2015-07-29, Version 2.2.0 +========================= + + * Add support for regex operator (Simon Ho) + + 2015-05-18, Version 2.1.0 ========================= diff --git a/package.json b/package.json index 9247a87..24b9691 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.1.0", + "version": "2.2.0", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -39,6 +39,6 @@ "url": "https://github.com/strongloop/loopback-connector-mssql/blob/master/LICENSE" }, "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.1.0" + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.2.0" } } From 63a14fc792c9f31519207c1541a417c8edccbb7f Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 13 Aug 2015 13:46:50 -0700 Subject: [PATCH 108/239] Allow the `multipleResultSets` flag for execute --- lib/mssql.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/mssql.js b/lib/mssql.js index 1c891d5..062a368 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -96,6 +96,10 @@ MsSQL.prototype.executeSQL = function(sql, params, options, callback) { }; var request = new mssql.Request(connection); + // Allow multiple result sets + if (options.multipleResultSets) { + request.multiple = true; + } // request.verbose = true; request.query(sql, innerCB); }; From ce1cecfd1beba54fb1e162123f7859d96b541d03 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 13 Aug 2015 13:47:38 -0700 Subject: [PATCH 109/239] 2.2.1 * Allow the `multipleResultSets` flag for execute (Raymond Feng) --- CHANGES.md | 6 ++++++ package.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3a709c1..df66c19 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,9 @@ +2015-08-13, Version 2.2.1 +========================= + + * Allow the `multipleResultSets` flag for execute (Raymond Feng) + + 2015-07-29, Version 2.2.0 ========================= diff --git a/package.json b/package.json index 24b9691..a515acd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.2.0", + "version": "2.2.1", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -39,6 +39,6 @@ "url": "https://github.com/strongloop/loopback-connector-mssql/blob/master/LICENSE" }, "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.2.0" + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.2.1" } } From 6c1e8c520a0e8e21f0ab8a7396bb64bcc7a12515 Mon Sep 17 00:00:00 2001 From: Ahmed Abdul Moniem Date: Fri, 14 Aug 2015 20:14:35 +0200 Subject: [PATCH 110/239] Added support to unicode --- lib/mssql.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mssql.js b/lib/mssql.js index 062a368..552bed0 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -263,7 +263,7 @@ function escape(val) { } }); // return "q'#"+val+"#'"; - return "'" + val + "'"; + return "N'" + val + "'"; } MsSQL.prototype.toColumnValue = function(prop, val) { From ed5d9299a49a9f476233eb8945dcf2b1f94449f2 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 14 Aug 2015 15:03:07 -0700 Subject: [PATCH 111/239] 2.3.0 * Added support to unicode (Ahmed Abdul Moniem) --- CHANGES.md | 6 ++++++ package.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index df66c19..65bcac8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,9 @@ +2015-08-14, Version 2.3.0 +========================= + + * Added support to unicode (Ahmed Abdul Moniem) + + 2015-08-13, Version 2.2.1 ========================= diff --git a/package.json b/package.json index a515acd..272dc65 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.2.1", + "version": "2.3.0", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -39,6 +39,6 @@ "url": "https://github.com/strongloop/loopback-connector-mssql/blob/master/LICENSE" }, "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.2.1" + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.3.0" } } From 0ca61aa38e443d6f11472bb70d2f559e0e7f7222 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 11 Sep 2015 11:16:26 -0700 Subject: [PATCH 112/239] Allow models without PK --- lib/migration.js | 11 +++++++---- lib/mssql.js | 16 ++++++++++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/migration.js b/lib/migration.js index 3586753..7121c5e 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -340,10 +340,13 @@ function mixinMigration(MsSQL) { sql.push(self.columnEscaped(model, prop) + " " + self.propertySettingsSQL(model, prop)); } var joinedSql = sql.join("," + MsSQL.newline + " "); - var cmd = "PRIMARY KEY CLUSTERED" + MsSQL.newline + "(" + MsSQL.newline; - cmd += " " + self.columnEscaped(model, modelPKID) + " ASC" + MsSQL.newline; - cmd += ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, " + + var cmd = ""; + if (modelPKID) { + cmd = "PRIMARY KEY CLUSTERED" + MsSQL.newline + "(" + MsSQL.newline; + cmd += " " + self.columnEscaped(model, modelPKID) + " ASC" + MsSQL.newline; + cmd += ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, " + "IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)" + } joinedSql += "," + MsSQL.newline + cmd; @@ -555,4 +558,4 @@ function mixinMigration(MsSQL) { } return dt; } -} \ No newline at end of file +} diff --git a/lib/mssql.js b/lib/mssql.js index 552bed0..5e38b20 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -140,15 +140,17 @@ MsSQL.prototype.buildInsertDefaultValues = function(model, data, options) { MsSQL.prototype.buildInsertInto = function(model, fields, options) { var stmt = this.invokeSuper('buildInsertInto', model, fields, options); var idName = this.idName(model); - stmt.merge(MsSQL.newline + 'OUTPUT INSERTED.' + - this.columnEscaped(model, idName) + ' AS insertId'); + if (idName) { + stmt.merge(MsSQL.newline + 'OUTPUT INSERTED.' + + this.columnEscaped(model, idName) + ' AS insertId'); + } return stmt; }; MsSQL.prototype.buildInsert = function(model, data, options) { var idName = this.idName(model); var prop = this.getPropertyDefinition(model, idName); - var isIdentity = (prop.type === Number && prop.generated !== false); + var isIdentity = (prop && prop.type === Number && prop.generated !== false); if (isIdentity && data[idName] == null) { //remove the pkid column if it's in the data, since we're going to insert a // new record, not update an existing one. @@ -344,7 +346,13 @@ MsSQL.prototype.buildColumnNames = function(model, filter) { var columnNames = this.invokeSuper('buildColumnNames', model, filter); if (filter.limit || filter.offset || filter.skip) { var orderBy = this.buildOrderBy(model, filter.order); - columnNames += ',ROW_NUMBER() OVER' + ' (' + orderBy + ') AS RowNum'; + var orderClause = ''; + if (orderBy) { + orderClause = 'OVER (' + orderBy + ') '; + } else { + orderClause = 'OVER (ORDER BY (SELECT 1)) '; + } + columnNames += ',ROW_NUMBER() ' + orderClause + 'AS RowNum'; } return columnNames; }; From 4f0f90a5764566e278f475593e3a1421d554c5ca Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 11 Sep 2015 11:16:39 -0700 Subject: [PATCH 113/239] 2.3.1 * Allow models without PK (Raymond Feng) --- CHANGES.md | 6 ++++++ package.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 65bcac8..f210cd7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,9 @@ +2015-09-11, Version 2.3.1 +========================= + + * Allow models without PK (Raymond Feng) + + 2015-08-14, Version 2.3.0 ========================= diff --git a/package.json b/package.json index 272dc65..3b6497c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.3.0", + "version": "2.3.1", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -39,6 +39,6 @@ "url": "https://github.com/strongloop/loopback-connector-mssql/blob/master/LICENSE" }, "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.3.0" + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.3.1" } } From cc5d69378071daa505ce38966c6fad20d08dad8b Mon Sep 17 00:00:00 2001 From: Sam Roberts Date: Mon, 21 Sep 2015 16:42:54 -0700 Subject: [PATCH 114/239] Use strongloop conventions for licensing --- LICENSE => LICENSE.md | 169 +++++++++++++++++++++++++----------------- package.json | 5 +- 2 files changed, 103 insertions(+), 71 deletions(-) rename LICENSE => LICENSE.md (77%) diff --git a/LICENSE b/LICENSE.md similarity index 77% rename from LICENSE rename to LICENSE.md index 6374e5d..a49c14b 100644 --- a/LICENSE +++ b/LICENSE.md @@ -1,13 +1,15 @@ -Copyright (c) 2013-2014 StrongLoop, Inc. +Copyright (c) 2013-2015 StrongLoop, Inc. StrongLoop License STRONGLOOP SUBSCRIPTION AGREEMENT + PLEASE READ THIS AGREEMENT CAREFULLY BEFORE YOU AGREE TO THESE TERMS. IF YOU ARE ACTING ON BEHALF OF AN ENTITY, THEN YOU REPRESENT THAT YOU HAVE THE AUTHORITY TO ENTER INTO THIS AGREEMENT ON BEHALF OF THAT ENTITY. IF YOU DO NOT AGREE TO THESE TERMS, YOU SHOULD NOT AGREE TO THE TERMS OF THIS AGREEMENT OR INSTALL OR USE THE SOFTWARE. + This StrongLoop Subscription Agreement ("Agreement") is made by and between StrongLoop, Inc. ("StrongLoop") with its principal place of business at 107 S. B St, Suite 220, San Mateo, CA 94401 and the person or entity entering into this @@ -16,7 +18,9 @@ is the date Customer agrees to these terms or installs or uses the Software (as defined below). This Agreement applies to Customer's use of the Software but it shall be superseded by any signed agreement between you and StrongLoop concerning the Software. + 1. Subscriptions and Licenses. + 1.1 Subscriptions. StrongLoop offers five different subscription levels to its customers, each as more particularly described on StrongLoop's website located at www.strongloop.com (the "StrongLoop Site"): (1) Free; (2) Developer; (3) @@ -31,6 +35,7 @@ applicable Subscription, subject to Customer paying the fees applicable to the Subscription level purchased, if any (the "Subscription Fees"). StrongLoop may modify the services to be provided under any Subscription upon notice to Customer. + 1.2 License Grant. Subject to the terms and conditions of this Agreement, StrongLoop grants to Customer, during the Subscription Term (as defined in Section 7.1 (Term and Termination) of this Agreement, a limited, non-exclusive, @@ -44,42 +49,51 @@ including but not limited to the license and restrictions contained herein, apply to Customer regardless of whether Customer accesses the Software via download from the StrongLoop Site or through a third-party website or service, even if Customer acquired the Software prior to agreeing to this Agreement. + 1.3 License Restrictions. Customer shall not itself, or through any parent, subsidiary, affiliate, agent or other third party: - 1.3.1 sell, lease, license, distribute, sublicense or otherwise transfer - in whole or in part, any Software or the Documentation to a third party; - or - 1.3.2 decompile, disassemble, translate, reverse engineer or otherwise - attempt to derive source code from the Software, in whole or in part, nor - shall Customer use any mechanical, electronic or other method to trace, - decompile, disassemble, or identify the source code of the Software or - encourage others to do so, except to the limited extent, if any, that - applicable law permits such acts notwithstanding any contractual - prohibitions, provided, however, before Customer exercises any rights that - Customer believes to be entitled to based on mandatory law, Customer shall - provide StrongLoop with thirty (30) days prior written notice and provide - all reasonably requested information to allow StrongLoop to assess - Customer's claim and, at StrongLoop's sole discretion, to provide - alternatives that reduce any adverse impact on StrongLoop's intellectual - property or other rights; or - 1.3.3 allow access or permit use of the Software by any users other than - Customer's employees or authorized third-party contractors who are - providing services to Customer and agree in writing to abide by the terms - of this Agreement, provided further that Customer shall be liable for any - failure by such employees and third-party contractors to comply with the - terms of this Agreement and no usage restrictions, if any, shall be - exceeded; or - 1.3.4 create, develop, license, install, use, or deploy any third party - software or services to circumvent or provide access, permissions or - rights which violate the license keys embedded within the Software; or - 1.3.5 modify or create derivative works based upon the Software or - Documentation; or disclose the results of any benchmark test of the - Software to any third party without StrongLoop's prior written approval; - or - 1.3.6 change any proprietary rights notices which appear in the Software - or Documentation; or - 1.3.7 use the Software as part of a time sharing or service bureau - purposes or in any other resale capacity. + +1.3.1 sell, lease, license, distribute, sublicense or otherwise transfer +in whole or in part, any Software or the Documentation to a third party; +or + +1.3.2 decompile, disassemble, translate, reverse engineer or otherwise +attempt to derive source code from the Software, in whole or in part, nor +shall Customer use any mechanical, electronic or other method to trace, +decompile, disassemble, or identify the source code of the Software or +encourage others to do so, except to the limited extent, if any, that +applicable law permits such acts notwithstanding any contractual +prohibitions, provided, however, before Customer exercises any rights that +Customer believes to be entitled to based on mandatory law, Customer shall +provide StrongLoop with thirty (30) days prior written notice and provide +all reasonably requested information to allow StrongLoop to assess +Customer's claim and, at StrongLoop's sole discretion, to provide +alternatives that reduce any adverse impact on StrongLoop's intellectual +property or other rights; or + +1.3.3 allow access or permit use of the Software by any users other than +Customer's employees or authorized third-party contractors who are +providing services to Customer and agree in writing to abide by the terms +of this Agreement, provided further that Customer shall be liable for any +failure by such employees and third-party contractors to comply with the +terms of this Agreement and no usage restrictions, if any, shall be +exceeded; or + +1.3.4 create, develop, license, install, use, or deploy any third party +software or services to circumvent or provide access, permissions or +rights which violate the license keys embedded within the Software; or + +1.3.5 modify or create derivative works based upon the Software or +Documentation; or disclose the results of any benchmark test of the +Software to any third party without StrongLoop's prior written approval; +or + +1.3.6 change any proprietary rights notices which appear in the Software +or Documentation; or + +1.3.7 use the Software as part of a time sharing or service bureau +purposes or in any other resale capacity. + 1.4 Third-Party Software. The Software may include individual certain software that is owned by third parties, including individual open source software components (the "Third-Party Software"), each of which has its own copyright and @@ -90,6 +104,7 @@ materials accompanying the Software, except that Sections 5 (Warranty Disclaimer) and 6 (Limitation of Liability) also govern Customer's use of the third-party software. Customer agrees to comply with the terms and conditions of the relevant third-party software licenses. + 2. Support Services. StrongLoop has no obligation to provide any support for the Software other than the support services specifically described on the StrongLoop Site for the Subscription level procured by Customer. However, @@ -99,6 +114,7 @@ in using the Software. You can find that community and user feedback on the StrongLoop Site. The use of any information, content or other materials from, contained in or on the StrongLoop Site are subject to the StrongLoop website terms of use located here http://www.strongloop.com/terms-of-service. + 3. Confidentiality. For purposes of this Agreement, "Confidential Information" means any and all information or proprietary materials (in every form and media) not generally known in the relevant trade or industry and which has been or is @@ -141,6 +157,7 @@ StrongLoop, its employees and agents shall be free to use and employ their general skills, know-how, and expertise, and to use, disclose, and employ any generalized ideas, concepts, know-how, methods, techniques or skills gained or learned during the Term or thereafter. + 4. Ownership. StrongLoop shall retain all intellectual property and proprietary rights in the Software, Documentation, and related works, including but not limited to any derivative work of the foregoing and StrongLoop's licensors shall @@ -153,6 +170,7 @@ otherwise disposing, encumbering, or suffering a lien or encumbrance upon or against any interest in the Software. The Software (including any Third-Party Software) contain copyrighted material, trade secrets and other proprietary material of StrongLoop and/or its licensors. + 5. Warranty Disclaimer. THE SOFTWARE (INCLUDING ANY THIRD-PARTY SOFTWARE) AND DOCUMENTATION MADE AVAILABLE TO CUSTOMER ARE PROVIDED "AS-IS" AND STRONGLOOP, ON BEHALF OF ITSELF AND ITS LICENSORS, EXPRESSLY DISCLAIMS ALL WARRANTIES OF ANY @@ -173,39 +191,46 @@ SOFTWARE AND DOCUMENTATION ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE IN THE PLANNING, CONSTRUCTION, MAINTENANCE, CONTROL, OR DIRECT OPERATION OF NUCLEAR FACILITIES, AIRCRAFT NAVIGATION, CONTROL OR COMMUNICATION SYSTEMS, WEAPONS SYSTEMS, OR DIRECT LIFE SUPPORT SYSTEMS. + 6. Limitation of Liability. - 6.1 Exclusion of Liability. IN NO EVENT WILL STRONGLOOP OR ITS LICENSORS - BE LIABLE UNDER THIS AGREEMENT FOR ANY INDIRECT, RELIANCE, PUNITIVE, - CONSEQUENTIAL, SPECIAL, EXEMPLARY, OR INCIDENTAL DAMAGES OF ANY KIND AND - HOWEVER CAUSED (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF - BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION AND - THE LIKE), EVEN IF STRONGLOOP HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH - DAMAGES. CUSTOMER BEARS FULL RESPONSIBILITY FOR USE OF THE SOFTWARE AND - THE SUBSCRIPTION AND STRONGLOOP DOES NOT GUARANTEE THAT THE USE OF THE - SOFTWARE AND SUBSCRIPTION WILL ENSURE THAT CUSTOMER'S NETWORK WILL BE - AVAILABLE, SECURE, MONITORED OR PROTECTED AGAINST ANY DOWNTIME, DENIAL OF - SERVICE ATTACKS, SECUITY BREACHES, HACKERS AND THE LIKE. IN NO EVENT WILL - STRONGLOOP'S CUMULATIVE LIABILITY FOR ANY DAMAGES, LOSSES AND CAUSES OF - ACTION (WHETHER IN CONTRACT, TORT, INCLUDING NEGLIGENCE, OR OTHERWISE) - ARISING OUT OF OR RELATED TO THIS AGREEMENT EXCEED THE GREATER OF ONE - HUNDRED DOLLARS (US$100) OR THE TOTAL SUBSCRIPTION FEES PAID BY CUSTOMER - TO STRONGLOOP IN THE TWELVE (12) MONTHS PRECEDING THE DATE THE CLAIM - ARISES. - 6.2 Limitation of Damages. IN NO EVENT WILL STRONGLOOP'S LICENSORS HAVE - ANY LIABILITY FOR ANY CLAIM ARISING IN CONNECTION WITH THIS AGREEMENT. - THE PROVISIONS OF THIS SECTION 6 ALLOCATE RISKS UNDER THIS AGREEMENT - BETWEEN CUSTOMER, STRONGLOOP AND STRONGLOOP'S SUPPLIERS. THE FOREGOING - LIMITATIONS, EXCLUSIONS AND DISCLAIMERS APPLY TO THE MAXIMUM EXTENT - PERMITTED BY APPLICABLE LAW, EVEN IF ANY REMEDY FAILS IN ITS ESSENTIAL - PURPOSE. - 6.3 Failure of Essential Purpose. THE PARTIES AGREE THAT THESE - LIMITATIONS SHALL APPLY EVEN IF THIS AGREEMENT OR ANY LIMITED REMEDY - SPECIFIED HEREIN IS FOUND TO HAVE FAILED OF ITS ESSENTIAL PURPOSE. - 6.4 Allocation of Risk. The sections on limitation of liability and - disclaimer of warranties allocate the risks in the Agreement between the - parties. This allocation is an essential element of the basis of the - bargain between the parties. + +6.1 Exclusion of Liability. IN NO EVENT WILL STRONGLOOP OR ITS LICENSORS +BE LIABLE UNDER THIS AGREEMENT FOR ANY INDIRECT, RELIANCE, PUNITIVE, +CONSEQUENTIAL, SPECIAL, EXEMPLARY, OR INCIDENTAL DAMAGES OF ANY KIND AND +HOWEVER CAUSED (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF +BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION AND +THE LIKE), EVEN IF STRONGLOOP HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. CUSTOMER BEARS FULL RESPONSIBILITY FOR USE OF THE SOFTWARE AND +THE SUBSCRIPTION AND STRONGLOOP DOES NOT GUARANTEE THAT THE USE OF THE +SOFTWARE AND SUBSCRIPTION WILL ENSURE THAT CUSTOMER'S NETWORK WILL BE +AVAILABLE, SECURE, MONITORED OR PROTECTED AGAINST ANY DOWNTIME, DENIAL OF +SERVICE ATTACKS, SECUITY BREACHES, HACKERS AND THE LIKE. IN NO EVENT WILL +STRONGLOOP'S CUMULATIVE LIABILITY FOR ANY DAMAGES, LOSSES AND CAUSES OF +ACTION (WHETHER IN CONTRACT, TORT, INCLUDING NEGLIGENCE, OR OTHERWISE) +ARISING OUT OF OR RELATED TO THIS AGREEMENT EXCEED THE GREATER OF ONE +HUNDRED DOLLARS (US$100) OR THE TOTAL SUBSCRIPTION FEES PAID BY CUSTOMER +TO STRONGLOOP IN THE TWELVE (12) MONTHS PRECEDING THE DATE THE CLAIM +ARISES. + +6.2 Limitation of Damages. IN NO EVENT WILL STRONGLOOP'S LICENSORS HAVE +ANY LIABILITY FOR ANY CLAIM ARISING IN CONNECTION WITH THIS AGREEMENT. +THE PROVISIONS OF THIS SECTION 6 ALLOCATE RISKS UNDER THIS AGREEMENT +BETWEEN CUSTOMER, STRONGLOOP AND STRONGLOOP'S SUPPLIERS. THE FOREGOING +LIMITATIONS, EXCLUSIONS AND DISCLAIMERS APPLY TO THE MAXIMUM EXTENT +PERMITTED BY APPLICABLE LAW, EVEN IF ANY REMEDY FAILS IN ITS ESSENTIAL +PURPOSE. + +6.3 Failure of Essential Purpose. THE PARTIES AGREE THAT THESE +LIMITATIONS SHALL APPLY EVEN IF THIS AGREEMENT OR ANY LIMITED REMEDY +SPECIFIED HEREIN IS FOUND TO HAVE FAILED OF ITS ESSENTIAL PURPOSE. + +6.4 Allocation of Risk. The sections on limitation of liability and +disclaimer of warranties allocate the risks in the Agreement between the +parties. This allocation is an essential element of the basis of the +bargain between the parties. + 7. Term and Termination. + 7.1 This Agreement shall commence on the Effective Date and continue for so long as Customer has a valid Subscription and is current on the payment of any Subscription Fees required to be paid for that Subscription (the "Subscription @@ -214,8 +239,10 @@ notice to the other party, and the Subscription and licenses granted hereunder automatically terminate upon the termination of this Agreement. This Agreement will terminate immediately without notice from StrongLoop if Customer fails to comply with or otherwise breaches any provision of this Agreement. + 7.2 All Sections other than Section 1.1 (Subscriptions) and 1.2 (Licenses) shall survive the expiration or termination of this Agreement. + 8. Subscription Fees and Payments. StrongLoop, Customer agrees to pay StrongLoop the Subscription Fees as described on the StrongLoop Site for the Subscription purchased unless a different amount has been agreed to in a @@ -242,7 +269,9 @@ ensure that, after such deduction or withholding, StrongLoop receives and retains, free from liability for such deduction or withholding, a net amount equal to the amount StrongLoop would have received and retained absent the required deduction or withholding. + 9. General. + 9.1 Compliance with Laws. Customer shall abide by all local, state, federal and international laws, rules, regulations and orders applying to Customer's use of the Software, including, without limitation, the laws and regulations of the @@ -250,6 +279,7 @@ United States that may restrict the export and re-export of certain commodities and technical data of United States origin, including the Software. Customer agrees that it will not export or re-export the Software without the appropriate United States or foreign government licenses. + 9.2 Entire Agreement. This Agreement constitutes the entire agreement between the parties concerning the subject matter hereof. This Agreement supersedes all prior or contemporaneous discussions, proposals and agreements between the @@ -258,19 +288,24 @@ waiver of any provision of this Agreement shall be effective unless in writing and signed by both parties. Any additional or different terms on any purchase orders issued by Customer to StrongLoop shall not be binding on either party, are hereby rejected by StrongLoop and void. + 9.3 Severability. If any provision of this Agreement is held to be invalid or unenforceable, the remaining portions shall remain in full force and effect and such provision shall be enforced to the maximum extent possible so as to effect the intent of the parties and shall be reformed to the extent necessary to make such provision valid and enforceable. + 9.4 Waiver. No waiver of rights by either party may be implied from any actions or failures to enforce rights under this Agreement. + 9.5 Force Majeure. Neither party shall be liable to the other for any delay or failure to perform due to causes beyond its reasonable control (excluding payment of monies due). + 9.6 No Third Party Beneficiaries. Unless otherwise specifically stated, the terms of this Agreement are intended to be and are solely for the benefit of StrongLoop and Customer and do not create any right in favor of any third party. + 9.7 Governing Law and Jurisdiction. This Agreement shall be governed by the laws of the State of California, without reference to the principles of conflicts of law. The provisions of the Uniform Computerized Information @@ -282,7 +317,7 @@ County, California. Any litigation related to this Agreement shall be brought in the state or federal courts located in San Francisco County, California, and only in those courts and each party irrevocably waives any objections to such venue. + 9.8 Notices. All notices must be in writing and shall be effective three (3) days after the date sent to the other party's headquarters, Attention Chief Financial Officer. - diff --git a/package.json b/package.json index 3b6497c..057cc70 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,7 @@ "type": "git", "url": "https://github.com/strongloop/loopback-connector-mssql.git" }, - "license": { - "name": "StrongLoop", - "url": "https://github.com/strongloop/loopback-connector-mssql/blob/master/LICENSE" - }, + "license": "SEE LICENSE IN LICENSE.md", "optionalDependencies": { "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.3.1" } From 65561e527423fa14e9160fb88c2b46e7c2252743 Mon Sep 17 00:00:00 2001 From: Sam Roberts Date: Tue, 3 Nov 2015 12:27:49 -0800 Subject: [PATCH 115/239] Refer to licenses with a link --- LICENSE.md | 322 +---------------------------------------------------- 1 file changed, 2 insertions(+), 320 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index a49c14b..8c40f09 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,323 +1,5 @@ Copyright (c) 2013-2015 StrongLoop, Inc. -StrongLoop License +You may use this library under the terms of the [StrongLoop Subscription Agreement][]. -STRONGLOOP SUBSCRIPTION AGREEMENT - -PLEASE READ THIS AGREEMENT CAREFULLY BEFORE YOU AGREE TO THESE TERMS. IF YOU -ARE ACTING ON BEHALF OF AN ENTITY, THEN YOU REPRESENT THAT YOU HAVE THE -AUTHORITY TO ENTER INTO THIS AGREEMENT ON BEHALF OF THAT ENTITY. IF YOU DO NOT -AGREE TO THESE TERMS, YOU SHOULD NOT AGREE TO THE TERMS OF THIS AGREEMENT OR -INSTALL OR USE THE SOFTWARE. - -This StrongLoop Subscription Agreement ("Agreement") is made by and between -StrongLoop, Inc. ("StrongLoop") with its principal place of business at 107 S. -B St, Suite 220, San Mateo, CA 94401 and the person or entity entering into this -Agreement ("Customer"). The effective date ("Effective Date") of this Agreement -is the date Customer agrees to these terms or installs or uses the Software (as -defined below). This Agreement applies to Customer's use of the Software but it -shall be superseded by any signed agreement between you and StrongLoop -concerning the Software. - -1. Subscriptions and Licenses. - -1.1 Subscriptions. StrongLoop offers five different subscription levels to its -customers, each as more particularly described on StrongLoop's website located -at www.strongloop.com (the "StrongLoop Site"): (1) Free; (2) Developer; (3) -Professional; (4) Gold; and (5) Platinum. The actual subscription level -applicable to Customer (the "Subscription") will be specified in the purchase -order that Customer issues to StrongLoop. This Agreement applies to Customer -regardless of the level of the Subscription selected by Customer and whether or -not Customer upgrades or downgrades its Subscription. StrongLoop hereby agrees -to provide the services as described on the StrongLoop Site for each -Subscription level during the term for which Customer has purchased the -applicable Subscription, subject to Customer paying the fees applicable to the -Subscription level purchased, if any (the "Subscription Fees"). StrongLoop may -modify the services to be provided under any Subscription upon notice to -Customer. - -1.2 License Grant. Subject to the terms and conditions of this Agreement, -StrongLoop grants to Customer, during the Subscription Term (as defined in -Section 7.1 (Term and Termination) of this Agreement, a limited, non-exclusive, -non-transferable right and license, to install and use the StrongLoop Suite -software (the "Software") and the documentation made available electronically as -part of the Software (the "Documentation"), either of which may be modified -during the Term (as defined in Section 7.1 below), solely for development, -production and commercial purposes so long as Customer is using the Software to -run only one process on a given operating system at a time. This Agreement, -including but not limited to the license and restrictions contained herein, -apply to Customer regardless of whether Customer accesses the Software via -download from the StrongLoop Site or through a third-party website or service, -even if Customer acquired the Software prior to agreeing to this Agreement. - -1.3 License Restrictions. Customer shall not itself, or through any parent, -subsidiary, affiliate, agent or other third party: - -1.3.1 sell, lease, license, distribute, sublicense or otherwise transfer -in whole or in part, any Software or the Documentation to a third party; -or - -1.3.2 decompile, disassemble, translate, reverse engineer or otherwise -attempt to derive source code from the Software, in whole or in part, nor -shall Customer use any mechanical, electronic or other method to trace, -decompile, disassemble, or identify the source code of the Software or -encourage others to do so, except to the limited extent, if any, that -applicable law permits such acts notwithstanding any contractual -prohibitions, provided, however, before Customer exercises any rights that -Customer believes to be entitled to based on mandatory law, Customer shall -provide StrongLoop with thirty (30) days prior written notice and provide -all reasonably requested information to allow StrongLoop to assess -Customer's claim and, at StrongLoop's sole discretion, to provide -alternatives that reduce any adverse impact on StrongLoop's intellectual -property or other rights; or - -1.3.3 allow access or permit use of the Software by any users other than -Customer's employees or authorized third-party contractors who are -providing services to Customer and agree in writing to abide by the terms -of this Agreement, provided further that Customer shall be liable for any -failure by such employees and third-party contractors to comply with the -terms of this Agreement and no usage restrictions, if any, shall be -exceeded; or - -1.3.4 create, develop, license, install, use, or deploy any third party -software or services to circumvent or provide access, permissions or -rights which violate the license keys embedded within the Software; or - -1.3.5 modify or create derivative works based upon the Software or -Documentation; or disclose the results of any benchmark test of the -Software to any third party without StrongLoop's prior written approval; -or - -1.3.6 change any proprietary rights notices which appear in the Software -or Documentation; or - -1.3.7 use the Software as part of a time sharing or service bureau -purposes or in any other resale capacity. - -1.4 Third-Party Software. The Software may include individual certain software -that is owned by third parties, including individual open source software -components (the "Third-Party Software"), each of which has its own copyright and -its own applicable license conditions. Such third-party software is licensed to -Customer under the terms of the applicable third-party licenses and/or copyright -notices that can be found in the LICENSES file, the Documentation or other -materials accompanying the Software, except that Sections 5 (Warranty -Disclaimer) and 6 (Limitation of Liability) also govern Customer's use of the -third-party software. Customer agrees to comply with the terms and conditions -of the relevant third-party software licenses. - -2. Support Services. StrongLoop has no obligation to provide any support for -the Software other than the support services specifically described on the -StrongLoop Site for the Subscription level procured by Customer. However, -StrongLoop has endeavored to establish a community of users of the Software who -have provided their own feedback, hints and advice regarding their experiences -in using the Software. You can find that community and user feedback on the -StrongLoop Site. The use of any information, content or other materials from, -contained in or on the StrongLoop Site are subject to the StrongLoop website -terms of use located here http://www.strongloop.com/terms-of-service. - -3. Confidentiality. For purposes of this Agreement, "Confidential Information" -means any and all information or proprietary materials (in every form and media) -not generally known in the relevant trade or industry and which has been or is -hereafter disclosed or made available by StrongLoop to Customer in connection -with the transactions contemplated under this Agreement, including (i) all trade -secrets, (ii) existing or contemplated Software, services, designs, technology, -processes, technical data, engineering, techniques, methodologies and concepts -and any related information, and (iii) information relating to business plans, -sales or marketing methods and customer lists or requirements. For a period of -five (5) years from the date of disclosure of the applicable Confidential -Information, Customer shall (i) hold the Confidential Information in trust and -confidence and avoid the disclosure or release thereof to any other person or -entity by using the same degree of care as it uses to avoid unauthorized use, -disclosure, or dissemination of its own Confidential Information of a similar -nature, but not less than reasonable care, and (ii) not use the Confidential -Information for any purpose whatsoever except as expressly contemplated under -this Agreement; provided that, to the extent the Confidential Information -constitutes a trade secret under law, Customer agrees to protect such -information for so long as it qualifies as a trade secret under applicable law. -Customer shall disclose the Confidential Information only to those of its -employees and contractors having a need to know such Confidential Information -and shall take all reasonable precautions to ensure that such employees and -contractors comply with the provisions of this Section. The obligations of -Customer under this Section shall not apply to information that Customer can -demonstrate (i) was in its possession at the time of disclosure and without -restriction as to confidentiality, (ii) at the time of disclosure is generally -available to the public or after disclosure becomes generally available to the -public through no breach of agreement or other wrongful act by Customer, (iii) -has been received from a third party without restriction on disclosure and -without breach of agreement by Customer, or (iv) is independently developed by -Customer without regard to the Confidential Information. In addition, Customer -may disclose Confidential Information as required to comply with binding orders -of governmental entities that have jurisdiction over it; provided that Customer -gives StrongLoop reasonable written notice to allow StrongLoop to seek a -protective order or other appropriate remedy, discloses only such Confidential -Information as is required by the governmental entity, and uses commercially -reasonable efforts to obtain confidential treatment for any Confidential -Information disclosed. Notwithstanding the above, Customer agrees that -StrongLoop, its employees and agents shall be free to use and employ their -general skills, know-how, and expertise, and to use, disclose, and employ any -generalized ideas, concepts, know-how, methods, techniques or skills gained or -learned during the Term or thereafter. - -4. Ownership. StrongLoop shall retain all intellectual property and proprietary -rights in the Software, Documentation, and related works, including but not -limited to any derivative work of the foregoing and StrongLoop's licensors shall -retain all intellectual property and proprietary rights in any Third-Party -Software that may be provided with or as a part of the Software. Customer shall -do nothing inconsistent with StrongLoop's or its licensors' title to the -Software and the intellectual property rights embodied therein, including, but -not limited to, transferring, loaning, selling, assigning, pledging, or -otherwise disposing, encumbering, or suffering a lien or encumbrance upon or -against any interest in the Software. The Software (including any Third-Party -Software) contain copyrighted material, trade secrets and other proprietary -material of StrongLoop and/or its licensors. - -5. Warranty Disclaimer. THE SOFTWARE (INCLUDING ANY THIRD-PARTY SOFTWARE) AND -DOCUMENTATION MADE AVAILABLE TO CUSTOMER ARE PROVIDED "AS-IS" AND STRONGLOOP, -ON BEHALF OF ITSELF AND ITS LICENSORS, EXPRESSLY DISCLAIMS ALL WARRANTIES OF ANY -KIND, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, TITLE, -PERFORMANCE, AND ACCURACY AND ANY IMPLIED WARRANTIES ARISING FROM STATUTE, -COURSE OF DEALING, COURSE OF PERFORMANCE, OR USAGE OF TRADE. STRONGLOOP DOES -NOT WARRANT THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR -ERROR-FREE, THAT DEFECTS IN THE SOFTWARE WILL BE CORRECTED OR THAT THE SOFTWARE -WILL PROVIDE OR ENSURE ANY PARTICULAR RESULTS OR OUTCOME. NO ORAL OR WRITTEN -INFORMATION OR ADVICE GIVEN BY STRONGLOOP OR ITS AUTHORIZED REPRESENTATIVES -SHALL CREATE A WARRANTY OR IN ANY WAY INCREASE THE SCOPE OF THIS WARRANTY. -STRONGLOOP IS NOT OBLIGATED TO PROVIDE CUSTOMER WITH UPGRADES TO THE SOFTWARE, -BUT MAY ELECT TO DO SO IN ITS SOLE DISCRETION. SOME JURISDICTIONS DO NOT ALLOW -THE EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT APPLY TO -CUSTOMER.WITHOUT LIMITING THE GENERALITY OF THE FOREGOING DISCLAIMER, THE -SOFTWARE AND DOCUMENTATION ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE IN -THE PLANNING, CONSTRUCTION, MAINTENANCE, CONTROL, OR DIRECT OPERATION OF NUCLEAR -FACILITIES, AIRCRAFT NAVIGATION, CONTROL OR COMMUNICATION SYSTEMS, WEAPONS -SYSTEMS, OR DIRECT LIFE SUPPORT SYSTEMS. - -6. Limitation of Liability. - -6.1 Exclusion of Liability. IN NO EVENT WILL STRONGLOOP OR ITS LICENSORS -BE LIABLE UNDER THIS AGREEMENT FOR ANY INDIRECT, RELIANCE, PUNITIVE, -CONSEQUENTIAL, SPECIAL, EXEMPLARY, OR INCIDENTAL DAMAGES OF ANY KIND AND -HOWEVER CAUSED (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF -BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION AND -THE LIKE), EVEN IF STRONGLOOP HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. CUSTOMER BEARS FULL RESPONSIBILITY FOR USE OF THE SOFTWARE AND -THE SUBSCRIPTION AND STRONGLOOP DOES NOT GUARANTEE THAT THE USE OF THE -SOFTWARE AND SUBSCRIPTION WILL ENSURE THAT CUSTOMER'S NETWORK WILL BE -AVAILABLE, SECURE, MONITORED OR PROTECTED AGAINST ANY DOWNTIME, DENIAL OF -SERVICE ATTACKS, SECUITY BREACHES, HACKERS AND THE LIKE. IN NO EVENT WILL -STRONGLOOP'S CUMULATIVE LIABILITY FOR ANY DAMAGES, LOSSES AND CAUSES OF -ACTION (WHETHER IN CONTRACT, TORT, INCLUDING NEGLIGENCE, OR OTHERWISE) -ARISING OUT OF OR RELATED TO THIS AGREEMENT EXCEED THE GREATER OF ONE -HUNDRED DOLLARS (US$100) OR THE TOTAL SUBSCRIPTION FEES PAID BY CUSTOMER -TO STRONGLOOP IN THE TWELVE (12) MONTHS PRECEDING THE DATE THE CLAIM -ARISES. - -6.2 Limitation of Damages. IN NO EVENT WILL STRONGLOOP'S LICENSORS HAVE -ANY LIABILITY FOR ANY CLAIM ARISING IN CONNECTION WITH THIS AGREEMENT. -THE PROVISIONS OF THIS SECTION 6 ALLOCATE RISKS UNDER THIS AGREEMENT -BETWEEN CUSTOMER, STRONGLOOP AND STRONGLOOP'S SUPPLIERS. THE FOREGOING -LIMITATIONS, EXCLUSIONS AND DISCLAIMERS APPLY TO THE MAXIMUM EXTENT -PERMITTED BY APPLICABLE LAW, EVEN IF ANY REMEDY FAILS IN ITS ESSENTIAL -PURPOSE. - -6.3 Failure of Essential Purpose. THE PARTIES AGREE THAT THESE -LIMITATIONS SHALL APPLY EVEN IF THIS AGREEMENT OR ANY LIMITED REMEDY -SPECIFIED HEREIN IS FOUND TO HAVE FAILED OF ITS ESSENTIAL PURPOSE. - -6.4 Allocation of Risk. The sections on limitation of liability and -disclaimer of warranties allocate the risks in the Agreement between the -parties. This allocation is an essential element of the basis of the -bargain between the parties. - -7. Term and Termination. - -7.1 This Agreement shall commence on the Effective Date and continue for so long -as Customer has a valid Subscription and is current on the payment of any -Subscription Fees required to be paid for that Subscription (the "Subscription -Term"). Either party may terminate this Agreement immediately upon written -notice to the other party, and the Subscription and licenses granted hereunder -automatically terminate upon the termination of this Agreement. This Agreement -will terminate immediately without notice from StrongLoop if Customer fails to -comply with or otherwise breaches any provision of this Agreement. - -7.2 All Sections other than Section 1.1 (Subscriptions) and 1.2 (Licenses) shall -survive the expiration or termination of this Agreement. - -8. Subscription Fees and Payments. StrongLoop, Customer agrees to pay -StrongLoop the Subscription Fees as described on the StrongLoop Site for the -Subscription purchased unless a different amount has been agreed to in a -separate agreement between Customer and StrongLoop. In addition, Customer shall -pay all sales, use, value added, withholding, excise taxes and other tax, duty, -custom and similar fees levied upon the delivery or use of the Software and the -Subscriptions described in this Agreement. Fees shall be invoiced in full upon -StrongLoop's acceptance of Customer's purchase order for the Subscription. All -invoices shall be paid in US dollars and are due upon receipt and shall be paid -within thirty (30) days. Payments shall be made without right of set-off or -chargeback. If Customer does not pay the invoices when due, StrongLoop may -charge interest at one percent (1%) per month or the highest rate permitted by -law, whichever is lower, on the unpaid balance from the original due date. If -Customer fails to pay fees in accordance with this Section, StrongLoop may -suspend fulfilling its obligations under this Agreement (including but not -limited to suspending the services under the Subscription) until payment is -received by StrongLoop. If any applicable law requires Customer to withhold -amounts from any payments to StrongLoop under this Agreement, (a) Customer shall -effect such withholding, remit such amounts to the appropriate taxing -authorities and promptly furnish StrongLoop with tax receipts evidencing the -payments of such amounts and (b) the sum payable by Customer upon which the -deduction or withholding is based shall be increased to the extent necessary to -ensure that, after such deduction or withholding, StrongLoop receives and -retains, free from liability for such deduction or withholding, a net amount -equal to the amount StrongLoop would have received and retained absent the -required deduction or withholding. - -9. General. - -9.1 Compliance with Laws. Customer shall abide by all local, state, federal and -international laws, rules, regulations and orders applying to Customer's use of -the Software, including, without limitation, the laws and regulations of the -United States that may restrict the export and re-export of certain commodities -and technical data of United States origin, including the Software. Customer -agrees that it will not export or re-export the Software without the appropriate -United States or foreign government licenses. - -9.2 Entire Agreement. This Agreement constitutes the entire agreement between -the parties concerning the subject matter hereof. This Agreement supersedes all -prior or contemporaneous discussions, proposals and agreements between the -parties relating to the subject matter hereof. No amendment, modification or -waiver of any provision of this Agreement shall be effective unless in writing -and signed by both parties. Any additional or different terms on any purchase -orders issued by Customer to StrongLoop shall not be binding on either party, -are hereby rejected by StrongLoop and void. - -9.3 Severability. If any provision of this Agreement is held to be invalid or -unenforceable, the remaining portions shall remain in full force and effect and -such provision shall be enforced to the maximum extent possible so as to effect -the intent of the parties and shall be reformed to the extent necessary to make -such provision valid and enforceable. - -9.4 Waiver. No waiver of rights by either party may be implied from any actions -or failures to enforce rights under this Agreement. - -9.5 Force Majeure. Neither party shall be liable to the other for any delay or -failure to perform due to causes beyond its reasonable control (excluding -payment of monies due). - -9.6 No Third Party Beneficiaries. Unless otherwise specifically stated, the -terms of this Agreement are intended to be and are solely for the benefit of -StrongLoop and Customer and do not create any right in favor of any third party. - -9.7 Governing Law and Jurisdiction. This Agreement shall be governed by the -laws of the State of California, without reference to the principles of -conflicts of law. The provisions of the Uniform Computerized Information -Transaction Act and United Nations Convention on Contracts for the International -Sale of Goods shall not apply to this Agreement. The parties shall attempt to -resolve any dispute related to this Agreement informally, initially through -their respective management, and then by non-binding mediation in San Francisco -County, California. Any litigation related to this Agreement shall be brought -in the state or federal courts located in San Francisco County, California, and -only in those courts and each party irrevocably waives any objections to such -venue. - -9.8 Notices. All notices must be in writing and shall be effective three (3) -days after the date sent to the other party's headquarters, Attention Chief -Financial Officer. +[StrongLoop Subscription Agreement]: http://strongloop.com/license From c54db7d5561ff1a11fa0e324d8a16331fa4631b3 Mon Sep 17 00:00:00 2001 From: eugene-frb Date: Wed, 11 Nov 2015 10:47:02 -0800 Subject: [PATCH 116/239] Inject Partition By clause into buildColumnNames of SQL query for include filter --- lib/mssql.js | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 5e38b20..ac52bfe 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -342,15 +342,39 @@ function buildLimit(limit, offset) { return sql; } -MsSQL.prototype.buildColumnNames = function(model, filter) { +MsSQL.prototype.buildPartitionByFirst = function(model, where) { + if (!where) { + return ''; + } + + if (typeof where !== 'object' || Array.isArray(where)) { + return ''; + } + + for (var key in where) { + if (key === 'and' || key === 'or') { + return ''; + } + var firstColumnName = this.columnEscaped(model, key); + break; + } + + return 'PARTITION BY ' + firstColumnName; +}; + +MsSQL.prototype.buildColumnNames = function(model, filter, options) { var columnNames = this.invokeSuper('buildColumnNames', model, filter); if (filter.limit || filter.offset || filter.skip) { var orderBy = this.buildOrderBy(model, filter.order); var orderClause = ''; + var partitionByClause = ''; + if(options && options.filterWhere) { + partitionByClause = this.buildPartitionByFirst(model, options.filterWhere); + } if (orderBy) { - orderClause = 'OVER (' + orderBy + ') '; + orderClause = 'OVER (' + partitionByClause + ' ' + orderBy + ') '; } else { - orderClause = 'OVER (ORDER BY (SELECT 1)) '; + orderClause = 'OVER (' + partitionByClause + ' ' + 'ORDER BY (SELECT 1)) '; } columnNames += ',ROW_NUMBER() ' + orderClause + 'AS RowNum'; } @@ -366,7 +390,7 @@ MsSQL.prototype.buildSelect = function(model, filter, options) { } var selectStmt = new ParameterizedSQL('SELECT ' + - this.buildColumnNames(model, filter) + + this.buildColumnNames(model, filter, options) + ' FROM ' + this.tableEscaped(model) ); From af80ab386b4ff099d57344e97fdbb5437a1b17b5 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 18 Nov 2015 14:35:59 -0800 Subject: [PATCH 117/239] 2.3.2 * Inject Partition By clause into buildColumnNames of SQL query for include filter (eugene-frb) * Refer to licenses with a link (Sam Roberts) * Use strongloop conventions for licensing (Sam Roberts) --- CHANGES.md | 10 ++++++++++ package.json | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f210cd7..cbbcf48 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,13 @@ +2015-11-18, Version 2.3.2 +========================= + + * Inject Partition By clause into buildColumnNames of SQL query for include filter (eugene-frb) + + * Refer to licenses with a link (Sam Roberts) + + * Use strongloop conventions for licensing (Sam Roberts) + + 2015-09-11, Version 2.3.1 ========================= diff --git a/package.json b/package.json index 057cc70..4647d3a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.3.1", + "version": "2.3.2", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -36,6 +36,6 @@ }, "license": "SEE LICENSE IN LICENSE.md", "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.3.1" + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.3.2" } } From 99ec9108e66cc8e7cf2fac4a0a6d79242836cbef Mon Sep 17 00:00:00 2001 From: eugene-frb Date: Thu, 19 Nov 2015 11:26:59 -0800 Subject: [PATCH 118/239] Updated option that triggers PARTITION BY injection, fixed buildPartitionByFirst's 'where' argument. Depends on juggler's PR https://github.com/strongloop/loopback-datasource-juggler/pull/764 . --- lib/mssql.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index ac52bfe..789c77f 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -368,8 +368,8 @@ MsSQL.prototype.buildColumnNames = function(model, filter, options) { var orderBy = this.buildOrderBy(model, filter.order); var orderClause = ''; var partitionByClause = ''; - if(options && options.filterWhere) { - partitionByClause = this.buildPartitionByFirst(model, options.filterWhere); + if(options && options.findCaller === 'includeHasMany') { + partitionByClause = this.buildPartitionByFirst(model, filter.where); } if (orderBy) { orderClause = 'OVER (' + partitionByClause + ' ' + orderBy + ') '; From 3927118b260b1dfb7faf6a873359e12d2324240d Mon Sep 17 00:00:00 2001 From: eugene-frb Date: Wed, 25 Nov 2015 13:59:33 -0800 Subject: [PATCH 119/239] Remove buildPartitionBy() that became redundant Depends on juggler's change that now initializes partition by column at its level rather than at mssql level. --- lib/mssql.js | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 789c77f..e933d90 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -342,34 +342,14 @@ function buildLimit(limit, offset) { return sql; } -MsSQL.prototype.buildPartitionByFirst = function(model, where) { - if (!where) { - return ''; - } - - if (typeof where !== 'object' || Array.isArray(where)) { - return ''; - } - - for (var key in where) { - if (key === 'and' || key === 'or') { - return ''; - } - var firstColumnName = this.columnEscaped(model, key); - break; - } - - return 'PARTITION BY ' + firstColumnName; -}; - MsSQL.prototype.buildColumnNames = function(model, filter, options) { var columnNames = this.invokeSuper('buildColumnNames', model, filter); if (filter.limit || filter.offset || filter.skip) { var orderBy = this.buildOrderBy(model, filter.order); var orderClause = ''; var partitionByClause = ''; - if(options && options.findCaller === 'includeHasMany') { - partitionByClause = this.buildPartitionByFirst(model, filter.where); + if(options && options.partitionBy) { + partitionByClause = 'PARTITION BY ' + this.columnEscaped(model, options.partitionBy); } if (orderBy) { orderClause = 'OVER (' + partitionByClause + ' ' + orderBy + ') '; From e8e3eb3a1cb852bfd1211c08644696d33de60b52 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 27 Nov 2015 10:50:05 -0800 Subject: [PATCH 120/239] 2.3.3 * Remove buildPartitionBy() that became redundant (eugene-frb) * Updated option that triggers PARTITION BY injection, fixed buildPartitionByFirst's 'where' argument. (eugene-frb) --- CHANGES.md | 8 ++++++++ package.json | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index cbbcf48..a42faef 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,11 @@ +2015-11-27, Version 2.3.3 +========================= + + * Remove buildPartitionBy() that became redundant (eugene-frb) + + * Updated option that triggers PARTITION BY injection, fixed buildPartitionByFirst's 'where' argument. (eugene-frb) + + 2015-11-18, Version 2.3.2 ========================= diff --git a/package.json b/package.json index 4647d3a..9f508b0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.3.2", + "version": "2.3.3", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -36,6 +36,6 @@ }, "license": "SEE LICENSE IN LICENSE.md", "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.3.2" + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.3.3" } } From 203c930de671650950a17ec3c3d107de33fa7c9d Mon Sep 17 00:00:00 2001 From: Oleksandr Sochka Date: Wed, 20 Jan 2016 05:41:14 +0200 Subject: [PATCH 121/239] Add help for Azure SQL users I spent so much time to learn about this config option, this commit might be useful for other Azure users. --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 40d2abe..ab98b88 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,10 @@ To use it you need `loopback-datasource-juggler`. port: 1433, database: 'mydb', username: 'myuser', - password: 'mypass' + password: 'mypass', + + // You need this if using Microsoft Azure SQL database + // options: { encrypt: true } }); ``` From 9f25190798c88b9d97d50e5fa2d126acd6620a99 Mon Sep 17 00:00:00 2001 From: Simon Ho Date: Wed, 23 Dec 2015 18:03:50 -0800 Subject: [PATCH 122/239] Upgrade should to 8.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9f508b0..f3af766 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "loopback-datasource-juggler": "^2.28.0", "mocha": "^2.1.0", "rc": "^1.0.0", - "should": "^5.0.0", + "should": "^8.0.2", "sinon": "^1.15.4" }, "scripts": { From 6996e6a0a9facff45c3239652512b9e311b3969a Mon Sep 17 00:00:00 2001 From: Oleksandr Sochka Date: Wed, 20 Jan 2016 05:41:14 +0200 Subject: [PATCH 123/239] Add help for Azure SQL users I spent so much time to learn about this config option, this commit might be useful for other Azure users. --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 40d2abe..ab98b88 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,10 @@ To use it you need `loopback-datasource-juggler`. port: 1433, database: 'mydb', username: 'myuser', - password: 'mypass' + password: 'mypass', + + // You need this if using Microsoft Azure SQL database + // options: { encrypt: true } }); ``` From a6fd32c34c49419882aae66c0416ad9591f3db96 Mon Sep 17 00:00:00 2001 From: Simon Ho Date: Wed, 23 Dec 2015 18:03:50 -0800 Subject: [PATCH 124/239] Upgrade should to 8.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9f508b0..f3af766 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "loopback-datasource-juggler": "^2.28.0", "mocha": "^2.1.0", "rc": "^1.0.0", - "should": "^5.0.0", + "should": "^8.0.2", "sinon": "^1.15.4" }, "scripts": { From 52550ed31155b1534c4851fe4c1fa2cbe2281b79 Mon Sep 17 00:00:00 2001 From: FoysalOsmany Date: Fri, 5 Feb 2016 16:33:37 +0600 Subject: [PATCH 125/239] Fix for Insert into Table with Active Trigger https://github.com/strongloop/loopback-connector-mssql/issues/21 --- lib/mssql.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index e933d90..5da0c06 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -140,9 +140,17 @@ MsSQL.prototype.buildInsertDefaultValues = function(model, data, options) { MsSQL.prototype.buildInsertInto = function(model, fields, options) { var stmt = this.invokeSuper('buildInsertInto', model, fields, options); var idName = this.idName(model); + + stmt.sql = idName ? (MsSQL.newline + + 'DECLARE @ids TABLE (id VARCHAR(50))' + + MsSQL.newline) + stmt.sql : stmt.sql; + if (idName) { - stmt.merge(MsSQL.newline + 'OUTPUT INSERTED.' + - this.columnEscaped(model, idName) + ' AS insertId'); + stmt.merge( + 'OUTPUT INSERTED.' + + this.columnEscaped(model, idName) + + ' into @ids' + ); } return stmt; }; @@ -170,6 +178,10 @@ MsSQL.prototype.buildInsert = function(model, data, options) { stmt.sql += MsSQL.newline + 'SET IDENTITY_INSERT ' + tblName + ' OFF;' + MsSQL.newline; } + if (idName) { + stmt.sql += MsSQL.newline + 'SELECT id AS insertId from @ids' + MsSQL.newline; + } + return stmt; }; From 2f87f507d072fb8581279f807ba4d791f565117b Mon Sep 17 00:00:00 2001 From: FoysalOsmany Date: Sat, 6 Feb 2016 02:22:01 +0600 Subject: [PATCH 126/239] Refactor Fix for Insert into Table with Active Trigger by getting the column data type instead of varchar. https://github.com/strongloop/loopback-connector-mssql/issues/21 --- lib/mssql.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 5da0c06..c59d7d8 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -142,14 +142,14 @@ MsSQL.prototype.buildInsertInto = function(model, fields, options) { var idName = this.idName(model); stmt.sql = idName ? (MsSQL.newline + - 'DECLARE @ids TABLE (id VARCHAR(50))' + + 'DECLARE @insertedIds TABLE (id ' + this.columnDataType(model, idName) + ')' + MsSQL.newline) + stmt.sql : stmt.sql; if (idName) { stmt.merge( 'OUTPUT INSERTED.' + this.columnEscaped(model, idName) + - ' into @ids' + ' into @insertedIds' ); } return stmt; @@ -179,7 +179,7 @@ MsSQL.prototype.buildInsert = function(model, data, options) { MsSQL.newline; } if (idName) { - stmt.sql += MsSQL.newline + 'SELECT id AS insertId from @ids' + MsSQL.newline; + stmt.sql += MsSQL.newline + 'SELECT id AS insertId from @insertedIds' + MsSQL.newline; } return stmt; From 1a23b94583e84dce8714096b5d3b0d099c15bcaf Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 9 Feb 2016 16:50:44 -0800 Subject: [PATCH 127/239] 2.4.0 * Refactor Fix for Insert into Table with Active Trigger by getting the column data type instead of varchar. https://github.com/strongloop/loopback-connector-mssql/issues/21 (FoysalOsmany) * Fix for Insert into Table with Active Trigger https://github.com/strongloop/loopback-connector-mssql/issues/21 (FoysalOsmany) * Upgrade should to 8.0.2 (Simon Ho) * Add help for Azure SQL users (Oleksandr Sochka) --- CHANGES.md | 12 ++++++++++++ package.json | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a42faef..d3783e7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,15 @@ +2016-02-10, Version 2.4.0 +========================= + + * Refactor Fix for Insert into Table with Active Trigger by getting the column data type instead of varchar. https://github.com/strongloop/loopback-connector-mssql/issues/21 (FoysalOsmany) + + * Fix for Insert into Table with Active Trigger https://github.com/strongloop/loopback-connector-mssql/issues/21 (FoysalOsmany) + + * Upgrade should to 8.0.2 (Simon Ho) + + * Add help for Azure SQL users (Oleksandr Sochka) + + 2015-11-27, Version 2.3.3 ========================= diff --git a/package.json b/package.json index f3af766..a4d3049 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.3.3", + "version": "2.4.0", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -36,6 +36,6 @@ }, "license": "SEE LICENSE IN LICENSE.md", "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.3.3" + "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.4.0" } } From 1a00250e375c2c396ebb8b4931083d17cfce6da0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 19 Feb 2016 10:38:05 +0100 Subject: [PATCH 128/239] Remove sl-blip from dependencies --- package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/package.json b/package.json index a4d3049..c707ebd 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,5 @@ "type": "git", "url": "https://github.com/strongloop/loopback-connector-mssql.git" }, - "license": "SEE LICENSE IN LICENSE.md", - "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback-connector-mssql@2.4.0" - } + "license": "SEE LICENSE IN LICENSE.md" } From f1d7b6deb15786a6a1a074683920d71e885db906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 19 Feb 2016 10:38:48 +0100 Subject: [PATCH 129/239] 2.4.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove sl-blip from dependencies (Miroslav Bajtoš) --- CHANGES.md | 8 +++++++- package.json | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d3783e7..1d5e6f2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,10 @@ -2016-02-10, Version 2.4.0 +2016-02-19, Version 2.4.1 +========================= + + * Remove sl-blip from dependencies (Miroslav Bajtoš) + + +2016-02-09, Version 2.4.0 ========================= * Refactor Fix for Insert into Table with Active Trigger by getting the column data type instead of varchar. https://github.com/strongloop/loopback-connector-mssql/issues/21 (FoysalOsmany) diff --git a/package.json b/package.json index c707ebd..6103b64 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.4.0", + "version": "2.4.1", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", From 2ae6e53879d519c1c95f343084745923a3e10cd7 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 4 Mar 2016 10:41:23 -0800 Subject: [PATCH 130/239] 2.5.0 --- package.json | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 6103b64..5db1699 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.4.1", + "version": "2.5.0", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -13,14 +13,13 @@ ], "main": "index.js", "dependencies": { - "async": "^0.9.0", + "async": "^1.5.2", "debug": "^2.1.1", "loopback-connector": "^2.1.0", - "mssql": "^2.1.0", - "strongloop-license": "^1.0.2" + "mssql": "^3.1.1" }, "devDependencies": { - "bluebird": "^2.9.12", + "bluebird": "^3.3.3", "loopback-datasource-juggler": "^2.28.0", "mocha": "^2.1.0", "rc": "^1.0.0", From 51b4a8e052098230029fd7950cb820f0b31a15bf Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 10 Mar 2016 09:05:23 -0800 Subject: [PATCH 131/239] Remove the license check --- index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/index.js b/index.js index 94446aa..a9094f3 100644 --- a/index.js +++ b/index.js @@ -1,2 +1 @@ -require('strongloop-license')('connectors:mssql'); module.exports = require("./lib/mssql.js"); From 3852f529e98b6619670edc57e3b4e63ef54881b8 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 10 Mar 2016 09:05:56 -0800 Subject: [PATCH 132/239] 2.5.1 * Remove the license check (Raymond Feng) --- CHANGES.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 1d5e6f2..1549715 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,14 @@ +2016-03-10, Version 2.5.1 +========================= + + * Remove the license check (Raymond Feng) + + +2016-03-04, Version 2.5.0 +========================= + + + 2016-02-19, Version 2.4.1 ========================= diff --git a/package.json b/package.json index 5db1699..da751b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.5.0", + "version": "2.5.1", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", From cad27c56ef1b1599c646d5d87b4939551035e130 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 15 Mar 2016 16:49:45 -0700 Subject: [PATCH 133/239] Remove regenerator from babel-runtime and bundle mssql --- package.json | 4 +++- remove-regenerator.js | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 remove-regenerator.js diff --git a/package.json b/package.json index da751b3..49c988a 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "loopback-connector": "^2.1.0", "mssql": "^3.1.1" }, + "bundledDependencies": ["mssql"], "devDependencies": { "bluebird": "^3.3.3", "loopback-datasource-juggler": "^2.28.0", @@ -27,7 +28,8 @@ "sinon": "^1.15.4" }, "scripts": { - "test": "./node_modules/.bin/mocha --timeout 5000 -R spec" + "test": "./node_modules/.bin/mocha --timeout 5000 -R spec", + "prepublish": "node remove-regenerator.js" }, "repository": { "type": "git", diff --git a/remove-regenerator.js b/remove-regenerator.js new file mode 100644 index 0000000..38aeba0 --- /dev/null +++ b/remove-regenerator.js @@ -0,0 +1,19 @@ +/** + * This script removes regenerator bundled with babel-runtime + */ +var fs = require('fs'); +try { + var index = require.resolve('babel-runtime/regenerator/index.js'); + var runtime = require.resolve( + 'babel-runtime/regenerator/runtime.js'); + if (index) { + fs.unlink(index, function(err) { + if (err) console.error(err); + if (runtime) fs.unlink(runtime, function(err) { + if (err) console.error(err); + });; + }); + } +} catch (err) { + // Ignore +} From 9d91d9569f6da0edc8efab36460509de2f2808db Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 15 Mar 2016 22:48:20 -0700 Subject: [PATCH 134/239] 2.6.0 * Remove regenerator from babel-runtime and bundle mssql (Raymond Feng) --- CHANGES.md | 6 ++++++ package.json | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1549715..2a30a2b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,9 @@ +2016-03-16, Version 2.6.0 +========================= + + * Remove regenerator from babel-runtime and bundle mssql (Raymond Feng) + + 2016-03-10, Version 2.5.1 ========================= diff --git a/package.json b/package.json index 49c988a..d93b454 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.5.1", + "version": "2.6.0", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", @@ -18,7 +18,9 @@ "loopback-connector": "^2.1.0", "mssql": "^3.1.1" }, - "bundledDependencies": ["mssql"], + "bundledDependencies": [ + "mssql" + ], "devDependencies": { "bluebird": "^3.3.3", "loopback-datasource-juggler": "^2.28.0", From 012acd314198b27abe484bb6b271d9062a1feba5 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 4 Apr 2016 15:00:21 -0700 Subject: [PATCH 135/239] Use request.input to avoid SQL injection --- lib/mssql.js | 34 +++++++++++++++++++++++++++++----- test/mssql.test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index c59d7d8..eb82078 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -5,6 +5,8 @@ var ParameterizedSQL = SqlConnector.ParameterizedSQL; var util = require("util"); var debug = require('debug')('loopback:connector:mssql'); +mssql.map.register(Number, mssql.BigInt); + var name = "mssql"; exports.name = name; @@ -76,12 +78,27 @@ MsSQL.prototype.connect = function(callback) { }); }; +function parameterizedSQL(sql) { + var count = 0; + var index = -1; + while (true) { + index = sql.indexOf('(?)'); + if (index === -1) { + break; + } + count++; + sql = sql.substring(0, index) + ('@param' + count) + + sql.substring(index + 3); + + } + return sql; +} + MsSQL.prototype.executeSQL = function(sql, params, options, callback) { debug('SQL: %s Parameters: %j', sql, params); - if (Array.isArray(params) && params.length > 0) { - sql = format(sql, params); - debug('Formatted SQL: %s', sql); - } + + // Convert (?) to @paramX + sql = parameterizedSQL(sql); var connection = this.client; @@ -100,6 +117,13 @@ MsSQL.prototype.executeSQL = function(sql, params, options, callback) { if (options.multipleResultSets) { request.multiple = true; } + + if (Array.isArray(params) && params.length > 0) { + for (var i = 0, n = params.length; i < n; i++) { + request.input('param' + (i + 1), params[i]); + } + } + // request.verbose = true; request.query(sql, innerCB); }; @@ -130,7 +154,7 @@ MsSQL.prototype.define = function(modelDefinition) { }; MsSQL.prototype.getPlaceholderForValue = function(key) { - return '(?)'; + return '@param' + key; }; MsSQL.prototype.buildInsertDefaultValues = function(model, data, options) { diff --git a/test/mssql.test.js b/test/mssql.test.js index 581be56..4ce7dca 100644 --- a/test/mssql.test.js +++ b/test/mssql.test.js @@ -150,6 +150,32 @@ describe('mssql connector', function () { }); }); + it('should avoid SQL injection for parameters containing (?)', + function(done) { + var connector = db.connector; + var value1 = '(?)'; + var value2 = ', 1 ); INSERT INTO SQLI_TEST VALUES (1, 2); --'; + + connector.execute('DROP TABLE SQLI_TEST;', function(err) { + connector.execute('CREATE TABLE SQLI_TEST' + + '(V1 VARCHAR(100), V2 VARCHAR(100) )', + function(err) { + if (err) return done(err); + connector.execute('INSERT INTO SQLI_TEST VALUES ( (?), (?) )', + [value1, value2], function(err) { + if (err) return done(err); + connector.execute('SELECT * FROM SQLI_TEST', function(err, data) { + if (err) return done(err); + data.should.be.eql( + [ { V1: '(?)', + V2: ', 1 ); INSERT INTO SQLI_TEST VALUES (1, 2); --' } ]); + done(); + }); + }); + }); + }); + }); + it('should allow string array for inq', function(done) { Post.find({where: {content: {inq: ['C1', 'C2']}}}, function(err, p) { From f7a6880af6a3d1578aac149c0dab93d2b110727b Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 5 Apr 2016 16:21:47 -0700 Subject: [PATCH 136/239] 2.7.0 * Use request.input to avoid SQL injection (Raymond Feng) --- CHANGES.md | 8 +++++++- package.json | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2a30a2b..9a12666 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,10 @@ -2016-03-16, Version 2.6.0 +2016-04-05, Version 2.7.0 +========================= + + * Use request.input to avoid SQL injection (Raymond Feng) + + +2016-03-15, Version 2.6.0 ========================= * Remove regenerator from babel-runtime and bundle mssql (Raymond Feng) diff --git a/package.json b/package.json index d93b454..c049e7f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.6.0", + "version": "2.7.0", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", From 12ab3d1cd3559807222d1e1d16294b56b0c2e99f Mon Sep 17 00:00:00 2001 From: juehou Date: Thu, 24 Mar 2016 15:32:12 -0400 Subject: [PATCH 137/239] override other settings if url provided Handle url in config Override other settings if url provided --- lib/mssql.js | 4 +++- test/connection.js | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 test/connection.js diff --git a/lib/mssql.js b/lib/mssql.js index c59d7d8..ddb97cf 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -28,6 +28,8 @@ function MsSQL(settings) { // this.settings = settings || {}; this.settings.server = this.settings.host || this.settings.hostname; this.settings.user = this.settings.user || this.settings.username; + // use url to override settings if url provided + this.connConfig = this.settings.url || this.settings; this._models = {}; this._idxNames = {}; } @@ -65,7 +67,7 @@ MsSQL.prototype.connect = function(callback) { if (self.client) { return process.nextTick(callback); } - var connection = new mssql.Connection(this.settings, function(err) { + var connection = new mssql.Connection(this.connConfig, function(err) { if (err) { debug('Connection error: ', err); return callback(err); diff --git a/test/connection.js b/test/connection.js new file mode 100644 index 0000000..5638331 --- /dev/null +++ b/test/connection.js @@ -0,0 +1,49 @@ +/* eslint-env node, mocha */ +require('./init.js'); +var assert = require('assert'); +var DataSource = require('loopback-datasource-juggler').DataSource; +var url = require('url'); +var mssqlConnector = require('../'); + +var config; + +before(function() { + config = global.getConfig(); + console.log("config", config); +}); + +describe('testConnection', function() { + it('should pass with valid settings', function(done) { + var db = new DataSource(mssqlConnector, config); + db.ping(done); + }); + + it('ignores all other settings when url is present', function(done) { + var formatedUrl = generateURL(config); + var dbConfig = { + url: formatedUrl, + host: 'invalid-hostname', + port: 80, + database: 'invalid-database', + username: 'invalid-username', + password: 'invalid-password', + }; + + var db = new DataSource(mssqlConnector, dbConfig); + db.ping(done); + }); +}); + +function generateURL(config) { + var urlObj = { + protocol: 'mssql', + auth:config.user + ':' + config.password, + hostname:config.host, + port: config.port, + pathname: config.database, + query: {encrypt: true}, + slashes: true + }; + var formatedUrl = url.format(urlObj); + return formatedUrl; +} From 68773581f349262de20166e4ca50bd69e6218f76 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 7 Apr 2016 14:29:26 -0700 Subject: [PATCH 138/239] Keep float numbers See https://github.com/strongloop/loopback-connector-mssql/issues/77 --- lib/mssql.js | 8 +++++++- test/mssql.test.js | 5 ++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 7d85254..164c279 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -122,7 +122,13 @@ MsSQL.prototype.executeSQL = function(sql, params, options, callback) { if (Array.isArray(params) && params.length > 0) { for (var i = 0, n = params.length; i < n; i++) { - request.input('param' + (i + 1), params[i]); + if (typeof params[i] === 'number' && + params[i] % 1 !== 0) { + // Float number + request.input('param' + (i + 1), mssql.Real, params[i]); + } else { + request.input('param' + (i + 1), params[i]); + } } } diff --git a/test/mssql.test.js b/test/mssql.test.js index 4ce7dca..da7c812 100644 --- a/test/mssql.test.js +++ b/test/mssql.test.js @@ -26,6 +26,7 @@ describe('mssql connector', function () { id: {type: String, id: true, generated: false}, title: { type: String, length: 255, index: true }, content: { type: String }, + rating: {type: Number, mssql: {dataType: 'FLOAT'}}, approved: Boolean }); @@ -221,7 +222,8 @@ describe('mssql connector', function () { }); it('should support string id', function(done) { - PostWithStringId.create({title: 'T1', content: 'C1', approved: true}, + PostWithStringId.create( + {title: 'T1', content: 'C1', approved: true, rating: 3.5}, function(err, p) { should.not.exists(err); p.should.have.property('id'); @@ -229,6 +231,7 @@ describe('mssql connector', function () { PostWithStringId.findById(p.id, function(err, p) { should.not.exists(err); p.should.have.property('title', 'T1'); + p.should.have.property('rating', 3.5); done(); }); }); From ee6ea3aaee5a2c959ba428762ddca3ca4469f9b3 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 7 Apr 2016 16:32:53 -0700 Subject: [PATCH 139/239] 2.7.1 * Keep float numbers (Raymond Feng) * override other settings if url provided (juehou) --- CHANGES.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 9a12666..a0fd8af 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,11 @@ +2016-04-07, Version 2.7.1 +========================= + + * Keep float numbers (Raymond Feng) + + * override other settings if url provided (juehou) + + 2016-04-05, Version 2.7.0 ========================= diff --git a/package.json b/package.json index c049e7f..40f9dbb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.7.0", + "version": "2.7.1", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", From 1febd1cf6f623ee5d13fb1e2ac604ad38b276bed Mon Sep 17 00:00:00 2001 From: Amir Jafarian Date: Wed, 27 Apr 2016 17:22:44 -0400 Subject: [PATCH 140/239] Add eslint infrastructure --- .eslintignore | 0 .eslintrc | 10 ++++++++++ package.json | 4 ++++ 3 files changed, 14 insertions(+) create mode 100644 .eslintignore create mode 100644 .eslintrc diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..e69de29 diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..4ed9690 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,10 @@ +{ + "extends": "loopback", + "rules": { + "max-len": ["error", 80, 4, { + "ignoreComments": true, + "ignoreUrls": true, + "ignorePattern": "^\\s*var\\s.+=\\s*(require\\s*\\()|(/)" + }] + } +} \ No newline at end of file diff --git a/package.json b/package.json index 40f9dbb..8e6e059 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,8 @@ ], "devDependencies": { "bluebird": "^3.3.3", + "eslint": "^2.8.0", + "eslint-config-loopback": "^2.0.0", "loopback-datasource-juggler": "^2.28.0", "mocha": "^2.1.0", "rc": "^1.0.0", @@ -31,6 +33,8 @@ }, "scripts": { "test": "./node_modules/.bin/mocha --timeout 5000 -R spec", + "posttest": "npm run lint", + "lint": "eslint .", "prepublish": "node remove-regenerator.js" }, "repository": { From add7e21ddc2cb7f53601d428217ec64a632264ce Mon Sep 17 00:00:00 2001 From: Amir Jafarian Date: Wed, 27 Apr 2016 17:24:58 -0400 Subject: [PATCH 141/239] Auto-update by eslint --fix --- index.js | 2 +- lib/discovery.js | 24 +++--- lib/migration.js | 88 +++++++++---------- lib/mssql.js | 42 ++++----- remove-regenerator.js | 4 +- test/autoupdate.test.js | 144 +++++++++++++++---------------- test/commontests.js | 104 +++++++++++------------ test/connection.js | 6 +- test/discover.test.js | 76 ++++++++--------- test/id.test.js | 178 +++++++++++++++++++-------------------- test/imported.test.js | 4 +- test/init.js | 14 +-- test/mapping.test.js | 36 ++++---- test/mssql.test.js | 68 +++++++-------- test/transaction.test.js | 12 +-- 15 files changed, 401 insertions(+), 401 deletions(-) diff --git a/index.js b/index.js index a9094f3..c893e7e 100644 --- a/index.js +++ b/index.js @@ -1 +1 @@ -module.exports = require("./lib/mssql.js"); +module.exports = require('./lib/mssql.js'); diff --git a/lib/discovery.js b/lib/discovery.js index 9f2f229..5881688 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -23,7 +23,7 @@ function mixinDiscovery(MsSQL) { if (options.limit) { fetch = fetch + ' FETCH NEXT ' + options.limit + 'ROWS ONLY'; } - if(!orderBy) { + if (!orderBy) { sql += ' ORDER BY 1'; } } @@ -126,7 +126,7 @@ function mixinDiscovery(MsSQL) { * @param {Object} options Options for discovery * @param {Function} [cb] The callback function */ - MsSQL.prototype.discoverModelDefinitions = function (options, cb) { + MsSQL.prototype.discoverModelDefinitions = function(options, cb) { if (!cb && typeof options === 'function') { cb = options; options = {}; @@ -134,16 +134,16 @@ function mixinDiscovery(MsSQL) { options = options || {}; var self = this; - var calls = [function (callback) { + var calls = [function(callback) { self.execute(queryTables(options), callback); }]; if (options.views) { - calls.push(function (callback) { + calls.push(function(callback) { self.execute(queryViews(options), callback); }); } - async.parallel(calls, function (err, data) { + async.parallel(calls, function(err, data) { if (err) { cb(err, data); } else { @@ -179,7 +179,7 @@ function mixinDiscovery(MsSQL) { owner: options.owner || options.schema, table: table, options: options, - cb: cb + cb: cb, }; } @@ -215,7 +215,7 @@ function mixinDiscovery(MsSQL) { * @param {Function} [cb] The callback function * */ - MsSQL.prototype.discoverModelProperties = function (table, options, cb) { + MsSQL.prototype.discoverModelProperties = function(table, options, cb) { var args = getArgs(table, options, cb); var owner = args.owner; table = args.table; @@ -223,11 +223,11 @@ function mixinDiscovery(MsSQL) { cb = args.cb; var sql = queryColumns(owner, table); - var callback = function (err, results) { + var callback = function(err, results) { if (err) { cb(err, results); } else { - results.map(function (r) { + results.map(function(r) { r.type = mysqlDataTypeToJSONType(r.dataType, r.dataLength); }); cb(err, results); @@ -282,7 +282,7 @@ function mixinDiscovery(MsSQL) { * @param {Object} options The options for discovery * @param {Function} [cb] The callback function */ - MsSQL.prototype.discoverPrimaryKeys = function (table, options, cb) { + MsSQL.prototype.discoverPrimaryKeys = function(table, options, cb) { var args = getArgs(table, options, cb); var owner = args.owner; table = args.table; @@ -340,7 +340,7 @@ function mixinDiscovery(MsSQL) { * @param {Object} options The options for discovery * @param {Function} [cb] The callback function */ - MsSQL.prototype.discoverForeignKeys = function (table, options, cb) { + MsSQL.prototype.discoverForeignKeys = function(table, options, cb) { var args = getArgs(table, options, cb); var owner = args.owner; table = args.table; @@ -385,7 +385,7 @@ function mixinDiscovery(MsSQL) { * @param {Object} options The options for discovery * @param {Function} [cb] The callback function */ - MsSQL.prototype.discoverExportedForeignKeys = function (table, options, cb) { + MsSQL.prototype.discoverExportedForeignKeys = function(table, options, cb) { var args = getArgs(table, options, cb); var owner = args.owner; table = args.table; diff --git a/lib/migration.js b/lib/migration.js index 7121c5e..8a5e3aa 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -11,7 +11,7 @@ function mixinMigration(MsSQL) { + ' from INFORMATION_SCHEMA.COLUMNS' + ' where [TABLE_SCHEMA] = \'' + this.schema(model) + '\'' + ' and [TABLE_NAME] = \'' + this.table(model) + '\'' - + ' order by [ORDINAL_POSITION]' + + ' order by [ORDINAL_POSITION]'; this.execute(sql, function(err, fields) { if (err) { return cb && cb(err); @@ -132,7 +132,7 @@ function mixinMigration(MsSQL) { if (!ai[name]) { ai[name] = { info: i, - columns: [] + columns: [], }; } ai[name].columns[i.Seq_in_index - 1] = i.Column_name; @@ -272,7 +272,7 @@ function mixinMigration(MsSQL) { async.each(statements, function(query, fn) { if (checkOnly) { - fn(null, true, {statements: statements, query: query}); + fn(null, true, { statements: statements, query: query }); } else { self.execute(query, fn); } @@ -319,36 +319,36 @@ function mixinMigration(MsSQL) { if (idProp.type === Number) { if (idProp.generated !== false) { sql.push(self.columnEscaped(model, modelPKID) + - " " + self.columnDataType(model, modelPKID) + " IDENTITY(1,1) NOT NULL"); + ' ' + self.columnDataType(model, modelPKID) + ' IDENTITY(1,1) NOT NULL'); } else { sql.push(self.columnEscaped(model, modelPKID) + - " " + self.columnDataType(model, modelPKID) + " NOT NULL"); + ' ' + self.columnDataType(model, modelPKID) + ' NOT NULL'); } continue; } else if (idProp.type === String) { if (idProp.generated !== false) { sql.push(self.columnEscaped(model, modelPKID) + - " [uniqueidentifier] DEFAULT newid() NOT NULL"); + ' [uniqueidentifier] DEFAULT newid() NOT NULL'); } else { - sql.push(self.columnEscaped(model, modelPKID) + " " + - self.propertySettingsSQL(model, prop) + " DEFAULT newid()"); + sql.push(self.columnEscaped(model, modelPKID) + ' ' + + self.propertySettingsSQL(model, prop) + ' DEFAULT newid()'); } continue; } } - sql.push(self.columnEscaped(model, prop) + " " + self.propertySettingsSQL(model, prop)); + sql.push(self.columnEscaped(model, prop) + ' ' + self.propertySettingsSQL(model, prop)); } - var joinedSql = sql.join("," + MsSQL.newline + " "); - var cmd = ""; + var joinedSql = sql.join(',' + MsSQL.newline + ' '); + var cmd = ''; if (modelPKID) { - cmd = "PRIMARY KEY CLUSTERED" + MsSQL.newline + "(" + MsSQL.newline; - cmd += " " + self.columnEscaped(model, modelPKID) + " ASC" + MsSQL.newline; - cmd += ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, " + - "IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)" + cmd = 'PRIMARY KEY CLUSTERED' + MsSQL.newline + '(' + MsSQL.newline; + cmd += ' ' + self.columnEscaped(model, modelPKID) + ' ASC' + MsSQL.newline; + cmd += ') WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ' + + 'IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)'; } - joinedSql += "," + MsSQL.newline + cmd; + joinedSql += ',' + MsSQL.newline + cmd; return joinedSql; }; @@ -369,18 +369,18 @@ function mixinMigration(MsSQL) { if (i.unique) { unique = true; } - var name = prop + "_" + kind + "_" + type + "_idx" + var name = prop + '_' + kind + '_' + type + '_idx'; if (i.name) { name = i.name; } this._idxNames[model].push(name); - var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON " + var cmd = 'CREATE ' + (unique ? 'UNIQUE ' : '') + kind + ' INDEX [' + name + '] ON ' + tblName + MsSQL.newline; - cmd += "(" + MsSQL.newline; - cmd += " [" + prop + "] " + type; - cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF," + - " SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, " + - "ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);" + cmd += '(' + MsSQL.newline; + cmd += ' [' + prop + '] ' + type; + cmd += MsSQL.newline + ') WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,' + + ' SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ' + + 'ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);' + MsSQL.newline; return cmd; }; @@ -401,26 +401,26 @@ function mixinMigration(MsSQL) { if (i.unique) { unique = true; } - var splitcolumns = i.columns.split(","); + var splitcolumns = i.columns.split(','); var columns = []; - var name = ""; + var name = ''; splitcolumns.forEach(function(elem, ind) { var trimmed = elem.trim(); - name += trimmed + "_"; - trimmed = "[" + trimmed + "] " + type; + name += trimmed + '_'; + trimmed = '[' + trimmed + '] ' + type; columns.push(trimmed); }); - name += kind + "_" + type + "_idx" + name += kind + '_' + type + '_idx'; this._idxNames[model].push(name); - var cmd = "CREATE " + (unique ? "UNIQUE " : "") + kind + " INDEX [" + name + "] ON " + var cmd = 'CREATE ' + (unique ? 'UNIQUE ' : '') + kind + ' INDEX [' + name + '] ON ' + tblName + MsSQL.newline; - cmd += "(" + MsSQL.newline; - cmd += columns.join("," + MsSQL.newline); - cmd += MsSQL.newline + ") WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, " + - "SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, " + - "ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);" + cmd += '(' + MsSQL.newline; + cmd += columns.join(',' + MsSQL.newline); + cmd += MsSQL.newline + ') WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ' + + 'SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ' + + 'ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);' + MsSQL.newline; return cmd; }; @@ -469,21 +469,21 @@ function mixinMigration(MsSQL) { var tblName = this.tableEscaped(model); var cmd = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" + tblName + "') AND type in (N'U'))"; - cmd += MsSQL.newline + "BEGIN" + MsSQL.newline; - cmd += " DROP TABLE " + tblName; - cmd += MsSQL.newline + "END"; + cmd += MsSQL.newline + 'BEGIN' + MsSQL.newline; + cmd += ' DROP TABLE ' + tblName; + cmd += MsSQL.newline + 'END'; this.execute(cmd, cb); }; MsSQL.prototype.createTable = function(model, cb) { var tblName = this.tableEscaped(model); - var cmd = "SET ANSI_NULLS ON;" + MsSQL.newline + "SET QUOTED_IDENTIFIER ON;" - + MsSQL.newline + "SET ANSI_PADDING ON;" + MsSQL.newline; + var cmd = 'SET ANSI_NULLS ON;' + MsSQL.newline + 'SET QUOTED_IDENTIFIER ON;' + + MsSQL.newline + 'SET ANSI_PADDING ON;' + MsSQL.newline; cmd += "IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" - + tblName + "') AND type in (N'U'))" + MsSQL.newline + "BEGIN" + MsSQL.newline; - cmd += "CREATE TABLE " + this.tableEscaped(model) + " ("; - cmd += MsSQL.newline + " " + this.propertiesSQL(model) + MsSQL.newline; - cmd += ")" + MsSQL.newline + "END;" + MsSQL.newline; + + tblName + "') AND type in (N'U'))" + MsSQL.newline + 'BEGIN' + MsSQL.newline; + cmd += 'CREATE TABLE ' + this.tableEscaped(model) + ' ('; + cmd += MsSQL.newline + ' ' + this.propertiesSQL(model) + MsSQL.newline; + cmd += ')' + MsSQL.newline + 'END;' + MsSQL.newline; cmd += this.createIndexes(model); this.execute(cmd, cb); }; @@ -508,7 +508,7 @@ function mixinMigration(MsSQL) { } return sql.join(MsSQL.newline); - } + }; MsSQL.prototype.columnDataType = function(model, property) { var columnMetadata = this.columnMetadata(model, property); diff --git a/lib/mssql.js b/lib/mssql.js index 164c279..dab931c 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -1,13 +1,13 @@ /*! Module dependencies */ -var mssql = require("mssql"); +var mssql = require('mssql'); var SqlConnector = require('loopback-connector').SqlConnector; var ParameterizedSQL = SqlConnector.ParameterizedSQL; -var util = require("util"); +var util = require('util'); var debug = require('debug')('loopback:connector:mssql'); mssql.map.register(Number, mssql.BigInt); -var name = "mssql"; +var name = 'mssql'; exports.name = name; exports.initialize = function initializeSchema(dataSource, callback) { @@ -38,7 +38,7 @@ function MsSQL(settings) { util.inherits(MsSQL, SqlConnector); -MsSQL.newline = "\r\n"; +MsSQL.newline = '\r\n'; /*! * This is a workaround to the limitation that 'msssql' driver doesn't support @@ -178,7 +178,7 @@ MsSQL.prototype.buildInsertInto = function(model, fields, options) { MsSQL.newline) + stmt.sql : stmt.sql; if (idName) { - stmt.merge( + stmt.merge( 'OUTPUT INSERTED.' + this.columnEscaped(model, idName) + ' into @insertedIds' @@ -288,24 +288,24 @@ function escape(val) { 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 '\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 "''"; // For sql server, double quote - case "\"": + case '"': return s; // For oracle default: - return "\\" + s; + return '\\' + s; } }); // return "q'#"+val+"#'"; @@ -344,7 +344,7 @@ MsSQL.prototype.toColumnValue = function(prop, val) { } return this.serializeObject(val); -} +}; MsSQL.prototype.fromColumnValue = function(prop, val) { if (val == null) { @@ -392,7 +392,7 @@ MsSQL.prototype.buildColumnNames = function(model, filter, options) { var orderBy = this.buildOrderBy(model, filter.order); var orderClause = ''; var partitionByClause = ''; - if(options && options.partitionBy) { + if (options && options.partitionBy) { partitionByClause = 'PARTITION BY ' + this.columnEscaped(model, options.partitionBy); } if (orderBy) { diff --git a/remove-regenerator.js b/remove-regenerator.js index 38aeba0..5b38d12 100644 --- a/remove-regenerator.js +++ b/remove-regenerator.js @@ -1,6 +1,6 @@ /** * This script removes regenerator bundled with babel-runtime - */ + */ var fs = require('fs'); try { var index = require.resolve('babel-runtime/regenerator/index.js'); @@ -11,7 +11,7 @@ try { if (err) console.error(err); if (runtime) fs.unlink(runtime, function(err) { if (err) console.error(err); - });; + }); ; }); } } catch (err) { diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js index f327b7d..6adc07d 100644 --- a/test/autoupdate.test.js +++ b/test/autoupdate.test.js @@ -2,94 +2,94 @@ require('./init.js'); var assert = require('assert'); var ds; -before(function () { +before(function() { ds = getDataSource(); }); -describe('MS SQL server connector', function () { - it('should auto migrate/update tables', function (done) { +describe('MS SQL server connector', function() { + it('should auto migrate/update tables', function(done) { var schema_v1 = - { - "name": "CustomerTest", - "options": { - "idInjection": false, - "mssql": { - "schema": "dbo", - "table": "CUSTOMER_TEST" - } - }, - "properties": { - "id": { - "type": "String", - "length": 20, - "id": 1 + { + 'name': 'CustomerTest', + 'options': { + 'idInjection': false, + 'mssql': { + 'schema': 'dbo', + 'table': 'CUSTOMER_TEST', }, - "name": { - "type": "String", - "required": false, - "length": 40 }, - "email": { - "type": "String", - "required": true, - "length": 40 + 'properties': { + 'id': { + 'type': 'String', + 'length': 20, + 'id': 1, }, - "age": { - "type": "Number", - "required": false - } - } - } + 'name': { + 'type': 'String', + 'required': false, + 'length': 40, + }, + 'email': { + 'type': 'String', + 'required': true, + 'length': 40, + }, + 'age': { + 'type': 'Number', + 'required': false, + }, + }, + }; var schema_v2 = - { - "name": "CustomerTest", - "options": { - "idInjection": false, - "mssql": { - "schema": "dbo", - "table": "CUSTOMER_TEST" - } - }, - "properties": { - "id": { - "type": "String", - "length": 20, - "id": 1 + { + 'name': 'CustomerTest', + 'options': { + 'idInjection': false, + 'mssql': { + 'schema': 'dbo', + 'table': 'CUSTOMER_TEST', + }, + }, + 'properties': { + 'id': { + 'type': 'String', + 'length': 20, + 'id': 1, + }, + 'email': { + 'type': 'String', + 'required': false, + 'length': 60, + 'mssql': { + 'columnName': 'EMAIL', + 'dataType': 'nvarchar', + 'dataLength': 60, + 'nullable': 'YES', + }, + }, + 'firstName': { + 'type': 'String', + 'required': false, + 'length': 40, }, - "email": { - "type": "String", - "required": false, - "length": 60, - "mssql": { - "columnName": "EMAIL", - "dataType": "nvarchar", - "dataLength": 60, - "nullable": "YES" - } + 'lastName': { + 'type': 'String', + 'required': false, + 'length': 40, }, - "firstName": { - "type": "String", - "required": false, - "length": 40 }, - "lastName": { - "type": "String", - "required": false, - "length": 40 - } - } - } + }; ds.createModel(schema_v1.name, schema_v1.properties, schema_v1.options); - ds.automigrate(function (err) { + ds.automigrate(function(err) { assert(!err); - ds.discoverModelProperties('CUSTOMER_TEST', function (err, props) { + ds.discoverModelProperties('CUSTOMER_TEST', function(err, props) { assert(!err); assert.equal(props.length, 4); - var names = props.map(function (p) { + var names = props.map(function(p) { return p.columnName; }); assert.equal(props[0].nullable, 'NO'); @@ -104,10 +104,10 @@ describe('MS SQL server connector', function () { ds.createModel(schema_v2.name, schema_v2.properties, schema_v2.options); - ds.autoupdate(function (err, result) { - ds.discoverModelProperties('CUSTOMER_TEST', function (err, props) { + ds.autoupdate(function(err, result) { + ds.discoverModelProperties('CUSTOMER_TEST', function(err, props) { assert.equal(props.length, 4); - var names = props.map(function (p) { + var names = props.map(function(p) { return p.columnName; }); assert.equal(names[0], 'id'); diff --git a/test/commontests.js b/test/commontests.js index 5245d26..e6adf94 100644 --- a/test/commontests.js +++ b/test/commontests.js @@ -1,5 +1,5 @@ var jdb = require('loopback-datasource-juggler'), - commonTest = jdb.test; + commonTest = jdb.test; require('./init'); @@ -13,31 +13,31 @@ commonTest.skip('should handle ORDER clause'); //re-implement the order test as pretty much the same thing, but run an automigration beforehand commonTest.it('should automigrate', function(test) { - schema.automigrate(function (err) { - test.ifError(err); - test.done(); + schema.automigrate(function(err) { + test.ifError(err); + test.done(); }); }); commonTest.it('should be able to ORDER results', function(test) { - var titles = [ { title: 'Title A', subject: "B" }, - { title: 'Title Z', subject: "A" }, - { title: 'Title M', subject: "C" }, - { title: 'Title A', subject: "A" }, - { title: 'Title B', subject: "A" }, - { title: 'Title C', subject: "D" }]; + var titles = [{ title: 'Title A', subject: 'B' }, + { title: 'Title Z', subject: 'A' }, + { title: 'Title M', subject: 'C' }, + { title: 'Title A', subject: 'A' }, + { title: 'Title B', subject: 'A' }, + { title: 'Title C', subject: 'D' }]; var dates = [ - new Date(1000 * 5 ), + new Date(1000 * 5), new Date(1000 * 9), new Date(1000 * 0), new Date(1000 * 17), new Date(1000 * 10), - new Date(1000 * 9) + new Date(1000 * 9), ]; - titles.forEach(function (t, i) { - schema.models.Post.create({title: t.title, subject: t.subject, date: dates[i]}, done); + titles.forEach(function(t, i) { + schema.models.Post.create({ title: t.title, subject: t.subject, date: dates[i] }, done); }); var i = 0, tests = 0; @@ -60,10 +60,10 @@ commonTest.it('should be able to ORDER results', function(test) { function doStringTest() { tests += 1; - schema.models.Post.all({order: 'title'}, function (err, posts) { + schema.models.Post.all({ order: 'title' }, function(err, posts) { if (err) console.log(err); test.equal(posts.length, 6); - titles.sort(compare).forEach(function (t, i) { + titles.sort(compare).forEach(function(t, i) { if (posts[i]) test.equal(posts[i].title, t.title); }); finished(); @@ -72,12 +72,12 @@ commonTest.it('should be able to ORDER results', function(test) { function doNumberTest() { tests += 1; - schema.models.Post.all({order: 'date'}, function (err, posts) { + schema.models.Post.all({ order: 'date' }, function(err, posts) { if (err) console.log(err); test.equal(posts.length, 6); - dates.sort(numerically).forEach(function (d, i) { + dates.sort(numerically).forEach(function(d, i) { if (posts[i]) - test.equal(posts[i].date.toString(), d.toString(), 'doNumberTest'); + test.equal(posts[i].date.toString(), d.toString(), 'doNumberTest'); }); finished(); }); @@ -85,11 +85,11 @@ commonTest.it('should be able to ORDER results', function(test) { function doFilterAndSortTest() { tests += 1; - schema.models.Post.all({where: {date: new Date(1000 * 9)}, order: 'title', limit: 3}, function (err, posts) { + schema.models.Post.all({ where: { date: new Date(1000 * 9) }, order: 'title', limit: 3 }, function(err, posts) { if (err) console.log(err); console.log(posts.length); test.equal(posts.length, 2, 'Exactly 2 posts returned by query'); - [ 'Title C', 'Title Z' ].forEach(function (t, i) { + ['Title C', 'Title Z'].forEach(function(t, i) { if (posts[i]) { test.equal(posts[i].title, t, 'doFilterAndSortTest'); } @@ -100,10 +100,10 @@ commonTest.it('should be able to ORDER results', function(test) { function doFilterAndSortReverseTest() { tests += 1; - schema.models.Post.all({where: {date: new Date(1000 * 9)}, order: 'title DESC', limit: 3}, function (err, posts) { + schema.models.Post.all({ where: { date: new Date(1000 * 9) }, order: 'title DESC', limit: 3 }, function(err, posts) { if (err) console.log(err); test.equal(posts.length, 2, 'Exactly 2 posts returned by query'); - [ 'Title Z', 'Title C' ].forEach(function (t, i) { + ['Title Z', 'Title C'].forEach(function(t, i) { if (posts[i]) { test.equal(posts[i].title, t, 'doFilterAndSortReverseTest'); } @@ -114,28 +114,28 @@ commonTest.it('should be able to ORDER results', function(test) { function doMultipleSortTest() { tests += 1; - schema.models.Post.all({order: "title ASC, subject ASC"}, function(err, posts) { + schema.models.Post.all({ order: 'title ASC, subject ASC' }, function(err, posts) { if (err) console.log(err); test.equal(posts.length, 6); - test.equal(posts[0].title, "Title A"); - test.equal(posts[0].subject, "A"); - test.equal(posts[1].title, "Title A"); - test.equal(posts[1].subject, "B"); - test.equal(posts[5].title, "Title Z"); + test.equal(posts[0].title, 'Title A'); + test.equal(posts[0].subject, 'A'); + test.equal(posts[1].title, 'Title A'); + test.equal(posts[1].subject, 'B'); + test.equal(posts[5].title, 'Title Z'); finished(); }); } function doMultipleReverseSortTest() { tests += 1; - schema.models.Post.all({order: "title ASC, subject DESC"}, function(err, posts) { + schema.models.Post.all({ order: 'title ASC, subject DESC' }, function(err, posts) { if (err) console.log(err); test.equal(posts.length, 6); - test.equal(posts[0].title, "Title A"); - test.equal(posts[0].subject, "B"); - test.equal(posts[1].title,"Title A"); - test.equal(posts[1].subject, "A"); - test.equal(posts[5].title, "Title Z"); + test.equal(posts[0].title, 'Title A'); + test.equal(posts[0].subject, 'B'); + test.equal(posts[1].title, 'Title A'); + test.equal(posts[1].subject, 'A'); + test.equal(posts[5].title, 'Title Z'); finished(); }); } @@ -154,20 +154,20 @@ commonTest.it('should be able to ORDER results', function(test) { } }); -commonTest.it("should count posts", function(test) { +commonTest.it('should count posts', function(test) { test.expect(2); - schema.models.Post.count({title:"Title A"}, function(err, cnt) { + schema.models.Post.count({ title:'Title A' }, function(err, cnt) { test.ifError(err); test.equal(cnt, 2); test.done(); }); }); -commonTest.it("should delete a post", function(test) { +commonTest.it('should delete a post', function(test) { schema.models.Post.all({ where:{ - "title":"Title Z" - } + 'title':'Title Z', + }, }, function(err, posts) { test.ifError(err); test.equal(posts.length, 1); @@ -183,11 +183,11 @@ commonTest.it("should delete a post", function(test) { }); }); -commonTest.it("should delete all posts", function(test) { +commonTest.it('should delete all posts', function(test) { test.expect(3); schema.models.Post.destroyAll(function(err) { test.ifError(err); - schema.models.Post.count(function(err, cnt){ + schema.models.Post.count(function(err, cnt) { test.ifError(err); test.equal(cnt, 0); test.done(); @@ -196,34 +196,34 @@ commonTest.it("should delete all posts", function(test) { }); //custom primary keys not quite working :(, hopefully 1602 will implement that functionality in jugglingdb soon. -commonTest.it("should support custom primary key", function(test) { +commonTest.it('should support custom primary key', function(test) { test.expect(3); - var AppliesTo = schema.define("AppliesTo", { + var AppliesTo = schema.define('AppliesTo', { AppliesToID: { type:Number, - primaryKey:true + primaryKey:true, }, Title: { type:String, - limit:100 + limit:100, }, Identifier: { type:String, - limit:100 + limit:100, }, Editable: { - type:Number - } + type:Number, + }, }); - schema.automigrate(function (err) { + schema.automigrate(function(err) { test.ifError(err); - AppliesTo.create({Title:"custom key", Identifier:"ck", Editable:false}, function(err, data) { + AppliesTo.create({ Title:'custom key', Identifier:'ck', Editable:false }, function(err, data) { test.ifError(err); test.notStrictEqual(typeof data.AppliesToID, 'undefined'); test.done(); }); }); -}); \ No newline at end of file +}); diff --git a/test/connection.js b/test/connection.js index 5638331..4ea3ecc 100644 --- a/test/connection.js +++ b/test/connection.js @@ -9,7 +9,7 @@ var config; before(function() { config = global.getConfig(); - console.log("config", config); + console.log('config', config); }); describe('testConnection', function() { @@ -41,8 +41,8 @@ function generateURL(config) { hostname:config.host, port: config.port, pathname: config.database, - query: {encrypt: true}, - slashes: true + query: { encrypt: true }, + slashes: true, }; var formatedUrl = url.format(urlObj); return formatedUrl; diff --git a/test/discover.test.js b/test/discover.test.js index 954e23a..8968dce 100644 --- a/test/discover.test.js +++ b/test/discover.test.js @@ -6,20 +6,20 @@ var assert = require('assert'); var db = getDataSource(); -describe('discoverModels', function () { - describe('Discover models including views', function () { - it('should return an array of tables and views', function (done) { +describe('discoverModels', function() { + describe('Discover models including views', function() { + it('should return an array of tables and views', function(done) { db.discoverModelDefinitions({ views: true, - limit: 3 - }, function (err, models) { + limit: 3, + }, function(err, models) { if (err) { console.error(err); done(err); } else { var views = false; - models.forEach(function (m) { + models.forEach(function(m) { // console.dir(m); if (m.type === 'view') { views = true; @@ -32,19 +32,19 @@ describe('discoverModels', function () { }); }); - describe('Discover models excluding views', function () { - it('should return an array of only tables', function (done) { + describe('Discover models excluding views', function() { + it('should return an array of only tables', function(done) { db.discoverModelDefinitions({ views: false, - limit: 3 - }, function (err, models) { + limit: 3, + }, function(err, models) { if (err) { console.error(err); done(err); } else { var views = false; - models.forEach(function (m) { + models.forEach(function(m) { // console.dir(m); if (m.type === 'view') { views = true; @@ -59,19 +59,19 @@ describe('discoverModels', function () { }); }); -describe('Discover models including other users', function () { - it('should return an array of all tables and views', function (done) { +describe('Discover models including other users', function() { + it('should return an array of all tables and views', function(done) { db.discoverModelDefinitions({ all: true, - limit: 100 - }, function (err, models) { + limit: 100, + }, function(err, models) { if (err) { console.error(err); done(err); } else { var others = false; - models.forEach(function (m) { + models.forEach(function(m) { // console.dir(m); if (m.owner !== 'dbo') { others = true; @@ -84,15 +84,15 @@ describe('Discover models including other users', function () { }); }); -describe('Discover model properties', function () { - describe('Discover a named model', function () { - it('should return an array of columns for product', function (done) { - db.discoverModelProperties('product', function (err, models) { +describe('Discover model properties', function() { + describe('Discover a named model', function() { + it('should return an array of columns for product', function(done) { + db.discoverModelProperties('product', function(err, models) { if (err) { console.error(err); done(err); } else { - models.forEach(function (m) { + models.forEach(function(m) { // console.dir(m); assert(m.tableName === 'product'); }); @@ -104,14 +104,14 @@ describe('Discover model properties', function () { }); -describe('Discover model primary keys', function () { - it('should return an array of primary keys for product', function (done) { - db.discoverPrimaryKeys('product', function (err, models) { +describe('Discover model primary keys', function() { + it('should return an array of primary keys for product', function(done) { + db.discoverPrimaryKeys('product', function(err, models) { if (err) { console.error(err); done(err); } else { - models.forEach(function (m) { + models.forEach(function(m) { // console.dir(m); assert(m.tableName === 'product'); }); @@ -120,13 +120,13 @@ describe('Discover model primary keys', function () { }); }); - it('should return an array of primary keys for dbo.product', function (done) { - db.discoverPrimaryKeys('product', {owner: 'dbo'}, function (err, models) { + it('should return an array of primary keys for dbo.product', function(done) { + db.discoverPrimaryKeys('product', { owner: 'dbo' }, function(err, models) { if (err) { console.error(err); done(err); } else { - models.forEach(function (m) { + models.forEach(function(m) { // console.dir(m); assert(m.tableName === 'product'); }); @@ -136,14 +136,14 @@ describe('Discover model primary keys', function () { }); }); -describe('Discover model foreign keys', function () { - it('should return an array of foreign keys for inventory', function (done) { - db.discoverForeignKeys('inventory', function (err, models) { +describe('Discover model foreign keys', function() { + it('should return an array of foreign keys for inventory', function(done) { + db.discoverForeignKeys('inventory', function(err, models) { if (err) { console.error(err); done(err); } else { - models.forEach(function (m) { + models.forEach(function(m) { // console.dir(m); assert(m.fkTableName === 'inventory'); }); @@ -151,13 +151,13 @@ describe('Discover model foreign keys', function () { } }); }); - it('should return an array of foreign keys for dbo.inventory', function (done) { - db.discoverForeignKeys('inventory', {owner: 'dbo'}, function (err, models) { + it('should return an array of foreign keys for dbo.inventory', function(done) { + db.discoverForeignKeys('inventory', { owner: 'dbo' }, function(err, models) { if (err) { console.error(err); done(err); } else { - models.forEach(function (m) { + models.forEach(function(m) { // console.dir(m); assert(m.fkTableName === 'inventory'); }); @@ -167,9 +167,9 @@ describe('Discover model foreign keys', function () { }); }); -describe('Discover adl schema from a table', function () { - it('should return an adl schema for inventory', function (done) { - db.discoverSchema('inventory', {owner: 'dbo'}, function (err, schema) { +describe('Discover adl schema from a table', function() { + it('should return an adl schema for inventory', function(done) { + db.discoverSchema('inventory', { owner: 'dbo' }, function(err, schema) { // console.log('%j', schema); assert(schema.name === 'Inventory'); assert(schema.options.mssql.schema === 'dbo'); diff --git a/test/id.test.js b/test/id.test.js index ba814ab..34bad2d 100644 --- a/test/id.test.js +++ b/test/id.test.js @@ -4,179 +4,179 @@ var assert = require('assert'); var async = require('async'); var ds; -before(function () { +before(function() { ds = getDataSource(); }); -describe('Manipulating id column', function () { - it('should auto generate id', function (done) { +describe('Manipulating id column', function() { + it('should auto generate id', function(done) { var schema = - { - "name": "WarehouseTest", - "options": { - "mssql": { - "schema": "dbo", - "table": "WAREHOUSE_TEST" - } - }, - "properties": { - "id": { - "type": "Number", - "id": true - }, - "name": { - "type": "String", - "required": false, - "length": 40 - } - } - } + { + 'name': 'WarehouseTest', + 'options': { + 'mssql': { + 'schema': 'dbo', + 'table': 'WAREHOUSE_TEST', + }, + }, + 'properties': { + 'id': { + 'type': 'Number', + 'id': true, + }, + 'name': { + 'type': 'String', + 'required': false, + 'length': 40, + }, + }, + }; var models = ds.modelBuilder.buildModels(schema); var Model = models.WarehouseTest; Model.attachTo(ds); - ds.automigrate(function (err) { + ds.automigrate(function(err) { assert(!err); async.series([ function(callback) { Model.destroyAll(callback); }, function(callback) { - Model.create({name: 'w1'}, + Model.create({ name: 'w1' }, callback); }, function(callback) { - Model.create({name: 'w2'}, + Model.create({ name: 'w2' }, callback); }, function(callback) { - Model.create({name: 'w3'}, + Model.create({ name: 'w3' }, callback); }, function(callback) { - Model.find({order: 'id asc'}, + Model.find({ order: 'id asc' }, function(err, results) { assert(!err); results.should.have.lengthOf(3); for (var i = 0; i < results.length; i++) { - should.equal(results[i].id, i + 1); + should.equal(results[i].id, i + 1); } callback(); }); - } + }, ], done); }); }); - it('should use manual id', function (done) { + it('should use manual id', function(done) { var schema = - { - "name": "WarehouseTest", - "options": { - "idInjection": false, - "mssql": { - "schema": "dbo", - "table": "WAREHOUSE_TEST" - } - }, - "properties": { - "id": { - "type": "Number", - "id": true, - "generated": false - }, - "name": { - "type": "String", - "required": false, - "length": 40 - } - } - } + { + 'name': 'WarehouseTest', + 'options': { + 'idInjection': false, + 'mssql': { + 'schema': 'dbo', + 'table': 'WAREHOUSE_TEST', + }, + }, + 'properties': { + 'id': { + 'type': 'Number', + 'id': true, + 'generated': false, + }, + 'name': { + 'type': 'String', + 'required': false, + 'length': 40, + }, + }, + }; var models = ds.modelBuilder.buildModels(schema); var Model = models.WarehouseTest; Model.attachTo(ds); - ds.automigrate(function (err) { + ds.automigrate(function(err) { assert(!err); async.series([ function(callback) { Model.destroyAll(callback); }, function(callback) { - Model.create({id: 501, name: 'w1'}, + Model.create({ id: 501, name: 'w1' }, callback); }, function(callback) { - Model.find({order: 'id asc'}, + Model.find({ order: 'id asc' }, function(err, results) { assert(!err); results.should.have.lengthOf(1); should.equal(results[0].id, 501); callback(); }); - } + }, ], done); }); }); - it('should use bigint id', function (done) { + it('should use bigint id', function(done) { var schema = - { - "name": "WarehouseTest", - "options": { - "idInjection": false, - "mssql": { - "schema": "dbo", - "table": "WAREHOUSE_TEST", - } - }, - "properties": { - "id": { - "type": "Number", - "id": true, - "generated": false, - "mssql": { - "dataType": "bigint", - "dataPrecision": 20, - "dataScale": 0 - } - }, - "name": { - "type": "String", - "required": false, - "length": 40 - } - } - } + { + 'name': 'WarehouseTest', + 'options': { + 'idInjection': false, + 'mssql': { + 'schema': 'dbo', + 'table': 'WAREHOUSE_TEST', + }, + }, + 'properties': { + 'id': { + 'type': 'Number', + 'id': true, + 'generated': false, + 'mssql': { + 'dataType': 'bigint', + 'dataPrecision': 20, + 'dataScale': 0, + }, + }, + 'name': { + 'type': 'String', + 'required': false, + 'length': 40, + }, + }, + }; var models = ds.modelBuilder.buildModels(schema); var Model = models.WarehouseTest; Model.attachTo(ds); - ds.automigrate(function (err) { + ds.automigrate(function(err) { assert(!err); async.series([ function(callback) { Model.destroyAll(callback); }, function(callback) { - Model.create({id: 962744456683738, name: 'w1'}, + Model.create({ id: 962744456683738, name: 'w1' }, callback); }, function(callback) { - Model.find({order: 'id asc'}, + Model.find({ order: 'id asc' }, function(err, results) { assert(!err); results.should.have.lengthOf(1); should.equal(results[0].id, 962744456683738); callback(); }); - } + }, ], done); }); }); diff --git a/test/imported.test.js b/test/imported.test.js index 1c55e2d..6a01341 100644 --- a/test/imported.test.js +++ b/test/imported.test.js @@ -1,6 +1,6 @@ -describe('mssql imported features', function () { +describe('mssql imported features', function() { - before(function () { + before(function() { require('./init.js'); }); diff --git a/test/init.js b/test/init.js index 7fc619b..bb0f82d 100644 --- a/test/init.js +++ b/test/init.js @@ -4,18 +4,18 @@ var DataSource = require('loopback-datasource-juggler').DataSource; var config = {}; try { - config = require('rc')('loopback', {test: {mssql: {}}}).test.mssql; -} catch(err) { + config = require('rc')('loopback', { test: { mssql: {}}}).test.mssql; +} catch (err) { config = { user: 'demo', password: 'L00pBack', host: 'localhost', database: 'demo', - supportsOffSetFetch: Math.random() > 0.5 + supportsOffSetFetch: Math.random() > 0.5, }; } -global.getConfig = function (options) { +global.getConfig = function(options) { var dbConf = { host: config.host || config.hostname || config.server || 'localhost', @@ -26,8 +26,8 @@ global.getConfig = function (options) { pool: { max: 10, min: 0, - idleTimeoutMillis: 30000 - } + idleTimeoutMillis: 30000, + }, }; if (options) { @@ -39,7 +39,7 @@ global.getConfig = function (options) { return dbConf; }; -global.getDataSource = global.getSchema = function (options) { +global.getDataSource = global.getSchema = function(options) { var db = new DataSource(require('../'), getConfig(options)); return db; }; diff --git a/test/mapping.test.js b/test/mapping.test.js index d460c21..c31d22c 100644 --- a/test/mapping.test.js +++ b/test/mapping.test.js @@ -17,8 +17,8 @@ describe('Mapping models', function() { options: { idInjection: false, mssql: { - schema: 'dbo', table: 'INVENTORYTEST' - } + schema: 'dbo', table: 'INVENTORYTEST', + }, }, properties: { productId: { @@ -27,8 +27,8 @@ describe('Mapping models', function() { generated: true, mssql: { columnName: 'PRODUCT_ID', - nullable: 'N' - } + nullable: 'N', + }, }, locationId: { type: 'String', @@ -37,8 +37,8 @@ describe('Mapping models', function() { mssql: { columnName: 'LOCATION_ID', dataType: 'nvarchar', - nullable: 'N' - } + nullable: 'N', + }, }, available: { type: 'Number', @@ -46,8 +46,8 @@ describe('Mapping models', function() { mssql: { columnName: 'AVAILABLE', dataType: 'int', - nullable: 'Y' - } + nullable: 'Y', + }, }, total: { type: 'Number', @@ -55,10 +55,10 @@ describe('Mapping models', function() { mssql: { columnName: 'TOTAL', dataType: 'int', - nullable: 'Y' - } - } - } + nullable: 'Y', + }, + }, + }, }; var models = db.modelBuilder.buildModels(schema); var Model = models.TestInventory; @@ -70,19 +70,19 @@ describe('Mapping models', function() { Model.destroyAll(callback); }, function(callback) { - Model.create({locationId: 'l001', available: 10, total: 50}, + Model.create({ locationId: 'l001', available: 10, total: 50 }, callback); }, function(callback) { - Model.create({locationId: 'l002', available: 30, total: 40}, + Model.create({ locationId: 'l002', available: 30, total: 40 }, callback); }, function(callback) { - Model.create({locationId: 'l001', available: 15, total: 30}, + Model.create({ locationId: 'l001', available: 15, total: 30 }, callback); }, function(callback) { - Model.find({fields: ['productId', 'locationId', 'available']}, + Model.find({ fields: ['productId', 'locationId', 'available'] }, function(err, results) { // console.log(results); results.should.have.lengthOf(3); @@ -96,7 +96,7 @@ describe('Mapping models', function() { }); }, function(callback) { - Model.find({fields: {'total': false}}, function(err, results) { + Model.find({ fields: { 'total': false }}, function(err, results) { // console.log(results); results.should.have.lengthOf(3); results.forEach(function(r) { @@ -107,7 +107,7 @@ describe('Mapping models', function() { }); callback(null, results); }); - } + }, ], done); }); diff --git a/test/mssql.test.js b/test/mssql.test.js index da7c812..bb123fb 100644 --- a/test/mssql.test.js +++ b/test/mssql.test.js @@ -4,30 +4,30 @@ var should = require('should'); var Post, PostWithUUID, PostWithStringId, db; -describe('mssql connector', function () { +describe('mssql connector', function() { - before(function () { + before(function() { db = getDataSource(); Post = db.define('PostWithBoolean', { title: { type: String, length: 255, index: true }, content: { type: String }, - approved: Boolean + approved: Boolean, }); PostWithUUID = db.define('PostWithUUID', { - id: {type: String, generated: true, id: true}, + id: { type: String, generated: true, id: true }, title: { type: String, length: 255, index: true }, content: { type: String }, - approved: Boolean + approved: Boolean, }); PostWithStringId = db.define('PostWithStringId', { - id: {type: String, id: true, generated: false}, + id: { type: String, id: true, generated: false }, title: { type: String, length: 255, index: true }, content: { type: String }, - rating: {type: Number, mssql: {dataType: 'FLOAT'}}, - approved: Boolean + rating: { type: Number, mssql: { dataType: 'FLOAT' }}, + approved: Boolean, }); }); @@ -38,10 +38,10 @@ describe('mssql connector', function () { done(err); }); }); - + var post; it('should support boolean types with true value', function(done) { - Post.create({title: 'T1', content: 'C1', approved: true}, function(err, p) { + Post.create({ title: 'T1', content: 'C1', approved: true }, function(err, p) { should.not.exists(err); post = p; Post.findById(p.id, function(err, p) { @@ -53,7 +53,7 @@ describe('mssql connector', function () { }); it('should support updating boolean types with false value', function(done) { - Post.update({id: post.id}, {approved: false}, function(err) { + Post.update({ id: post.id }, { approved: false }, function(err) { should.not.exists(err); Post.findById(post.id, function(err, p) { should.not.exists(err); @@ -65,7 +65,7 @@ describe('mssql connector', function () { it('should support boolean types with false value', function(done) { - Post.create({title: 'T2', content: 'C2', approved: false}, function(err, p) { + Post.create({ title: 'T2', content: 'C2', approved: false }, function(err, p) { should.not.exists(err); post = p; Post.findById(p.id, function(err, p) { @@ -77,20 +77,20 @@ describe('mssql connector', function () { }); it('should single quote escape', function(done) { - Post.create({title: "T2", content: "C,D", approved: false}, function(err, p) { + Post.create({ title: 'T2', content: 'C,D', approved: false }, function(err, p) { should.not.exists(err); post = p; Post.findById(p.id, function(err, p) { should.not.exists(err); - p.should.have.property('content', "C,D"); + p.should.have.property('content', 'C,D'); done(); }); }); }); it('should return the model instance for upsert', function(done) { - Post.upsert({id: post.id, title: 'T2_new', content: 'C2_new', - approved: true}, function(err, p) { + Post.upsert({ id: post.id, title: 'T2_new', content: 'C2_new', + approved: true }, function(err, p) { p.should.have.property('id', post.id); p.should.have.property('title', 'T2_new'); p.should.have.property('content', 'C2_new'); @@ -101,7 +101,7 @@ describe('mssql connector', function () { it('should return the model instance for upsert when id is not present', function(done) { - Post.upsert({title: 'T2_new', content: 'C2_new', approved: true}, + Post.upsert({ title: 'T2_new', content: 'C2_new', approved: true }, function(err, p) { p.should.have.property('id'); p.should.have.property('title', 'T2_new'); @@ -121,7 +121,7 @@ describe('mssql connector', function () { it('should escape number values to defect SQL injection in find', function(done) { - Post.find({where: {id: '(SELECT 1+1)'}}, function(err, p) { + Post.find({ where: { id: '(SELECT 1+1)' }}, function(err, p) { should.exists(err); done(); }); @@ -129,7 +129,7 @@ describe('mssql connector', function () { it('should escape number values to defect SQL injection in find with gt', function(done) { - Post.find({where: {id: {gt: '(SELECT 1+1)'}}}, function(err, p) { + Post.find({ where: { id: { gt: '(SELECT 1+1)' }}}, function(err, p) { should.exists(err); done(); }); @@ -137,7 +137,7 @@ describe('mssql connector', function () { it('should escape number values to defect SQL injection in find', function(done) { - Post.find({limit: '(SELECT 1+1)'}, function(err, p) { + Post.find({ limit: '(SELECT 1+1)' }, function(err, p) { should.exists(err); done(); }); @@ -145,7 +145,7 @@ describe('mssql connector', function () { it('should escape number values to defect SQL injection in find with inq', function(done) { - Post.find({where: {id: {inq: ['(SELECT 1+1)']}}}, function(err, p) { + Post.find({ where: { id: { inq: ['(SELECT 1+1)'] }}}, function(err, p) { should.exists(err); done(); }); @@ -168,18 +168,18 @@ describe('mssql connector', function () { connector.execute('SELECT * FROM SQLI_TEST', function(err, data) { if (err) return done(err); data.should.be.eql( - [ { V1: '(?)', - V2: ', 1 ); INSERT INTO SQLI_TEST VALUES (1, 2); --' } ]); + [{ V1: '(?)', + V2: ', 1 ); INSERT INTO SQLI_TEST VALUES (1, 2); --' }]); done(); }); - }); }); }); }); + }); it('should allow string array for inq', function(done) { - Post.find({where: {content: {inq: ['C1', 'C2']}}}, function(err, p) { + Post.find({ where: { content: { inq: ['C1', 'C2'] }}}, function(err, p) { should.not.exist(err); should.exist(p); p.should.have.length(2); @@ -189,7 +189,7 @@ describe('mssql connector', function () { it('should perform an empty inq', function(done) { - Post.find({where: {id: {inq: []}}}, function(err, p) { + Post.find({ where: { id: { inq: [] }}}, function(err, p) { should.not.exist(err); should.exist(p); p.should.have.length(0); @@ -199,7 +199,7 @@ describe('mssql connector', function () { it('should perform an empty nin', function(done) { - Post.find({where: {id: {nin: []}}}, function(err, p) { + Post.find({ where: { id: { nin: [] }}}, function(err, p) { should.not.exist(err); should.exist(p); p.should.have.length(4); @@ -208,7 +208,7 @@ describe('mssql connector', function () { }); it('should support uuid', function(done) { - PostWithUUID.create({title: 'T1', content: 'C1', approved: true}, + PostWithUUID.create({ title: 'T1', content: 'C1', approved: true }, function(err, p) { should.not.exists(err); p.should.have.property('id'); @@ -223,7 +223,7 @@ describe('mssql connector', function () { it('should support string id', function(done) { PostWithStringId.create( - {title: 'T1', content: 'C1', approved: true, rating: 3.5}, + { title: 'T1', content: 'C1', approved: true, rating: 3.5 }, function(err, p) { should.not.exists(err); p.should.have.property('id'); @@ -243,8 +243,8 @@ describe('mssql connector', function () { }); beforeEach(function createTestFixtures(done) { Post.create([ - {title: 'a', content: 'AAA'}, - {title: 'b', content: 'BBB'} + { title: 'a', content: 'AAA' }, + { title: 'b', content: 'BBB' }, ], done); }); beforeEach(function addSpy() { @@ -259,7 +259,7 @@ describe('mssql connector', function () { context('with regex strings', function() { it('should print a warning and return an error', function(done) { - Post.find({where: {content: {regexp: '^A'}}}, function(err, posts) { + Post.find({ where: { content: { regexp: '^A' }}}, function(err, posts) { console.warn.calledOnce.should.be.ok; should.exist(err); done(); @@ -269,7 +269,7 @@ describe('mssql connector', function () { context('with regex literals', function() { it('should print a warning and return an error', function(done) { - Post.find({where: {content: {regexp: /^A/}}}, function(err, posts) { + Post.find({ where: { content: { regexp: /^A/ }}}, function(err, posts) { console.warn.calledOnce.should.be.ok; should.exist(err); done(); @@ -279,7 +279,7 @@ describe('mssql connector', function () { context('with regex objects', function() { it('should print a warning and return an error', function(done) { - Post.find({where: {content: {regexp: new RegExp(/^A/)}}}, function(err, + Post.find({ where: { content: { regexp: new RegExp(/^A/) }}}, function(err, posts) { console.warn.calledOnce.should.be.ok; should.exist(err); diff --git a/test/transaction.test.js b/test/transaction.test.js index 24f6b88..11d7984 100644 --- a/test/transaction.test.js +++ b/test/transaction.test.js @@ -10,8 +10,8 @@ describe('transactions', function() { before(function(done) { db = getDataSource(); Post = db.define('PostTX', { - title: {type: String, length: 255, index: true}, - content: {type: String} + title: { type: String, length: 255, index: true }, + content: { type: String }, }); db.automigrate('PostTX', done); }); @@ -24,7 +24,7 @@ describe('transactions', function() { function(err, tx) { if (err) return done(err); currentTx = tx; - Post.create(post, {transaction: tx}, + Post.create(post, { transaction: tx }, function(err, p) { if (err) { done(err); @@ -44,7 +44,7 @@ describe('transactions', function() { if (inTx) { options.transaction = currentTx; } - Post.find({where: where}, options, + Post.find({ where: where }, options, function(err, posts) { if (err) return done(err); posts.length.should.be.eql(count); @@ -55,7 +55,7 @@ describe('transactions', function() { describe('commit', function() { - var post = {title: 't1', content: 'c1'}; + var post = { title: 't1', content: 'c1' }; before(createPostInTx(post)); // FIXME: [rfeng] SQL server creates LCK_M_S (Shared Lock on the table @@ -75,7 +75,7 @@ describe('transactions', function() { describe('rollback', function() { - var post = {title: 't2', content: 'c2'}; + var post = { title: 't2', content: 'c2' }; before(createPostInTx(post)); // FIXME: [rfeng] SQL server creates LCK_M_S (Shared Lock on the table From 4564714345b571ad8b65bab4b4854369ec3c75ee Mon Sep 17 00:00:00 2001 From: Amir Jafarian Date: Wed, 27 Apr 2016 17:50:44 -0400 Subject: [PATCH 142/239] Fix linting errors --- .eslintrc | 2 +- lib/discovery.js | 112 +++++++++++++++++----------------- lib/migration.js | 91 ++++++++++++++-------------- lib/mssql.js | 27 ++++----- lib/transaction.js | 1 - package.json | 4 +- test/autoupdate.test.js | 127 ++++++++++++++++++++------------------- test/commontests.js | 57 +++++++++--------- test/connection.js | 4 +- test/discover.test.js | 4 -- test/id.test.js | 115 +++++++++++++++++------------------ test/imported.test.js | 2 - test/init.js | 1 - test/mapping.test.js | 2 - test/mssql.test.js | 18 +++--- test/transaction.test.js | 10 --- 16 files changed, 276 insertions(+), 301 deletions(-) diff --git a/.eslintrc b/.eslintrc index 4ed9690..4e1231e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,7 +1,7 @@ { "extends": "loopback", "rules": { - "max-len": ["error", 80, 4, { + "max-len": ["error", 125, 4, { "ignoreComments": true, "ignoreUrls": true, "ignorePattern": "^\\s*var\\s.+=\\s*(require\\s*\\()|(/)" diff --git a/lib/discovery.js b/lib/discovery.js index 5881688..24a0efe 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -54,13 +54,13 @@ function mixinDiscovery(MsSQL) { } else { orderBy = 'ORDER BY 1'; } - var paginatedSQL = 'SELECT *' + MsSQL.newline - + 'FROM (' + MsSQL.newline - + select + ', ROW_NUMBER() OVER' - + ' (' + orderBy + ') AS rowNum' + MsSQL.newline - + from + MsSQL.newline; - paginatedSQL += ') AS S' + MsSQL.newline - + 'WHERE S.rowNum > ' + offset; + var paginatedSQL = 'SELECT *' + MsSQL.newline + + 'FROM (' + MsSQL.newline + + select + ', ROW_NUMBER() OVER' + + ' (' + orderBy + ') AS rowNum' + MsSQL.newline + + from + MsSQL.newline; + paginatedSQL += ') AS S' + MsSQL.newline + + 'WHERE S.rowNum > ' + offset; if (limit !== -1) { paginatedSQL += ' AND S.rowNum <= ' + (offset + limit); @@ -79,14 +79,14 @@ function mixinDiscovery(MsSQL) { var owner = options.owner || options.schema; if (options.all && !owner) { - sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' - + ' FROM information_schema.tables', 'table_schema, table_name', options); + sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' + + ' FROM information_schema.tables', 'table_schema, table_name', options); } else if (owner) { - sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' - + ' FROM information_schema.tables WHERE table_schema=\'' + owner + '\'', 'table_schema, table_name', options); + sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' + + ' FROM information_schema.tables WHERE table_schema=\'' + owner + '\'', 'table_schema, table_name', options); } else { - sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name",' - + ' table_schema AS "owner" FROM information_schema.tables WHERE table_schema=schema_name()', + sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name",' + + ' table_schema AS "owner" FROM information_schema.tables WHERE table_schema=schema_name()', 'table_name', options); } return sqlTables; @@ -100,20 +100,19 @@ function mixinDiscovery(MsSQL) { function queryViews(options) { var sqlViews = null; if (options.views) { - var owner = options.owner || options.schema; if (options.all && !owner) { - sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' - + ' table_schema AS "owner" FROM information_schema.views', + sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + + ' table_schema AS "owner" FROM information_schema.views', 'table_schema, table_name', options); } else if (owner) { - sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' - + ' table_schema AS "owner" FROM information_schema.views WHERE table_schema=\'' + owner + '\'', + sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + + ' table_schema AS "owner" FROM information_schema.views WHERE table_schema=\'' + owner + '\'', 'table_schema, table_name', options); } else { - sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' - + ' schema_name() AS "owner" FROM information_schema.views', + sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + + ' schema_name() AS "owner" FROM information_schema.views', 'table_name', options); } } @@ -192,17 +191,21 @@ function mixinDiscovery(MsSQL) { function queryColumns(owner, table) { var sql = null; if (owner) { - sql = paginateSQL('SELECT table_schema AS "owner", table_name AS "tableName", column_name AS "columnName", data_type AS "dataType",' - + ' character_maximum_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"' - + ' FROM information_schema.columns' - + ' WHERE table_schema=\'' + owner + '\'' - + (table ? ' AND table_name=\'' + table + '\'' : ''), + sql = paginateSQL('SELECT table_schema AS "owner", table_name AS "tableName", column_name' + + ' AS "columnName", data_type AS "dataType",' + + ' character_maximum_length AS "dataLength", numeric_precision AS' + + ' "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"' + + ' FROM information_schema.columns' + + ' WHERE table_schema=\'' + owner + '\'' + + (table ? ' AND table_name=\'' + table + '\'' : ''), 'table_schema, table_name, ordinal_position', {}); } else { - sql = paginateSQL('SELECT schema_name() AS "owner", table_name AS "tableName", column_name AS "columnName", data_type AS "dataType",' - + ' character_maximum_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"' - + ' FROM information_schema.columns' - + (table ? ' WHERE table_name=\'' + table + '\'' : ''), + sql = paginateSQL('SELECT schema_name() AS "owner", table_name' + + ' AS "tableName", column_name AS "columnName", data_type AS "dataType",' + + ' character_maximum_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS' + + ' "dataScale", is_nullable AS "nullable"' + + ' FROM information_schema.columns' + + (table ? ' WHERE table_name=\'' + table + '\'' : ''), 'table_name, ordinal_position', {}); } return sql; @@ -259,12 +262,13 @@ function mixinDiscovery(MsSQL) { */ function queryForPrimaryKeys(owner, table) { - var sql = 'SELECT kc.table_schema AS "owner", ' - + 'kc.table_name AS "tableName", kc.column_name AS "columnName", kc.ordinal_position AS "keySeq", kc.constraint_name AS "pkName" FROM' - + ' information_schema.key_column_usage kc' - + ' JOIN information_schema.table_constraints tc' - + ' ON kc.table_name = tc.table_name AND kc.table_schema = tc.table_schema' - + ' WHERE tc.constraint_type=\'PRIMARY KEY\' AND kc.ordinal_position IS NOT NULL'; + var sql = 'SELECT kc.table_schema AS "owner", ' + + 'kc.table_name AS "tableName", kc.column_name AS "columnName", kc.ordinal_position' + + ' AS "keySeq", kc.constraint_name AS "pkName" FROM' + + ' information_schema.key_column_usage kc' + + ' JOIN information_schema.table_constraints tc' + + ' ON kc.table_name = tc.table_name AND kc.table_schema = tc.table_schema' + + ' WHERE tc.constraint_type=\'PRIMARY KEY\' AND kc.ordinal_position IS NOT NULL'; if (owner) { sql += ' AND kc.table_schema=\'' + owner + '\''; @@ -315,16 +319,16 @@ function mixinDiscovery(MsSQL) { */ function queryForeignKeys(owner, table) { var sql = - 'SELECT tc.table_schema AS "fkOwner", tc.constraint_name AS "fkName", tc.table_name AS "fkTableName",' - + ' kcu.column_name AS "fkColumnName", kcu.ordinal_position AS "keySeq",' - + ' ccu.table_schema AS "pkOwner", \'PK\' AS "pkName", ' - + ' ccu.table_name AS "pkTableName", ccu.column_name AS "pkColumnName"' - + ' FROM information_schema.table_constraints tc' - + ' JOIN information_schema.key_column_usage AS kcu' - + ' ON tc.constraint_schema = kcu.constraint_schema AND tc.constraint_name = kcu.constraint_name' - + ' JOIN information_schema.constraint_column_usage ccu' - + ' ON ccu.constraint_schema = tc.constraint_schema AND ccu.constraint_name = tc.constraint_name' - + ' WHERE tc.constraint_type = \'FOREIGN KEY\''; + 'SELECT tc.table_schema AS "fkOwner", tc.constraint_name AS "fkName", tc.table_name AS "fkTableName",' + + ' kcu.column_name AS "fkColumnName", kcu.ordinal_position AS "keySeq",' + + ' ccu.table_schema AS "pkOwner", \'PK\' AS "pkName", ' + + ' ccu.table_name AS "pkTableName", ccu.column_name AS "pkColumnName"' + + ' FROM information_schema.table_constraints tc' + + ' JOIN information_schema.key_column_usage AS kcu' + + ' ON tc.constraint_schema = kcu.constraint_schema AND tc.constraint_name = kcu.constraint_name' + + ' JOIN information_schema.constraint_column_usage ccu' + + ' ON ccu.constraint_schema = tc.constraint_schema AND ccu.constraint_name = tc.constraint_name' + + ' WHERE tc.constraint_type = \'FOREIGN KEY\''; if (owner) { sql += ' AND tc.table_schema=\'' + owner + '\''; } @@ -359,15 +363,15 @@ function mixinDiscovery(MsSQL) { * @returns {string} */ function queryExportedForeignKeys(owner, table) { - var sql = 'SELECT kcu.constraint_name AS "fkName", kcu.table_schema AS "fkOwner", kcu.table_name AS "fkTableName",' - + ' kcu.column_name AS "fkColumnName", kcu.ordinal_position AS "keySeq",' - + ' \'PK\' AS "pkName", ccu.table_schema AS "pkOwner",' - + ' ccu.table_name AS "pkTableName", ccu.column_name AS "pkColumnName"' - + ' FROM' - + ' information_schema.constraint_column_usage ccu' - + ' JOIN information_schema.key_column_usage kcu' - + ' ON ccu.constraint_schema = kcu.constraint_schema AND ccu.constraint_name = kcu.constraint_name' - + ' WHERE kcu.ordinal_position IS NOT NULL'; + var sql = 'SELECT kcu.constraint_name AS "fkName", kcu.table_schema AS "fkOwner", kcu.table_name AS "fkTableName",' + + ' kcu.column_name AS "fkColumnName", kcu.ordinal_position AS "keySeq",' + + ' \'PK\' AS "pkName", ccu.table_schema AS "pkOwner",' + + ' ccu.table_name AS "pkTableName", ccu.column_name AS "pkColumnName"' + + ' FROM' + + ' information_schema.constraint_column_usage ccu' + + ' JOIN information_schema.key_column_usage kcu' + + ' ON ccu.constraint_schema = kcu.constraint_schema AND ccu.constraint_name = kcu.constraint_name' + + ' WHERE kcu.ordinal_position IS NOT NULL'; if (owner) { sql += ' and ccu.table_schema=\'' + owner + '\''; } diff --git a/lib/migration.js b/lib/migration.js index 8a5e3aa..5bc88f3 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -4,14 +4,14 @@ module.exports = mixinMigration; function mixinMigration(MsSQL) { MsSQL.prototype.showFields = function(model, cb) { - var sql = 'select [COLUMN_NAME] as [Field], ' - + ' [IS_NULLABLE] as [Null], [DATA_TYPE] as [Type],' - + ' [CHARACTER_MAXIMUM_LENGTH] as [Length],' - + ' [NUMERIC_PRECISION] as [Precision], NUMERIC_SCALE as [Scale]' - + ' from INFORMATION_SCHEMA.COLUMNS' - + ' where [TABLE_SCHEMA] = \'' + this.schema(model) + '\'' - + ' and [TABLE_NAME] = \'' + this.table(model) + '\'' - + ' order by [ORDINAL_POSITION]'; + var sql = 'select [COLUMN_NAME] as [Field], ' + + ' [IS_NULLABLE] as [Null], [DATA_TYPE] as [Type],' + + ' [CHARACTER_MAXIMUM_LENGTH] as [Length],' + + ' [NUMERIC_PRECISION] as [Precision], NUMERIC_SCALE as [Scale]' + + ' from INFORMATION_SCHEMA.COLUMNS' + + ' where [TABLE_SCHEMA] = \'' + this.schema(model) + '\'' + + ' and [TABLE_NAME] = \'' + this.table(model) + '\'' + + ' order by [ORDINAL_POSITION]'; this.execute(sql, function(err, fields) { if (err) { return cb && cb(err); @@ -176,8 +176,8 @@ function mixinMigration(MsSQL) { if (indexName === idName || indexName === 'PRIMARY') { return; } - if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] - || m.properties[indexName] && !m.properties[indexName].index) { + if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || + m.properties[indexName] && !m.properties[indexName].index) { sql.push('DROP INDEX ' + self.columnEscaped(model, indexName)); } else { // first: check single (only type and kind) @@ -219,11 +219,11 @@ function mixinMigration(MsSQL) { // kind = i.kind; } if (kind && type) { - sql.push('ADD ' + kind + ' INDEX ' + columnName - + ' (' + columnName + ') ' + type); + sql.push('ADD ' + kind + ' INDEX ' + columnName + + ' (' + columnName + ') ' + type); } else { - sql.push('ADD ' + kind + ' INDEX [' + propName + '] ' - + type + ' ([' + propName + ']) '); + sql.push('ADD ' + kind + ' INDEX [' + propName + '] ' + + type + ' ([' + propName + ']) '); } } }); @@ -242,32 +242,32 @@ function mixinMigration(MsSQL) { kind = i.kind; } if (kind && type) { - sql.push('ADD ' + kind + ' INDEX [' + indexName + '] (' - + i.columns + ') ' + type); + sql.push('ADD ' + kind + ' INDEX [' + indexName + '] (' + + i.columns + ') ' + type); } else { - sql.push('ADD ' + kind + ' INDEX [' + indexName + '] (' - + i.columns + ') '); + sql.push('ADD ' + kind + ' INDEX [' + indexName + '] (' + + i.columns + ') '); } } }); var statements = []; if (columnsToAdd.length) { - statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' ADD ' - + columnsToAdd.join(',' + MsSQL.newline)); + statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' ADD ' + + columnsToAdd.join(',' + MsSQL.newline)); } if (columnsToAlter.length) { // SQL Server doesn't allow multiple columns to be altered in one statement columnsToAlter.forEach(function(c) { - statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' ALTER COLUMN ' - + c); + statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' ALTER COLUMN ' + + c); }); } if (columnsToDrop.length) { - statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' DROP COLUMN' - + columnsToDrop.join(',' + MsSQL.newline)); + statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' DROP COLUMN' + + columnsToDrop.join(',' + MsSQL.newline)); } async.each(statements, function(query, fn) { @@ -283,18 +283,18 @@ function mixinMigration(MsSQL) { function actualize(propName, oldSettings) { var newSettings = m.properties[propName]; if (newSettings && changed(newSettings, oldSettings)) { - columnsToAlter.push(self.columnEscaped(model, propName) + ' ' - + self.propertySettingsSQL(model, propName)); + columnsToAlter.push(self.columnEscaped(model, propName) + ' ' + + self.propertySettingsSQL(model, propName)); } } function changed(newSettings, oldSettings) { - if (oldSettings.Null === 'YES' - && (newSettings.allowNull === false || newSettings.null === false)) { + if (oldSettings.Null === 'YES' && + (newSettings.allowNull === false || newSettings.null === false)) { return true; } - if (oldSettings.Null === 'NO' && !(newSettings.allowNull === false - || newSettings.null === false)) { + if (oldSettings.Null === 'NO' && !(newSettings.allowNull === false || + newSettings.null === false)) { return true; } if (oldSettings.Type.toUpperCase() !== datatype(newSettings)) { @@ -320,8 +320,7 @@ function mixinMigration(MsSQL) { if (idProp.generated !== false) { sql.push(self.columnEscaped(model, modelPKID) + ' ' + self.columnDataType(model, modelPKID) + ' IDENTITY(1,1) NOT NULL'); - } - else { + } else { sql.push(self.columnEscaped(model, modelPKID) + ' ' + self.columnDataType(model, modelPKID) + ' NOT NULL'); } @@ -374,14 +373,14 @@ function mixinMigration(MsSQL) { name = i.name; } this._idxNames[model].push(name); - var cmd = 'CREATE ' + (unique ? 'UNIQUE ' : '') + kind + ' INDEX [' + name + '] ON ' - + tblName + MsSQL.newline; + var cmd = 'CREATE ' + (unique ? 'UNIQUE ' : '') + kind + ' INDEX [' + name + '] ON ' + + tblName + MsSQL.newline; cmd += '(' + MsSQL.newline; cmd += ' [' + prop + '] ' + type; cmd += MsSQL.newline + ') WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,' + ' SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ' + - 'ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);' - + MsSQL.newline; + 'ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);' + + MsSQL.newline; return cmd; }; @@ -414,14 +413,14 @@ function mixinMigration(MsSQL) { name += kind + '_' + type + '_idx'; this._idxNames[model].push(name); - var cmd = 'CREATE ' + (unique ? 'UNIQUE ' : '') + kind + ' INDEX [' + name + '] ON ' - + tblName + MsSQL.newline; + var cmd = 'CREATE ' + (unique ? 'UNIQUE ' : '') + kind + ' INDEX [' + name + '] ON ' + + tblName + MsSQL.newline; cmd += '(' + MsSQL.newline; cmd += columns.join(',' + MsSQL.newline); cmd += MsSQL.newline + ') WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ' + 'SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ' + - 'ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);' - + MsSQL.newline; + 'ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);' + + MsSQL.newline; return cmd; }; @@ -467,8 +466,8 @@ function mixinMigration(MsSQL) { MsSQL.prototype.dropTable = function(model, cb) { var tblName = this.tableEscaped(model); - var cmd = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" - + tblName + "') AND type in (N'U'))"; + var cmd = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" + + tblName + "') AND type in (N'U'))"; cmd += MsSQL.newline + 'BEGIN' + MsSQL.newline; cmd += ' DROP TABLE ' + tblName; cmd += MsSQL.newline + 'END'; @@ -477,10 +476,10 @@ function mixinMigration(MsSQL) { MsSQL.prototype.createTable = function(model, cb) { var tblName = this.tableEscaped(model); - var cmd = 'SET ANSI_NULLS ON;' + MsSQL.newline + 'SET QUOTED_IDENTIFIER ON;' - + MsSQL.newline + 'SET ANSI_PADDING ON;' + MsSQL.newline; - cmd += "IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" - + tblName + "') AND type in (N'U'))" + MsSQL.newline + 'BEGIN' + MsSQL.newline; + var cmd = 'SET ANSI_NULLS ON;' + MsSQL.newline + 'SET QUOTED_IDENTIFIER ON;' + + MsSQL.newline + 'SET ANSI_PADDING ON;' + MsSQL.newline; + cmd += "IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" + + tblName + "') AND type in (N'U'))" + MsSQL.newline + 'BEGIN' + MsSQL.newline; cmd += 'CREATE TABLE ' + this.tableEscaped(model) + ' ('; cmd += MsSQL.newline + ' ' + this.propertiesSQL(model) + MsSQL.newline; cmd += ')' + MsSQL.newline + 'END;' + MsSQL.newline; diff --git a/lib/mssql.js b/lib/mssql.js index dab931c..09f8f9e 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -11,7 +11,6 @@ var name = 'mssql'; exports.name = name; exports.initialize = function initializeSchema(dataSource, callback) { - var settings = dataSource.settings || {}; debug('Settings: %j', settings); var driver = new MsSQL(settings); @@ -91,7 +90,6 @@ function parameterizedSQL(sql) { count++; sql = sql.substring(0, index) + ('@param' + count) + sql.substring(index + 3); - } return sql; } @@ -179,10 +177,10 @@ MsSQL.prototype.buildInsertInto = function(model, fields, options) { if (idName) { stmt.merge( - 'OUTPUT INSERTED.' + - this.columnEscaped(model, idName) + - ' into @insertedIds' - ); + 'OUTPUT INSERTED.' + + this.columnEscaped(model, idName) + + ' into @insertedIds' + ); } return stmt; }; @@ -245,11 +243,10 @@ MsSQL.prototype.buildUpdate = function(model, where, data, options) { // Convert to ISO8601 format YYYY-MM-DDThh:mm:ss[.mmm] function dateToMsSql(val) { - - var dateStr = val.getUTCFullYear() + '-' - + fillZeros(val.getUTCMonth() + 1) + '-' - + fillZeros(val.getUTCDate()) - + 'T' + fillZeros(val.getUTCHours()) + ':' + + var dateStr = val.getUTCFullYear() + '-' + + fillZeros(val.getUTCMonth() + 1) + '-' + + fillZeros(val.getUTCDate()) + + 'T' + fillZeros(val.getUTCHours()) + ':' + fillZeros(val.getUTCMinutes()) + ':' + fillZeros(val.getUTCSeconds()) + '.'; @@ -281,9 +278,9 @@ function escape(val) { } if (typeof val === 'object') { - val = (typeof val.toISOString === 'function') - ? val.toISOString() - : val.toString(); + val = (typeof val.toISOString === 'function') ? + val.toISOString() : + val.toString(); } val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) { @@ -419,7 +416,6 @@ MsSQL.prototype.buildSelect = function(model, filter, options) { ); if (filter) { - if (filter.where) { var whereStmt = this.buildWhere(model, filter.where); selectStmt.merge(whereStmt); @@ -433,7 +429,6 @@ MsSQL.prototype.buildSelect = function(model, filter, options) { selectStmt.merge(this.buildOrderBy(model, filter.order)); } } - } return this.parameterize(selectStmt); }; diff --git a/lib/transaction.js b/lib/transaction.js index 547b376..44977d3 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -6,7 +6,6 @@ module.exports = mixinTransaction; * @param {MsSQL} MsSQL connector class */ function mixinTransaction(MsSQL, mssql) { - /** * Begin a new transaction * @param isolationLevel diff --git a/package.json b/package.json index 8e6e059..29ce576 100644 --- a/package.json +++ b/package.json @@ -32,9 +32,9 @@ "sinon": "^1.15.4" }, "scripts": { + "lint": "eslint .", "test": "./node_modules/.bin/mocha --timeout 5000 -R spec", - "posttest": "npm run lint", - "lint": "eslint .", + "posttest": "npm run lint", "prepublish": "node remove-regenerator.js" }, "repository": { diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js index 6adc07d..d5f76af 100644 --- a/test/autoupdate.test.js +++ b/test/autoupdate.test.js @@ -8,81 +8,82 @@ before(function() { describe('MS SQL server connector', function() { it('should auto migrate/update tables', function(done) { - + /* eslint-disable camelcase */ var schema_v1 = { - 'name': 'CustomerTest', - 'options': { - 'idInjection': false, - 'mssql': { - 'schema': 'dbo', - 'table': 'CUSTOMER_TEST', - }, - }, - 'properties': { - 'id': { - 'type': 'String', - 'length': 20, - 'id': 1, - }, - 'name': { - 'type': 'String', - 'required': false, - 'length': 40, - }, - 'email': { - 'type': 'String', - 'required': true, - 'length': 40, - }, - 'age': { - 'type': 'Number', - 'required': false, + name: 'CustomerTest', + options: { + idInjection: false, + mssql: { + schema: 'dbo', + table: 'CUSTOMER_TEST', + }, }, + properties: { + id: { + type: 'String', + length: 20, + id: 1, + }, + name: { + type: 'String', + required: false, + length: 40, + }, + email: { + type: 'String', + required: true, + length: 40, + }, + age: { + type: 'Number', + required: false, + }, }, }; var schema_v2 = { - 'name': 'CustomerTest', - 'options': { - 'idInjection': false, - 'mssql': { - 'schema': 'dbo', - 'table': 'CUSTOMER_TEST', - }, - }, - 'properties': { - 'id': { - 'type': 'String', - 'length': 20, - 'id': 1, - }, - 'email': { - 'type': 'String', - 'required': false, - 'length': 60, - 'mssql': { - 'columnName': 'EMAIL', - 'dataType': 'nvarchar', - 'dataLength': 60, - 'nullable': 'YES', + name: 'CustomerTest', + options: { + idInjection: false, + mssql: { + schema: 'dbo', + table: 'CUSTOMER_TEST', }, }, - 'firstName': { - 'type': 'String', - 'required': false, - 'length': 40, - }, - 'lastName': { - 'type': 'String', - 'required': false, - 'length': 40, - }, + properties: { + id: { + type: 'String', + length: 20, + id: 1, + }, + email: { + type: 'String', + required: false, + length: 60, + mssql: { + columnName: 'EMAIL', + dataType: 'nvarchar', + dataLength: 60, + nullable: 'YES', + }, + }, + firstName: { + type: 'String', + required: false, + length: 40, + }, + lastName: { + type: 'String', + required: false, + length: 40, + }, }, }; ds.createModel(schema_v1.name, schema_v1.properties, schema_v1.options); + /* eslint-enable camelcase */ ds.automigrate(function(err) { assert(!err); @@ -101,9 +102,9 @@ describe('MS SQL server connector', function() { assert.equal(names[1], 'name'); assert.equal(names[2], 'email'); assert.equal(names[3], 'age'); - + /* eslint-disable camelcase */ ds.createModel(schema_v2.name, schema_v2.properties, schema_v2.options); - + /* eslint-enable camelcase */ ds.autoupdate(function(err, result) { ds.discoverModelProperties('CUSTOMER_TEST', function(err, props) { assert.equal(props.length, 4); diff --git a/test/commontests.js b/test/commontests.js index e6adf94..4eb3414 100644 --- a/test/commontests.js +++ b/test/commontests.js @@ -1,5 +1,5 @@ -var jdb = require('loopback-datasource-juggler'), - commonTest = jdb.test; +var jdb = require('loopback-datasource-juggler'); +var commonTest = jdb.test; require('./init'); @@ -13,19 +13,21 @@ commonTest.skip('should handle ORDER clause'); //re-implement the order test as pretty much the same thing, but run an automigration beforehand commonTest.it('should automigrate', function(test) { - schema.automigrate(function(err) { - test.ifError(err); - test.done(); - }); + schema.automigrate(function(err) { + test.ifError(err); + test.done(); + }); }); commonTest.it('should be able to ORDER results', function(test) { - var titles = [{ title: 'Title A', subject: 'B' }, - { title: 'Title Z', subject: 'A' }, - { title: 'Title M', subject: 'C' }, - { title: 'Title A', subject: 'A' }, - { title: 'Title B', subject: 'A' }, - { title: 'Title C', subject: 'D' }]; + var titles = [ + { title: 'Title A', subject: 'B' }, + { title: 'Title Z', subject: 'A' }, + { title: 'Title M', subject: 'C' }, + { title: 'Title A', subject: 'A' }, + { title: 'Title B', subject: 'A' }, + { title: 'Title C', subject: 'D' }, + ]; var dates = [ new Date(1000 * 5), @@ -39,8 +41,8 @@ commonTest.it('should be able to ORDER results', function(test) { titles.forEach(function(t, i) { schema.models.Post.create({ title: t.title, subject: t.subject, date: dates[i] }, done); }); - - var i = 0, tests = 0; + var i = 0; + var tests = 0; function done(err, obj) { if (++i === titles.length) { doFilterAndSortTest(); @@ -100,7 +102,8 @@ commonTest.it('should be able to ORDER results', function(test) { function doFilterAndSortReverseTest() { tests += 1; - schema.models.Post.all({ where: { date: new Date(1000 * 9) }, order: 'title DESC', limit: 3 }, function(err, posts) { + schema.models.Post.all({ where: { date: new Date(1000 * 9) }, order: 'title DESC', limit: 3 }, + function(err, posts) { if (err) console.log(err); test.equal(posts.length, 2, 'Exactly 2 posts returned by query'); ['Title Z', 'Title C'].forEach(function(t, i) { @@ -156,7 +159,7 @@ commonTest.it('should be able to ORDER results', function(test) { commonTest.it('should count posts', function(test) { test.expect(2); - schema.models.Post.count({ title:'Title A' }, function(err, cnt) { + schema.models.Post.count({ title: 'Title A' }, function(err, cnt) { test.ifError(err); test.equal(cnt, 2); test.done(); @@ -165,8 +168,8 @@ commonTest.it('should count posts', function(test) { commonTest.it('should delete a post', function(test) { schema.models.Post.all({ - where:{ - 'title':'Title Z', + where: { + title: 'Title Z', }, }, function(err, posts) { test.ifError(err); @@ -200,30 +203,28 @@ commonTest.it('should support custom primary key', function(test) { test.expect(3); var AppliesTo = schema.define('AppliesTo', { AppliesToID: { - type:Number, - primaryKey:true, + type: Number, + primaryKey: true, }, Title: { - type:String, - limit:100, + type: String, + limit: 100, }, Identifier: { - type:String, - limit:100, + type: String, + limit: 100, }, Editable: { - type:Number, + type: Number, }, }); schema.automigrate(function(err) { test.ifError(err); - - AppliesTo.create({ Title:'custom key', Identifier:'ck', Editable:false }, function(err, data) { + AppliesTo.create({ Title: 'custom key', Identifier: 'ck', Editable: false }, function(err, data) { test.ifError(err); test.notStrictEqual(typeof data.AppliesToID, 'undefined'); test.done(); }); }); - }); diff --git a/test/connection.js b/test/connection.js index 4ea3ecc..955ec47 100644 --- a/test/connection.js +++ b/test/connection.js @@ -37,8 +37,8 @@ describe('testConnection', function() { function generateURL(config) { var urlObj = { protocol: 'mssql', - auth:config.user + ':' + config.password, - hostname:config.host, + auth: config.user + ':' + config.password, + hostname: config.host, port: config.port, pathname: config.database, query: { encrypt: true }, diff --git a/test/discover.test.js b/test/discover.test.js index 8968dce..2255634 100644 --- a/test/discover.test.js +++ b/test/discover.test.js @@ -9,7 +9,6 @@ var db = getDataSource(); describe('discoverModels', function() { describe('Discover models including views', function() { it('should return an array of tables and views', function(done) { - db.discoverModelDefinitions({ views: true, limit: 3, @@ -34,7 +33,6 @@ describe('discoverModels', function() { describe('Discover models excluding views', function() { it('should return an array of only tables', function(done) { - db.discoverModelDefinitions({ views: false, limit: 3, @@ -61,7 +59,6 @@ describe('discoverModels', function() { describe('Discover models including other users', function() { it('should return an array of all tables and views', function(done) { - db.discoverModelDefinitions({ all: true, limit: 100, @@ -101,7 +98,6 @@ describe('Discover model properties', function() { }); }); }); - }); describe('Discover model primary keys', function() { diff --git a/test/id.test.js b/test/id.test.js index 34bad2d..7f0a68f 100644 --- a/test/id.test.js +++ b/test/id.test.js @@ -10,26 +10,25 @@ before(function() { describe('Manipulating id column', function() { it('should auto generate id', function(done) { - var schema = { - 'name': 'WarehouseTest', - 'options': { - 'mssql': { - 'schema': 'dbo', - 'table': 'WAREHOUSE_TEST', - }, - }, - 'properties': { - 'id': { - 'type': 'Number', - 'id': true, - }, - 'name': { - 'type': 'String', - 'required': false, - 'length': 40, + name: 'WarehouseTest', + options: { + mssql: { + schema: 'dbo', + table: 'WAREHOUSE_TEST', + }, }, + properties: { + id: { + type: 'Number', + id: true, + }, + name: { + type: 'String', + required: false, + length: 40, + }, }, }; @@ -71,28 +70,27 @@ describe('Manipulating id column', function() { }); it('should use manual id', function(done) { - var schema = { - 'name': 'WarehouseTest', - 'options': { - 'idInjection': false, - 'mssql': { - 'schema': 'dbo', - 'table': 'WAREHOUSE_TEST', - }, - }, - 'properties': { - 'id': { - 'type': 'Number', - 'id': true, - 'generated': false, - }, - 'name': { - 'type': 'String', - 'required': false, - 'length': 40, + name: 'WarehouseTest', + options: { + idInjection: false, + mssql: { + schema: 'dbo', + table: 'WAREHOUSE_TEST', + }, }, + properties: { + id: { + type: 'Number', + id: true, + generated: false, + }, + name: { + type: 'String', + required: false, + length: 40, + }, }, }; @@ -124,33 +122,32 @@ describe('Manipulating id column', function() { }); it('should use bigint id', function(done) { - var schema = { - 'name': 'WarehouseTest', - 'options': { - 'idInjection': false, - 'mssql': { - 'schema': 'dbo', - 'table': 'WAREHOUSE_TEST', - }, - }, - 'properties': { - 'id': { - 'type': 'Number', - 'id': true, - 'generated': false, - 'mssql': { - 'dataType': 'bigint', - 'dataPrecision': 20, - 'dataScale': 0, + name: 'WarehouseTest', + options: { + idInjection: false, + mssql: { + schema: 'dbo', + table: 'WAREHOUSE_TEST', }, }, - 'name': { - 'type': 'String', - 'required': false, - 'length': 40, - }, + properties: { + id: { + type: 'Number', + id: true, + generated: false, + mssql: { + dataType: 'bigint', + dataPrecision: 20, + dataScale: 0, + }, + }, + name: { + type: 'String', + required: false, + length: 40, + }, }, }; diff --git a/test/imported.test.js b/test/imported.test.js index 6a01341..ee0f1fd 100644 --- a/test/imported.test.js +++ b/test/imported.test.js @@ -1,10 +1,8 @@ describe('mssql imported features', function() { - before(function() { require('./init.js'); }); require('loopback-datasource-juggler/test/common.batch.js'); require('loopback-datasource-juggler/test/include.test.js'); - }); diff --git a/test/init.js b/test/init.js index bb0f82d..314f535 100644 --- a/test/init.js +++ b/test/init.js @@ -16,7 +16,6 @@ try { } global.getConfig = function(options) { - var dbConf = { host: config.host || config.hostname || config.server || 'localhost', port: config.port || 1433, diff --git a/test/mapping.test.js b/test/mapping.test.js index c31d22c..11496cf 100644 --- a/test/mapping.test.js +++ b/test/mapping.test.js @@ -11,7 +11,6 @@ before(function() { describe('Mapping models', function() { it('should honor the mssql settings for table/column', function(done) { - var schema = { name: 'TestInventory', options: { @@ -110,6 +109,5 @@ describe('Mapping models', function() { }, ], done); }); - }); }); diff --git a/test/mssql.test.js b/test/mssql.test.js index bb123fb..7b7c390 100644 --- a/test/mssql.test.js +++ b/test/mssql.test.js @@ -1,11 +1,9 @@ require('./init'); var should = require('should'); - var Post, PostWithUUID, PostWithStringId, db; describe('mssql connector', function() { - before(function() { db = getDataSource(); @@ -29,7 +27,6 @@ describe('mssql connector', function() { rating: { type: Number, mssql: { dataType: 'FLOAT' }}, approved: Boolean, }); - }); it('should run migration', function(done) { @@ -63,7 +60,6 @@ describe('mssql connector', function() { }); }); - it('should support boolean types with false value', function(done) { Post.create({ title: 'T2', content: 'C2', approved: false }, function(err, p) { should.not.exists(err); @@ -279,12 +275,14 @@ describe('mssql connector', function() { context('with regex objects', function() { it('should print a warning and return an error', function(done) { - Post.find({ where: { content: { regexp: new RegExp(/^A/) }}}, function(err, - posts) { - console.warn.calledOnce.should.be.ok; - should.exist(err); - done(); - }); + Post.find( + { where: { content: { regexp: new RegExp(/^A/) }}}, + function(err, posts) { + console.warn.calledOnce.should.be.ok; + should.exist(err); + done(); + } + ); }); }); }); diff --git a/test/transaction.test.js b/test/transaction.test.js index 11d7984..d2f2fbf 100644 --- a/test/transaction.test.js +++ b/test/transaction.test.js @@ -6,7 +6,6 @@ var Transaction = require('loopback-connector').Transaction; var db, Post; describe('transactions', function() { - before(function(done) { db = getDataSource(); Post = db.define('PostTX', { @@ -54,7 +53,6 @@ describe('transactions', function() { } describe('commit', function() { - var post = { title: 't1', content: 'c1' }; before(createPostInTx(post)); @@ -74,7 +72,6 @@ describe('transactions', function() { }); describe('rollback', function() { - var post = { title: 't2', content: 'c2' }; before(createPostInTx(post)); @@ -92,12 +89,5 @@ describe('transactions', function() { it('should not see the rolledback insert', expectToFindPosts(post, 0)); }); - }); - - - - - - From 5987e461fc12748f2e291d9c423aed8089e85323 Mon Sep 17 00:00:00 2001 From: juehou Date: Thu, 14 Apr 2016 16:35:38 -0400 Subject: [PATCH 143/239] Lazy connect when booting app --- lib/mssql.js | 19 +++++++++++++------ test/connection.js | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 09f8f9e..9c370b2 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -15,12 +15,19 @@ exports.initialize = function initializeSchema(dataSource, callback) { debug('Settings: %j', settings); var driver = new MsSQL(settings); dataSource.connector = driver; - driver.connect(function(err, connection) { - dataSource.client = connection; - dataSource.connector.dataSource = dataSource; - dataSource.connector.tableNameID = dataSource.settings.tableNameID; - callback && callback(err, connection); - }); + dataSource.connector.dataSource = dataSource; + dataSource.connector.tableNameID = dataSource.settings.tableNameID; + + if (settings.lazyConnect) { + process.nextTick(function() { + callback(); + }); + } else { + driver.connect(function(err, connection) { + dataSource.client = connection; + callback && callback(err, connection); + }); + } }; function MsSQL(settings) { diff --git a/test/connection.js b/test/connection.js index 955ec47..2f8056f 100644 --- a/test/connection.js +++ b/test/connection.js @@ -46,4 +46,42 @@ function generateURL(config) { }; var formatedUrl = url.format(urlObj); return formatedUrl; -} +}; + +describe('lazyConnect', function() { + var getDS = function(myconfig) { + var db = new DataSource(mssqlConnector, myconfig); + return db; + }; + + it('should skip connect phase (lazyConnect = true)', function(done) { + var dsConfig = { + host: 'invalid-hostname', + port: 80, + lazyConnect: true, + }; + var ds = getDS(dsConfig); + + var errTimeout = setTimeout(function() { + done(); + }, 2000); + ds.on('error', function(err) { + clearTimeout(errTimeout); + done(err); + }); + }); + + it('should report connection error (lazyConnect = false)', function(done) { + var dsConfig = { + host: 'invalid-hostname', + port: 80, + lazyConnect: false, + }; + var ds = getDS(dsConfig); + + ds.on('error', function(err) { + err.message.should.containEql('ENOTFOUND'); + done(); + }); + }); +}); From eb2b07de4417df3e4177631b9feffaede07c6849 Mon Sep 17 00:00:00 2001 From: Ryan Graham Date: Thu, 5 May 2016 17:30:44 -0700 Subject: [PATCH 144/239] update copyright notices and license --- CONTRIBUTING.md | 44 ++++++++++++++++++++-------------------- LICENSE.md | 11 ++++++---- index.js | 5 +++++ lib/discovery.js | 5 +++++ lib/migration.js | 5 +++++ lib/mssql.js | 5 +++++ lib/transaction.js | 5 +++++ package.json | 4 ++-- remove-regenerator.js | 5 +++++ test/autoupdate.test.js | 5 +++++ test/commontests.js | 5 +++++ test/connection.js | 5 +++++ test/discover.test.js | 5 +++++ test/id.test.js | 5 +++++ test/imported.test.js | 5 +++++ test/init.js | 5 +++++ test/mapping.test.js | 5 +++++ test/mssql.test.js | 5 +++++ test/transaction.test.js | 5 +++++ 19 files changed, 111 insertions(+), 28 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6cece43..306c3a6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ ### Contributing ### Thank you for your interest in `loopback-connector-mssql`, an open source project -administered by StrongLoop. +administered by IBM. Contributing to `loopback-connector-mssql` is easy. In a few simple steps: @@ -26,53 +26,53 @@ Contributing to `loopback-connector-mssql` is easy. In a few simple steps: By signing this Individual Contributor License Agreement ("Agreement"), and making a Contribution (as defined below) to - StrongLoop, Inc. ("StrongLoop"), You (as defined below) accept and + IBM Corp. ("IBM"), You (as defined below) accept and agree to the following terms and conditions for Your present and - future Contributions submitted to StrongLoop. Except for the license - granted in this Agreement to StrongLoop and recipients of software - distributed by StrongLoop, You reserve all right, title, and interest + future Contributions submitted to IBM. Except for the license + granted in this Agreement to IBM and recipients of software + distributed by IBM, You reserve all right, title, and interest in and to Your Contributions. 1. Definitions "You" or "Your" shall mean the copyright owner or the individual authorized by the copyright owner that is entering into this - Agreement with StrongLoop. + Agreement with IBM. "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that - is intentionally submitted by You to StrongLoop for inclusion in, + is intentionally submitted by You to IBM for inclusion in, or documentation of, any of the products owned or managed by - StrongLoop ("Work"). For purposes of this definition, "submitted" + IBM ("Work"). For purposes of this definition, "submitted" means any form of electronic, verbal, or written communication - sent to StrongLoop or its representatives, including but not + sent to IBM or its representatives, including but not limited to communication or electronic mailing lists, source code control systems, and issue tracking systems that are managed by, - or on behalf of, StrongLoop for the purpose of discussing and + or on behalf of, IBM for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." - 2. You Grant a Copyright License to StrongLoop + 2. You Grant a Copyright License to IBM Subject to the terms and conditions of this Agreement, You hereby - grant to StrongLoop and recipients of software distributed by - StrongLoop, a perpetual, worldwide, non-exclusive, no-charge, + grant to IBM and recipients of software distributed by + IBM, a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works under any license and without any restrictions. - 3. You Grant a Patent License to StrongLoop + 3. You Grant a Patent License to IBM Subject to the terms and conditions of this Agreement, You hereby - grant to StrongLoop and to recipients of software distributed by - StrongLoop a perpetual, worldwide, non-exclusive, no-charge, + grant to IBM and to recipients of software distributed by + IBM a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this Section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work under any license and without any restrictions. The patent license You grant to - StrongLoop under this Section applies only to those patent claims + IBM under this Section applies only to those patent claims licensable by You that are necessarily infringed by Your Contributions(s) alone or by combination of Your Contributions(s) with the Work to which such Contribution(s) was submitted. If any @@ -84,7 +84,7 @@ Contributing to `loopback-connector-mssql` is easy. In a few simple steps: this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. - 4. You Have the Right to Grant Licenses to StrongLoop + 4. You Have the Right to Grant Licenses to IBM You represent that You are legally entitled to grant the licenses in this Agreement. @@ -94,7 +94,7 @@ Contributing to `loopback-connector-mssql` is easy. In a few simple steps: the Contributions on behalf of that employer, that Your employer has waived such rights for Your Contributions, or that Your employer has executed a separate Corporate Contributor License - Agreement with StrongLoop. + Agreement with IBM. 5. The Contributions Are Your Original Work @@ -123,7 +123,7 @@ Contributing to `loopback-connector-mssql` is easy. In a few simple steps: 6. No Warranties or Conditions - StrongLoop acknowledges that unless required by applicable law or + IBM acknowledges that unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES @@ -133,7 +133,7 @@ Contributing to `loopback-connector-mssql` is easy. In a few simple steps: 7. Submission on Behalf of Others If You wish to submit work that is not Your original creation, You - may submit it to StrongLoop separately from any Contribution, + may submit it to IBM separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which You are @@ -142,7 +142,7 @@ Contributing to `loopback-connector-mssql` is easy. In a few simple steps: 8. Agree to Notify of Change of Circumstances - You agree to notify StrongLoop of any facts or circumstances of + You agree to notify IBM of any facts or circumstances of which You become aware that would make these representations inaccurate in any respect. Email us at callback@strongloop.com. ``` diff --git a/LICENSE.md b/LICENSE.md index 8c40f09..c586309 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,5 +1,8 @@ -Copyright (c) 2013-2015 StrongLoop, Inc. +Licensed Materials - Property of IBM +IBM StrongLoop Software +Copyright IBM Corp. 2016. All Rights Reserved. +US Government Users Restricted Rights - Use, duplication or disclosure +restricted by GSA ADP Schedule Contract with IBM Corp. -You may use this library under the terms of the [StrongLoop Subscription Agreement][]. - -[StrongLoop Subscription Agreement]: http://strongloop.com/license +See full text of IBM International Program License Agreement (IPLA) +http://www-03.ibm.com/software/sla/sladb.nsf/pdf/ipla/$file/ipla.pdf diff --git a/index.js b/index.js index c893e7e..797b5f8 100644 --- a/index.js +++ b/index.js @@ -1 +1,6 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback-connector-mssql +// US Government Users Restricted Rights - Use, duplication or disclosure +// restricted by GSA ADP Schedule Contract with IBM Corp. + module.exports = require('./lib/mssql.js'); diff --git a/lib/discovery.js b/lib/discovery.js index 24a0efe..5b349c4 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback-connector-mssql +// US Government Users Restricted Rights - Use, duplication or disclosure +// restricted by GSA ADP Schedule Contract with IBM Corp. + module.exports = mixinDiscovery; function mixinDiscovery(MsSQL) { diff --git a/lib/migration.js b/lib/migration.js index 5bc88f3..4bf753a 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback-connector-mssql +// US Government Users Restricted Rights - Use, duplication or disclosure +// restricted by GSA ADP Schedule Contract with IBM Corp. + var async = require('async'); module.exports = mixinMigration; diff --git a/lib/mssql.js b/lib/mssql.js index 9c370b2..5f7dd30 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback-connector-mssql +// US Government Users Restricted Rights - Use, duplication or disclosure +// restricted by GSA ADP Schedule Contract with IBM Corp. + /*! Module dependencies */ var mssql = require('mssql'); var SqlConnector = require('loopback-connector').SqlConnector; diff --git a/lib/transaction.js b/lib/transaction.js index 44977d3..ff28a57 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback-connector-mssql +// US Government Users Restricted Rights - Use, duplication or disclosure +// restricted by GSA ADP Schedule Contract with IBM Corp. + var debug = require('debug')('loopback:connector:mssql:transaction'); module.exports = mixinTransaction; diff --git a/package.json b/package.json index 29ce576..836b800 100644 --- a/package.json +++ b/package.json @@ -32,9 +32,9 @@ "sinon": "^1.15.4" }, "scripts": { - "lint": "eslint .", + "lint": "eslint .", "test": "./node_modules/.bin/mocha --timeout 5000 -R spec", - "posttest": "npm run lint", + "posttest": "npm run lint", "prepublish": "node remove-regenerator.js" }, "repository": { diff --git a/remove-regenerator.js b/remove-regenerator.js index 5b38d12..aa6d16b 100644 --- a/remove-regenerator.js +++ b/remove-regenerator.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2016. All Rights Reserved. +// Node module: loopback-connector-mssql +// US Government Users Restricted Rights - Use, duplication or disclosure +// restricted by GSA ADP Schedule Contract with IBM Corp. + /** * This script removes regenerator bundled with babel-runtime */ diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js index d5f76af..c2ec740 100644 --- a/test/autoupdate.test.js +++ b/test/autoupdate.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback-connector-mssql +// US Government Users Restricted Rights - Use, duplication or disclosure +// restricted by GSA ADP Schedule Contract with IBM Corp. + require('./init.js'); var assert = require('assert'); var ds; diff --git a/test/commontests.js b/test/commontests.js index 4eb3414..134c2ce 100644 --- a/test/commontests.js +++ b/test/commontests.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback-connector-mssql +// US Government Users Restricted Rights - Use, duplication or disclosure +// restricted by GSA ADP Schedule Contract with IBM Corp. + var jdb = require('loopback-datasource-juggler'); var commonTest = jdb.test; diff --git a/test/connection.js b/test/connection.js index 2f8056f..b62f562 100644 --- a/test/connection.js +++ b/test/connection.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2016. All Rights Reserved. +// Node module: loopback-connector-mssql +// US Government Users Restricted Rights - Use, duplication or disclosure +// restricted by GSA ADP Schedule Contract with IBM Corp. + /* eslint-env node, mocha */ require('./init.js'); var assert = require('assert'); diff --git a/test/discover.test.js b/test/discover.test.js index 2255634..515e360 100644 --- a/test/discover.test.js +++ b/test/discover.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback-connector-mssql +// US Government Users Restricted Rights - Use, duplication or disclosure +// restricted by GSA ADP Schedule Contract with IBM Corp. + process.env.NODE_ENV = 'test'; require('./init.js'); require('should'); diff --git a/test/id.test.js b/test/id.test.js index 7f0a68f..ee8de15 100644 --- a/test/id.test.js +++ b/test/id.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback-connector-mssql +// US Government Users Restricted Rights - Use, duplication or disclosure +// restricted by GSA ADP Schedule Contract with IBM Corp. + require('./init.js'); var should = require('should'); var assert = require('assert'); diff --git a/test/imported.test.js b/test/imported.test.js index ee0f1fd..60bbe63 100644 --- a/test/imported.test.js +++ b/test/imported.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback-connector-mssql +// US Government Users Restricted Rights - Use, duplication or disclosure +// restricted by GSA ADP Schedule Contract with IBM Corp. + describe('mssql imported features', function() { before(function() { require('./init.js'); diff --git a/test/init.js b/test/init.js index 314f535..346d87b 100644 --- a/test/init.js +++ b/test/init.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback-connector-mssql +// US Government Users Restricted Rights - Use, duplication or disclosure +// restricted by GSA ADP Schedule Contract with IBM Corp. + module.exports = require('should'); var DataSource = require('loopback-datasource-juggler').DataSource; diff --git a/test/mapping.test.js b/test/mapping.test.js index 11496cf..6af7053 100644 --- a/test/mapping.test.js +++ b/test/mapping.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback-connector-mssql +// US Government Users Restricted Rights - Use, duplication or disclosure +// restricted by GSA ADP Schedule Contract with IBM Corp. + var should = require('should'); require('./init'); diff --git a/test/mssql.test.js b/test/mssql.test.js index 7b7c390..7ca703d 100644 --- a/test/mssql.test.js +++ b/test/mssql.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback-connector-mssql +// US Government Users Restricted Rights - Use, duplication or disclosure +// restricted by GSA ADP Schedule Contract with IBM Corp. + require('./init'); var should = require('should'); diff --git a/test/transaction.test.js b/test/transaction.test.js index d2f2fbf..0d8d97e 100644 --- a/test/transaction.test.js +++ b/test/transaction.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback-connector-mssql +// US Government Users Restricted Rights - Use, duplication or disclosure +// restricted by GSA ADP Schedule Contract with IBM Corp. + require('./init.js'); require('should'); From 7ea7003f1dfc084278f9ff83cb94bbfeab0b52ae Mon Sep 17 00:00:00 2001 From: Simon Ho Date: Tue, 21 Jun 2016 13:45:47 -0700 Subject: [PATCH 145/239] 2.8.0 * update copyright notices and license (Ryan Graham) * Lazy connect when booting app (juehou) * Add feature/eslint (Amir-61) * Fix linting errors (Amir Jafarian) * Auto-update by eslint --fix (Amir Jafarian) * Add eslint infrastructure (Amir Jafarian) --- CHANGES.md | 16 ++++++++++++++++ package.json | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index a0fd8af..0b036c8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,19 @@ +2016-06-21, Version 2.8.0 +========================= + + * update copyright notices and license (Ryan Graham) + + * Lazy connect when booting app (juehou) + + * Add feature/eslint (Amir-61) + + * Fix linting errors (Amir Jafarian) + + * Auto-update by eslint --fix (Amir Jafarian) + + * Add eslint infrastructure (Amir Jafarian) + + 2016-04-07, Version 2.7.1 ========================= diff --git a/package.json b/package.json index 836b800..8826a4d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.7.1", + "version": "2.8.0", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", From d5bafa7a111b6060b066b1c112e3869ca6650b6a Mon Sep 17 00:00:00 2001 From: Ryan Graham Date: Wed, 13 Jul 2016 17:24:52 -0700 Subject: [PATCH 146/239] Update URLs in CONTRIBUTING.md (#88) --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 306c3a6..6aac285 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -147,5 +147,5 @@ Contributing to `loopback-connector-mssql` is easy. In a few simple steps: inaccurate in any respect. Email us at callback@strongloop.com. ``` -[Google C++ Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml -[Google Javascript Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml +[Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html +[Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml From 055e4c9ccc3bd230b5d62c0bafbe32ec0fe2248c Mon Sep 17 00:00:00 2001 From: Loay Date: Thu, 28 Jul 2016 11:28:15 -0400 Subject: [PATCH 147/239] Update Fix --- lib/migration.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/migration.js b/lib/migration.js index 4bf753a..64b6823 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -3,6 +3,7 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +'use strict'; var async = require('async'); module.exports = mixinMigration; @@ -277,7 +278,7 @@ function mixinMigration(MsSQL) { async.each(statements, function(query, fn) { if (checkOnly) { - fn(null, true, { statements: statements, query: query }); + fn(null, true, {statements: statements, query: query}); } else { self.execute(query, fn); } @@ -333,7 +334,7 @@ function mixinMigration(MsSQL) { } else if (idProp.type === String) { if (idProp.generated !== false) { sql.push(self.columnEscaped(model, modelPKID) + - ' [uniqueidentifier] DEFAULT newid() NOT NULL'); + ' [varchar](64) DEFAULT newid() NOT NULL'); } else { sql.push(self.columnEscaped(model, modelPKID) + ' ' + self.propertySettingsSQL(model, prop) + ' DEFAULT newid()'); From 17edd0279099aa8659fe01897f5c2e1d98684ee4 Mon Sep 17 00:00:00 2001 From: Loay Date: Thu, 28 Jul 2016 11:39:09 -0400 Subject: [PATCH 148/239] Revert "Update Fix" This reverts commit 055e4c9ccc3bd230b5d62c0bafbe32ec0fe2248c. --- lib/migration.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/migration.js b/lib/migration.js index 64b6823..4bf753a 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -3,7 +3,6 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. -'use strict'; var async = require('async'); module.exports = mixinMigration; @@ -278,7 +277,7 @@ function mixinMigration(MsSQL) { async.each(statements, function(query, fn) { if (checkOnly) { - fn(null, true, {statements: statements, query: query}); + fn(null, true, { statements: statements, query: query }); } else { self.execute(query, fn); } @@ -334,7 +333,7 @@ function mixinMigration(MsSQL) { } else if (idProp.type === String) { if (idProp.generated !== false) { sql.push(self.columnEscaped(model, modelPKID) + - ' [varchar](64) DEFAULT newid() NOT NULL'); + ' [uniqueidentifier] DEFAULT newid() NOT NULL'); } else { sql.push(self.columnEscaped(model, modelPKID) + ' ' + self.propertySettingsSQL(model, prop) + ' DEFAULT newid()'); From 44f16cc7589ee87ab31f9e8dd45edd966343b49b Mon Sep 17 00:00:00 2001 From: Candy Date: Fri, 22 Jul 2016 17:02:52 -0400 Subject: [PATCH 149/239] Add globalization --- .gitignore | 2 ++ index.js | 3 +++ intl/en/messages.json | 6 ++++++ lib/discovery.js | 6 ++++-- lib/migration.js | 6 ++++-- lib/mssql.js | 4 +++- package.json | 3 ++- 7 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 intl/en/messages.json diff --git a/.gitignore b/.gitignore index 9672873..ce0562f 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ node_modules/ db/ npm-debug.log +intl/* +!intl/en/ \ No newline at end of file diff --git a/index.js b/index.js index 797b5f8..6a60109 100644 --- a/index.js +++ b/index.js @@ -3,4 +3,7 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +var SG = require('strong-globalize'); +SG.SetRootDir(__dirname); + module.exports = require('./lib/mssql.js'); diff --git a/intl/en/messages.json b/intl/en/messages.json new file mode 100644 index 0000000..f90e5dc --- /dev/null +++ b/intl/en/messages.json @@ -0,0 +1,6 @@ +{ + "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} must be an {{object}}: {0}", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} is a required string argument: {0}", + "80a32e80cbed65eba2103201a7c94710": "Model not found: {0}", + "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} does not support the regular expression operator" +} diff --git a/lib/discovery.js b/lib/discovery.js index 5b349c4..9044188 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -3,6 +3,8 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +var g = require('strong-globalize')(); + module.exports = mixinDiscovery; function mixinDiscovery(MsSQL) { @@ -169,7 +171,7 @@ function mixinDiscovery(MsSQL) { */ function getArgs(table, options, cb) { if ('string' !== typeof table || !table) { - throw new Error('table is a required string argument: ' + table); + throw new Error(g.f('{{table}} is a required string argument: %s', table)); } options = options || {}; if (!cb && 'function' === typeof options) { @@ -177,7 +179,7 @@ function mixinDiscovery(MsSQL) { options = {}; } if (typeof options !== 'object') { - throw new Error('options must be an object: ' + options); + throw new Error(g.f('{{options}} must be an {{object}}: %s', options)); } return { owner: options.owner || options.schema, diff --git a/lib/migration.js b/lib/migration.js index 4bf753a..50bd2dd 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -3,6 +3,8 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +var g = require('strong-globalize')(); + var async = require('async'); module.exports = mixinMigration; @@ -78,7 +80,7 @@ function mixinMigration(MsSQL) { async.each(models, function(model, done) { if (!(model in self._models)) { return process.nextTick(function() { - done(new Error('Model not found: ' + model)); + done(new Error(g.f('Model not found: %s', model))); }); } self.showFields(model, function(err, fields) { @@ -455,7 +457,7 @@ function mixinMigration(MsSQL) { async.each(models, function(model, done) { if (!(model in self._models)) { return process.nextTick(function() { - done(new Error('Model not found: ' + model)); + done(new Error(g.f('Model not found: %s', model))); }); } self.dropTable(model, function(err) { diff --git a/lib/mssql.js b/lib/mssql.js index 5f7dd30..d4a5d82 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -3,6 +3,8 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +var g = require('strong-globalize')(); + /*! Module dependencies */ var mssql = require('mssql'); var SqlConnector = require('loopback-connector').SqlConnector; @@ -478,7 +480,7 @@ MsSQL.prototype.buildExpression = function(columnName, operator, operatorValue, return new ParameterizedSQL(columnName + " NOT LIKE ? ESCAPE '\\'", [operatorValue]); case 'regexp': - console.warn('Microsoft SQL Server doe not support the regular ' + + g.warn('{{Microsoft SQL Server}} does not support the regular ' + 'expression operator'); default: // invoke the base implementation of `buildExpression` diff --git a/package.json b/package.json index 8826a4d..92e7ca7 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "async": "^1.5.2", "debug": "^2.1.1", "loopback-connector": "^2.1.0", - "mssql": "^3.1.1" + "mssql": "^3.1.1", + "strong-globalize": "^2.5.8" }, "bundledDependencies": [ "mssql" From ed24e801252d485ec1cb443057727e6d3549f3c4 Mon Sep 17 00:00:00 2001 From: Simon Ho Date: Tue, 6 Sep 2016 18:01:14 -0700 Subject: [PATCH 150/239] Use juggler@3 for running tests --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 92e7ca7..1e9876a 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "bluebird": "^3.3.3", "eslint": "^2.8.0", "eslint-config-loopback": "^2.0.0", - "loopback-datasource-juggler": "^2.28.0", + "loopback-datasource-juggler": "^3.0.0-alpha.7", "mocha": "^2.1.0", "rc": "^1.0.0", "should": "^8.0.2", From 5fb54c728b902073e337cabf2d4a21eb31112582 Mon Sep 17 00:00:00 2001 From: Loay Date: Thu, 15 Sep 2016 15:33:56 -0400 Subject: [PATCH 151/239] Update eslint infrastructure --- .eslintrc | 5 ++-- index.js | 1 + lib/discovery.js | 1 + lib/migration.js | 3 +- lib/mssql.js | 1 + lib/transaction.js | 1 + package.json | 4 +-- remove-regenerator.js | 1 + test/autoupdate.test.js | 2 +- test/commontests.js | 31 +++++++++---------- test/connection.js | 3 +- test/discover.test.js | 7 +++-- test/id.test.js | 17 ++++++----- test/imported.test.js | 1 + test/init.js | 3 +- test/mapping.test.js | 11 +++---- test/mssql.test.js | 65 ++++++++++++++++++++-------------------- test/transaction.test.js | 14 ++++----- 18 files changed, 93 insertions(+), 78 deletions(-) diff --git a/.eslintrc b/.eslintrc index 4e1231e..6101320 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,6 +5,7 @@ "ignoreComments": true, "ignoreUrls": true, "ignorePattern": "^\\s*var\\s.+=\\s*(require\\s*\\()|(/)" - }] + }], + "no-unused-expressions": "off" } -} \ No newline at end of file +} diff --git a/index.js b/index.js index 6a60109..f0eb6bd 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +'use strict'; var SG = require('strong-globalize'); SG.SetRootDir(__dirname); diff --git a/lib/discovery.js b/lib/discovery.js index 9044188..ed6cbd6 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -3,6 +3,7 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +'use strict'; var g = require('strong-globalize')(); module.exports = mixinDiscovery; diff --git a/lib/migration.js b/lib/migration.js index 50bd2dd..e31f2de 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -3,6 +3,7 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +'use strict'; var g = require('strong-globalize')(); var async = require('async'); @@ -279,7 +280,7 @@ function mixinMigration(MsSQL) { async.each(statements, function(query, fn) { if (checkOnly) { - fn(null, true, { statements: statements, query: query }); + fn(null, true, {statements: statements, query: query}); } else { self.execute(query, fn); } diff --git a/lib/mssql.js b/lib/mssql.js index d4a5d82..54f3461 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -3,6 +3,7 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +'use strict'; var g = require('strong-globalize')(); /*! Module dependencies */ diff --git a/lib/transaction.js b/lib/transaction.js index ff28a57..ed2ead3 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -3,6 +3,7 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +'use strict'; var debug = require('debug')('loopback:connector:mssql:transaction'); module.exports = mixinTransaction; diff --git a/package.json b/package.json index 1e9876a..2291260 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,8 @@ ], "devDependencies": { "bluebird": "^3.3.3", - "eslint": "^2.8.0", - "eslint-config-loopback": "^2.0.0", + "eslint": "^2.13.1", + "eslint-config-loopback": "^4.0.0", "loopback-datasource-juggler": "^3.0.0-alpha.7", "mocha": "^2.1.0", "rc": "^1.0.0", diff --git a/remove-regenerator.js b/remove-regenerator.js index aa6d16b..f61b5b0 100644 --- a/remove-regenerator.js +++ b/remove-regenerator.js @@ -6,6 +6,7 @@ /** * This script removes regenerator bundled with babel-runtime */ +'use strict'; var fs = require('fs'); try { var index = require.resolve('babel-runtime/regenerator/index.js'); diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js index c2ec740..95833e3 100644 --- a/test/autoupdate.test.js +++ b/test/autoupdate.test.js @@ -3,6 +3,7 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +'use strict'; require('./init.js'); var assert = require('assert'); var ds; @@ -140,4 +141,3 @@ describe('MS SQL server connector', function() { }); }); }); - diff --git a/test/commontests.js b/test/commontests.js index 134c2ce..a6bc09c 100644 --- a/test/commontests.js +++ b/test/commontests.js @@ -3,6 +3,7 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +'use strict'; var jdb = require('loopback-datasource-juggler'); var commonTest = jdb.test; @@ -26,12 +27,12 @@ commonTest.it('should automigrate', function(test) { commonTest.it('should be able to ORDER results', function(test) { var titles = [ - { title: 'Title A', subject: 'B' }, - { title: 'Title Z', subject: 'A' }, - { title: 'Title M', subject: 'C' }, - { title: 'Title A', subject: 'A' }, - { title: 'Title B', subject: 'A' }, - { title: 'Title C', subject: 'D' }, + {title: 'Title A', subject: 'B'}, + {title: 'Title Z', subject: 'A'}, + {title: 'Title M', subject: 'C'}, + {title: 'Title A', subject: 'A'}, + {title: 'Title B', subject: 'A'}, + {title: 'Title C', subject: 'D'}, ]; var dates = [ @@ -44,7 +45,7 @@ commonTest.it('should be able to ORDER results', function(test) { ]; titles.forEach(function(t, i) { - schema.models.Post.create({ title: t.title, subject: t.subject, date: dates[i] }, done); + schema.models.Post.create({title: t.title, subject: t.subject, date: dates[i]}, done); }); var i = 0; var tests = 0; @@ -67,7 +68,7 @@ commonTest.it('should be able to ORDER results', function(test) { function doStringTest() { tests += 1; - schema.models.Post.all({ order: 'title' }, function(err, posts) { + schema.models.Post.all({order: 'title'}, function(err, posts) { if (err) console.log(err); test.equal(posts.length, 6); titles.sort(compare).forEach(function(t, i) { @@ -79,7 +80,7 @@ commonTest.it('should be able to ORDER results', function(test) { function doNumberTest() { tests += 1; - schema.models.Post.all({ order: 'date' }, function(err, posts) { + schema.models.Post.all({order: 'date'}, function(err, posts) { if (err) console.log(err); test.equal(posts.length, 6); dates.sort(numerically).forEach(function(d, i) { @@ -92,7 +93,7 @@ commonTest.it('should be able to ORDER results', function(test) { function doFilterAndSortTest() { tests += 1; - schema.models.Post.all({ where: { date: new Date(1000 * 9) }, order: 'title', limit: 3 }, function(err, posts) { + schema.models.Post.all({where: {date: new Date(1000 * 9)}, order: 'title', limit: 3}, function(err, posts) { if (err) console.log(err); console.log(posts.length); test.equal(posts.length, 2, 'Exactly 2 posts returned by query'); @@ -107,7 +108,7 @@ commonTest.it('should be able to ORDER results', function(test) { function doFilterAndSortReverseTest() { tests += 1; - schema.models.Post.all({ where: { date: new Date(1000 * 9) }, order: 'title DESC', limit: 3 }, + schema.models.Post.all({where: {date: new Date(1000 * 9)}, order: 'title DESC', limit: 3}, function(err, posts) { if (err) console.log(err); test.equal(posts.length, 2, 'Exactly 2 posts returned by query'); @@ -122,7 +123,7 @@ commonTest.it('should be able to ORDER results', function(test) { function doMultipleSortTest() { tests += 1; - schema.models.Post.all({ order: 'title ASC, subject ASC' }, function(err, posts) { + schema.models.Post.all({order: 'title ASC, subject ASC'}, function(err, posts) { if (err) console.log(err); test.equal(posts.length, 6); test.equal(posts[0].title, 'Title A'); @@ -136,7 +137,7 @@ commonTest.it('should be able to ORDER results', function(test) { function doMultipleReverseSortTest() { tests += 1; - schema.models.Post.all({ order: 'title ASC, subject DESC' }, function(err, posts) { + schema.models.Post.all({order: 'title ASC, subject DESC'}, function(err, posts) { if (err) console.log(err); test.equal(posts.length, 6); test.equal(posts[0].title, 'Title A'); @@ -164,7 +165,7 @@ commonTest.it('should be able to ORDER results', function(test) { commonTest.it('should count posts', function(test) { test.expect(2); - schema.models.Post.count({ title: 'Title A' }, function(err, cnt) { + schema.models.Post.count({title: 'Title A'}, function(err, cnt) { test.ifError(err); test.equal(cnt, 2); test.done(); @@ -226,7 +227,7 @@ commonTest.it('should support custom primary key', function(test) { schema.automigrate(function(err) { test.ifError(err); - AppliesTo.create({ Title: 'custom key', Identifier: 'ck', Editable: false }, function(err, data) { + AppliesTo.create({Title: 'custom key', Identifier: 'ck', Editable: false}, function(err, data) { test.ifError(err); test.notStrictEqual(typeof data.AppliesToID, 'undefined'); test.done(); diff --git a/test/connection.js b/test/connection.js index b62f562..7a5f87e 100644 --- a/test/connection.js +++ b/test/connection.js @@ -4,6 +4,7 @@ // restricted by GSA ADP Schedule Contract with IBM Corp. /* eslint-env node, mocha */ +'use strict'; require('./init.js'); var assert = require('assert'); var DataSource = require('loopback-datasource-juggler').DataSource; @@ -46,7 +47,7 @@ function generateURL(config) { hostname: config.host, port: config.port, pathname: config.database, - query: { encrypt: true }, + query: {encrypt: true}, slashes: true, }; var formatedUrl = url.format(urlObj); diff --git a/test/discover.test.js b/test/discover.test.js index 515e360..b9d7c1e 100644 --- a/test/discover.test.js +++ b/test/discover.test.js @@ -3,6 +3,7 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +'use strict'; process.env.NODE_ENV = 'test'; require('./init.js'); require('should'); @@ -122,7 +123,7 @@ describe('Discover model primary keys', function() { }); it('should return an array of primary keys for dbo.product', function(done) { - db.discoverPrimaryKeys('product', { owner: 'dbo' }, function(err, models) { + db.discoverPrimaryKeys('product', {owner: 'dbo'}, function(err, models) { if (err) { console.error(err); done(err); @@ -153,7 +154,7 @@ describe('Discover model foreign keys', function() { }); }); it('should return an array of foreign keys for dbo.inventory', function(done) { - db.discoverForeignKeys('inventory', { owner: 'dbo' }, function(err, models) { + db.discoverForeignKeys('inventory', {owner: 'dbo'}, function(err, models) { if (err) { console.error(err); done(err); @@ -170,7 +171,7 @@ describe('Discover model foreign keys', function() { describe('Discover adl schema from a table', function() { it('should return an adl schema for inventory', function(done) { - db.discoverSchema('inventory', { owner: 'dbo' }, function(err, schema) { + db.discoverSchema('inventory', {owner: 'dbo'}, function(err, schema) { // console.log('%j', schema); assert(schema.name === 'Inventory'); assert(schema.options.mssql.schema === 'dbo'); diff --git a/test/id.test.js b/test/id.test.js index ee8de15..1197890 100644 --- a/test/id.test.js +++ b/test/id.test.js @@ -3,6 +3,7 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +'use strict'; require('./init.js'); var should = require('should'); var assert = require('assert'); @@ -48,19 +49,19 @@ describe('Manipulating id column', function() { Model.destroyAll(callback); }, function(callback) { - Model.create({ name: 'w1' }, + Model.create({name: 'w1'}, callback); }, function(callback) { - Model.create({ name: 'w2' }, + Model.create({name: 'w2'}, callback); }, function(callback) { - Model.create({ name: 'w3' }, + Model.create({name: 'w3'}, callback); }, function(callback) { - Model.find({ order: 'id asc' }, + Model.find({order: 'id asc'}, function(err, results) { assert(!err); results.should.have.lengthOf(3); @@ -110,11 +111,11 @@ describe('Manipulating id column', function() { Model.destroyAll(callback); }, function(callback) { - Model.create({ id: 501, name: 'w1' }, + Model.create({id: 501, name: 'w1'}, callback); }, function(callback) { - Model.find({ order: 'id asc' }, + Model.find({order: 'id asc'}, function(err, results) { assert(!err); results.should.have.lengthOf(1); @@ -167,11 +168,11 @@ describe('Manipulating id column', function() { Model.destroyAll(callback); }, function(callback) { - Model.create({ id: 962744456683738, name: 'w1' }, + Model.create({id: 962744456683738, name: 'w1'}, callback); }, function(callback) { - Model.find({ order: 'id asc' }, + Model.find({order: 'id asc'}, function(err, results) { assert(!err); results.should.have.lengthOf(1); diff --git a/test/imported.test.js b/test/imported.test.js index 60bbe63..634db62 100644 --- a/test/imported.test.js +++ b/test/imported.test.js @@ -3,6 +3,7 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +'use strict'; describe('mssql imported features', function() { before(function() { require('./init.js'); diff --git a/test/init.js b/test/init.js index 346d87b..a0e0f8a 100644 --- a/test/init.js +++ b/test/init.js @@ -3,13 +3,14 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +'use strict'; module.exports = require('should'); var DataSource = require('loopback-datasource-juggler').DataSource; var config = {}; try { - config = require('rc')('loopback', { test: { mssql: {}}}).test.mssql; + config = require('rc')('loopback', {test: {mssql: {}}}).test.mssql; } catch (err) { config = { user: 'demo', diff --git a/test/mapping.test.js b/test/mapping.test.js index 6af7053..c79aaee 100644 --- a/test/mapping.test.js +++ b/test/mapping.test.js @@ -3,6 +3,7 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +'use strict'; var should = require('should'); require('./init'); @@ -74,19 +75,19 @@ describe('Mapping models', function() { Model.destroyAll(callback); }, function(callback) { - Model.create({ locationId: 'l001', available: 10, total: 50 }, + Model.create({locationId: 'l001', available: 10, total: 50}, callback); }, function(callback) { - Model.create({ locationId: 'l002', available: 30, total: 40 }, + Model.create({locationId: 'l002', available: 30, total: 40}, callback); }, function(callback) { - Model.create({ locationId: 'l001', available: 15, total: 30 }, + Model.create({locationId: 'l001', available: 15, total: 30}, callback); }, function(callback) { - Model.find({ fields: ['productId', 'locationId', 'available'] }, + Model.find({fields: ['productId', 'locationId', 'available']}, function(err, results) { // console.log(results); results.should.have.lengthOf(3); @@ -100,7 +101,7 @@ describe('Mapping models', function() { }); }, function(callback) { - Model.find({ fields: { 'total': false }}, function(err, results) { + Model.find({fields: {'total': false}}, function(err, results) { // console.log(results); results.should.have.lengthOf(3); results.forEach(function(r) { diff --git a/test/mssql.test.js b/test/mssql.test.js index 7ca703d..39a7f47 100644 --- a/test/mssql.test.js +++ b/test/mssql.test.js @@ -3,6 +3,7 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +'use strict'; require('./init'); var should = require('should'); @@ -13,23 +14,23 @@ describe('mssql connector', function() { db = getDataSource(); Post = db.define('PostWithBoolean', { - title: { type: String, length: 255, index: true }, - content: { type: String }, + title: {type: String, length: 255, index: true}, + content: {type: String}, approved: Boolean, }); PostWithUUID = db.define('PostWithUUID', { - id: { type: String, generated: true, id: true }, - title: { type: String, length: 255, index: true }, - content: { type: String }, + id: {type: String, generated: true, id: true}, + title: {type: String, length: 255, index: true}, + content: {type: String}, approved: Boolean, }); PostWithStringId = db.define('PostWithStringId', { - id: { type: String, id: true, generated: false }, - title: { type: String, length: 255, index: true }, - content: { type: String }, - rating: { type: Number, mssql: { dataType: 'FLOAT' }}, + id: {type: String, id: true, generated: false}, + title: {type: String, length: 255, index: true}, + content: {type: String}, + rating: {type: Number, mssql: {dataType: 'FLOAT'}}, approved: Boolean, }); }); @@ -43,7 +44,7 @@ describe('mssql connector', function() { var post; it('should support boolean types with true value', function(done) { - Post.create({ title: 'T1', content: 'C1', approved: true }, function(err, p) { + Post.create({title: 'T1', content: 'C1', approved: true}, function(err, p) { should.not.exists(err); post = p; Post.findById(p.id, function(err, p) { @@ -55,7 +56,7 @@ describe('mssql connector', function() { }); it('should support updating boolean types with false value', function(done) { - Post.update({ id: post.id }, { approved: false }, function(err) { + Post.update({id: post.id}, {approved: false}, function(err) { should.not.exists(err); Post.findById(post.id, function(err, p) { should.not.exists(err); @@ -66,7 +67,7 @@ describe('mssql connector', function() { }); it('should support boolean types with false value', function(done) { - Post.create({ title: 'T2', content: 'C2', approved: false }, function(err, p) { + Post.create({title: 'T2', content: 'C2', approved: false}, function(err, p) { should.not.exists(err); post = p; Post.findById(p.id, function(err, p) { @@ -78,7 +79,7 @@ describe('mssql connector', function() { }); it('should single quote escape', function(done) { - Post.create({ title: 'T2', content: 'C,D', approved: false }, function(err, p) { + Post.create({title: 'T2', content: 'C,D', approved: false}, function(err, p) { should.not.exists(err); post = p; Post.findById(p.id, function(err, p) { @@ -90,8 +91,8 @@ describe('mssql connector', function() { }); it('should return the model instance for upsert', function(done) { - Post.upsert({ id: post.id, title: 'T2_new', content: 'C2_new', - approved: true }, function(err, p) { + Post.upsert({id: post.id, title: 'T2_new', content: 'C2_new', + approved: true}, function(err, p) { p.should.have.property('id', post.id); p.should.have.property('title', 'T2_new'); p.should.have.property('content', 'C2_new'); @@ -102,7 +103,7 @@ describe('mssql connector', function() { it('should return the model instance for upsert when id is not present', function(done) { - Post.upsert({ title: 'T2_new', content: 'C2_new', approved: true }, + Post.upsert({title: 'T2_new', content: 'C2_new', approved: true}, function(err, p) { p.should.have.property('id'); p.should.have.property('title', 'T2_new'); @@ -122,7 +123,7 @@ describe('mssql connector', function() { it('should escape number values to defect SQL injection in find', function(done) { - Post.find({ where: { id: '(SELECT 1+1)' }}, function(err, p) { + Post.find({where: {id: '(SELECT 1+1)'}}, function(err, p) { should.exists(err); done(); }); @@ -130,7 +131,7 @@ describe('mssql connector', function() { it('should escape number values to defect SQL injection in find with gt', function(done) { - Post.find({ where: { id: { gt: '(SELECT 1+1)' }}}, function(err, p) { + Post.find({where: {id: {gt: '(SELECT 1+1)'}}}, function(err, p) { should.exists(err); done(); }); @@ -138,7 +139,7 @@ describe('mssql connector', function() { it('should escape number values to defect SQL injection in find', function(done) { - Post.find({ limit: '(SELECT 1+1)' }, function(err, p) { + Post.find({limit: '(SELECT 1+1)'}, function(err, p) { should.exists(err); done(); }); @@ -146,7 +147,7 @@ describe('mssql connector', function() { it('should escape number values to defect SQL injection in find with inq', function(done) { - Post.find({ where: { id: { inq: ['(SELECT 1+1)'] }}}, function(err, p) { + Post.find({where: {id: {inq: ['(SELECT 1+1)']}}}, function(err, p) { should.exists(err); done(); }); @@ -169,8 +170,8 @@ describe('mssql connector', function() { connector.execute('SELECT * FROM SQLI_TEST', function(err, data) { if (err) return done(err); data.should.be.eql( - [{ V1: '(?)', - V2: ', 1 ); INSERT INTO SQLI_TEST VALUES (1, 2); --' }]); + [{V1: '(?)', + V2: ', 1 ); INSERT INTO SQLI_TEST VALUES (1, 2); --'}]); done(); }); }); @@ -180,7 +181,7 @@ describe('mssql connector', function() { it('should allow string array for inq', function(done) { - Post.find({ where: { content: { inq: ['C1', 'C2'] }}}, function(err, p) { + Post.find({where: {content: {inq: ['C1', 'C2']}}}, function(err, p) { should.not.exist(err); should.exist(p); p.should.have.length(2); @@ -190,7 +191,7 @@ describe('mssql connector', function() { it('should perform an empty inq', function(done) { - Post.find({ where: { id: { inq: [] }}}, function(err, p) { + Post.find({where: {id: {inq: []}}}, function(err, p) { should.not.exist(err); should.exist(p); p.should.have.length(0); @@ -200,7 +201,7 @@ describe('mssql connector', function() { it('should perform an empty nin', function(done) { - Post.find({ where: { id: { nin: [] }}}, function(err, p) { + Post.find({where: {id: {nin: []}}}, function(err, p) { should.not.exist(err); should.exist(p); p.should.have.length(4); @@ -209,7 +210,7 @@ describe('mssql connector', function() { }); it('should support uuid', function(done) { - PostWithUUID.create({ title: 'T1', content: 'C1', approved: true }, + PostWithUUID.create({title: 'T1', content: 'C1', approved: true}, function(err, p) { should.not.exists(err); p.should.have.property('id'); @@ -224,7 +225,7 @@ describe('mssql connector', function() { it('should support string id', function(done) { PostWithStringId.create( - { title: 'T1', content: 'C1', approved: true, rating: 3.5 }, + {title: 'T1', content: 'C1', approved: true, rating: 3.5}, function(err, p) { should.not.exists(err); p.should.have.property('id'); @@ -244,8 +245,8 @@ describe('mssql connector', function() { }); beforeEach(function createTestFixtures(done) { Post.create([ - { title: 'a', content: 'AAA' }, - { title: 'b', content: 'BBB' }, + {title: 'a', content: 'AAA'}, + {title: 'b', content: 'BBB'}, ], done); }); beforeEach(function addSpy() { @@ -260,7 +261,7 @@ describe('mssql connector', function() { context('with regex strings', function() { it('should print a warning and return an error', function(done) { - Post.find({ where: { content: { regexp: '^A' }}}, function(err, posts) { + Post.find({where: {content: {regexp: '^A'}}}, function(err, posts) { console.warn.calledOnce.should.be.ok; should.exist(err); done(); @@ -270,7 +271,7 @@ describe('mssql connector', function() { context('with regex literals', function() { it('should print a warning and return an error', function(done) { - Post.find({ where: { content: { regexp: /^A/ }}}, function(err, posts) { + Post.find({where: {content: {regexp: /^A/}}}, function(err, posts) { console.warn.calledOnce.should.be.ok; should.exist(err); done(); @@ -281,7 +282,7 @@ describe('mssql connector', function() { context('with regex objects', function() { it('should print a warning and return an error', function(done) { Post.find( - { where: { content: { regexp: new RegExp(/^A/) }}}, + {where: {content: {regexp: new RegExp(/^A/)}}}, function(err, posts) { console.warn.calledOnce.should.be.ok; should.exist(err); diff --git a/test/transaction.test.js b/test/transaction.test.js index 0d8d97e..641273e 100644 --- a/test/transaction.test.js +++ b/test/transaction.test.js @@ -3,6 +3,7 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +'use strict'; require('./init.js'); require('should'); @@ -14,8 +15,8 @@ describe('transactions', function() { before(function(done) { db = getDataSource(); Post = db.define('PostTX', { - title: { type: String, length: 255, index: true }, - content: { type: String }, + title: {type: String, length: 255, index: true}, + content: {type: String}, }); db.automigrate('PostTX', done); }); @@ -28,7 +29,7 @@ describe('transactions', function() { function(err, tx) { if (err) return done(err); currentTx = tx; - Post.create(post, { transaction: tx }, + Post.create(post, {transaction: tx}, function(err, p) { if (err) { done(err); @@ -48,7 +49,7 @@ describe('transactions', function() { if (inTx) { options.transaction = currentTx; } - Post.find({ where: where }, options, + Post.find({where: where}, options, function(err, posts) { if (err) return done(err); posts.length.should.be.eql(count); @@ -58,7 +59,7 @@ describe('transactions', function() { } describe('commit', function() { - var post = { title: 't1', content: 'c1' }; + var post = {title: 't1', content: 'c1'}; before(createPostInTx(post)); // FIXME: [rfeng] SQL server creates LCK_M_S (Shared Lock on the table @@ -77,7 +78,7 @@ describe('transactions', function() { }); describe('rollback', function() { - var post = { title: 't2', content: 'c2' }; + var post = {title: 't2', content: 'c2'}; before(createPostInTx(post)); // FIXME: [rfeng] SQL server creates LCK_M_S (Shared Lock on the table @@ -95,4 +96,3 @@ describe('transactions', function() { it('should not see the rolledback insert', expectToFindPosts(post, 0)); }); }); - From 83f4d0f8f9d141be7646fbbb12d642dcbb1077ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 22 Sep 2016 14:01:22 +0200 Subject: [PATCH 152/239] Update deps to loopback 3.0.0 RC --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2291260..41493df 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "bluebird": "^3.3.3", "eslint": "^2.13.1", "eslint-config-loopback": "^4.0.0", - "loopback-datasource-juggler": "^3.0.0-alpha.7", + "loopback-datasource-juggler": "^3.0.0", "mocha": "^2.1.0", "rc": "^1.0.0", "should": "^8.0.2", From d4e00bc716c30c23eea5c410435c7623b626ec15 Mon Sep 17 00:00:00 2001 From: gunjpan Date: Mon, 19 Sep 2016 15:10:40 -0400 Subject: [PATCH 153/239] Add translated files --- .gitignore | 3 +-- intl/de/messages.json | 7 +++++++ intl/es/messages.json | 7 +++++++ intl/fr/messages.json | 7 +++++++ intl/it/messages.json | 7 +++++++ intl/ja/messages.json | 7 +++++++ intl/ko/messages.json | 7 +++++++ intl/nl/messages.json | 7 +++++++ intl/pt/messages.json | 7 +++++++ intl/tr/messages.json | 7 +++++++ intl/zh-Hans/messages.json | 7 +++++++ intl/zh-Hant/messages.json | 7 +++++++ 12 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 intl/de/messages.json create mode 100644 intl/es/messages.json create mode 100644 intl/fr/messages.json create mode 100644 intl/it/messages.json create mode 100644 intl/ja/messages.json create mode 100644 intl/ko/messages.json create mode 100644 intl/nl/messages.json create mode 100644 intl/pt/messages.json create mode 100644 intl/tr/messages.json create mode 100644 intl/zh-Hans/messages.json create mode 100644 intl/zh-Hant/messages.json diff --git a/.gitignore b/.gitignore index ce0562f..8d9d09b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,4 @@ node_modules/ db/ npm-debug.log -intl/* -!intl/en/ \ No newline at end of file + diff --git a/intl/de/messages.json b/intl/de/messages.json new file mode 100644 index 0000000..79feca9 --- /dev/null +++ b/intl/de/messages.json @@ -0,0 +1,7 @@ +{ + "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} muss ein {{object}} sein: {0}", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} ist ein erforderliches Zeichenfolgeargument: {0}", + "80a32e80cbed65eba2103201a7c94710": "Modell nicht gefunden: {0}", + "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} unterstützt nicht den Operator für reguläre Ausdrücke" +} + diff --git a/intl/es/messages.json b/intl/es/messages.json new file mode 100644 index 0000000..bc6c908 --- /dev/null +++ b/intl/es/messages.json @@ -0,0 +1,7 @@ +{ + "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} debe ser un {{object}}: {0}", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} es un argumento de serie necesario: {0}", + "80a32e80cbed65eba2103201a7c94710": "No se ha encontrado el modelo: {0} ", + "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} no admite el operador de expresión regular" +} + diff --git a/intl/fr/messages.json b/intl/fr/messages.json new file mode 100644 index 0000000..4cee4eb --- /dev/null +++ b/intl/fr/messages.json @@ -0,0 +1,7 @@ +{ + "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} doit être un {{object}} : {0}", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} est un argument de chaîne obligatoire : {0}", + "80a32e80cbed65eba2103201a7c94710": "Modèle introuvable : {0}", + "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} ne prend pas en charge l'opérateur d'expression régulière" +} + diff --git a/intl/it/messages.json b/intl/it/messages.json new file mode 100644 index 0000000..4167b1f --- /dev/null +++ b/intl/it/messages.json @@ -0,0 +1,7 @@ +{ + "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} deve essere un {{object}}: {0}", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} è un argomento stringa obbligatorio: {0}", + "80a32e80cbed65eba2103201a7c94710": "Modello non trovato: {0}", + "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} non supporta l'operatore dell'espressione regolare" +} + diff --git a/intl/ja/messages.json b/intl/ja/messages.json new file mode 100644 index 0000000..c1d8c79 --- /dev/null +++ b/intl/ja/messages.json @@ -0,0 +1,7 @@ +{ + "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} は {{object}} でなければなりません: {0}", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} は必須のストリング引数です: {0}", + "80a32e80cbed65eba2103201a7c94710": "モデルが見つかりません: {0}", + "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} は正規表現演算子をサポートしません" +} + diff --git a/intl/ko/messages.json b/intl/ko/messages.json new file mode 100644 index 0000000..767d737 --- /dev/null +++ b/intl/ko/messages.json @@ -0,0 +1,7 @@ +{ + "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}}이(가) {{object}}이어야 함: {0}", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}}은 필수 문자열 인수임: {0}", + "80a32e80cbed65eba2103201a7c94710": "모델을 찾을 수 없음: {0}", + "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}}에서 정규식 연산자를 지원하지 않음" +} + diff --git a/intl/nl/messages.json b/intl/nl/messages.json new file mode 100644 index 0000000..90e4392 --- /dev/null +++ b/intl/nl/messages.json @@ -0,0 +1,7 @@ +{ + "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} moet een {{object}} zijn: {0}", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} is een verplicht tekenreeksargument: {0}", + "80a32e80cbed65eba2103201a7c94710": "Model is niet gevonden: {0}", + "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} biedt geen ondersteuning voor de expressieoperator" +} + diff --git a/intl/pt/messages.json b/intl/pt/messages.json new file mode 100644 index 0000000..1915943 --- /dev/null +++ b/intl/pt/messages.json @@ -0,0 +1,7 @@ +{ + "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} deve ser um {{object}}: {0}", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} é um argumento de sequência necessário: {0}", + "80a32e80cbed65eba2103201a7c94710": "Modelo não localizado: {0}", + "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} não suporta o operador de expressão regular" +} + diff --git a/intl/tr/messages.json b/intl/tr/messages.json new file mode 100644 index 0000000..99a07bc --- /dev/null +++ b/intl/tr/messages.json @@ -0,0 +1,7 @@ +{ + "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} bir {{object}} olmalıdır: {0}", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} zorunlu bir dize bağımsız değişkeni: {0}", + "80a32e80cbed65eba2103201a7c94710": "Model bulunamadı: {0}", + "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}}, düzenli ifade işlecini desteklemiyor" +} + diff --git a/intl/zh-Hans/messages.json b/intl/zh-Hans/messages.json new file mode 100644 index 0000000..e15c7b5 --- /dev/null +++ b/intl/zh-Hans/messages.json @@ -0,0 +1,7 @@ +{ + "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} 必须为 {{object}}:{0}", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} 是必需的字符串自变量:{0}", + "80a32e80cbed65eba2103201a7c94710": "找不到模型:{0}", + "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} 不支持正则表达式运算符" +} + diff --git a/intl/zh-Hant/messages.json b/intl/zh-Hant/messages.json new file mode 100644 index 0000000..ecb9970 --- /dev/null +++ b/intl/zh-Hant/messages.json @@ -0,0 +1,7 @@ +{ + "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} 必須是 {{object}}:{0}", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} 是必要的字串引數:{0}", + "80a32e80cbed65eba2103201a7c94710": "找不到模型:{0}", + "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} 不支援正規表示式運算子" +} + From 526d44de4c574f9300b85aeb49b9637ea2a54e48 Mon Sep 17 00:00:00 2001 From: Candy Date: Wed, 28 Sep 2016 13:56:31 -0400 Subject: [PATCH 154/239] Update translation files - round#2 --- intl/es/messages.json | 2 +- intl/ja/messages.json | 2 +- intl/tr/messages.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/intl/es/messages.json b/intl/es/messages.json index bc6c908..59a5950 100644 --- a/intl/es/messages.json +++ b/intl/es/messages.json @@ -1,7 +1,7 @@ { "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} debe ser un {{object}}: {0}", "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} es un argumento de serie necesario: {0}", - "80a32e80cbed65eba2103201a7c94710": "No se ha encontrado el modelo: {0} ", + "80a32e80cbed65eba2103201a7c94710": "No se ha encontrado el modelo: {0}", "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} no admite el operador de expresión regular" } diff --git a/intl/ja/messages.json b/intl/ja/messages.json index c1d8c79..f4a617c 100644 --- a/intl/ja/messages.json +++ b/intl/ja/messages.json @@ -2,6 +2,6 @@ "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} は {{object}} でなければなりません: {0}", "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} は必須のストリング引数です: {0}", "80a32e80cbed65eba2103201a7c94710": "モデルが見つかりません: {0}", - "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} は正規表現演算子をサポートしません" + "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} では正規表現演算子はサポートされません。" } diff --git a/intl/tr/messages.json b/intl/tr/messages.json index 99a07bc..e8d8b25 100644 --- a/intl/tr/messages.json +++ b/intl/tr/messages.json @@ -1,6 +1,6 @@ { "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} bir {{object}} olmalıdır: {0}", - "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} zorunlu bir dize bağımsız değişkeni: {0}", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} zorunlu bir dizgi bağımsız değişkeni: {0}", "80a32e80cbed65eba2103201a7c94710": "Model bulunamadı: {0}", "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}}, düzenli ifade işlecini desteklemiyor" } From 909a71107af661abf588e66dd2f722bbfb2c8773 Mon Sep 17 00:00:00 2001 From: Nicholas Duffy Date: Wed, 12 Oct 2016 00:15:31 -0600 Subject: [PATCH 155/239] Add connectorCapabilities global object (#102) --- test/init.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/init.js b/test/init.js index a0e0f8a..4e6b90b 100644 --- a/test/init.js +++ b/test/init.js @@ -49,4 +49,9 @@ global.getDataSource = global.getSchema = function(options) { return db; }; +global.connectorCapabilities = { + ilike: false, + nilike: false, +}; + global.sinon = require('sinon'); From ab3375571b62c4dc1ade1581c9a4191f5055bc03 Mon Sep 17 00:00:00 2001 From: Candy Date: Fri, 14 Oct 2016 16:00:58 -0400 Subject: [PATCH 156/239] 2.9.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add connectorCapabilities global object (#102) (Nicholas Duffy) * Update translation files - round#2 (Candy) * Add translated files (gunjpan) * Update deps to loopback 3.0.0 RC (Miroslav Bajtoš) * Update eslint infrastructure (Loay) * Use juggler@3 for running tests (Simon Ho) * Add globalization (Candy) * Revert "Update Fix" (Loay) * Update Fix (Loay) * Update URLs in CONTRIBUTING.md (#88) (Ryan Graham) --- CHANGES.md | 24 ++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 0b036c8..a381fe6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,27 @@ +2016-10-14, Version 2.9.0 +========================= + + * Add connectorCapabilities global object (#102) (Nicholas Duffy) + + * Update translation files - round#2 (Candy) + + * Add translated files (gunjpan) + + * Update deps to loopback 3.0.0 RC (Miroslav Bajtoš) + + * Update eslint infrastructure (Loay) + + * Use juggler@3 for running tests (Simon Ho) + + * Add globalization (Candy) + + * Revert "Update Fix" (Loay) + + * Update Fix (Loay) + + * Update URLs in CONTRIBUTING.md (#88) (Ryan Graham) + + 2016-06-21, Version 2.8.0 ========================= diff --git a/package.json b/package.json index 41493df..7fdd570 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.8.0", + "version": "2.9.0", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", From 842556273d0eb681e6942298fc269ca0afc61c45 Mon Sep 17 00:00:00 2001 From: Candy Date: Tue, 1 Nov 2016 11:44:01 -0400 Subject: [PATCH 157/239] Update README doc links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ab98b88..fbdc62a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -## loopback-connector-mssql +# loopback-connector-mssql `loopback-connector-mssql` is the Microsoft SQL Server connector module for [loopback-datasource-juggler](https://github.com/strongloop/loopback-datasource-juggler/). -For complete documentation, see [StrongLoop Documentation | SQL Server Connector](http://docs.strongloop.com/display/LB/SQL+Server+connector). +For complete documentation, see [Documentation | SQL Server Connector](http://loopback.io/doc/en/lb2/SQL-Server-connector.html). ## Installation From bcc432201b89a9182d1c704166c188eb2e5d1a13 Mon Sep 17 00:00:00 2001 From: Siddhi Pai Date: Sat, 19 Nov 2016 01:52:13 -0800 Subject: [PATCH 158/239] Start the development of the next major version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7fdd570..dec5be8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "2.9.0", + "version": "3.0.0-alpha.1", "description": "Microsoft SQL Server connector for LoopBack", "keywords": [ "StrongLoop", From 8b78616ae02eac5b1e81910e7a9665679e5e0c50 Mon Sep 17 00:00:00 2001 From: Siddhi Pai Date: Sat, 19 Nov 2016 01:52:40 -0800 Subject: [PATCH 159/239] Drop support for Node v0.10 and v0.12 --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index dec5be8..9488b67 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,9 @@ "name": "loopback-connector-mssql", "version": "3.0.0-alpha.1", "description": "Microsoft SQL Server connector for LoopBack", + "engines": { + "node": ">=4" + }, "keywords": [ "StrongLoop", "LoopBack", From 27e6cb66e5c0d318b6249681024fd411711f9aa5 Mon Sep 17 00:00:00 2001 From: Eddie Monge Date: Thu, 8 Dec 2016 15:03:41 -0800 Subject: [PATCH 160/239] increase the timeout for autoupdate test --- test/autoupdate.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js index 95833e3..30bde31 100644 --- a/test/autoupdate.test.js +++ b/test/autoupdate.test.js @@ -14,6 +14,8 @@ before(function() { describe('MS SQL server connector', function() { it('should auto migrate/update tables', function(done) { + this.timeout(30000); + /* eslint-disable camelcase */ var schema_v1 = { From e5fd10a23e21505b25f90eedc83ece4417916e27 Mon Sep 17 00:00:00 2001 From: Siddhi Pai Date: Mon, 5 Dec 2016 23:20:27 -0800 Subject: [PATCH 161/239] Update paid support URL --- .github/ISSUE_TEMPLATE.md | 36 ++++++++++++++++++++++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 24 +++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..ccc915a --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,36 @@ + + +### Bug or feature request + + + +- [ ] Bug +- [ ] Feature request + +### Description of feature (or steps to reproduce if bug) + + + +### Link to sample repo to reproduce issue (if bug) + + + +### Expected result + + + +### Actual result (if bug) + + + +### Additional information (Node.js version, LoopBack version, etc) + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..d2b240f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,24 @@ +### Description + + +#### Related issues + + + +- None + +### Checklist + + + +- [ ] New tests added or existing tests modified to cover all changes +- [ ] Code conforms with the [style + guide](http://loopback.io/doc/en/contrib/style-guide.html) From 009684dcf1d75f689ab5be1da8522f0260e5ee9b Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 11 Jan 2017 10:29:30 -0800 Subject: [PATCH 162/239] Update w info from docs (#115) --- README.md | 381 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 348 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index fbdc62a..58792a2 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,362 @@ # loopback-connector-mssql -`loopback-connector-mssql` is the Microsoft SQL Server connector module for [loopback-datasource-juggler](https://github.com/strongloop/loopback-datasource-juggler/). +[Microsoft SQL Server](https://www.microsoft.com/en-us/sql-server/default.aspx) is a relational database management system developed by Microsoft. +The `loopback-connector-mssql` module is the Microsoft SQL Server connector for the LoopBack framework. -For complete documentation, see [Documentation | SQL Server Connector](http://loopback.io/doc/en/lb2/SQL-Server-connector.html). +
+For more information, see [LoopBack documentation | SQL Server Connector](http://loopback.io/doc/en/lb3/SQL-Server-connector.html). +
+ +If you create a SQL Server data source using the data source generator as described below, you don't have to do this, since the generator will run `npm install` for you. ## Installation -````sh -npm install loopback-connector-mssql --save -```` +In your application root directory, enter: + +```shell +$ npm install loopback-connector-mssql --save +``` + +This will install the module from npm and add it as a dependency to the application's `package.json` file. + +## Creating a SQL Server data source + +Use the [Data source generator](http://loopback.io/doc/en/lb3/Data-source-generator.html) to add a SQL Server data source to your application. +The generator will prompt for the database server hostname, port, and other settings +required to connect to a SQL Server database. It will also run the `npm install` command above for you. + +The entry in the application's `/server/datasources.json` will look like this (for example): + +{% include code-caption.html content="/server/datasources.json" %} +```javascript +"sqlserverdb": { + "name": "sqlserverdb", + "connector": "mssql", + "host": "myhost", + "port": 1234, + "url": "mssql://username:password@dbhost/dbname", + "database": "mydb", + "password": "admin", + "user": "admin", + } +``` + +Edit `datasources.json` to add other properties that enable you to connect the data source to a SQL Server database. + +To connect to a SQL Server instance running in Azure, you must specify a qualified user name with hostname, and add the following to the data source declaration: + +```js +"options": { + "encrypt": true + ... +} +``` + +### Connector settings + +To configure the data source to use your MS SQL Server database, edit `datasources.json` and add the following settings as appropriate. +The MSSQL connector uses [node-mssql](https://github.com/patriksimek/node-mssql) as the driver. For more information about configuration parameters, +see [node-mssql documentation](https://github.com/patriksimek/node-mssql#configuration-1). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDefaultDescription
connectorString + Either "loopback-connector-mssql" or "mssql" +
databaseString Database name
debugBoolean If true, turn on verbose mode to debug database queries and lifecycle.
hostStringlocalhostDatabase host name
passwordString Password to connect to database
portNumber1433Database TCP port
schemaStringdboDatabase schema
urlString Use instead of the host, port, user, password, + and database properties. For example: 'mssql://test:mypassword@localhost:1433/dev'. +
userString Qualified username with host name, for example "user@your.sqlserver.dns.host".
+ +Instead of specifying individual connection properties, you can use a single `url` property that combines them into a single string, for example: + +```javascript +"accountDB": { + "url": "mssql://test:mypassword@localhost:1433/demo?schema=dbo" +} +``` + +The application will automatically load the data source when it starts. You can then refer to it in code, for example: + +{% include code-caption.html content="/server/boot/script.js" %} +```javascript +var app = require('./app'); +var dataSource = app.dataSources.accountDB; +``` + +Alternatively, you can create the data source in application code; for example: -## Basic use +{% include code-caption.html content="/server/script.js" %} +```javascript +var DataSource = require('loopback-datasource-juggler').DataSource; +var dataSource = new DataSource('mssql', config); +config = { ... }; // JSON object as specified above in "Connector settings" +``` -To use it you need `loopback-datasource-juggler`. -1. Setup dependencies in `package.json`: +### Model discovery - ```json - { - ... - "dependencies": { - "loopback-datasource-juggler": "latest", - "loopback-connector-mssql": "latest" +The SQL Server connector supports _model discovery_ that enables you to create LoopBack models +based on an existing database schema using the unified [database discovery API](http://apidocs.strongloop.com/loopback-datasource-juggler/#datasource-prototype-discoverandbuildmodels). For more information on discovery, see [Discovering models from relational databases](https://loopback.io/doc/en/lb3/Discovering-models-from-relational-databases.html). + +### Auto-migratiion + +The SQL Server connector also supports _auto-migration_ that enables you to create a database schema +from LoopBack models using the [LoopBack automigrate method](http://apidocs.strongloop.com/loopback-datasource-juggler/#datasource-prototype-automigrate). +For each model, the LoopBack SQL Server connector creates a table in the 'dbo' schema in the database. + +For more information on auto-migration, see [Creating a database schema from models](https://loopback.io/doc/en/lb3/Creating-a-database-schema-from-models.html) for more information. + +Destroying models may result in errors due to foreign key integrity. First delete any related models by calling delete on models with relationships. + +## Defining models + +The model definition consists of the following properties: + +* `name`: Name of the model, by default, the table name in camel-case. +* `options`: Model-level operations and mapping to Microsoft SQL Server schema/table. Use the `mssql` model property to specify additional SQL Server-specific properties for a LoopBack model. +* `properties`: Property definitions, including mapping to Microsoft SQL Server columns. + - For each property, use the `mssql` key to specify additional settings for that property/field. + +For example: + +{% include code-caption.html content="/common/models/inventory.json" %} +```javascript +{"name": "Inventory",  + "options": { + "idInjection": false, + "mssql": { + "schema": "strongloop", + "table": "inventory" + } + }, "properties": { + "id": { + "type": "String", + "required": false, + "length": 64, + "precision": null, + "scale": null, + "mssql": { + "columnName": "id", + "dataType": "varchar", + "dataLength": 64, + "dataPrecision": null, + "dataScale": null, + "nullable": "NO" + } }, - ... - } - ``` - -2. Use: - - ```javascript - var DataSource = require('loopback-datasource-juggler').DataSource; - var dataSource = new DataSource('mssql', { - host: 'demo.strongloop.com', - port: 1433, - database: 'mydb', - username: 'myuser', - password: 'mypass', - - // You need this if using Microsoft Azure SQL database - // options: { encrypt: true } - }); - ``` + "productId": { + "type": "String", + "required": false, + "length": 64, + "precision": null, + "scale": null, + "id": 1, + "mssql": { + "columnName": "product_id", + "dataType": "varchar", + "dataLength": 64, + "dataPrecision": null, + "dataScale": null, + "nullable": "YES" + } + }, + "locationId": { + "type": "String", + "required": false, + "length": 64, + "precision": null, + "scale": null, + "id": 1, + "mssql": { + "columnName": "location_id", + "dataType": "varchar", + "dataLength": 64, + "dataPrecision": null, + "dataScale": null, + "nullable": "YES" + } + }, + "available": { + "type": "Number", + "required": false, + "length": null, + "precision": 10, + "scale": 0, + "mssql": { + "columnName": "available", + "dataType": "int", + "dataLength": null, + "dataPrecision": 10, + "dataScale": 0, + "nullable": "YES" + } + }, + "total": { + "type": "Number", + "required": false, + "length": null, + "precision": 10, + "scale": 0, + "mssql": { + "columnName": "total", + "dataType": "int", + "dataLength": null, + "dataPrecision": 10, + "dataScale": 0, + "nullable": "YES" + } + } + }} +``` + +## Type mapping + +See [LoopBack types](http://loopback.io/doc/en/lb3/LoopBack-types.html) for details on LoopBack's data types. + +### LoopBack to SQL Server types + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LoopBack TypeSQL Server Type
BooleanBIT
DateDATETIME
GeoPointFLOAT
NumberINT
+ String + JSON + + NVARCHAR +
+ +### SQL Server to LoopBack types + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SQL Server TypeLoopBack Type
BITBoolean
+ BINARY
VARBINARY
IMAGE +
Node.js Buffer object
+ DATE
DATETIMEOFFSET
DATETIME2
SMALLDATETIME
DATETIME
TIME +
Date
POINTGeoPoint
+ BIGINT
NUMERIC
SMALLINT
DECIMAL
SMALLMONEY
INT
TINYINT
MONEY
FLOAT
REAL +
Number
+ CHAR
VARCHAR
TEXT
NCHAR
NVARCHAR
NTEXT
CHARACTER VARYING
CHARACTER +
String
## Running tests From ab2abdfdb485a92709e73633995f7d5ab1454582 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Wed, 11 Jan 2017 16:06:36 -0800 Subject: [PATCH 163/239] Update README.md (#117) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 58792a2..956d4d5 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,9 @@ The `loopback-connector-mssql` module is the Microsoft SQL Server connector for the LoopBack framework.
-For more information, see [LoopBack documentation | SQL Server Connector](http://loopback.io/doc/en/lb3/SQL-Server-connector.html). +For more information, see LoopBack documentation.
-If you create a SQL Server data source using the data source generator as described below, you don't have to do this, since the generator will run `npm install` for you. - ## Installation In your application root directory, enter: @@ -19,6 +17,8 @@ $ npm install loopback-connector-mssql --save This will install the module from npm and add it as a dependency to the application's `package.json` file. +If you create a SQL Server data source using the data source generator as described below, you don't have to do this, since the generator will run `npm install` for you. + ## Creating a SQL Server data source Use the [Data source generator](http://loopback.io/doc/en/lb3/Data-source-generator.html) to add a SQL Server data source to your application. From 81e3cfa76e5944aaa74178f02daf735a4cc37243 Mon Sep 17 00:00:00 2001 From: Loay Gewily Date: Thu, 19 Jan 2017 17:01:27 -0500 Subject: [PATCH 164/239] Add buildreplace method --- lib/mssql.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/mssql.js b/lib/mssql.js index 54f3461..c92d9f8 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -124,6 +124,10 @@ MsSQL.prototype.executeSQL = function(sql, params, options, callback) { } var innerCB = function(err, data) { debug('Result: %j %j', err, data); + var updatedStatement = sql.includes('UPDATE'); + if (data && data[0] && typeof data[0].affectedRows === 'number' && updatedStatement) { + data = data[0]; + } callback && callback(err, data); }; @@ -240,6 +244,12 @@ MsSQL.prototype.buildDelete = function(model, where, options) { return stmt; }; +MsSQL.prototype.buildReplace = function(model, where, data, options) { + var stmt = this.invokeSuper('buildReplace', model, where, data, options); + stmt.merge(';SELECT @@ROWCOUNT as affectedRows', ''); + return stmt; +}; + MsSQL.prototype.getCountForAffectedRows = function(model, info) { var affectedCountQueryResult = info && info[0]; if (!affectedCountQueryResult) { From cea5ad1a4075053cc074cc7f64c7179ba60748e1 Mon Sep 17 00:00:00 2001 From: Loay Date: Thu, 26 Jan 2017 11:05:32 -0500 Subject: [PATCH 165/239] Update LB-connector version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9488b67..e56dad3 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "dependencies": { "async": "^1.5.2", "debug": "^2.1.1", - "loopback-connector": "^2.1.0", + "loopback-connector": "^3.0.0", "mssql": "^3.1.1", "strong-globalize": "^2.5.8" }, From 2b2980380f31ab3cb92298d366751ae1ab5a620d Mon Sep 17 00:00:00 2001 From: Loay Gewily Date: Fri, 17 Feb 2017 13:32:41 -0500 Subject: [PATCH 166/239] Update mocha timeout --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e56dad3..46db014 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ }, "scripts": { "lint": "eslint .", - "test": "./node_modules/.bin/mocha --timeout 5000 -R spec", + "test": "./node_modules/.bin/mocha --timeout 9999 -R spec", "posttest": "npm run lint", "prepublish": "node remove-regenerator.js" }, From 0147266d336dc931f915ed386e07dc629988a1b5 Mon Sep 17 00:00:00 2001 From: Loay Gewily Date: Tue, 14 Feb 2017 22:08:56 -0500 Subject: [PATCH 167/239] Refactor discovery methods --- lib/discovery.js | 157 ++++++++++++++---------------------------- test/discover.test.js | 11 +++ 2 files changed, 61 insertions(+), 107 deletions(-) diff --git a/lib/discovery.js b/lib/discovery.js index ed6cbd6..debb699 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -41,7 +41,7 @@ function mixinDiscovery(MsSQL) { return sql + fetch; } - function paginateSQL(sql, orderBy, options) { + MsSQL.prototype.paginateSQL = function(sql, orderBy, options) { options = options || {}; var offset = options.offset || options.skip; if (isNaN(offset)) { @@ -75,57 +75,57 @@ function mixinDiscovery(MsSQL) { } return paginatedSQL + MsSQL.newline; - } + }; /*! * Build sql for listing tables * @param options {all: for all owners, owner: for a given owner} * @returns {string} The sql statement */ - function queryTables(options) { + MsSQL.prototype.buildQueryTables = function(options) { var sqlTables = null; var owner = options.owner || options.schema; if (options.all && !owner) { - sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' + + sqlTables = this.paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' + ' FROM information_schema.tables', 'table_schema, table_name', options); } else if (owner) { - sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' + + sqlTables = this.paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' + ' FROM information_schema.tables WHERE table_schema=\'' + owner + '\'', 'table_schema, table_name', options); } else { - sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name",' + + sqlTables = this.paginateSQL('SELECT \'table\' AS "type", table_name AS "name",' + ' table_schema AS "owner" FROM information_schema.tables WHERE table_schema=schema_name()', 'table_name', options); } return sqlTables; - } + }; /*! * Build sql for listing views * @param options {all: for all owners, owner: for a given owner} * @returns {string} The sql statement */ - function queryViews(options) { + MsSQL.prototype.buildQueryViews = function(options) { var sqlViews = null; if (options.views) { var owner = options.owner || options.schema; if (options.all && !owner) { - sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + + sqlViews = this.paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + ' table_schema AS "owner" FROM information_schema.views', 'table_schema, table_name', options); } else if (owner) { - sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + + sqlViews = this.paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + ' table_schema AS "owner" FROM information_schema.views WHERE table_schema=\'' + owner + '\'', 'table_schema, table_name', options); } else { - sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + + sqlViews = this.paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + ' schema_name() AS "owner" FROM information_schema.views', 'table_name', options); } } return sqlViews; - } + }; /** * Discover model definitions @@ -133,36 +133,6 @@ function mixinDiscovery(MsSQL) { * @param {Object} options Options for discovery * @param {Function} [cb] The callback function */ - MsSQL.prototype.discoverModelDefinitions = function(options, cb) { - if (!cb && typeof options === 'function') { - cb = options; - options = {}; - } - options = options || {}; - - var self = this; - var calls = [function(callback) { - self.execute(queryTables(options), callback); - }]; - - if (options.views) { - calls.push(function(callback) { - self.execute(queryViews(options), callback); - }); - } - async.parallel(calls, function(err, data) { - if (err) { - cb(err, data); - } else { - var merged = []; - merged = merged.concat(data.shift()); - if (data.length) { - merged = merged.concat(data.shift()); - } - cb(err, merged); - } - }); - }; /*! * Normalize the arguments @@ -170,7 +140,7 @@ function mixinDiscovery(MsSQL) { * @param options object, optional * @param cb function, optional */ - function getArgs(table, options, cb) { + MsSQL.prototype.getArgs = function(table, options, cb) { if ('string' !== typeof table || !table) { throw new Error(g.f('{{table}} is a required string argument: %s', table)); } @@ -183,12 +153,13 @@ function mixinDiscovery(MsSQL) { throw new Error(g.f('{{options}} must be an {{object}}: %s', options)); } return { + schema: options.owner || options.schema, owner: options.owner || options.schema, table: table, options: options, cb: cb, }; - } + }; /*! * Build the sql statement to query columns for a given table @@ -196,10 +167,10 @@ function mixinDiscovery(MsSQL) { * @param table * @returns {String} The sql statement */ - function queryColumns(owner, table) { + MsSQL.prototype.buildQueryColumns = function(owner, table) { var sql = null; if (owner) { - sql = paginateSQL('SELECT table_schema AS "owner", table_name AS "tableName", column_name' + + sql = this.paginateSQL('SELECT table_schema AS "owner", table_name AS "tableName", column_name' + ' AS "columnName", data_type AS "dataType",' + ' character_maximum_length AS "dataLength", numeric_precision AS' + ' "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"' + @@ -208,7 +179,7 @@ function mixinDiscovery(MsSQL) { (table ? ' AND table_name=\'' + table + '\'' : ''), 'table_schema, table_name, ordinal_position', {}); } else { - sql = paginateSQL('SELECT schema_name() AS "owner", table_name' + + sql = this.paginateSQL('SELECT schema_name() AS "owner", table_name' + ' AS "tableName", column_name AS "columnName", data_type AS "dataType",' + ' character_maximum_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS' + ' "dataScale", is_nullable AS "nullable"' + @@ -217,7 +188,7 @@ function mixinDiscovery(MsSQL) { 'table_name, ordinal_position', {}); } return sql; - } + }; /** * Discover model properties from a table @@ -226,26 +197,6 @@ function mixinDiscovery(MsSQL) { * @param {Function} [cb] The callback function * */ - MsSQL.prototype.discoverModelProperties = function(table, options, cb) { - var args = getArgs(table, options, cb); - var owner = args.owner; - table = args.table; - options = args.options; - cb = args.cb; - - var sql = queryColumns(owner, table); - var callback = function(err, results) { - if (err) { - cb(err, results); - } else { - results.map(function(r) { - r.type = mysqlDataTypeToJSONType(r.dataType, r.dataLength); - }); - cb(err, results); - } - }; - this.execute(sql, callback); - }; /*! * Build the sql statement for querying primary keys of a given table @@ -269,7 +220,7 @@ function mixinDiscovery(MsSQL) { kc.ordinal_position; */ - function queryForPrimaryKeys(owner, table) { + MsSQL.prototype.buildQueryPrimaryKeys = function(owner, table) { var sql = 'SELECT kc.table_schema AS "owner", ' + 'kc.table_name AS "tableName", kc.column_name AS "columnName", kc.ordinal_position' + ' AS "keySeq", kc.constraint_name AS "pkName" FROM' + @@ -286,7 +237,7 @@ function mixinDiscovery(MsSQL) { } sql += ' ORDER BY kc.table_schema, kc.table_name, kc.ordinal_position'; return sql; - } + }; /** * Discover primary keys for a given table @@ -294,16 +245,16 @@ function mixinDiscovery(MsSQL) { * @param {Object} options The options for discovery * @param {Function} [cb] The callback function */ - MsSQL.prototype.discoverPrimaryKeys = function(table, options, cb) { - var args = getArgs(table, options, cb); - var owner = args.owner; - table = args.table; - options = args.options; - cb = args.cb; - - var sql = queryForPrimaryKeys(owner, table); - this.execute(sql, cb); - }; + // MsSQL.prototype.discoverPrimaryKeys = function(table, options, cb) { + // var args = this.getArgs(table, options, cb); + // var owner = args.owner; + // table = args.table; + // options = args.options; + // cb = args.cb; + // + // var sql = this.queryPrimaryKeys(owner, table); + // this.execute(sql, cb); + // }; /*! * Build the sql statement for querying foreign keys of a given table @@ -325,7 +276,7 @@ function mixinDiscovery(MsSQL) { WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name='mytable'; */ - function queryForeignKeys(owner, table) { + MsSQL.prototype.buildQueryForeignKeys = function(owner, table) { var sql = 'SELECT tc.table_schema AS "fkOwner", tc.constraint_name AS "fkName", tc.table_name AS "fkTableName",' + ' kcu.column_name AS "fkColumnName", kcu.ordinal_position AS "keySeq",' + @@ -344,7 +295,7 @@ function mixinDiscovery(MsSQL) { sql += ' AND tc.table_name=\'' + table + '\''; } return sql; - } + }; /** * Discover foreign keys for a given table @@ -352,16 +303,6 @@ function mixinDiscovery(MsSQL) { * @param {Object} options The options for discovery * @param {Function} [cb] The callback function */ - MsSQL.prototype.discoverForeignKeys = function(table, options, cb) { - var args = getArgs(table, options, cb); - var owner = args.owner; - table = args.table; - options = args.options; - cb = args.cb; - - var sql = queryForeignKeys(owner, table); - this.execute(sql, cb); - }; /*! * Retrieves a description of the foreign key columns that reference the given table's primary key columns (the foreign keys exported by a table). @@ -370,7 +311,7 @@ function mixinDiscovery(MsSQL) { * @param table * @returns {string} */ - function queryExportedForeignKeys(owner, table) { + MsSQL.prototype.buildQueryExportedForeignKeys = function(owner, table) { var sql = 'SELECT kcu.constraint_name AS "fkName", kcu.table_schema AS "fkOwner", kcu.table_name AS "fkTableName",' + ' kcu.column_name AS "fkColumnName", kcu.ordinal_position AS "keySeq",' + ' \'PK\' AS "pkName", ccu.table_schema AS "pkOwner",' + @@ -389,26 +330,18 @@ function mixinDiscovery(MsSQL) { sql += ' order by kcu.table_schema, kcu.table_name, kcu.ordinal_position'; return sql; - } + }; /** * Discover foreign keys that reference to the primary key of this table * @param {String} table The table name * @param {Object} options The options for discovery * @param {Function} [cb] The callback function - */ - MsSQL.prototype.discoverExportedForeignKeys = function(table, options, cb) { - var args = getArgs(table, options, cb); - var owner = args.owner; - table = args.table; - options = args.options; - cb = args.cb; - - var sql = queryExportedForeignKeys(owner, table); - this.execute(sql, cb); - }; + // */ - function mysqlDataTypeToJSONType(mysqlType, dataLength) { + MsSQL.prototype.buildPropertyType = function(columnDefinition, dataLength) { + var mysqlType = columnDefinition.dataType; + var dataLength = columnDefinition.dataLength; var type = mysqlType.toUpperCase(); switch (type) { case 'BIT': @@ -456,5 +389,15 @@ function mixinDiscovery(MsSQL) { default: return 'String'; } - } + }; + + MsSQL.prototype.setDefaultOptions = function(options) { + }; + + MsSQL.prototype.setNullableProperty = function(property) { + }; + + MsSQL.prototype.getDefaultSchema = function() { + return ''; + }; } diff --git a/test/discover.test.js b/test/discover.test.js index b9d7c1e..7e0ef27 100644 --- a/test/discover.test.js +++ b/test/discover.test.js @@ -13,6 +13,17 @@ var assert = require('assert'); var db = getDataSource(); describe('discoverModels', function() { + describe('Discover database schemas', function() { + it('should return an array of db schemas', function(done) { + db.connector.discoverDatabaseSchemas(function(err, schemas) { + if (err) return done(err); + schemas.should.be.an.array; + schemas.length.should.be.above(0); + done(); + }); + }); + }); + describe('Discover models including views', function() { it('should return an array of tables and views', function(done) { db.discoverModelDefinitions({ From 98c696cb430f817357e83c27324d430431d62337 Mon Sep 17 00:00:00 2001 From: ssh24 Date: Thu, 23 Feb 2017 11:36:04 -0500 Subject: [PATCH 168/239] Refactor migration methods remove common functions getTableStatus() and autoupdate() are implemented in base connector and are common to all the RDBMS connectors. Extract out isActual function Revert "Extract out isActual function" This reverts commit e16a30ea1148f64476f7581c365b51f263540a2d. Reverted back because the implementation is different from base. --- lib/migration.js | 50 +++++++++--------------------------------------- 1 file changed, 9 insertions(+), 41 deletions(-) diff --git a/lib/migration.js b/lib/migration.js index e31f2de..ec1882b 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -66,51 +66,19 @@ function mixinMigration(MsSQL) { }); }; - MsSQL.prototype.autoupdate = function(models, cb) { - var self = this; - if ((!cb) && ('function' === typeof models)) { - cb = models; - models = undefined; - } - // First argument is a model name - if ('string' === typeof models) { - models = [models]; - } - - models = models || Object.keys(this._models); - async.each(models, function(model, done) { - if (!(model in self._models)) { - return process.nextTick(function() { - done(new Error(g.f('Model not found: %s', model))); - }); - } - self.showFields(model, function(err, fields) { - self.showIndexes(model, function(err, indexes) { - if (!err && fields.length) { - self.alterTable(model, fields, indexes, done); - } else { - self.createTable(model, done); - } - }); - }); - }, cb); - }; - MsSQL.prototype.isActual = function(cb) { var ok = false; var self = this; async.each(Object.keys(this._models), function(model, done) { - self.showFields(model, function(err, fields) { - self.showIndexes(model, function(err, indexes) { - self.alterTable(model, fields, indexes, function(err, needAlter) { - if (err) { - return done(err); - } else { - ok = ok || needAlter; - done(err); - } - }, true); - }); + self.getTableStatus(model, function(err, fields, indexes) { + self.alterTable(model, fields, indexes, function(err, needAlter) { + if (err) { + return done(err); + } else { + ok = ok || needAlter; + done(err); + } + }, true); }); }, function(err) { if (err) { From ff20b00d30a4e3c62ee69ae744d94493074fab16 Mon Sep 17 00:00:00 2001 From: Loay Date: Mon, 6 Mar 2017 19:20:11 -0500 Subject: [PATCH 169/239] Upgrade to loopback-connector@4.x --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 46db014..bb35d5a 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "dependencies": { "async": "^1.5.2", "debug": "^2.1.1", - "loopback-connector": "^3.0.0", + "loopback-connector": "^4.0.0", "mssql": "^3.1.1", "strong-globalize": "^2.5.8" }, From 050ed8baf6f79fb00488d0de52110b35ef7a41ed Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Mon, 20 Mar 2017 15:52:34 +0000 Subject: [PATCH 170/239] Refactor alter table (#134) * refactor alter table * use common fn names * Fix child functions Update child functions that are used in alterTable Update alterTable as well Fix broken tests * Fix requested changes * Extract functions to base connector move getAddModifyColumns and getDropColumns to base --- lib/migration.js | 184 ++++++++++++++++++++++++++++------------------- 1 file changed, 109 insertions(+), 75 deletions(-) diff --git a/lib/migration.js b/lib/migration.js index ec1882b..a5e8acd 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -88,19 +88,116 @@ function mixinMigration(MsSQL) { }); }; - MsSQL.prototype.alterTable = function(model, actualFields, actualIndexes, done, checkOnly) { + MsSQL.prototype.getColumnsToAdd = function(model, actualFields) { var self = this; - var m = this._models[model]; + var m = self._models[model]; + var propNames = Object.keys(m.properties).filter(function(name) { + return !!m.properties[name]; + }); var idName = this.idName(model); + var statements = []; + var columnsToAdd = []; + var columnsToAlter = []; + + // change/add new fields + propNames.forEach(function(propName) { + if (propName === idName) return; + var found; + if (actualFields) { + actualFields.forEach(function(f) { + if (f.Field === propName) { + found = f; + } + }); + } + + if (found) { + actualize(propName, found); + } else { + columnsToAdd.push(self.columnEscaped(model, propName) + + ' ' + self.propertySettingsSQL(model, propName)); + } + }); + + if (columnsToAdd.length) { + statements.push('ADD ' + columnsToAdd.join(',' + MsSQL.newline)); + } + + if (columnsToAlter.length) { + // SQL Server doesn't allow multiple columns to be altered in one statement + columnsToAlter.forEach(function(c) { + statements.push('ALTER COLUMN ' + c); + }); + } + + function actualize(propName, oldSettings) { + var newSettings = m.properties[propName]; + if (newSettings && changed(newSettings, oldSettings)) { + columnsToAlter.push(self.columnEscaped(model, propName) + ' ' + + self.propertySettingsSQL(model, propName)); + } + } + + function changed(newSettings, oldSettings) { + if (oldSettings.Null === 'YES' && + (newSettings.allowNull === false || newSettings.null === false)) { + return true; + } + if (oldSettings.Null === 'NO' && !(newSettings.allowNull === false || + newSettings.null === false)) { + return true; + } + if (oldSettings.Type.toUpperCase() !== datatype(newSettings)) { + return true; + } + return false; + } + return statements; + }; + + MsSQL.prototype.getColumnsToDrop = function(model, actualFields) { + var self = this; + var m = this._models[model]; var propNames = Object.keys(m.properties).filter(function(name) { return !!m.properties[name]; }); + var idName = this.idName(model); + + var statements = []; + var columnsToDrop = []; + + if (actualFields) { + // drop columns + actualFields.forEach(function(f) { + var notFound = !~propNames.indexOf(f.Field); + if (f.Field === idName) return; + if (notFound || !m.properties[f.Field]) { + columnsToDrop.push(self.columnEscaped(model, f.Field)); + } + }); + + if (columnsToDrop.length) { + statements.push('DROP COLUMN' + columnsToDrop.join(',' + MsSQL.newline)); + } + }; + return statements; + }; + + MsSQL.prototype.addIndexes = function(model, actualIndexes) { + var self = this; + var m = this._models[model]; + var idName = this.idName(model); + var indexNames = m.settings.indexes ? Object.keys(m.settings.indexes).filter(function(name) { return !!m.settings.indexes[name]; }) : []; - var sql = []; + var propNames = Object.keys(m.properties).filter(function(name) { + return !!m.properties[name]; + }); + var ai = {}; + var sql = []; if (actualIndexes) { actualIndexes.forEach(function(i) { @@ -114,38 +211,8 @@ function mixinMigration(MsSQL) { ai[name].columns[i.Seq_in_index - 1] = i.Column_name; }); } - var aiNames = Object.keys(ai); - - var columnsToAdd = []; - var columnsToDrop = []; - var columnsToAlter = []; - - // change/add new fields - propNames.forEach(function(propName) { - if (propName === idName) return; - var found; - actualFields.forEach(function(f) { - if (f.Field === propName) { - found = f; - } - }); - - if (found) { - actualize(propName, found); - } else { - columnsToAdd.push(self.columnEscaped(model, propName) + - ' ' + self.propertySettingsSQL(model, propName)); - } - }); - // drop columns - actualFields.forEach(function(f) { - var notFound = !~propNames.indexOf(f.Field); - if (f.Field === idName) return; - if (notFound || !m.properties[f.Field]) { - columnsToDrop.push(self.columnEscaped(model, f.Field)); - } - }); + var aiNames = Object.keys(ai); // remove indexes aiNames.forEach(function(indexName) { @@ -226,58 +293,25 @@ function mixinMigration(MsSQL) { } } }); + return sql; + }; - var statements = []; - if (columnsToAdd.length) { - statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' ADD ' + - columnsToAdd.join(',' + MsSQL.newline)); - } - - if (columnsToAlter.length) { - // SQL Server doesn't allow multiple columns to be altered in one statement - columnsToAlter.forEach(function(c) { - statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' ALTER COLUMN ' + - c); - }); - } + MsSQL.prototype.alterTable = function(model, actualFields, actualIndexes, done, checkOnly) { + var self = this; - if (columnsToDrop.length) { - statements.push('ALTER TABLE ' + self.tableEscaped(model) + ' DROP COLUMN' + - columnsToDrop.join(',' + MsSQL.newline)); - } + var statements = self.getAddModifyColumns(model, actualFields); + statements = statements.concat(self.getDropColumns(model, actualFields)); + statements = statements.concat(self.addIndexes(model, actualIndexes)); async.each(statements, function(query, fn) { if (checkOnly) { fn(null, true, {statements: statements, query: query}); } else { - self.execute(query, fn); + self.applySqlChanges(model, [query], fn); } }, function(err, results) { done && done(err, results); }); - - function actualize(propName, oldSettings) { - var newSettings = m.properties[propName]; - if (newSettings && changed(newSettings, oldSettings)) { - columnsToAlter.push(self.columnEscaped(model, propName) + ' ' + - self.propertySettingsSQL(model, propName)); - } - } - - function changed(newSettings, oldSettings) { - if (oldSettings.Null === 'YES' && - (newSettings.allowNull === false || newSettings.null === false)) { - return true; - } - if (oldSettings.Null === 'NO' && !(newSettings.allowNull === false || - newSettings.null === false)) { - return true; - } - if (oldSettings.Type.toUpperCase() !== datatype(newSettings)) { - return true; - } - return false; - } }; MsSQL.prototype.propertiesSQL = function(model) { From ed6078405ed8fab6c6c24fa7da8caa28253d8b1f Mon Sep 17 00:00:00 2001 From: siddhipai Date: Wed, 22 Mar 2017 15:30:17 -0700 Subject: [PATCH 171/239] Replicate issue_template from loopback repo (#120) * Replicate issue_template from loopback repo * Replicate new issue_template from loopback --- .github/ISSUE_TEMPLATE.md | 41 ++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index ccc915a..269b11d 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,36 +1,29 @@ -### Bug or feature request +# Description/Steps to reproduce -- [ ] Bug -- [ ] Feature request - -### Description of feature (or steps to reproduce if bug) - - - -### Link to sample repo to reproduce issue (if bug) - - - -### Expected result - +# Expected result + -### Actual result (if bug) - - - -### Additional information (Node.js version, LoopBack version, etc) - +# Additional information + From bc1f6d778448852fe4228610282970113ba80bb0 Mon Sep 17 00:00:00 2001 From: Kevin Delisle Date: Fri, 31 Mar 2017 17:35:45 -0400 Subject: [PATCH 172/239] 3.0.0 * Replicate issue_template from loopback repo (#120) (siddhipai) * Refactor alter table (#134) (Diana Lau) * Upgrade to loopback-connector@4.x (Loay) * Refactor migration methods (ssh24) * Refactor discovery methods (Loay Gewily) * Update mocha timeout (Loay Gewily) * Update LB-connector version (Loay) * Add buildreplace method (Loay Gewily) * Update README.md (#117) (Rand McKinney) * Update w info from docs (#115) (Rand McKinney) * Update paid support URL (Siddhi Pai) * increase the timeout for autoupdate test (Eddie Monge) * Start 3.x + drop support for Node v0.10/v0.12 (siddhipai) * Drop support for Node v0.10 and v0.12 (Siddhi Pai) * Start the development of the next major version (Siddhi Pai) * Update README doc links (Candy) --- CHANGES.md | 36 ++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index a381fe6..25f28ac 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,39 @@ +2017-03-31, Version 3.0.0 +========================= + + * Replicate issue_template from loopback repo (#120) (siddhipai) + + * Refactor alter table (#134) (Diana Lau) + + * Upgrade to loopback-connector@4.x (Loay) + + * Refactor migration methods (ssh24) + + * Refactor discovery methods (Loay Gewily) + + * Update mocha timeout (Loay Gewily) + + * Update LB-connector version (Loay) + + * Add buildreplace method (Loay Gewily) + + * Update README.md (#117) (Rand McKinney) + + * Update w info from docs (#115) (Rand McKinney) + + * Update paid support URL (Siddhi Pai) + + * increase the timeout for autoupdate test (Eddie Monge) + + * Start 3.x + drop support for Node v0.10/v0.12 (siddhipai) + + * Drop support for Node v0.10 and v0.12 (Siddhi Pai) + + * Start the development of the next major version (Siddhi Pai) + + * Update README doc links (Candy) + + 2016-10-14, Version 2.9.0 ========================= diff --git a/package.json b/package.json index bb35d5a..9384b18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "3.0.0-alpha.1", + "version": "3.0.0", "description": "Microsoft SQL Server connector for LoopBack", "engines": { "node": ">=4" From d05960486bb19aa330423bd84c377417daf56f25 Mon Sep 17 00:00:00 2001 From: emrahcetiner Date: Mon, 3 Apr 2017 18:41:10 +0300 Subject: [PATCH 173/239] fix discovery turkish collation (#123) --- lib/discovery.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/discovery.js b/lib/discovery.js index debb699..5719d78 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -88,13 +88,13 @@ function mixinDiscovery(MsSQL) { if (options.all && !owner) { sqlTables = this.paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' + - ' FROM information_schema.tables', 'table_schema, table_name', options); + ' FROM INFORMATION_SCHEMA.tables', 'table_schema, table_name', options); } else if (owner) { sqlTables = this.paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' + - ' FROM information_schema.tables WHERE table_schema=\'' + owner + '\'', 'table_schema, table_name', options); + ' FROM INFORMATION_SCHEMA.tables WHERE table_schema=\'' + owner + '\'', 'table_schema, table_name', options); } else { sqlTables = this.paginateSQL('SELECT \'table\' AS "type", table_name AS "name",' + - ' table_schema AS "owner" FROM information_schema.tables WHERE table_schema=schema_name()', + ' table_schema AS "owner" FROM INFORMATION_SCHEMA.tables WHERE table_schema=schema_name()', 'table_name', options); } return sqlTables; @@ -112,15 +112,15 @@ function mixinDiscovery(MsSQL) { if (options.all && !owner) { sqlViews = this.paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + - ' table_schema AS "owner" FROM information_schema.views', + ' table_schema AS "owner" FROM INFORMATION_SCHEMA.views', 'table_schema, table_name', options); } else if (owner) { sqlViews = this.paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + - ' table_schema AS "owner" FROM information_schema.views WHERE table_schema=\'' + owner + '\'', + ' table_schema AS "owner" FROM INFORMATION_SCHEMA.views WHERE table_schema=\'' + owner + '\'', 'table_schema, table_name', options); } else { sqlViews = this.paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + - ' schema_name() AS "owner" FROM information_schema.views', + ' schema_name() AS "owner" FROM INFORMATION_SCHEMA.views', 'table_name', options); } } @@ -174,7 +174,7 @@ function mixinDiscovery(MsSQL) { ' AS "columnName", data_type AS "dataType",' + ' character_maximum_length AS "dataLength", numeric_precision AS' + ' "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"' + - ' FROM information_schema.columns' + + ' FROM INFORMATION_SCHEMA.columns' + ' WHERE table_schema=\'' + owner + '\'' + (table ? ' AND table_name=\'' + table + '\'' : ''), 'table_schema, table_name, ordinal_position', {}); @@ -183,7 +183,7 @@ function mixinDiscovery(MsSQL) { ' AS "tableName", column_name AS "columnName", data_type AS "dataType",' + ' character_maximum_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS' + ' "dataScale", is_nullable AS "nullable"' + - ' FROM information_schema.columns' + + ' FROM INFORMATION_SCHEMA.columns' + (table ? ' WHERE table_name=\'' + table + '\'' : ''), 'table_name, ordinal_position', {}); } @@ -209,8 +209,8 @@ function mixinDiscovery(MsSQL) { /* select tc.table_schema, tc.table_name, kc.column_name from - information_schema.table_constraints tc - join information_schema.key_column_usage kc + INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc + join INFORMATION_SCHEMA.key_column_usage kc on kc.table_name = tc.table_name and kc.table_schema = tc.table_schema where tc.constraint_type = 'PRIMARY KEY' @@ -224,8 +224,8 @@ function mixinDiscovery(MsSQL) { var sql = 'SELECT kc.table_schema AS "owner", ' + 'kc.table_name AS "tableName", kc.column_name AS "columnName", kc.ordinal_position' + ' AS "keySeq", kc.constraint_name AS "pkName" FROM' + - ' information_schema.key_column_usage kc' + - ' JOIN information_schema.table_constraints tc' + + ' INFORMATION_SCHEMA.key_column_usage kc' + + ' JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc' + ' ON kc.table_name = tc.table_name AND kc.table_schema = tc.table_schema' + ' WHERE tc.constraint_type=\'PRIMARY KEY\' AND kc.ordinal_position IS NOT NULL'; @@ -268,10 +268,10 @@ function mixinDiscovery(MsSQL) { ccu.table_name AS foreign_table_name, ccu.column_name AS foreign_column_name FROM - information_schema.table_constraints AS tc - JOIN information_schema.key_column_usage AS kcu + INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc + JOIN INFORMATION_SCHEMA.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name - JOIN information_schema.constraint_column_usage AS ccu + JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu ON ccu.constraint_name = tc.constraint_name WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name='mytable'; @@ -282,10 +282,10 @@ function mixinDiscovery(MsSQL) { ' kcu.column_name AS "fkColumnName", kcu.ordinal_position AS "keySeq",' + ' ccu.table_schema AS "pkOwner", \'PK\' AS "pkName", ' + ' ccu.table_name AS "pkTableName", ccu.column_name AS "pkColumnName"' + - ' FROM information_schema.table_constraints tc' + - ' JOIN information_schema.key_column_usage AS kcu' + + ' FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc' + + ' JOIN INFORMATION_SCHEMA.key_column_usage AS kcu' + ' ON tc.constraint_schema = kcu.constraint_schema AND tc.constraint_name = kcu.constraint_name' + - ' JOIN information_schema.constraint_column_usage ccu' + + ' JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu' + ' ON ccu.constraint_schema = tc.constraint_schema AND ccu.constraint_name = tc.constraint_name' + ' WHERE tc.constraint_type = \'FOREIGN KEY\''; if (owner) { @@ -317,8 +317,8 @@ function mixinDiscovery(MsSQL) { ' \'PK\' AS "pkName", ccu.table_schema AS "pkOwner",' + ' ccu.table_name AS "pkTableName", ccu.column_name AS "pkColumnName"' + ' FROM' + - ' information_schema.constraint_column_usage ccu' + - ' JOIN information_schema.key_column_usage kcu' + + ' INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu' + + ' JOIN INFORMATION_SCHEMA.key_column_usage kcu' + ' ON ccu.constraint_schema = kcu.constraint_schema AND ccu.constraint_name = kcu.constraint_name' + ' WHERE kcu.ordinal_position IS NOT NULL'; if (owner) { From 2035c86915888c15812b67fe74a0946ad83e2a68 Mon Sep 17 00:00:00 2001 From: Ryan Graham Date: Wed, 19 Apr 2017 07:50:25 -0700 Subject: [PATCH 174/239] add appveyor for CI (#137) * ci: initial support for testing on Appveyor * Add dummy table with different owner Tests require seeding of a dummy table with a different schema owner * Use env variables to set db config Allow usage of env variables if any to set db config --- appveyor.yml | 26 ++++++++++++++++++++++++++ test/init.js | 10 +++++----- test/tables.sql | 17 ++++++++++++++--- 3 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..a3b537b --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,26 @@ +environment: + matrix: + - nodejs_version: '7' + - nodejs_version: '6' + - nodejs_version: '4' + MSSQL_HOST: 'localhost' + MSSQL_PORT: 1433 + MSSQL_USER: 'sa' + MSSQL_PASSWORD: 'Password12!' + MSSQL_DATABASE: 'master' +services: mssql2012sp1 +install: + - ps: Install-Product node $env:nodejs_version + - set CI=true + - set PATH=%APPDATA%\npm;%PATH% + - npm install +matrix: + fast_finish: true +build: off +skip_branch_with_pr: true +version: '{build}' +shallow_clone: true +clone_depth: 1 +test_script: + - sqlcmd -S "(local)\SQL2012SP1" -U "sa" -P "Password12!" -d "master" -i "test\\tables.sql" + - npm test diff --git a/test/init.js b/test/init.js index 4e6b90b..bd00da6 100644 --- a/test/init.js +++ b/test/init.js @@ -23,11 +23,11 @@ try { global.getConfig = function(options) { var dbConf = { - host: config.host || config.hostname || config.server || 'localhost', - port: config.port || 1433, - database: config.database || 'test', - user: config.user || config.username, - password: config.password, + host: process.env.MSSQL_HOST || config.host || config.hostname || config.server || 'localhost', + port: process.env.MSSQL_PORT || config.port || 1433, + database: process.env.MSSQL_DATABASE || config.database || 'test', + user: process.env.MSSQL_USER || config.user || config.username, + password: process.env.MSSQL_PASSWORD || config.password, pool: { max: 10, min: 0, diff --git a/test/tables.sql b/test/tables.sql index d7a47e9..5c888f4 100644 --- a/test/tables.sql +++ b/test/tables.sql @@ -5,6 +5,10 @@ drop table inventory; drop table reservation; drop table session; +go + + CREATE SCHEMA sa; + go create table customer @@ -69,6 +73,14 @@ go ttl integer ) ; + create table movies + ( id varchar(64) not null, + name varchar(1024), + year integer + ) ; + + ALTER SCHEMA sa TRANSFER dbo.movies + insert into customer (id,[username],email,password,name,military_agency,realm,emailverified,verificationtoken,credentials,challenges,status,created,lastupdated) values ('612','bat','bat@bar.com','$2a$10$beg18wcyqn7trkfic59eb.vmnsewqjwmlym4dng73izb.mka1rjac',null,null,null,null,null,']',']',null,null,null); insert into customer (id,username,email,password,name,military_agency,realm,emailverified,verificationtoken,credentials,challenges,status,created,lastupdated) values ('613','baz','baz@bar.com','$2a$10$jksyf2glmdi4cwvqh8astos0b24ldu9p8jccnmri/0rvhtwsicm9c',null,null,null,null,null,']',']',null,null,null); insert into customer (id,username,email,password,name,military_agency,realm,emailverified,verificationtoken,credentials,challenges,status,created,lastupdated) values ('610','foo','foo@bar.com','$2a$10$tn1hn7xv6x74ccb7tvfwkeaajtd4/6q4rbcmzgmajewe40xqrrsui',null,null,null,null,null,']',']',null,null,null); @@ -701,7 +713,7 @@ insert into product (id,name,audible_range,effective_range,rounds,extras,fire_mo go - create view inventory_view + create view inventory_view as select p.name as product, l.name as location, @@ -711,5 +723,4 @@ insert into product (id,name,audible_range,effective_range,rounds,extras,fire_mo location l where p.id = i.product_id and l.id = i.location_id; - - + From 2067f0bf6474782abcf1f1c152857dbf7bb32c88 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 27 Apr 2017 21:45:19 -0700 Subject: [PATCH 175/239] Upgrade deps to mssql@4.x --- lib/mssql.js | 9 ++++----- package.json | 12 ++++++------ test/connection.js | 1 - 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index c92d9f8..5eadde1 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -83,7 +83,7 @@ MsSQL.prototype.connect = function(callback) { if (self.client) { return process.nextTick(callback); } - var connection = new mssql.Connection(this.connConfig, function(err) { + var connection = new mssql.ConnectionPool(this.connConfig, function(err) { if (err) { debug('Connection error: ', err); return callback(err); @@ -124,9 +124,8 @@ MsSQL.prototype.executeSQL = function(sql, params, options, callback) { } var innerCB = function(err, data) { debug('Result: %j %j', err, data); - var updatedStatement = sql.includes('UPDATE'); - if (data && data[0] && typeof data[0].affectedRows === 'number' && updatedStatement) { - data = data[0]; + if (data) { + data = data.recordset; } callback && callback(err, data); }; @@ -246,7 +245,7 @@ MsSQL.prototype.buildDelete = function(model, where, options) { MsSQL.prototype.buildReplace = function(model, where, data, options) { var stmt = this.invokeSuper('buildReplace', model, where, data, options); - stmt.merge(';SELECT @@ROWCOUNT as affectedRows', ''); + stmt.merge(';SELECT @@ROWCOUNT as count', ''); return stmt; }; diff --git a/package.json b/package.json index 9384b18..96a4e16 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,10 @@ ], "main": "index.js", "dependencies": { - "async": "^1.5.2", + "async": "^2.3.0", "debug": "^2.1.1", "loopback-connector": "^4.0.0", - "mssql": "^3.1.1", + "mssql": "^4.0.4", "strong-globalize": "^2.5.8" }, "bundledDependencies": [ @@ -27,13 +27,13 @@ ], "devDependencies": { "bluebird": "^3.3.3", - "eslint": "^2.13.1", - "eslint-config-loopback": "^4.0.0", + "eslint": "^3.19.0", + "eslint-config-loopback": "^8.0.0", "loopback-datasource-juggler": "^3.0.0", - "mocha": "^2.1.0", + "mocha": "^3.3.0", "rc": "^1.0.0", "should": "^8.0.2", - "sinon": "^1.15.4" + "sinon": "^2.1.0" }, "scripts": { "lint": "eslint .", diff --git a/test/connection.js b/test/connection.js index 7a5f87e..afb03b2 100644 --- a/test/connection.js +++ b/test/connection.js @@ -15,7 +15,6 @@ var config; before(function() { config = global.getConfig(); - console.log('config', config); }); describe('testConnection', function() { From 7918aead4a55bab4fa885b02862bf2318f567801 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 28 Apr 2017 23:08:08 -0700 Subject: [PATCH 176/239] Fix eslint issues & buildPropertyType() signature --- lib/discovery.js | 4 ++-- lib/mssql.js | 10 +++++----- test/autoupdate.test.js | 1 + test/commontests.js | 13 +++++++------ test/discover.test.js | 1 + test/id.test.js | 1 + test/init.js | 1 + test/mapping.test.js | 1 + test/mssql.test.js | 2 ++ test/transaction.test.js | 1 + 10 files changed, 22 insertions(+), 13 deletions(-) diff --git a/lib/discovery.js b/lib/discovery.js index 5719d78..745ed7b 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -339,9 +339,9 @@ function mixinDiscovery(MsSQL) { * @param {Function} [cb] The callback function // */ - MsSQL.prototype.buildPropertyType = function(columnDefinition, dataLength) { + MsSQL.prototype.buildPropertyType = function(columnDefinition, options) { var mysqlType = columnDefinition.dataType; - var dataLength = columnDefinition.dataLength; + var dataLength = options.dataLength || columnDefinition.dataLength; var type = mysqlType.toUpperCase(); switch (type) { case 'BIT': diff --git a/lib/mssql.js b/lib/mssql.js index 5eadde1..821fc36 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -160,7 +160,7 @@ MsSQL.prototype.disconnect = function disconnect(cb) { // return this.execute(sql, callback); // }; -//params +// params // descr = { // model: ... // properties: ... @@ -173,7 +173,7 @@ MsSQL.prototype.define = function(modelDefinition) { this._models[modelDefinition.model.modelName] = modelDefinition; - //track database index names for this model + // track database index names for this model this._idxNames[modelDefinition.model.modelName] = []; }; @@ -208,10 +208,10 @@ MsSQL.prototype.buildInsert = function(model, data, options) { var prop = this.getPropertyDefinition(model, idName); var isIdentity = (prop && prop.type === Number && prop.generated !== false); if (isIdentity && data[idName] == null) { - //remove the pkid column if it's in the data, since we're going to insert a + // remove the pkid column if it's in the data, since we're going to insert a // new record, not update an existing one. delete data[idName]; - //delete the hardcoded id property that jugglindb automatically creates + // delete the hardcoded id property that jugglindb automatically creates // delete data.id; } @@ -373,7 +373,7 @@ MsSQL.prototype.fromColumnValue = function(prop, val) { } var type = prop && prop.type; if (type === Boolean) { - val = !!val; //convert to a boolean type from number + val = !!val; // convert to a boolean type from number } if (type === Date) { if (!(val instanceof Date)) { diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js index 30bde31..6b481fc 100644 --- a/test/autoupdate.test.js +++ b/test/autoupdate.test.js @@ -9,6 +9,7 @@ var assert = require('assert'); var ds; before(function() { + /* global getDataSource */ ds = getDataSource(); }); diff --git a/test/commontests.js b/test/commontests.js index a6bc09c..a921dd6 100644 --- a/test/commontests.js +++ b/test/commontests.js @@ -9,15 +9,16 @@ var commonTest = jdb.test; require('./init'); -var schema = getSchema(); +/* global getDataSource */ +var schema = getDataSource(); -//run the tests exposed by jugglingdb +// run the tests exposed by jugglingdb commonTest(module.exports, schema); -//skip the order test from jugglingdb, it wasn't working right +// skip the order test from jugglingdb, it wasn't working right commonTest.skip('should handle ORDER clause'); -//re-implement the order test as pretty much the same thing, but run an automigration beforehand +// re-implement the order test as pretty much the same thing, but run an automigration beforehand commonTest.it('should automigrate', function(test) { schema.automigrate(function(err) { test.ifError(err); @@ -180,7 +181,7 @@ commonTest.it('should delete a post', function(test) { }, function(err, posts) { test.ifError(err); test.equal(posts.length, 1); - id = posts[0].id; + var id = posts[0].id; posts[0].destroy(function(err) { test.ifError(err); schema.models.Post.find(id, function(err, post) { @@ -204,7 +205,7 @@ commonTest.it('should delete all posts', function(test) { }); }); -//custom primary keys not quite working :(, hopefully 1602 will implement that functionality in jugglingdb soon. +// custom primary keys not quite working :(, hopefully 1602 will implement that functionality in jugglingdb soon. commonTest.it('should support custom primary key', function(test) { test.expect(3); var AppliesTo = schema.define('AppliesTo', { diff --git a/test/discover.test.js b/test/discover.test.js index 7e0ef27..1c20955 100644 --- a/test/discover.test.js +++ b/test/discover.test.js @@ -10,6 +10,7 @@ require('should'); var assert = require('assert'); +/* global getDataSource */ var db = getDataSource(); describe('discoverModels', function() { diff --git a/test/id.test.js b/test/id.test.js index 1197890..26c700a 100644 --- a/test/id.test.js +++ b/test/id.test.js @@ -11,6 +11,7 @@ var async = require('async'); var ds; before(function() { + /* global getDataSource */ ds = getDataSource(); }); diff --git a/test/init.js b/test/init.js index bd00da6..5f4ee74 100644 --- a/test/init.js +++ b/test/init.js @@ -45,6 +45,7 @@ global.getConfig = function(options) { }; global.getDataSource = global.getSchema = function(options) { + /* global getConfig */ var db = new DataSource(require('../'), getConfig(options)); return db; }; diff --git a/test/mapping.test.js b/test/mapping.test.js index c79aaee..97dc179 100644 --- a/test/mapping.test.js +++ b/test/mapping.test.js @@ -12,6 +12,7 @@ var async = require('async'); var db; before(function() { + /* global getDataSource */ db = getDataSource(); }); diff --git a/test/mssql.test.js b/test/mssql.test.js index 39a7f47..9a521ea 100644 --- a/test/mssql.test.js +++ b/test/mssql.test.js @@ -11,6 +11,7 @@ var Post, PostWithUUID, PostWithStringId, db; describe('mssql connector', function() { before(function() { + /* global getDataSource */ db = getDataSource(); Post = db.define('PostWithBoolean', { @@ -250,6 +251,7 @@ describe('mssql connector', function() { ], done); }); beforeEach(function addSpy() { + /* global sinon */ sinon.stub(console, 'warn'); }); afterEach(function removeSpy() { diff --git a/test/transaction.test.js b/test/transaction.test.js index 641273e..5eb721c 100644 --- a/test/transaction.test.js +++ b/test/transaction.test.js @@ -13,6 +13,7 @@ var db, Post; describe('transactions', function() { before(function(done) { + /* global getDataSource */ db = getDataSource(); Post = db.define('PostTX', { title: {type: String, length: 255, index: true}, From aec78de37c2715c442da29177258dd46c2b66f5c Mon Sep 17 00:00:00 2001 From: Sakib Hasan Date: Tue, 2 May 2017 10:14:05 -0400 Subject: [PATCH 177/239] Enable clean DB seed as pre-test (#142) * Update sql file Allow sequential commit of the sql file For every CI run seed the database clean * Add sqlcmd as a dependency sqlcmdjs allows us to run sql query from an input file * Add pretest file Uses a sqlcmd node_module to seed the mssql db * Add .bin to run sqlcmd * Fix global variables * Refactor pretest file * Remove calls to init file for getConfig * Add require init with global calls --- package.json | 4 ++- pretest.js | 28 ++++++++++++++++ test/tables.sql | 89 +++++++++++++++++++++++++++++++------------------ 3 files changed, 87 insertions(+), 34 deletions(-) create mode 100644 pretest.js diff --git a/package.json b/package.json index 96a4e16..a84019c 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "debug": "^2.1.1", "loopback-connector": "^4.0.0", "mssql": "^4.0.4", - "strong-globalize": "^2.5.8" + "strong-globalize": "^2.5.8", + "sqlcmdjs": "^1.5.0" }, "bundledDependencies": [ "mssql" @@ -37,6 +38,7 @@ }, "scripts": { "lint": "eslint .", + "pretest": "node pretest.js", "test": "./node_modules/.bin/mocha --timeout 9999 -R spec", "posttest": "npm run lint", "prepublish": "node remove-regenerator.js" diff --git a/pretest.js b/pretest.js new file mode 100644 index 0000000..1c979b5 --- /dev/null +++ b/pretest.js @@ -0,0 +1,28 @@ +'use strict'; + +require('./test/init'); +var exec = require('child_process').exec; +var path = require('path'); + +var isWin = (process.platform === 'win32'); +var sqlFileDir = path.resolve(__dirname, 'test', 'tables.sql'); +var sqlDependencyDir = path.resolve(__dirname, 'node_modules', '.bin', 'sqlcmd'); + +// since appveyor seeds its database on the fly, we do not need to seed the share the database +if (process.env.APPVEYOR) return console.log('Not seeding DB with test db'); + +if (!process.env.MSSQL_HOST) process.env.MSSQL_HOST = global.getConfig().host; +if (!process.env.MSSQL_PORT) process.env.MSSQL_PORT = global.getConfig().port; +if (!process.env.MSSQL_USER) process.env.MSSQL_USER = global.getConfig().user; +if (!process.env.MSSQL_PASSWORD) process.env.MSSQL_PASSWORD = global.getConfig().password; +if (!process.env.MSSQL_DATABASE) process.env.MSSQL_DATABASE = global.getConfig().database; + +var catFileCmd = (isWin ? 'type ' : 'cat ') + sqlFileDir; + +var sqlcmd = catFileCmd + ' | ' + sqlDependencyDir + ' -s ' + process.env.MSSQL_HOST + ' -o ' + process.env.MSSQL_PORT + +' -u ' + process.env.MSSQL_USER + ' -p ' + process.env.MSSQL_PASSWORD + ' -d ' + process.env.MSSQL_DATABASE; + +exec(sqlcmd, function(err, result) { + if (err) return console.log('Database seeding failed.\n%s', err); + return console.log('Database successfully seeded.'); +}); diff --git a/test/tables.sql b/test/tables.sql index 5c888f4..ce97a53 100644 --- a/test/tables.sql +++ b/test/tables.sql @@ -1,15 +1,39 @@ -drop table customer; -drop table location; -drop table product; -drop table inventory; -drop table reservation; -drop table session; +IF OBJECT_ID('dbo.inventory', 'U') IS NOT NULL +DROP TABLE inventory; -go +IF OBJECT_ID('dbo.reservation', 'U') IS NOT NULL +DROP TABLE reservation; - CREATE SCHEMA sa; +IF OBJECT_ID('dbo.customer', 'U') IS NOT NULL +DROP TABLE customer; -go +IF OBJECT_ID('dbo.location', 'U') IS NOT NULL +DROP TABLE location; + +IF OBJECT_ID('dbo.product', 'U') IS NOT NULL +DROP TABLE product; + +IF OBJECT_ID('dbo.session', 'U') IS NOT NULL +DROP TABLE session; + +IF OBJECT_ID('sa.movies', 'U') IS NOT NULL +DROP TABLE sa.movies; + +IF OBJECT_ID('inventory_view','v') IS NOT NULL +DROP VIEW inventory_view; + +GO + + IF NOT EXISTS ( + SELECT schema_name + FROM information_schema.schemata + WHERE schema_name = 'sa' ) + + BEGIN + EXEC sp_executesql N'CREATE SCHEMA sa' + END + +GO create table customer ( id varchar(64) not null, @@ -36,6 +60,18 @@ go total integer ) ; + create table reservation + ( id varchar(64) not null, + product_id varchar(64), + location_id varchar(64), + customer_id varchar(64), + qty integer, + status varchar(20), + reserve_date date, + pickup_date date, + return_date date + ) ; + create table location ( id varchar(64) not null, street varchar(64), @@ -55,18 +91,6 @@ go fire_modes varchar(64) ) ; - create table reservation - ( id varchar(64) not null, - product_id varchar(64), - location_id varchar(64), - customer_id varchar(64), - qty integer, - status varchar(20), - reserve_date date, - pickup_date date, - return_date date - ) ; - create table session ( id varchar(64) not null, uid varchar(1024), @@ -81,11 +105,6 @@ go ALTER SCHEMA sa TRANSFER dbo.movies -insert into customer (id,[username],email,password,name,military_agency,realm,emailverified,verificationtoken,credentials,challenges,status,created,lastupdated) values ('612','bat','bat@bar.com','$2a$10$beg18wcyqn7trkfic59eb.vmnsewqjwmlym4dng73izb.mka1rjac',null,null,null,null,null,']',']',null,null,null); -insert into customer (id,username,email,password,name,military_agency,realm,emailverified,verificationtoken,credentials,challenges,status,created,lastupdated) values ('613','baz','baz@bar.com','$2a$10$jksyf2glmdi4cwvqh8astos0b24ldu9p8jccnmri/0rvhtwsicm9c',null,null,null,null,null,']',']',null,null,null); -insert into customer (id,username,email,password,name,military_agency,realm,emailverified,verificationtoken,credentials,challenges,status,created,lastupdated) values ('610','foo','foo@bar.com','$2a$10$tn1hn7xv6x74ccb7tvfwkeaajtd4/6q4rbcmzgmajewe40xqrrsui',null,null,null,null,null,']',']',null,null,null); -insert into customer (id,username,email,password,name,military_agency,realm,emailverified,verificationtoken,credentials,challenges,status,created,lastupdated) values ('611','bar','bar@bar.com','$2a$10$a8mcol6d5vqxm6vubqxl8e5v66steg6e8vzjqqppoyk95vm3smpik',null,null,null,null,null,']',']',null,null,null); - insert into inventory (id,product_id,location_id,available,total) values ('441','6','91',8,19); insert into inventory (id,product_id,location_id,available,total) values ('442','7','91',21,23); insert into inventory (id,product_id,location_id,available,total) values ('443','8','91',35,63); @@ -603,6 +622,11 @@ insert into inventory (id,product_id,location_id,available,total) values ('438', insert into inventory (id,product_id,location_id,available,total) values ('439','4','91',56,97); insert into inventory (id,product_id,location_id,available,total) values ('440','5','91',20,30); +insert into customer (id,[username],email,password,name,military_agency,realm,emailverified,verificationtoken,credentials,challenges,status,created,lastupdated) values ('612','bat','bat@bar.com','$2a$10$beg18wcyqn7trkfic59eb.vmnsewqjwmlym4dng73izb.mka1rjac',null,null,null,null,null,']',']',null,null,null); +insert into customer (id,username,email,password,name,military_agency,realm,emailverified,verificationtoken,credentials,challenges,status,created,lastupdated) values ('613','baz','baz@bar.com','$2a$10$jksyf2glmdi4cwvqh8astos0b24ldu9p8jccnmri/0rvhtwsicm9c',null,null,null,null,null,']',']',null,null,null); +insert into customer (id,username,email,password,name,military_agency,realm,emailverified,verificationtoken,credentials,challenges,status,created,lastupdated) values ('610','foo','foo@bar.com','$2a$10$tn1hn7xv6x74ccb7tvfwkeaajtd4/6q4rbcmzgmajewe40xqrrsui',null,null,null,null,null,']',']',null,null,null); +insert into customer (id,username,email,password,name,military_agency,realm,emailverified,verificationtoken,credentials,challenges,status,created,lastupdated) values ('611','bar','bar@bar.com','$2a$10$a8mcol6d5vqxm6vubqxl8e5v66steg6e8vzjqqppoyk95vm3smpik',null,null,null,null,null,']',']',null,null,null); + insert into location (id,street,city,zipcode,name,geo) values ('87','7153 east thomas road','scottsdale','85251','phoenix equipment rentals','-111.9271738,33.48034450000001'); insert into location (id,street,city,zipcode,name,geo) values ('91','2799 broadway','new york','10025','cascabel armory','-73.9676965,40.8029807'); insert into location (id,street,city,zipcode,name,geo) values ('89','1850 el camino real','menlo park','94027','military weaponry','-122.194253,37.459525'); @@ -697,21 +721,21 @@ insert into product (id,name,audible_range,effective_range,rounds,extras,fire_mo insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('2','g17',53,75,15,'flashlight','single'); insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('5','m9 sd',0,75,15,'silenced','single'); - - - alter table customer add primary key (id); alter table inventory add primary key (id); alter table location add primary key (id); + + alter table customer add primary key (id); alter table product add primary key (id); alter table session add primary key (id); + alter table inventory add constraint location_fk foreign key (location_id) references location (id); - alter table inventory add constraint product_fk foreign key (product_id) - references product (id); + alter table inventory add constraint product_fk foreign key (product_id) references product (id); + alter table reservation add constraint reservation_customer_fk foreign key (customer_id) references customer (id); alter table reservation add constraint reservation_location_fk foreign key (location_id) references location (id); alter table reservation add constraint reservation_product_fk foreign key (product_id) references product (id); - go +GO create view inventory_view as @@ -723,4 +747,3 @@ insert into product (id,name,audible_range,effective_range,rounds,extras,fire_mo location l where p.id = i.product_id and l.id = i.location_id; - From 77001fd41f8f31a2ef3c8d3e11caa760fbc9ff11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Kim=20Kha?= Date: Wed, 3 May 2017 02:33:54 +0700 Subject: [PATCH 178/239] Fix params for isActual function (#143) * Fix isActual function * Update isActual --- lib/migration.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/migration.js b/lib/migration.js index a5e8acd..b92e978 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -66,10 +66,22 @@ function mixinMigration(MsSQL) { }); }; - MsSQL.prototype.isActual = function(cb) { + MsSQL.prototype.isActual = function(models, cb) { var ok = false; var self = this; - async.each(Object.keys(this._models), function(model, done) { + + if ((!cb) && ('function' === typeof models)) { + cb = models; + models = undefined; + } + // First argument is a model name + if ('string' === typeof models) { + models = [models]; + } + + models = models || Object.keys(this._models); + + async.each(models, function(model, done) { self.getTableStatus(model, function(err, fields, indexes) { self.alterTable(model, fields, indexes, function(err, needAlter) { if (err) { From ab6dddbf84001291f092f54b827e989c3ed0179f Mon Sep 17 00:00:00 2001 From: Sakib Hasan Date: Wed, 3 May 2017 08:59:11 -0400 Subject: [PATCH 179/239] Remove appveyor service (#145) We do not need appveyor anymore since CI pre-seeds the db now --- appveyor.yml | 26 -------------------------- pretest.js | 3 --- 2 files changed, 29 deletions(-) delete mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index a3b537b..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,26 +0,0 @@ -environment: - matrix: - - nodejs_version: '7' - - nodejs_version: '6' - - nodejs_version: '4' - MSSQL_HOST: 'localhost' - MSSQL_PORT: 1433 - MSSQL_USER: 'sa' - MSSQL_PASSWORD: 'Password12!' - MSSQL_DATABASE: 'master' -services: mssql2012sp1 -install: - - ps: Install-Product node $env:nodejs_version - - set CI=true - - set PATH=%APPDATA%\npm;%PATH% - - npm install -matrix: - fast_finish: true -build: off -skip_branch_with_pr: true -version: '{build}' -shallow_clone: true -clone_depth: 1 -test_script: - - sqlcmd -S "(local)\SQL2012SP1" -U "sa" -P "Password12!" -d "master" -i "test\\tables.sql" - - npm test diff --git a/pretest.js b/pretest.js index 1c979b5..b3d0d19 100644 --- a/pretest.js +++ b/pretest.js @@ -8,9 +8,6 @@ var isWin = (process.platform === 'win32'); var sqlFileDir = path.resolve(__dirname, 'test', 'tables.sql'); var sqlDependencyDir = path.resolve(__dirname, 'node_modules', '.bin', 'sqlcmd'); -// since appveyor seeds its database on the fly, we do not need to seed the share the database -if (process.env.APPVEYOR) return console.log('Not seeding DB with test db'); - if (!process.env.MSSQL_HOST) process.env.MSSQL_HOST = global.getConfig().host; if (!process.env.MSSQL_PORT) process.env.MSSQL_PORT = global.getConfig().port; if (!process.env.MSSQL_USER) process.env.MSSQL_USER = global.getConfig().user; From 73181e55b50d5fcc72d6509747e72b26ab70d84a Mon Sep 17 00:00:00 2001 From: Christiaan Westerbeek Date: Wed, 3 May 2017 15:57:18 +0200 Subject: [PATCH 180/239] Fix discovery of primary keys (#99) * Fixes #79 discovery of pks * Change order of drop test table This helps test automation. In the original order you would see these errors: Could not drop object 'customer' because it is referenced by a FOREIGN KEY constraint. Msg 3726, Level 16, State 1, Line 2 Could not drop object 'location' because it is referenced by a FOREIGN KEY constraint. Msg 3726, Level 16, State 1, Line 3 Could not drop object 'product' because it is referenced by a FOREIGN KEY constraint. * Tests for getting just pks and not fks * Only drop test tables if they exists This helps automating the test * Reorder drop table calls --- lib/discovery.js | 2 +- test/discover.test.js | 66 +++++++++++++++++++++++++++++++++++++++++++ test/tables.sql | 20 +++++++++---- 3 files changed, 82 insertions(+), 6 deletions(-) diff --git a/lib/discovery.js b/lib/discovery.js index 745ed7b..cd3352c 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -226,7 +226,7 @@ function mixinDiscovery(MsSQL) { ' AS "keySeq", kc.constraint_name AS "pkName" FROM' + ' INFORMATION_SCHEMA.key_column_usage kc' + ' JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc' + - ' ON kc.table_name = tc.table_name AND kc.table_schema = tc.table_schema' + + ' ON kc.table_name = tc.table_name AND kc.constraint_name = tc.constraint_name AND kc.table_schema = tc.table_schema' + ' WHERE tc.constraint_type=\'PRIMARY KEY\' AND kc.ordinal_position IS NOT NULL'; if (owner) { diff --git a/test/discover.test.js b/test/discover.test.js index 1c20955..9d0c528 100644 --- a/test/discover.test.js +++ b/test/discover.test.js @@ -148,6 +148,72 @@ describe('Discover model primary keys', function() { } }); }); + + it('should return just the primary keys and not the foreign keys for inventory', function(done) { + db.discoverPrimaryKeys('inventory', function(err, models) { + if (err) { + console.error(err); + done(err); + } else { + models.forEach(function(m) { + // console.dir(m); + assert(m.tableName === 'inventory'); + assert(m.columnName === 'id'); + assert(m.pkName.match(/_pk$/)); + }); + done(null, models); + } + }); + }); + + it('should return just the primary keys and not the foreign keys for dbo.inventory', function(done) { + db.discoverPrimaryKeys('inventory', {owner: 'dbo'}, function(err, models) { + if (err) { + console.error(err); + done(err); + } else { + models.forEach(function(m) { + // console.dir(m); + assert(m.tableName === 'inventory'); + assert(m.columnName === 'id'); + assert(m.pkName === 'inventory_pk'); + }); + done(null, models); + } + }); + }); + it('should return the primary key for version which consists of two columns', function(done) { + db.discoverPrimaryKeys('version', function(err, models) { + if (err) { + console.error(err); + done(err); + } else { + assert(models.length === 2); + models.forEach(function(m) { + // console.dir(m); + assert(m.tableName === 'version'); + assert(m.pkName.match(/_pk$/)); + }); + done(null, models); + } + }); + }); + it('should return the primary key for dbo.version which consists of two columns', function(done) { + db.discoverPrimaryKeys('version', {owner: 'dbo'}, function(err, models) { + if (err) { + console.error(err); + done(err); + } else { + assert(models.length === 2); + models.forEach(function(m) { + // console.dir(m); + assert(m.tableName === 'version'); + assert(m.pkName.match(/_pk$/)); + }); + done(null, models); + } + }); + }); }); describe('Discover model foreign keys', function() { diff --git a/test/tables.sql b/test/tables.sql index ce97a53..569cc92 100644 --- a/test/tables.sql +++ b/test/tables.sql @@ -4,6 +4,9 @@ DROP TABLE inventory; IF OBJECT_ID('dbo.reservation', 'U') IS NOT NULL DROP TABLE reservation; +IF OBJECT_ID('dbo.version', 'U') IS NOT NULL +DROP TABLE version; + IF OBJECT_ID('dbo.customer', 'U') IS NOT NULL DROP TABLE customer; @@ -96,6 +99,11 @@ GO uid varchar(1024), ttl integer ) ; + + create table version + ( product_id varchar(64) not null, + version integer not null + ) ; create table movies ( id varchar(64) not null, @@ -721,12 +729,13 @@ insert into product (id,name,audible_range,effective_range,rounds,extras,fire_mo insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('2','g17',53,75,15,'flashlight','single'); insert into product (id,name,audible_range,effective_range,rounds,extras,fire_modes) values ('5','m9 sd',0,75,15,'silenced','single'); - alter table inventory add primary key (id); - alter table location add primary key (id); + alter table inventory add constraint inventory_pk primary key (id); + alter table location add constraint location_pk primary key (id); - alter table customer add primary key (id); - alter table product add primary key (id); - alter table session add primary key (id); + alter table customer add constraint customer_pk primary key (id); + alter table product add constraint product_pk primary key (id); + alter table session add constraint session_pk primary key (id); + alter table version add constraint version_pk primary key (product_id, version); alter table inventory add constraint location_fk foreign key (location_id) references location (id); alter table inventory add constraint product_fk foreign key (product_id) references product (id); @@ -734,6 +743,7 @@ insert into product (id,name,audible_range,effective_range,rounds,extras,fire_mo alter table reservation add constraint reservation_customer_fk foreign key (customer_id) references customer (id); alter table reservation add constraint reservation_location_fk foreign key (location_id) references location (id); alter table reservation add constraint reservation_product_fk foreign key (product_id) references product (id); + alter table version add constraint version_product_fk foreign key (product_id) references product (id); GO From 8c092433071c475505968fbfbeb96497a6d5f270 Mon Sep 17 00:00:00 2001 From: Christiaan Westerbeek Date: Fri, 5 May 2017 02:53:58 +0200 Subject: [PATCH 181/239] Return if column is generated or not (#100) * Return if column is generated or not Related to https://github.com/strongloop/loopback-datasource-juggler/issues/899 * Tests for generated columns * Fix a few dumb typos * Implement change request in review * Fix the assert.equal statements * assert.equal rules :/ * Fix discovery files --- lib/discovery.js | 2 + test/discover.test.js | 165 +++++++++++++++++++++--------------------- 2 files changed, 84 insertions(+), 83 deletions(-) diff --git a/lib/discovery.js b/lib/discovery.js index cd3352c..7d37234 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -174,6 +174,7 @@ function mixinDiscovery(MsSQL) { ' AS "columnName", data_type AS "dataType",' + ' character_maximum_length AS "dataLength", numeric_precision AS' + ' "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"' + + ' ,COLUMNPROPERTY(object_id(table_schema+\'.\'+table_name), column_name, \'IsIdentity\') AS "generated"' + ' FROM INFORMATION_SCHEMA.columns' + ' WHERE table_schema=\'' + owner + '\'' + (table ? ' AND table_name=\'' + table + '\'' : ''), @@ -183,6 +184,7 @@ function mixinDiscovery(MsSQL) { ' AS "tableName", column_name AS "columnName", data_type AS "dataType",' + ' character_maximum_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS' + ' "dataScale", is_nullable AS "nullable"' + + ' ,COLUMNPROPERTY(object_id(schema_name()+\'.\'+table_name), column_name, \'IsIdentity\') AS "generated"' + ' FROM INFORMATION_SCHEMA.columns' + (table ? ' WHERE table_name=\'' + table + '\'' : ''), 'table_name, ordinal_position', {}); diff --git a/test/discover.test.js b/test/discover.test.js index 9d0c528..3b5f5f9 100644 --- a/test/discover.test.js +++ b/test/discover.test.js @@ -31,20 +31,16 @@ describe('discoverModels', function() { views: true, limit: 3, }, function(err, models) { - if (err) { - console.error(err); - done(err); - } else { - var views = false; - models.forEach(function(m) { - // console.dir(m); - if (m.type === 'view') { - views = true; - } - }); - assert(views, 'Should have views'); - done(null, models); - } + if (err) return done(err); + var views = false; + models.forEach(function(m) { + // console.dir(m); + if (m.type === 'view') { + views = true; + } + }); + assert(views, 'Should have views'); + done(); }); }); }); @@ -55,21 +51,17 @@ describe('discoverModels', function() { views: false, limit: 3, }, function(err, models) { - if (err) { - console.error(err); - done(err); - } else { - var views = false; - models.forEach(function(m) { - // console.dir(m); - if (m.type === 'view') { - views = true; - } - }); - models.should.have.length(3); - assert(!views, 'Should not have views'); - done(null, models); - } + if (err) return done(err); + var views = false; + models.forEach(function(m) { + // console.dir(m); + if (m.type === 'view') { + views = true; + } + }); + models.should.have.length(3); + assert(!views, 'Should not have views'); + done(); }); }); }); @@ -81,20 +73,16 @@ describe('Discover models including other users', function() { all: true, limit: 100, }, function(err, models) { - if (err) { - console.error(err); - done(err); - } else { - var others = false; - models.forEach(function(m) { - // console.dir(m); - if (m.owner !== 'dbo') { - others = true; - } - }); - assert(others, 'Should have tables/views owned by others'); - done(err, models); - } + if (err) return done(err); + var others = false; + models.forEach(function(m) { + // console.dir(m); + if (m.owner !== 'dbo') { + others = true; + } + }); + assert(others, 'Should have tables/views owned by others'); + done(err, models); }); }); }); @@ -103,16 +91,12 @@ describe('Discover model properties', function() { describe('Discover a named model', function() { it('should return an array of columns for product', function(done) { db.discoverModelProperties('product', function(err, models) { - if (err) { - console.error(err); - done(err); - } else { - models.forEach(function(m) { - // console.dir(m); - assert(m.tableName === 'product'); - }); - done(null, models); - } + if (err) return done(err); + models.forEach(function(m) { + // console.dir(m); + assert.equal(m.tableName, 'product'); + }); + done(); }); }); }); @@ -121,16 +105,12 @@ describe('Discover model properties', function() { describe('Discover model primary keys', function() { it('should return an array of primary keys for product', function(done) { db.discoverPrimaryKeys('product', function(err, models) { - if (err) { - console.error(err); - done(err); - } else { - models.forEach(function(m) { - // console.dir(m); - assert(m.tableName === 'product'); - }); - done(null, models); - } + if (err) return done(err); + models.forEach(function(m) { + // console.dir(m); + assert.equal(m.tableName, 'product'); + }); + done(); }); }); @@ -219,30 +199,49 @@ describe('Discover model primary keys', function() { describe('Discover model foreign keys', function() { it('should return an array of foreign keys for inventory', function(done) { db.discoverForeignKeys('inventory', function(err, models) { - if (err) { - console.error(err); - done(err); - } else { - models.forEach(function(m) { - // console.dir(m); - assert(m.fkTableName === 'inventory'); - }); - done(null, models); - } + if (err) return done(err); + models.forEach(function(m) { + // console.dir(m); + assert.equal(m.fkTableName, 'inventory'); + }); + done(); }); }); it('should return an array of foreign keys for dbo.inventory', function(done) { db.discoverForeignKeys('inventory', {owner: 'dbo'}, function(err, models) { - if (err) { - console.error(err); - done(err); - } else { - models.forEach(function(m) { - // console.dir(m); - assert(m.fkTableName === 'inventory'); - }); - done(null, models); - } + if (err) return done(err); + models.forEach(function(m) { + // console.dir(m); + assert.equal(m.fkTableName, 'inventory'); + }); + done(); + }); + }); +}); + +describe('Discover model generated columns', function() { + it('should return an array of columns for product and none of them is generated', function(done) { + db.discoverModelProperties('product', function(err, models) { + if (err) return done(err); + models.forEach(function(m) { + // console.dir(m); + assert.equal(m.tableName, 'product'); + assert(!m.generated, 'product table should not have generated (identity) columns'); + }); + done(); + }); + }); + it('should return an array of columns for testgen and the first is generated', function(done) { + db.discoverModelProperties('testgen', function(err, models) { + if (err) return done(err); + models.forEach(function(m) { + // console.dir(m); + assert.equal(m.tableName, 'testgen'); + if (m.columnName === 'id') { + assert(m.generated, 'testgen.id should be a generated (identity) column'); + } + }); + done(); }); }); }); From 6da5c13fa36b0aae4487a7200e04f023d3bd34d7 Mon Sep 17 00:00:00 2001 From: Sakib Hasan Date: Mon, 15 May 2017 14:04:18 -0400 Subject: [PATCH 182/239] Add docker setup (#148) * Add docker setup * Allow to seed when CI=true only * Update readme on running test * Create database on the fly if database specified --- README.md | 26 +++++++++--- pretest.js | 4 ++ setup.sh | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 setup.sh diff --git a/README.md b/README.md index 956d4d5..cf5a89f 100644 --- a/README.md +++ b/README.md @@ -360,9 +360,25 @@ See [LoopBack types](http://loopback.io/doc/en/lb3/LoopBack-types.html) for det ## Running tests -The tests in this repository are mainly integration tests, meaning you will need -to run them using our preconfigured test server. +### Own instance +If you have a local or remote MSSQL instance and would like to use that to run the test suite, use the following command: +- Linux +```bash +MSSQL_HOST= MSSQL_PORT= MSSQL_USER= MSSQL_PASSWORD= MSSQL_DATABASE= CI=true npm test +``` +- Windows +```bash +SET MSSQL_HOST= SET MSSQL_PORT= SET MSSQL_USER= SET MSSQL_PASSWORD= SET MSSQL_DATABASE= SET CI=true npm test +``` -1. Ask a core developer for instructions on how to set up test server - credentials on your machine -2. `npm test` +### Docker +If you do not have a local MSSQL instance, you can also run the test suite with very minimal requirements. +- Assuming you have [Docker](https://docs.docker.com/engine/installation/) installed, run the following script which would spawn a MSSQL instance on your local: +```bash +source setup.sh +``` +where ``, ``, ``, `` and `` are optional parameters. The default values are `localhost`, `1433`, `sa`, `M55sqlT35t` and `master` respectively. +- Run the test: +```bash +npm test +``` diff --git a/pretest.js b/pretest.js index b3d0d19..f40c799 100644 --- a/pretest.js +++ b/pretest.js @@ -8,6 +8,10 @@ var isWin = (process.platform === 'win32'); var sqlFileDir = path.resolve(__dirname, 'test', 'tables.sql'); var sqlDependencyDir = path.resolve(__dirname, 'node_modules', '.bin', 'sqlcmd'); +if (!process.env.CI) { + return console.log('not seeding DB with test db'); +} + if (!process.env.MSSQL_HOST) process.env.MSSQL_HOST = global.getConfig().host; if (!process.env.MSSQL_PORT) process.env.MSSQL_PORT = global.getConfig().port; if (!process.env.MSSQL_USER) process.env.MSSQL_USER = global.getConfig().user; diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..b71a73d --- /dev/null +++ b/setup.sh @@ -0,0 +1,120 @@ +#!/bin/bash + +### Shell script to spin up a docker container for mssql. + +## color codes +RED='\033[1;31m' +GREEN='\033[1;32m' +YELLOW='\033[1;33m' +CYAN='\033[1;36m' +PLAIN='\033[0m' + +## variables +MSSQL_CONTAINER="mssql_c" +HOST="localhost" +PORT=1433 +USER="sa" +PASSWORD="M55sqlT35t" +DATABASE="master" +if [ "$1" ]; then + HOST=$1 +fi +if [ "$2" ]; then + PORT=$2 +fi +if [ "$3" ]; then + USER=$3 +fi +if [ "$4" ]; then + PASSWORD=$4 +fi +if [ "$5" ]; then + DATABASE=$5 +fi + +## check if docker exists +printf "\n${RED}>> Checking for docker${PLAIN} ${GREEN}...${PLAIN}" +docker -v > /dev/null 2>&1 +DOCKER_EXISTS=$? +if [ "$DOCKER_EXISTS" -ne 0 ]; then + printf "\n\n${CYAN}Status: ${PLAIN}${RED}Docker not found. Terminating setup.${PLAIN}\n\n" + exit 1 +fi +printf "\n${CYAN}Found docker. Moving on with the setup.${PLAIN}\n" + + +## cleaning up previous builds +printf "\n${RED}>> Finding old builds and cleaning up${PLAIN} ${GREEN}...${PLAIN}" +docker rm -f $MSSQL_CONTAINER > /dev/null 2>&1 +printf "\n${CYAN}Clean up complete.${PLAIN}\n" + +## pull latest mssql image +printf "\n${RED}>> Pulling latest mssql image${PLAIN} ${GREEN}...${PLAIN}" +docker pull microsoft/mssql-server-linux:latest > /dev/null 2>&1 +printf "\n${CYAN}Image successfully built.${PLAIN}\n" + +## run the mssql container +printf "\n${RED}>> Starting the mssql container${PLAIN} ${GREEN}...${PLAIN}" +CONTAINER_STATUS=$(docker run --name $MSSQL_CONTAINER -e ACCEPT_EULA=Y -e SA_PASSWORD=$PASSWORD -p $PORT:1433 -d microsoft/mssql-server-linux:latest 2>&1) +if [[ "$CONTAINER_STATUS" == *"Error"* ]]; then + printf "\n\n${CYAN}Status: ${PLAIN}${RED}Error starting container. Terminating setup.${PLAIN}\n\n" + exit 1 +fi +docker cp ./test/tables.sql $MSSQL_CONTAINER:/home/ > /dev/null 2>&1 +printf "\n${CYAN}Container is up and running.${PLAIN}\n" + +## export the schema to the mssql database +printf "\n${RED}>> Exporting schema to database${PLAIN} ${GREEN}...${PLAIN}\n" + +## command to export schema +docker exec $MSSQL_CONTAINER /bin/sh -c "/opt/mssql-tools/bin/sqlcmd -S $HOST -U $USER -P $PASSWORD -i /home/tables.sql" > /dev/null 2>&1 + +## variables needed to health check export schema +OUTPUT=$? +TIMEOUT=120 +TIME_PASSED=0 +WAIT_STRING="." + +printf "\n${GREEN}Waiting for database to respond with updated schema $WAIT_STRING${PLAIN}" +while [ "$OUTPUT" -ne 0 ] && [ "$TIMEOUT" -gt 0 ] + do + docker exec $MSSQL_CONTAINER /bin/sh -c "/opt/mssql-tools/bin/sqlcmd -S $HOST -U $USER -P $PASSWORD -i /home/tables.sql" > /dev/null 2>&1 + OUTPUT=$? + sleep 1s + TIMEOUT=$((TIMEOUT - 1)) + TIME_PASSED=$((TIME_PASSED + 1)) + + if [ "$TIME_PASSED" -eq 5 ]; then + printf "${GREEN}.${PLAIN}" + TIME_PASSED=0 + fi + done + +if [ "$TIMEOUT" -le 0 ]; then + printf "\n\n${CYAN}Status: ${PLAIN}${RED}Failed to export schema. Terminating setup.${PLAIN}\n\n" + exit 1 +fi +printf "\n${CYAN}Successfully exported schema to database.${PLAIN}\n" + +## create the database +printf "\n${RED}>> Create the database${PLAIN} ${GREEN}...${PLAIN}" +docker exec -it $MSSQL_CONTAINER /bin/sh -c "/opt/mssql-tools/bin/sqlcmd -S $HOST -U $USER -P $PASSWORD -Q 'CREATE DATABASE $DATABASE'" +CREATE_DATABASE=$? +if [ "$CREATE_DATABASE" -ne 0 ]; then + printf "\n\n${CYAN}Status: ${PLAIN}${RED}Error creating database: $DATABASE. Terminating setup.${PLAIN}\n\n" + exit 1 +fi +printf "\n${CYAN}Database created.${PLAIN}\n" + +## set env variables for running test +printf "\n${RED}>> Setting env variables to run test${PLAIN} ${GREEN}...${PLAIN}" +export MSSQL_HOST=$HOST +export MSSQL_PORT=$PORT +export MSSQL_USER=$USER +export MSSQL_PASSWORD=$PASSWORD +export MSSQL_DATABASE=$DATABASE +printf "\n${CYAN}Env variables set.${PLAIN}\n" + +printf "\n${CYAN}Status: ${PLAIN}${GREEN}Set up completed successfully.${PLAIN}\n" +printf "\n${CYAN}Instance url: ${YELLOW}mssql://$USER:$PASSWORD@$HOST/$DATABASE${PLAIN}\n" +printf "\n${CYAN}To run the test suite:${PLAIN} ${YELLOW}npm test${PLAIN}\n\n" From acb6cbd3a877309143c00fa0e050dfc81ab54875 Mon Sep 17 00:00:00 2001 From: ssh24 Date: Fri, 16 Jun 2017 12:28:20 -0400 Subject: [PATCH 183/239] Require init on mocha args --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a84019c..c4f405c 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "scripts": { "lint": "eslint .", "pretest": "node pretest.js", - "test": "./node_modules/.bin/mocha --timeout 9999 -R spec", + "test": "./node_modules/.bin/mocha --timeout 9999 --require test/init.js -R spec", "posttest": "npm run lint", "prepublish": "node remove-regenerator.js" }, From c5985cacb89f91a7e7eb088d284bf121ad628f73 Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Mon, 24 Jul 2017 19:47:22 -0400 Subject: [PATCH 184/239] Add CODEOWNER file --- CODEOWNERS | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..e8cad33 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,6 @@ +# Lines starting with '#' are comments. +# Each line is a file pattern followed by one or more owners, +# the last matching pattern has the most precendence. + +# Core team members from IBM +* @kjdelisle @jannyHou @loay @b-admike @ssh24 @virkt25 @dhmlau From b6a4005ddc017152cbd6b755eb7f68b0c7a57559 Mon Sep 17 00:00:00 2001 From: Sakib Hasan Date: Tue, 15 Aug 2017 16:25:44 -0400 Subject: [PATCH 185/239] Create Issue and PR Templates (#167) * create issue template * create pr template --- .github/ISSUE_TEMPLATE.md | 10 +++++++++- .github/PULL_REQUEST_TEMPLATE.md | 9 +++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 269b11d..795176c 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -11,7 +11,15 @@ Immediate support: + +# Link to reproduction sandbox + + # Expected result diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d2b240f..368cb4c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,17 +6,18 @@ -- None +- connect to ### Checklist - [ ] New tests added or existing tests modified to cover all changes From 7eaecf7c517084b3125bdb06b6c2aa6c95177e3a Mon Sep 17 00:00:00 2001 From: Kevin Delisle Date: Tue, 22 Aug 2017 13:14:16 -0400 Subject: [PATCH 186/239] Add stalebot configuration --- .github/stale.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..bebe60a --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,23 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 14 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security + - critical + - p1 + - major +# Label to use when marking an issue as stale +staleLabel: stale +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: > + This issue has been closed due to continued inactivity. Thank you for your understanding. + If you believe this to be in error, please contact one of the code owners, + listed in the `CODEOWNERS` file at the top-level of this repository. From c1541ea1d508ccc7fc36ecb2aaeab51269bff602 Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Wed, 20 Sep 2017 14:29:58 -0400 Subject: [PATCH 187/239] update globalize string --- intl/en/messages.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/intl/en/messages.json b/intl/en/messages.json index f90e5dc..5184e10 100644 --- a/intl/en/messages.json +++ b/intl/en/messages.json @@ -1,6 +1,6 @@ { "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} must be an {{object}}: {0}", - "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} is a required string argument: {0}", "80a32e80cbed65eba2103201a7c94710": "Model not found: {0}", - "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} does not support the regular expression operator" + "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} does not support the regular expression operator", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} is a required string argument: {0}" } From 04ac4859b786b853af87ffffcb3b91fa4a29596c Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Thu, 5 Oct 2017 13:51:38 -0400 Subject: [PATCH 188/239] 3.1.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update globalize string (Diana Lau) * Add stalebot configuration (Kevin Delisle) * Create Issue and PR Templates (#167) (Sakib Hasan) * Add CODEOWNER file (Diana Lau) * Require init on mocha args (ssh24) * Add docker setup (#148) (Sakib Hasan) * Return if column is generated or not (#100) (Christiaan Westerbeek) * Fix discovery of primary keys (#99) (Christiaan Westerbeek) * Remove appveyor service (#145) (Sakib Hasan) * Fix params for isActual function (#143) (Nguyễn Kim Kha) * Enable clean DB seed as pre-test (#142) (Sakib Hasan) * Fix eslint issues & buildPropertyType() signature (Raymond Feng) * Upgrade deps to mssql@4.x (Raymond Feng) * add appveyor for CI (#137) (Ryan Graham) * fix discovery turkish collation (#123) (emrahcetiner) --- CHANGES.md | 34 ++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 25f28ac..0cc9d41 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,37 @@ +2017-10-05, Version 3.1.0 +========================= + + * update globalize string (Diana Lau) + + * Add stalebot configuration (Kevin Delisle) + + * Create Issue and PR Templates (#167) (Sakib Hasan) + + * Add CODEOWNER file (Diana Lau) + + * Require init on mocha args (ssh24) + + * Add docker setup (#148) (Sakib Hasan) + + * Return if column is generated or not (#100) (Christiaan Westerbeek) + + * Fix discovery of primary keys (#99) (Christiaan Westerbeek) + + * Remove appveyor service (#145) (Sakib Hasan) + + * Fix params for isActual function (#143) (Nguyễn Kim Kha) + + * Enable clean DB seed as pre-test (#142) (Sakib Hasan) + + * Fix eslint issues & buildPropertyType() signature (Raymond Feng) + + * Upgrade deps to mssql@4.x (Raymond Feng) + + * add appveyor for CI (#137) (Ryan Graham) + + * fix discovery turkish collation (#123) (emrahcetiner) + + 2017-03-31, Version 3.0.0 ========================= diff --git a/package.json b/package.json index c4f405c..71f070d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "3.0.0", + "version": "3.1.0", "description": "Microsoft SQL Server connector for LoopBack", "engines": { "node": ">=4" From 7eaf2c5d283e00bd10bb765db825578347141002 Mon Sep 17 00:00:00 2001 From: jannyHou Date: Wed, 18 Oct 2017 16:27:34 -0400 Subject: [PATCH 189/239] Fix bigint --- lib/mssql.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/mssql.js b/lib/mssql.js index 821fc36..9659278 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -142,6 +142,8 @@ MsSQL.prototype.executeSQL = function(sql, params, options, callback) { params[i] % 1 !== 0) { // Float number request.input('param' + (i + 1), mssql.Real, params[i]); + } else if (typeof params[i] === 'number' && isBigInt(params[i])) { + request.input('param' + (i + 1), mssql.BigInt, params[i]); } else { request.input('param' + (i + 1), params[i]); } @@ -152,6 +154,12 @@ MsSQL.prototype.executeSQL = function(sql, params, options, callback) { request.query(sql, innerCB); }; +function isBigInt(num) { + if (num > 2147483647 && num <= 9223372036854775807) return true; + if (num < -2147483648 && num >= -9223372036854775808) return true; + return false; +}; + MsSQL.prototype.disconnect = function disconnect(cb) { this.client.close(cb); }; From 15380fe7ed36d3f6835c91e3121a51c3d7828e8c Mon Sep 17 00:00:00 2001 From: Daan Middendorp Date: Thu, 23 Nov 2017 16:54:42 +0100 Subject: [PATCH 190/239] Fix showIndexes function --- lib/migration.js | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/lib/migration.js b/lib/migration.js index b92e978..47983a8 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -39,30 +39,23 @@ function mixinMigration(MsSQL) { }; MsSQL.prototype.showIndexes = function(model, cb) { - // TODO: [rfeng] Implement SHOW INDEXES - /* - var schema = "'" + this.schemaName(model) +"'"; - var table = "'" + this.escapeName(this.table(model)) +"'"; - var sql = "SELECT OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) AS [table_schema]," - + " T.[name] AS [table_name], I.[name] AS [index_name], AC.[name] AS [column_name]," - + " I.[type_desc], I.[is_unique], I.[data_space_id], I.[ignore_dup_key], I.[is_primary_key]," - + " I.[is_unique_constraint], I.[fill_factor], I.[is_padded], I.[is_disabled], I.[is_hypothetical]," - + " I.[allow_row_locks], I.[allow_page_locks], IC.[is_descending_key], IC.[is_included_column]" - + " FROM sys.[tables] AS T" - + " INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]" - + " INNER JOIN sys.[index_columns] IC ON I.[object_id] = IC.[object_id]" - + " INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] AND IC.[column_id] = AC.[column_id]" - + " WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP'" - + " AND OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) = " + schema + " AND T.[name] = " + table - + " ORDER BY T.[name], I.[index_id], IC.[key_ordinal]"; - - this.execute(sql, function (err, fields) { - cb && cb(err, fields); - }); - */ - - process.nextTick(function() { - cb && cb(null, []); + var schema = "'" + this.schema(model) + "'"; + var table = "'" + this.table(model) + "'"; + var sql = 'SELECT OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) AS [table_schema],' + + ' T.[name] AS [table_name], I.[name] AS [index_name], AC.[name] AS [column_name],' + + ' I.[type_desc], I.[is_unique], I.[data_space_id], I.[ignore_dup_key], I.[is_primary_key],' + + ' I.[is_unique_constraint], I.[fill_factor], I.[is_padded], I.[is_disabled], I.[is_hypothetical],' + + ' I.[allow_row_locks], I.[allow_page_locks], IC.[is_descending_key], IC.[is_included_column]' + + ' FROM sys.[tables] AS T' + + ' INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]' + + ' INNER JOIN sys.[index_columns] IC ON I.[object_id] = IC.[object_id]' + + ' INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] AND IC.[column_id] = AC.[column_id]' + + ' WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> \'HEAP\'' + + ' AND OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) = ' + schema + ' AND T.[name] = ' + table + + ' ORDER BY T.[name], I.[index_id], IC.[key_ordinal]'; + + this.execute(sql, function(err, fields) { + cb && cb(err, fields); }); }; From 0c7b24da8e9ee36424d24d474ed238c751aef190 Mon Sep 17 00:00:00 2001 From: Daan Middendorp Date: Fri, 24 Nov 2017 13:33:09 +0100 Subject: [PATCH 191/239] Solved imported unittests for previous commit --- lib/migration.js | 6 +++--- lib/mssql.js | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/migration.js b/lib/migration.js index 47983a8..259cf24 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -42,7 +42,7 @@ function mixinMigration(MsSQL) { var schema = "'" + this.schema(model) + "'"; var table = "'" + this.table(model) + "'"; var sql = 'SELECT OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) AS [table_schema],' + - ' T.[name] AS [table_name], I.[name] AS [index_name], AC.[name] AS [column_name],' + + ' T.[name] AS [Table], I.[name] AS [Key_name], AC.[name] AS [Column_name],' + ' I.[type_desc], I.[is_unique], I.[data_space_id], I.[ignore_dup_key], I.[is_primary_key],' + ' I.[is_unique_constraint], I.[fill_factor], I.[is_padded], I.[is_disabled], I.[is_hypothetical],' + ' I.[allow_row_locks], I.[allow_page_locks], IC.[is_descending_key], IC.[is_included_column]' + @@ -221,12 +221,12 @@ function mixinMigration(MsSQL) { // remove indexes aiNames.forEach(function(indexName) { - if (indexName === idName || indexName === 'PRIMARY') { + if (ai[indexName].info.Column_name === idName || indexName.substr(0, 3) === 'PK_') { return; } if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || m.properties[indexName] && !m.properties[indexName].index) { - sql.push('DROP INDEX ' + self.columnEscaped(model, indexName)); + sql.push('DROP INDEX ' + indexName + ' ON ' + self.tableEscaped(model)); } else { // first: check single (only type and kind) if (m.properties[indexName] && !m.properties[indexName].index) { diff --git a/lib/mssql.js b/lib/mssql.js index 9659278..148ad7f 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -404,6 +404,22 @@ MsSQL.prototype.tableEscaped = function(model) { this.escapeName(this.table(model)); }; +MsSQL.prototype.applySqlChanges = function(model, pendingChanges, cb) { + var self = this; + if (pendingChanges.length) { + var thisQuery = pendingChanges[0].substring(0, 10) !== 'DROP INDEX' ? 'ALTER TABLE ' + self.tableEscaped(model) : ''; + var ranOnce = false; + pendingChanges.forEach(function(change) { + if (ranOnce) { + thisQuery = thisQuery + ' '; + } + thisQuery = thisQuery + ' ' + change; + ranOnce = true; + }); + self.execute(thisQuery, cb); + } +}; + function buildLimit(limit, offset) { if (isNaN(offset)) { offset = 0; From f1699e4d574e182151c674d67f1e776668065895 Mon Sep 17 00:00:00 2001 From: Daan Middendorp Date: Fri, 24 Nov 2017 15:10:30 +0100 Subject: [PATCH 192/239] Fix altering of indexes --- lib/migration.js | 12 ++++++------ lib/mssql.js | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/migration.js b/lib/migration.js index 259cf24..5b26e22 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -243,7 +243,7 @@ function mixinMigration(MsSQL) { }); } if (!orderMatched) { - sql.push('DROP INDEX [' + self.columnEscaped(model, indexName) + ']'); + sql.push('DROP INDEX ' + self.columnEscaped(model, indexName) + ' ON ' + self.tableEscaped(model)); delete ai[indexName]; } } @@ -267,10 +267,10 @@ function mixinMigration(MsSQL) { // kind = i.kind; } if (kind && type) { - sql.push('ADD ' + kind + ' INDEX ' + columnName + - ' (' + columnName + ') ' + type); + sql.push('CREATE ' + kind + ' INDEX ' + columnName + + 'ON ' + self.tableEscaped(model) + ' (' + columnName + ') ' + type); } else { - sql.push('ADD ' + kind + ' INDEX [' + propName + '] ' + + sql.push('CREATE ' + kind + ' INDEX [' + propName + '] ON ' + self.tableEscaped(model) + type + ' ([' + propName + ']) '); } } @@ -290,10 +290,10 @@ function mixinMigration(MsSQL) { kind = i.kind; } if (kind && type) { - sql.push('ADD ' + kind + ' INDEX [' + indexName + '] (' + + sql.push('CREATE ' + kind.toUpperCase() + ' INDEX [' + indexName + '] ON ' + self.tableEscaped(model) + ' (' + i.columns + ') ' + type); } else { - sql.push('ADD ' + kind + ' INDEX [' + indexName + '] (' + + sql.push('CREATE ' + kind.toUpperCase() + ' INDEX [' + indexName + '] ON ' + self.tableEscaped(model) + ' (' + i.columns + ') '); } } diff --git a/lib/mssql.js b/lib/mssql.js index 148ad7f..e325c89 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -407,7 +407,8 @@ MsSQL.prototype.tableEscaped = function(model) { MsSQL.prototype.applySqlChanges = function(model, pendingChanges, cb) { var self = this; if (pendingChanges.length) { - var thisQuery = pendingChanges[0].substring(0, 10) !== 'DROP INDEX' ? 'ALTER TABLE ' + self.tableEscaped(model) : ''; + var alterTable = (pendingChanges[0].substring(0, 10) !== 'DROP' && pendingChanges[0].substring(0, 6) !== 'CREATE'); + var thisQuery = alterTable ? 'ALTER TABLE ' + self.tableEscaped(model) : ''; var ranOnce = false; pendingChanges.forEach(function(change) { if (ranOnce) { From 6c1e3dd2fe95b6ce8d433a07483460b4e21c063b Mon Sep 17 00:00:00 2001 From: Daan Middendorp Date: Fri, 24 Nov 2017 15:11:13 +0100 Subject: [PATCH 193/239] Test update indexes --- test/autoupdate.test.js | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js index 6b481fc..879c109 100644 --- a/test/autoupdate.test.js +++ b/test/autoupdate.test.js @@ -60,6 +60,19 @@ describe('MS SQL server connector', function() { schema: 'dbo', table: 'CUSTOMER_TEST', }, + indexes: { + idEmailIndex: { + keys: { + id: 1, + email: 1, + }, + options: { + unique: true, + }, + columns: 'id,email', + kind: 'unique', + }, + }, }, properties: { id: { @@ -124,8 +137,32 @@ describe('MS SQL server connector', function() { assert.equal(names[1], 'email'); assert.equal(names[2], 'firstName'); assert.equal(names[3], 'lastName'); - // console.log(err, result); - done(err, result); + + var schema = "'dbo'"; + var table = "'CUSTOMER_TEST'"; + var sql = 'SELECT OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) AS [table_schema],' + + ' T.[name] AS [Table], I.[name] AS [Key_name], AC.[name] AS [Column_name],' + + ' I.[type_desc], I.[is_unique], I.[data_space_id], I.[ignore_dup_key], I.[is_primary_key],' + + ' I.[is_unique_constraint], I.[fill_factor], I.[is_padded], I.[is_disabled], I.[is_hypothetical],' + + ' I.[allow_row_locks], I.[allow_page_locks], IC.[is_descending_key], IC.[is_included_column]' + + ' FROM sys.[tables] AS T' + + ' INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]' + + ' INNER JOIN sys.[index_columns] IC ON I.[object_id] = IC.[object_id]' + + ' INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] AND IC.[column_id] = AC.[column_id]' + + ' WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> \'HEAP\'' + + ' AND OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) = ' + schema + ' AND T.[name] = ' + table + + ' ORDER BY T.[name], I.[index_id], IC.[key_ordinal]'; + + ds.connector.execute(sql, function(err, indexes) { + var count = 0; + for (var i = 0; i < indexes.length; i++) { + if (indexes[i].Key_name == 'idEmailIndex') { + count++; + } + } + assert.equal(count, 3); + done(err, result); + }); }); }); }); From 7ca705912bc40b3af1899cbe9cf8e734ceecb801 Mon Sep 17 00:00:00 2001 From: Daan Middendorp Date: Fri, 24 Nov 2017 15:31:10 +0100 Subject: [PATCH 194/239] Solve index naming issue --- lib/migration.js | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/lib/migration.js b/lib/migration.js index 5b26e22..f6b1aba 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -278,24 +278,45 @@ function mixinMigration(MsSQL) { // add multi-column indexes indexNames.forEach(function(indexName) { - var i = m.settings.indexes[indexName]; var found = ai[indexName] && ai[indexName].info; if (!found) { - var type = ''; - var kind = ''; + var tblName = self.tableEscaped(model); + var i = m.settings.indexes[indexName]; + var type = 'ASC'; + var kind = 'NONCLUSTERED'; + var unique = false; if (i.type) { - type = 'USING ' + i.type; + type = i.type; } if (i.kind) { kind = i.kind; } - if (kind && type) { - sql.push('CREATE ' + kind.toUpperCase() + ' INDEX [' + indexName + '] ON ' + self.tableEscaped(model) + ' (' + - i.columns + ') ' + type); - } else { - sql.push('CREATE ' + kind.toUpperCase() + ' INDEX [' + indexName + '] ON ' + self.tableEscaped(model) + ' (' + - i.columns + ') '); + if (i.unique) { + unique = true; } + var splitcolumns = i.columns.split(','); + var columns = []; + var name = ''; + + splitcolumns.forEach(function(elem, ind) { + var trimmed = elem.trim(); + name += trimmed + '_'; + trimmed = '[' + trimmed + '] ' + type; + columns.push(trimmed); + }); + + name += kind + '_' + type + '_idx'; + self._idxNames[model].push(name); + + var cmd = 'CREATE ' + (unique ? 'UNIQUE ' : '') + kind + ' INDEX [' + name + '] ON ' + + tblName + MsSQL.newline; + cmd += '(' + MsSQL.newline; + cmd += columns.join(',' + MsSQL.newline); + cmd += MsSQL.newline + ') WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ' + + 'SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ' + + 'ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);' + + MsSQL.newline; + sql.push(cmd); } }); return sql; From 28462da97c9f640e91db5a35b599305da157a9e5 Mon Sep 17 00:00:00 2001 From: Daan Middendorp Date: Fri, 24 Nov 2017 16:09:12 +0100 Subject: [PATCH 195/239] Fix deletion of indices and test --- lib/migration.js | 2 +- lib/mssql.js | 2 +- test/autoupdate.test.js | 40 ++++++++++++++++++++++++++++++++-------- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/lib/migration.js b/lib/migration.js index f6b1aba..6d7366a 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -221,7 +221,7 @@ function mixinMigration(MsSQL) { // remove indexes aiNames.forEach(function(indexName) { - if (ai[indexName].info.Column_name === idName || indexName.substr(0, 3) === 'PK_') { + if (indexName.substr(0, 3) === 'PK_') { return; } if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || diff --git a/lib/mssql.js b/lib/mssql.js index e325c89..77777ff 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -407,7 +407,7 @@ MsSQL.prototype.tableEscaped = function(model) { MsSQL.prototype.applySqlChanges = function(model, pendingChanges, cb) { var self = this; if (pendingChanges.length) { - var alterTable = (pendingChanges[0].substring(0, 10) !== 'DROP' && pendingChanges[0].substring(0, 6) !== 'CREATE'); + var alterTable = (pendingChanges[0].substring(0, 10) !== 'DROP INDEX' && pendingChanges[0].substring(0, 6) !== 'CREATE'); var thisQuery = alterTable ? 'ALTER TABLE ' + self.tableEscaped(model) : ''; var ranOnce = false; pendingChanges.forEach(function(change) { diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js index 879c109..c57b2b7 100644 --- a/test/autoupdate.test.js +++ b/test/autoupdate.test.js @@ -27,6 +27,19 @@ describe('MS SQL server connector', function() { schema: 'dbo', table: 'CUSTOMER_TEST', }, + indexes: { + idEmailIndex: { + keys: { + id: 1, + email: 1, + }, + options: { + unique: true, + }, + columns: 'id,email', + kind: 'unique', + }, + }, }, properties: { id: { @@ -61,15 +74,15 @@ describe('MS SQL server connector', function() { table: 'CUSTOMER_TEST', }, indexes: { - idEmailIndex: { + idCityIndex: { keys: { id: 1, - email: 1, + city: 1, }, options: { unique: true, }, - columns: 'id,email', + columns: 'id,city', kind: 'unique', }, }, @@ -101,6 +114,11 @@ describe('MS SQL server connector', function() { required: false, length: 40, }, + city: { + type: 'String', + required: false, + length: 40, + }, }, }; @@ -129,7 +147,7 @@ describe('MS SQL server connector', function() { /* eslint-enable camelcase */ ds.autoupdate(function(err, result) { ds.discoverModelProperties('CUSTOMER_TEST', function(err, props) { - assert.equal(props.length, 4); + assert.equal(props.length, 5); var names = props.map(function(p) { return p.columnName; }); @@ -137,6 +155,7 @@ describe('MS SQL server connector', function() { assert.equal(names[1], 'email'); assert.equal(names[2], 'firstName'); assert.equal(names[3], 'lastName'); + assert.equal(names[4], 'city'); var schema = "'dbo'"; var table = "'CUSTOMER_TEST'"; @@ -154,13 +173,18 @@ describe('MS SQL server connector', function() { ' ORDER BY T.[name], I.[index_id], IC.[key_ordinal]'; ds.connector.execute(sql, function(err, indexes) { - var count = 0; + var countIdEmailIndex = 0; + var countIdCityIndex = 0; for (var i = 0; i < indexes.length; i++) { - if (indexes[i].Key_name == 'idEmailIndex') { - count++; + if (indexes[i].Key_name == 'id_email_unique_ASC_idx') { + countIdEmailIndex++; + } + if (indexes[i].Key_name == 'id_city_unique_ASC_idx') { + countIdCityIndex++; } } - assert.equal(count, 3); + assert.equal(countIdEmailIndex, 0); + assert.equal(countIdCityIndex, 3); done(err, result); }); }); From a109a25b604ce8240e9594fb88c91074cb3aad32 Mon Sep 17 00:00:00 2001 From: Daan Middendorp Date: Fri, 24 Nov 2017 16:33:46 +0100 Subject: [PATCH 196/239] Fix single column index altering --- lib/migration.js | 41 ++++++++++++++++++++++++++--------------- test/autoupdate.test.js | 11 +++++++++-- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/lib/migration.js b/lib/migration.js index 6d7366a..d0bca61 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -251,28 +251,39 @@ function mixinMigration(MsSQL) { // add single-column indexes propNames.forEach(function(propName) { - var i = m.properties[propName].index; - if (!i) { - return; - } var found = ai[propName] && ai[propName].info; - var columnName = self.columnEscaped(model, propName); if (!found) { - var type = ''; - var kind = ''; + var tblName = self.tableEscaped(model); + var i = m.properties[propName].index; + if (!i) { + return; + } + var type = 'ASC'; + var kind = 'NONCLUSTERED'; + var unique = false; if (i.type) { - type = 'USING ' + i.type; + type = i.type; } if (i.kind) { - // kind = i.kind; + kind = i.kind; } - if (kind && type) { - sql.push('CREATE ' + kind + ' INDEX ' + columnName + - 'ON ' + self.tableEscaped(model) + ' (' + columnName + ') ' + type); - } else { - sql.push('CREATE ' + kind + ' INDEX [' + propName + '] ON ' + self.tableEscaped(model) + - type + ' ([' + propName + ']) '); + if (i.unique) { + unique = true; + } + var name = propName + '_' + kind + '_' + type + '_idx'; + if (i.name) { + name = i.name; } + self._idxNames[model].push(name); + var cmd = 'CREATE ' + (unique ? 'UNIQUE ' : '') + kind + ' INDEX [' + name + '] ON ' + + tblName + MsSQL.newline; + cmd += '(' + MsSQL.newline; + cmd += ' [' + propName + '] ' + type; + cmd += MsSQL.newline + ') WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,' + + ' SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ' + + 'ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);' + + MsSQL.newline; + sql.push(cmd); } }); diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js index c57b2b7..57ef40a 100644 --- a/test/autoupdate.test.js +++ b/test/autoupdate.test.js @@ -61,6 +61,10 @@ describe('MS SQL server connector', function() { type: 'Number', required: false, }, + firstName: { + type: 'String', + required: false, + }, }, }; @@ -118,6 +122,9 @@ describe('MS SQL server connector', function() { type: 'String', required: false, length: 40, + index: { + unique: true, + }, }, }, }; @@ -129,7 +136,7 @@ describe('MS SQL server connector', function() { assert(!err); ds.discoverModelProperties('CUSTOMER_TEST', function(err, props) { assert(!err); - assert.equal(props.length, 4); + assert.equal(props.length, 5); var names = props.map(function(p) { return p.columnName; }); @@ -184,7 +191,7 @@ describe('MS SQL server connector', function() { } } assert.equal(countIdEmailIndex, 0); - assert.equal(countIdCityIndex, 3); + assert.equal(countIdCityIndex > 0, true); done(err, result); }); }); From cfb0b036370de625af19b2fcb7badf8ffeb28674 Mon Sep 17 00:00:00 2001 From: Daan Middendorp Date: Fri, 24 Nov 2017 16:36:26 +0100 Subject: [PATCH 197/239] Implement test for single column indices --- test/autoupdate.test.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js index 57ef40a..84b3359 100644 --- a/test/autoupdate.test.js +++ b/test/autoupdate.test.js @@ -182,6 +182,8 @@ describe('MS SQL server connector', function() { ds.connector.execute(sql, function(err, indexes) { var countIdEmailIndex = 0; var countIdCityIndex = 0; + var countCityIndex = 0; + var countAgeIndex = 0; for (var i = 0; i < indexes.length; i++) { if (indexes[i].Key_name == 'id_email_unique_ASC_idx') { countIdEmailIndex++; @@ -189,9 +191,17 @@ describe('MS SQL server connector', function() { if (indexes[i].Key_name == 'id_city_unique_ASC_idx') { countIdCityIndex++; } + if (indexes[i].Key_name == 'city_NONCLUSTERED_ASC_idx') { + countCityIndex++; + } + if (indexes[i].Key_name == 'age_NONCLUSTERED_ASC_idx') { + countAgeIndex++; + } } assert.equal(countIdEmailIndex, 0); + assert.equal(countAgeIndex, 0); assert.equal(countIdCityIndex > 0, true); + assert.equal(countCityIndex > 0, true); done(err, result); }); }); From 442dd73dee7cc99a97f9da9613c2efcc35bd26a0 Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Wed, 29 Nov 2017 15:51:37 -0500 Subject: [PATCH 198/239] 3.2.0 * Implement test for single column indices (Daan Middendorp) * Fix single column index altering (Daan Middendorp) * Fix deletion of indices and test (Daan Middendorp) * Solve index naming issue (Daan Middendorp) * Test update indexes (Daan Middendorp) * Fix altering of indexes (Daan Middendorp) * Solved imported unittests for previous commit (Daan Middendorp) * Fix showIndexes function (Daan Middendorp) * Fix bigint (jannyHou) --- CHANGES.md | 22 ++++++++++++++++++++++ package.json | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 0cc9d41..049da2f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,25 @@ +2017-11-29, Version 3.2.0 +========================= + + * Implement test for single column indices (Daan Middendorp) + + * Fix single column index altering (Daan Middendorp) + + * Fix deletion of indices and test (Daan Middendorp) + + * Solve index naming issue (Daan Middendorp) + + * Test update indexes (Daan Middendorp) + + * Fix altering of indexes (Daan Middendorp) + + * Solved imported unittests for previous commit (Daan Middendorp) + + * Fix showIndexes function (Daan Middendorp) + + * Fix bigint (jannyHou) + + 2017-10-05, Version 3.1.0 ========================= diff --git a/package.json b/package.json index 71f070d..8974bb4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "3.1.0", + "version": "3.2.0", "description": "Microsoft SQL Server connector for LoopBack", "engines": { "node": ">=4" From 42e12ea42f4bfd0be47f855f420f2a4cd714ec80 Mon Sep 17 00:00:00 2001 From: "Rafael E. Ajuria" Date: Fri, 23 Feb 2018 14:53:59 -0600 Subject: [PATCH 199/239] Update Queries to Uppercase --- lib/discovery.js | 154 +++++++++++++++++++++++------------------------ 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/lib/discovery.js b/lib/discovery.js index 7d37234..79458dd 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -87,15 +87,15 @@ function mixinDiscovery(MsSQL) { var owner = options.owner || options.schema; if (options.all && !owner) { - sqlTables = this.paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' + - ' FROM INFORMATION_SCHEMA.tables', 'table_schema, table_name', options); + sqlTables = this.paginateSQL('SELECT \'table\' AS "type", TABLE_NAME AS "name", TABLE_SCHEMA AS "owner"' + + ' FROM INFORMATION_SCHEMA.TABLES', 'TABLE_SCHEMA, TABLE_NAME', options); } else if (owner) { - sqlTables = this.paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' + - ' FROM INFORMATION_SCHEMA.tables WHERE table_schema=\'' + owner + '\'', 'table_schema, table_name', options); + sqlTables = this.paginateSQL('SELECT \'table\' AS "type", TABLE_NAME AS "name", TABLE_SCHEMA AS "owner"' + + ' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=\'' + owner + '\'', 'TABLE_SCHEMA, TABLE_NAME', options); } else { - sqlTables = this.paginateSQL('SELECT \'table\' AS "type", table_name AS "name",' + - ' table_schema AS "owner" FROM INFORMATION_SCHEMA.tables WHERE table_schema=schema_name()', - 'table_name', options); + sqlTables = this.paginateSQL('SELECT \'table\' AS "type", TABLE_NAME AS "name",' + + ' TABLE_SCHEMA AS "owner" FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=schema_name()', + 'TABLE_NAME', options); } return sqlTables; }; @@ -111,17 +111,17 @@ function mixinDiscovery(MsSQL) { var owner = options.owner || options.schema; if (options.all && !owner) { - sqlViews = this.paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + - ' table_schema AS "owner" FROM INFORMATION_SCHEMA.views', - 'table_schema, table_name', options); + sqlViews = this.paginateSQL('SELECT \'view\' AS "type", TABLE_NAME AS "name",' + + ' TABLE_SCHEMA AS "owner" FROM INFORMATION_SCHEMA.VIEWS', + 'TABLE_SCHEMA, TABLE_NAME', options); } else if (owner) { - sqlViews = this.paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + - ' table_schema AS "owner" FROM INFORMATION_SCHEMA.views WHERE table_schema=\'' + owner + '\'', - 'table_schema, table_name', options); + sqlViews = this.paginateSQL('SELECT \'view\' AS "type", TABLE_NAME AS "name",' + + ' TABLE_SCHEMA AS "owner" FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA=\'' + owner + '\'', + 'TABLE_SCHEMA, TABLE_NAME', options); } else { - sqlViews = this.paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + - ' schema_name() AS "owner" FROM INFORMATION_SCHEMA.views', - 'table_name', options); + sqlViews = this.paginateSQL('SELECT \'view\' AS "type", TABLE_NAME AS "name",' + + ' schema_name() AS "owner" FROM INFORMATION_SCHEMA.VIEWS', + 'TABLE_NAME', options); } } return sqlViews; @@ -170,24 +170,24 @@ function mixinDiscovery(MsSQL) { MsSQL.prototype.buildQueryColumns = function(owner, table) { var sql = null; if (owner) { - sql = this.paginateSQL('SELECT table_schema AS "owner", table_name AS "tableName", column_name' + - ' AS "columnName", data_type AS "dataType",' + - ' character_maximum_length AS "dataLength", numeric_precision AS' + - ' "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"' + - ' ,COLUMNPROPERTY(object_id(table_schema+\'.\'+table_name), column_name, \'IsIdentity\') AS "generated"' + - ' FROM INFORMATION_SCHEMA.columns' + - ' WHERE table_schema=\'' + owner + '\'' + - (table ? ' AND table_name=\'' + table + '\'' : ''), - 'table_schema, table_name, ordinal_position', {}); + sql = this.paginateSQL('SELECT TABLE_SCHEMA AS "owner", TABLE_NAME AS "tableName", COLUMN_NAME' + + ' AS "columnName", DATA_TYPE AS "dataType",' + + ' CHARACTER_MAXIMUM_LENGTH AS "dataLength", NUMERIC_PRECISION AS' + + ' "dataPrecision", NUMERIC_SCALE AS "dataScale", IS_NULLABLE AS "nullable"' + + ' ,COLUMNPROPERTY(object_id(TABLE_SCHEMA+\'.\'+TABLE_NAME), COLUMN_NAME, \'IsIdentity\') AS "generated"' + + ' FROM INFORMATION_SCHEMA.COLUMNS' + + ' WHERE TABLE_SCHEMA=\'' + owner + '\'' + + (table ? ' AND TABLE_NAME=\'' + table + '\'' : ''), + 'TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION', {}); } else { - sql = this.paginateSQL('SELECT schema_name() AS "owner", table_name' + - ' AS "tableName", column_name AS "columnName", data_type AS "dataType",' + - ' character_maximum_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS' + - ' "dataScale", is_nullable AS "nullable"' + - ' ,COLUMNPROPERTY(object_id(schema_name()+\'.\'+table_name), column_name, \'IsIdentity\') AS "generated"' + - ' FROM INFORMATION_SCHEMA.columns' + - (table ? ' WHERE table_name=\'' + table + '\'' : ''), - 'table_name, ordinal_position', {}); + sql = this.paginateSQL('SELECT schema_name() AS "owner", TABLE_NAME' + + ' AS "tableName", COLUMN_NAME AS "columnName", DATA_TYPE AS "dataType",' + + ' CHARACTER_MAXIMUM_LENGTH AS "dataLength", NUMERIC_PRECISION AS "dataPrecision", NUMERIC_SCALE AS' + + ' "dataScale", IS_NULLABLE AS "nullable"' + + ' ,COLUMNPROPERTY(object_id(schema_name()+\'.\'+TABLE_NAME), COLUMN_NAME, \'IsIdentity\') AS "generated"' + + ' FROM INFORMATION_SCHEMA.COLUMNS' + + (table ? ' WHERE TABLE_NAME=\'' + table + '\'' : ''), + 'TABLE_NAME, ORDINAL_POSITION', {}); } return sql; }; @@ -209,35 +209,35 @@ function mixinDiscovery(MsSQL) { // http://docs.oracle.com/javase/6/docs/api/java/sql/DatabaseMetaData.html#getPrimaryKeys(java.lang.String, java.lang.String, java.lang.String) /* - select tc.table_schema, tc.table_name, kc.column_name + select tc.TABLE_SCHEMA, tc.TABLE_NAME, kc.COLUMN_NAME from INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc - join INFORMATION_SCHEMA.key_column_usage kc - on kc.table_name = tc.table_name and kc.table_schema = tc.table_schema + join INFORMATION_SCHEMA.KEY_COLUMN_USAGE kc + on kc.TABLE_NAME = tc.TABLE_NAME and kc.TABLE_SCHEMA = tc.TABLE_SCHEMA where - tc.constraint_type = 'PRIMARY KEY' - and kc.ordinal_position is not null - order by tc.table_schema, - tc.table_name, - kc.ordinal_position; + tc.CONSTRAINT_TYPE = 'PRIMARY KEY' + and kc.ORDINAL_POSITION is not null + order by tc.TABLE_SCHEMA, + tc.TABLE_NAME, + kc.ORDINAL_POSITION; */ MsSQL.prototype.buildQueryPrimaryKeys = function(owner, table) { - var sql = 'SELECT kc.table_schema AS "owner", ' + - 'kc.table_name AS "tableName", kc.column_name AS "columnName", kc.ordinal_position' + - ' AS "keySeq", kc.constraint_name AS "pkName" FROM' + - ' INFORMATION_SCHEMA.key_column_usage kc' + + var sql = 'SELECT kc.TABLE_SCHEMA AS "owner", ' + + 'kc.TABLE_NAME AS "tableName", kc.COLUMN_NAME AS "columnName", kc.ORDINAL_POSITION' + + ' AS "keySeq", kc.CONSTRAINT_NAME AS "pkName" FROM' + + ' INFORMATION_SCHEMA.KEY_COLUMN_USAGE kc' + ' JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc' + - ' ON kc.table_name = tc.table_name AND kc.constraint_name = tc.constraint_name AND kc.table_schema = tc.table_schema' + - ' WHERE tc.constraint_type=\'PRIMARY KEY\' AND kc.ordinal_position IS NOT NULL'; + ' ON kc.TABLE_NAME = tc.TABLE_NAME AND kc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME AND kc.TABLE_SCHEMA = tc.TABLE_SCHEMA' + + ' WHERE tc.CONSTRAINT_TYPE=\'PRIMARY KEY\' AND kc.ORDINAL_POSITION IS NOT NULL'; if (owner) { - sql += ' AND kc.table_schema=\'' + owner + '\''; + sql += ' AND kc.TABLE_SCHEMA=\'' + owner + '\''; } if (table) { - sql += ' AND kc.table_name=\'' + table + '\''; + sql += ' AND kc.TABLE_NAME=\'' + table + '\''; } - sql += ' ORDER BY kc.table_schema, kc.table_name, kc.ordinal_position'; + sql += ' ORDER BY kc.TABLE_SCHEMA, kc.TABLE_NAME, kc.ORDINAL_POSITION'; return sql; }; @@ -266,35 +266,35 @@ function mixinDiscovery(MsSQL) { */ /* SELECT - tc.constraint_name, tc.table_name, kcu.column_name, - ccu.table_name AS foreign_table_name, - ccu.column_name AS foreign_column_name + tc.CONSTRAINT_NAME, tc.TABLE_NAME, kcu.COLUMN_NAME, + ccu.TABLE_NAME AS foreign_table_name, + ccu.COLUMN_NAME AS foreign_column_name FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc - JOIN INFORMATION_SCHEMA.key_column_usage AS kcu - ON tc.constraint_name = kcu.constraint_name + JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu + ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu - ON ccu.constraint_name = tc.constraint_name - WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name='mytable'; + ON ccu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME + WHERE CONSTRAINT_TYPE = 'FOREIGN KEY' AND tc.TABLE_NAME='mytable'; */ MsSQL.prototype.buildQueryForeignKeys = function(owner, table) { var sql = - 'SELECT tc.table_schema AS "fkOwner", tc.constraint_name AS "fkName", tc.table_name AS "fkTableName",' + - ' kcu.column_name AS "fkColumnName", kcu.ordinal_position AS "keySeq",' + - ' ccu.table_schema AS "pkOwner", \'PK\' AS "pkName", ' + - ' ccu.table_name AS "pkTableName", ccu.column_name AS "pkColumnName"' + + 'SELECT tc.TABLE_SCHEMA AS "fkOwner", tc.CONSTRAINT_NAME AS "fkName", tc.TABLE_NAME AS "fkTableName",' + + ' kcu.COLUMN_NAME AS "fkColumnName", kcu.ORDINAL_POSITION AS "keySeq",' + + ' ccu.TABLE_SCHEMA AS "pkOwner", \'PK\' AS "pkName", ' + + ' ccu.TABLE_NAME AS "pkTableName", ccu.COLUMN_NAME AS "pkColumnName"' + ' FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc' + - ' JOIN INFORMATION_SCHEMA.key_column_usage AS kcu' + - ' ON tc.constraint_schema = kcu.constraint_schema AND tc.constraint_name = kcu.constraint_name' + + ' JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu' + + ' ON tc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA AND tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME' + ' JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu' + - ' ON ccu.constraint_schema = tc.constraint_schema AND ccu.constraint_name = tc.constraint_name' + - ' WHERE tc.constraint_type = \'FOREIGN KEY\''; + ' ON ccu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA AND ccu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME' + + ' WHERE tc.CONSTRAINT_TYPE = \'FOREIGN KEY\''; if (owner) { - sql += ' AND tc.table_schema=\'' + owner + '\''; + sql += ' AND tc.TABLE_SCHEMA=\'' + owner + '\''; } if (table) { - sql += ' AND tc.table_name=\'' + table + '\''; + sql += ' AND tc.TABLE_NAME=\'' + table + '\''; } return sql; }; @@ -314,22 +314,22 @@ function mixinDiscovery(MsSQL) { * @returns {string} */ MsSQL.prototype.buildQueryExportedForeignKeys = function(owner, table) { - var sql = 'SELECT kcu.constraint_name AS "fkName", kcu.table_schema AS "fkOwner", kcu.table_name AS "fkTableName",' + - ' kcu.column_name AS "fkColumnName", kcu.ordinal_position AS "keySeq",' + - ' \'PK\' AS "pkName", ccu.table_schema AS "pkOwner",' + - ' ccu.table_name AS "pkTableName", ccu.column_name AS "pkColumnName"' + + var sql = 'SELECT kcu.CONSTRAINT_NAME AS "fkName", kcu.TABLE_SCHEMA AS "fkOwner", kcu.TABLE_NAME AS "fkTableName",' + + ' kcu.COLUMN_NAME AS "fkColumnName", kcu.ORDINAL_POSITION AS "keySeq",' + + ' \'PK\' AS "pkName", ccu.TABLE_SCHEMA AS "pkOwner",' + + ' ccu.TABLE_NAME AS "pkTableName", ccu.COLUMN_NAME AS "pkColumnName"' + ' FROM' + ' INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu' + - ' JOIN INFORMATION_SCHEMA.key_column_usage kcu' + - ' ON ccu.constraint_schema = kcu.constraint_schema AND ccu.constraint_name = kcu.constraint_name' + - ' WHERE kcu.ordinal_position IS NOT NULL'; + ' JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu' + + ' ON ccu.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA AND ccu.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME' + + ' WHERE kcu.ORDINAL_POSITION IS NOT NULL'; if (owner) { - sql += ' and ccu.table_schema=\'' + owner + '\''; + sql += ' and ccu.TABLE_SCHEMA=\'' + owner + '\''; } if (table) { - sql += ' and ccu.table_name=\'' + table + '\''; + sql += ' and ccu.TABLE_NAME=\'' + table + '\''; } - sql += ' order by kcu.table_schema, kcu.table_name, kcu.ordinal_position'; + sql += ' order by kcu.TABLE_SCHEMA, kcu.TABLE_NAME, kcu.ORDINAL_POSITION'; return sql; }; From ecc53fd518ef8fb6313344f91b852827b6e999ce Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Thu, 26 Apr 2018 16:36:09 -0400 Subject: [PATCH 200/239] chore: update CODEOWNERS --- CODEOWNERS | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index e8cad33..0c736ba 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -2,5 +2,8 @@ # Each line is a file pattern followed by one or more owners, # the last matching pattern has the most precendence. +# Alumni members +# @kjdelisle @loay @ssh24 + # Core team members from IBM -* @kjdelisle @jannyHou @loay @b-admike @ssh24 @virkt25 @dhmlau +* @jannyHou @b-admike @virkt25 @dhmlau From b5332c06a0dc465bfc8ae44a3af13590fa6292c2 Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Fri, 4 May 2018 12:48:08 -0400 Subject: [PATCH 201/239] 3.2.1 * chore: update CODEOWNERS (Diana Lau) * Update Queries to Uppercase (Rafael E. Ajuria) --- CHANGES.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 049da2f..2a75628 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,11 @@ +2018-05-04, Version 3.2.1 +========================= + + * chore: update CODEOWNERS (Diana Lau) + + * Update Queries to Uppercase (Rafael E. Ajuria) + + 2017-11-29, Version 3.2.0 ========================= diff --git a/package.json b/package.json index 8974bb4..bef0972 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "3.2.0", + "version": "3.2.1", "description": "Microsoft SQL Server connector for LoopBack", "engines": { "node": ">=4" From 042c3fcb06b735772fa0cee18d1ff4667222d1de Mon Sep 17 00:00:00 2001 From: candytangnb Date: Fri, 29 Jun 2018 00:03:58 -0400 Subject: [PATCH 202/239] [WebFM] cs/pl/ru translation cs/pl/ru translation check-in by YI TANG (tangyinb@cn.ibm.com) using WebFM tool. --- intl/cs/messages.json | 7 +++++++ intl/pl/messages.json | 7 +++++++ intl/ru/messages.json | 7 +++++++ 3 files changed, 21 insertions(+) create mode 100644 intl/cs/messages.json create mode 100644 intl/pl/messages.json create mode 100644 intl/ru/messages.json diff --git a/intl/cs/messages.json b/intl/cs/messages.json new file mode 100644 index 0000000..31191b8 --- /dev/null +++ b/intl/cs/messages.json @@ -0,0 +1,7 @@ +{ + "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} musí být {{object}}: {0}", + "80a32e80cbed65eba2103201a7c94710": "Model nebyl nalezen: {0}", + "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} nepodporuje operátor regulárního výrazu", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} je povinný řetězcový argument: {0}" +} + diff --git a/intl/pl/messages.json b/intl/pl/messages.json new file mode 100644 index 0000000..f89e2e0 --- /dev/null +++ b/intl/pl/messages.json @@ -0,0 +1,7 @@ +{ + "6ce5c3a3d305e965ff06e2b3e16e1252": "Właściwość {{options}} musi być obiektem {{object}}: {0}", + "80a32e80cbed65eba2103201a7c94710": "Nie znaleziono modelu: {0}", + "9dca910dae94ac66098b3992911e1b23": "Program {{Microsoft SQL Server}} nie obsługuje operatora wyrażenia regularnego", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} jest wymaganym argumentem łańcuchowym: {0}" +} + diff --git a/intl/ru/messages.json b/intl/ru/messages.json new file mode 100644 index 0000000..d418aac --- /dev/null +++ b/intl/ru/messages.json @@ -0,0 +1,7 @@ +{ + "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} должны иметь тип {{object}}: {0}", + "80a32e80cbed65eba2103201a7c94710": "Модель не найдена: {0}", + "9dca910dae94ac66098b3992911e1b23": "{{Microsoft SQL Server}} не поддерживает оператор регулярного выражения", + "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} - это обязательный строковый аргумент: {0}" +} + From 83a542c9c8b3253b792f90babcf670300a8af813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Mon, 9 Jul 2018 13:50:48 +0200 Subject: [PATCH 203/239] Move Mocha config to `test/mocha.opts` --- package.json | 2 +- test/mocha.opts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 test/mocha.opts diff --git a/package.json b/package.json index bef0972..5fe3816 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "scripts": { "lint": "eslint .", "pretest": "node pretest.js", - "test": "./node_modules/.bin/mocha --timeout 9999 --require test/init.js -R spec", + "test": "mocha", "posttest": "npm run lint", "prepublish": "node remove-regenerator.js" }, diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 0000000..a99d99e --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1,3 @@ +--timeout 9999 +--require test/init.js +--reporter spec From 8b39e16e7f9ea94c92430e5fef0ffaf46be6c78e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Mon, 9 Jul 2018 13:51:18 +0200 Subject: [PATCH 204/239] Increase test limit from 9.9 to 30 seconds --- test/mocha.opts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mocha.opts b/test/mocha.opts index a99d99e..9cfbe1c 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,3 +1,3 @@ ---timeout 9999 +--timeout 30000 --require test/init.js --reporter spec From 8b455a10493fa8952e7a0e892f1af01ec779893f Mon Sep 17 00:00:00 2001 From: virkt25 Date: Mon, 9 Jul 2018 20:20:16 -0400 Subject: [PATCH 205/239] chore: update node + dependencies --- .npmrc | 1 + lib/discovery.js | 14 +++++++------- lib/mssql.js | 16 ++++++++-------- package.json | 24 ++++++++++++------------ remove-regenerator.js | 3 ++- test/commontests.js | 18 +++++++++--------- test/mocha.opts | 1 + test/mssql.test.js | 32 +++++++++++++++++--------------- 8 files changed, 57 insertions(+), 52 deletions(-) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/lib/discovery.js b/lib/discovery.js index 79458dd..68c5fdc 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -95,7 +95,7 @@ function mixinDiscovery(MsSQL) { } else { sqlTables = this.paginateSQL('SELECT \'table\' AS "type", TABLE_NAME AS "name",' + ' TABLE_SCHEMA AS "owner" FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=schema_name()', - 'TABLE_NAME', options); + 'TABLE_NAME', options); } return sqlTables; }; @@ -113,15 +113,15 @@ function mixinDiscovery(MsSQL) { if (options.all && !owner) { sqlViews = this.paginateSQL('SELECT \'view\' AS "type", TABLE_NAME AS "name",' + ' TABLE_SCHEMA AS "owner" FROM INFORMATION_SCHEMA.VIEWS', - 'TABLE_SCHEMA, TABLE_NAME', options); + 'TABLE_SCHEMA, TABLE_NAME', options); } else if (owner) { sqlViews = this.paginateSQL('SELECT \'view\' AS "type", TABLE_NAME AS "name",' + ' TABLE_SCHEMA AS "owner" FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA=\'' + owner + '\'', - 'TABLE_SCHEMA, TABLE_NAME', options); + 'TABLE_SCHEMA, TABLE_NAME', options); } else { sqlViews = this.paginateSQL('SELECT \'view\' AS "type", TABLE_NAME AS "name",' + ' schema_name() AS "owner" FROM INFORMATION_SCHEMA.VIEWS', - 'TABLE_NAME', options); + 'TABLE_NAME', options); } } return sqlViews; @@ -178,7 +178,7 @@ function mixinDiscovery(MsSQL) { ' FROM INFORMATION_SCHEMA.COLUMNS' + ' WHERE TABLE_SCHEMA=\'' + owner + '\'' + (table ? ' AND TABLE_NAME=\'' + table + '\'' : ''), - 'TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION', {}); + 'TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION', {}); } else { sql = this.paginateSQL('SELECT schema_name() AS "owner", TABLE_NAME' + ' AS "tableName", COLUMN_NAME AS "columnName", DATA_TYPE AS "dataType",' + @@ -187,7 +187,7 @@ function mixinDiscovery(MsSQL) { ' ,COLUMNPROPERTY(object_id(schema_name()+\'.\'+TABLE_NAME), COLUMN_NAME, \'IsIdentity\') AS "generated"' + ' FROM INFORMATION_SCHEMA.COLUMNS' + (table ? ' WHERE TABLE_NAME=\'' + table + '\'' : ''), - 'TABLE_NAME, ORDINAL_POSITION', {}); + 'TABLE_NAME, ORDINAL_POSITION', {}); } return sql; }; @@ -206,7 +206,7 @@ function mixinDiscovery(MsSQL) { * @param table * @returns {string} */ -// http://docs.oracle.com/javase/6/docs/api/java/sql/DatabaseMetaData.html#getPrimaryKeys(java.lang.String, java.lang.String, java.lang.String) + // http://docs.oracle.com/javase/6/docs/api/java/sql/DatabaseMetaData.html#getPrimaryKeys(java.lang.String, java.lang.String, java.lang.String) /* select tc.TABLE_SCHEMA, tc.TABLE_NAME, kc.COLUMN_NAME diff --git a/lib/mssql.js b/lib/mssql.js index 77777ff..683fff8 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -408,7 +408,7 @@ MsSQL.prototype.applySqlChanges = function(model, pendingChanges, cb) { var self = this; if (pendingChanges.length) { var alterTable = (pendingChanges[0].substring(0, 10) !== 'DROP INDEX' && pendingChanges[0].substring(0, 6) !== 'CREATE'); - var thisQuery = alterTable ? 'ALTER TABLE ' + self.tableEscaped(model) : ''; + var thisQuery = alterTable ? 'ALTER TABLE ' + self.tableEscaped(model) : ''; var ranOnce = false; pendingChanges.forEach(function(change) { if (ranOnce) { @@ -461,8 +461,7 @@ MsSQL.prototype.buildSelect = function(model, filter, options) { var selectStmt = new ParameterizedSQL('SELECT ' + this.buildColumnNames(model, filter, options) + - ' FROM ' + this.tableEscaped(model) - ); + ' FROM ' + this.tableEscaped(model)); if (filter) { if (filter.where) { @@ -472,7 +471,8 @@ MsSQL.prototype.buildSelect = function(model, filter, options) { if (filter.limit || filter.skip || filter.offset) { selectStmt = this.applyPagination( - model, selectStmt, filter); + model, selectStmt, filter + ); } else { if (filter.order) { selectStmt.merge(this.buildOrderBy(model, filter.order)); @@ -506,21 +506,21 @@ MsSQL.prototype.applyPagination = }; MsSQL.prototype.buildExpression = function(columnName, operator, operatorValue, - propertyDefinition) { + propertyDefinition) { switch (operator) { case 'like': return new ParameterizedSQL(columnName + " LIKE ? ESCAPE '\\'", - [operatorValue]); + [operatorValue]); case 'nlike': return new ParameterizedSQL(columnName + " NOT LIKE ? ESCAPE '\\'", - [operatorValue]); + [operatorValue]); case 'regexp': g.warn('{{Microsoft SQL Server}} does not support the regular ' + 'expression operator'); default: // invoke the base implementation of `buildExpression` return this.invokeSuper('buildExpression', columnName, operator, - operatorValue, propertyDefinition); + operatorValue, propertyDefinition); } }; diff --git a/package.json b/package.json index 5fe3816..4174818 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "3.2.1", "description": "Microsoft SQL Server connector for LoopBack", "engines": { - "node": ">=4" + "node": ">=6" }, "keywords": [ "StrongLoop", @@ -16,25 +16,25 @@ ], "main": "index.js", "dependencies": { - "async": "^2.3.0", - "debug": "^2.1.1", - "loopback-connector": "^4.0.0", - "mssql": "^4.0.4", - "strong-globalize": "^2.5.8", + "async": "^2.6.1", + "debug": "^3.1.0", + "loopback-connector": "^4.5.0", + "mssql": "^4.1.0", + "strong-globalize": "^4.1.1", "sqlcmdjs": "^1.5.0" }, "bundledDependencies": [ "mssql" ], "devDependencies": { - "bluebird": "^3.3.3", - "eslint": "^3.19.0", - "eslint-config-loopback": "^8.0.0", + "bluebird": "^3.5.1", + "eslint": "^5.2.0", + "eslint-config-loopback": "^11.0.0", "loopback-datasource-juggler": "^3.0.0", - "mocha": "^3.3.0", + "mocha": "^5.2.0", "rc": "^1.0.0", - "should": "^8.0.2", - "sinon": "^2.1.0" + "should": "^13.2.1", + "sinon": "^6.1.3" }, "scripts": { "lint": "eslint .", diff --git a/remove-regenerator.js b/remove-regenerator.js index f61b5b0..0cc8a85 100644 --- a/remove-regenerator.js +++ b/remove-regenerator.js @@ -11,7 +11,8 @@ var fs = require('fs'); try { var index = require.resolve('babel-runtime/regenerator/index.js'); var runtime = require.resolve( - 'babel-runtime/regenerator/runtime.js'); + 'babel-runtime/regenerator/runtime.js' + ); if (index) { fs.unlink(index, function(err) { if (err) console.error(err); diff --git a/test/commontests.js b/test/commontests.js index a921dd6..871da6e 100644 --- a/test/commontests.js +++ b/test/commontests.js @@ -110,16 +110,16 @@ commonTest.it('should be able to ORDER results', function(test) { function doFilterAndSortReverseTest() { tests += 1; schema.models.Post.all({where: {date: new Date(1000 * 9)}, order: 'title DESC', limit: 3}, - function(err, posts) { - if (err) console.log(err); - test.equal(posts.length, 2, 'Exactly 2 posts returned by query'); - ['Title Z', 'Title C'].forEach(function(t, i) { - if (posts[i]) { - test.equal(posts[i].title, t, 'doFilterAndSortReverseTest'); - } + function(err, posts) { + if (err) console.log(err); + test.equal(posts.length, 2, 'Exactly 2 posts returned by query'); + ['Title Z', 'Title C'].forEach(function(t, i) { + if (posts[i]) { + test.equal(posts[i].title, t, 'doFilterAndSortReverseTest'); + } + }); + finished(); }); - finished(); - }); } function doMultipleSortTest() { diff --git a/test/mocha.opts b/test/mocha.opts index 9cfbe1c..3735350 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,3 +1,4 @@ --timeout 30000 --require test/init.js --reporter spec +--exit diff --git a/test/mssql.test.js b/test/mssql.test.js index 9a521ea..88ecd88 100644 --- a/test/mssql.test.js +++ b/test/mssql.test.js @@ -138,7 +138,7 @@ describe('mssql connector', function() { }); }); - it('should escape number values to defect SQL injection in find', + it('should escape number values to defect SQL injection in find - test 2', function(done) { Post.find({limit: '(SELECT 1+1)'}, function(err, p) { should.exists(err); @@ -163,20 +163,21 @@ describe('mssql connector', function() { connector.execute('DROP TABLE SQLI_TEST;', function(err) { connector.execute('CREATE TABLE SQLI_TEST' + '(V1 VARCHAR(100), V2 VARCHAR(100) )', - function(err) { - if (err) return done(err); - connector.execute('INSERT INTO SQLI_TEST VALUES ( (?), (?) )', - [value1, value2], function(err) { + function(err) { + if (err) return done(err); + connector.execute('INSERT INTO SQLI_TEST VALUES ( (?), (?) )', + [value1, value2], function(err) { + if (err) return done(err); + connector.execute('SELECT * FROM SQLI_TEST', function(err, data) { if (err) return done(err); - connector.execute('SELECT * FROM SQLI_TEST', function(err, data) { - if (err) return done(err); - data.should.be.eql( - [{V1: '(?)', - V2: ', 1 ); INSERT INTO SQLI_TEST VALUES (1, 2); --'}]); - done(); - }); + data.should.be.eql( + [{V1: '(?)', + V2: ', 1 ); INSERT INTO SQLI_TEST VALUES (1, 2); --'}] + ); + done(); }); - }); + }); + }); }); }); @@ -237,7 +238,8 @@ describe('mssql connector', function() { p.should.have.property('rating', 3.5); done(); }); - }); + } + ); }); context('regexp operator', function() { @@ -254,7 +256,7 @@ describe('mssql connector', function() { /* global sinon */ sinon.stub(console, 'warn'); }); - afterEach(function removeSpy() { + afterEach(function removeSpy() { console.warn.restore(); }); after(function deleteTestFixtures(done) { From ca37a4c77d478f13b83e7a06c30fb11e3816efd6 Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Thu, 2 Aug 2018 12:04:25 -0400 Subject: [PATCH 206/239] update to MIT license --- LICENSE.md | 33 +++++++++++++++++++++++++-------- index.js | 6 +++--- lib/discovery.js | 6 +++--- lib/migration.js | 6 +++--- lib/mssql.js | 6 +++--- lib/transaction.js | 6 +++--- package.json | 3 ++- pretest.js | 5 +++++ remove-regenerator.js | 6 +++--- test/autoupdate.test.js | 6 +++--- test/commontests.js | 6 +++--- test/connection.js | 6 +++--- test/discover.test.js | 6 +++--- test/id.test.js | 6 +++--- test/imported.test.js | 6 +++--- test/init.js | 6 +++--- test/mapping.test.js | 6 +++--- test/mssql.test.js | 6 +++--- test/transaction.test.js | 6 +++--- 19 files changed, 80 insertions(+), 57 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index c586309..55807f9 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,8 +1,25 @@ -Licensed Materials - Property of IBM -IBM StrongLoop Software -Copyright IBM Corp. 2016. All Rights Reserved. -US Government Users Restricted Rights - Use, duplication or disclosure -restricted by GSA ADP Schedule Contract with IBM Corp. - -See full text of IBM International Program License Agreement (IPLA) -http://www-03.ibm.com/software/sla/sladb.nsf/pdf/ipla/$file/ipla.pdf +Copyright (c) IBM Corp. 2012,2018. All Rights Reserved. +Node module: loopback-connector-mssql +This project is licensed under the MIT License, full text below. + +-------- + +MIT license + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/index.js b/index.js index f0eb6bd..8361bf6 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ -// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Copyright IBM Corp. 2013,2018. All Rights Reserved. // Node module: loopback-connector-mssql -// US Government Users Restricted Rights - Use, duplication or disclosure -// restricted by GSA ADP Schedule Contract with IBM Corp. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT 'use strict'; var SG = require('strong-globalize'); diff --git a/lib/discovery.js b/lib/discovery.js index 68c5fdc..f7ddff6 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -1,7 +1,7 @@ -// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Copyright IBM Corp. 2014,2018. All Rights Reserved. // Node module: loopback-connector-mssql -// US Government Users Restricted Rights - Use, duplication or disclosure -// restricted by GSA ADP Schedule Contract with IBM Corp. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT 'use strict'; var g = require('strong-globalize')(); diff --git a/lib/migration.js b/lib/migration.js index d0bca61..9e40c46 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -1,7 +1,7 @@ -// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Copyright IBM Corp. 2015,2018. All Rights Reserved. // Node module: loopback-connector-mssql -// US Government Users Restricted Rights - Use, duplication or disclosure -// restricted by GSA ADP Schedule Contract with IBM Corp. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT 'use strict'; var g = require('strong-globalize')(); diff --git a/lib/mssql.js b/lib/mssql.js index 683fff8..f4eba89 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -1,7 +1,7 @@ -// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Copyright IBM Corp. 2013,2018. All Rights Reserved. // Node module: loopback-connector-mssql -// US Government Users Restricted Rights - Use, duplication or disclosure -// restricted by GSA ADP Schedule Contract with IBM Corp. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT 'use strict'; var g = require('strong-globalize')(); diff --git a/lib/transaction.js b/lib/transaction.js index ed2ead3..ef8b5a2 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -1,7 +1,7 @@ -// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Copyright IBM Corp. 2015,2018. All Rights Reserved. // Node module: loopback-connector-mssql -// US Government Users Restricted Rights - Use, duplication or disclosure -// restricted by GSA ADP Schedule Contract with IBM Corp. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT 'use strict'; var debug = require('debug')('loopback:connector:mssql:transaction'); diff --git a/package.json b/package.json index 4174818..1f7a295 100644 --- a/package.json +++ b/package.json @@ -47,5 +47,6 @@ "type": "git", "url": "https://github.com/strongloop/loopback-connector-mssql.git" }, - "license": "SEE LICENSE IN LICENSE.md" + "copyright.owner": "IBM Corp.", + "license": "MIT" } diff --git a/pretest.js b/pretest.js index f40c799..d55527f 100644 --- a/pretest.js +++ b/pretest.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2017,2018. All Rights Reserved. +// Node module: loopback-connector-mssql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + 'use strict'; require('./test/init'); diff --git a/remove-regenerator.js b/remove-regenerator.js index 0cc8a85..c1cb2a4 100644 --- a/remove-regenerator.js +++ b/remove-regenerator.js @@ -1,7 +1,7 @@ -// Copyright IBM Corp. 2016. All Rights Reserved. +// Copyright IBM Corp. 2016,2018. All Rights Reserved. // Node module: loopback-connector-mssql -// US Government Users Restricted Rights - Use, duplication or disclosure -// restricted by GSA ADP Schedule Contract with IBM Corp. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT /** * This script removes regenerator bundled with babel-runtime diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js index 84b3359..ac04099 100644 --- a/test/autoupdate.test.js +++ b/test/autoupdate.test.js @@ -1,7 +1,7 @@ -// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Copyright IBM Corp. 2014,2018. All Rights Reserved. // Node module: loopback-connector-mssql -// US Government Users Restricted Rights - Use, duplication or disclosure -// restricted by GSA ADP Schedule Contract with IBM Corp. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT 'use strict'; require('./init.js'); diff --git a/test/commontests.js b/test/commontests.js index 871da6e..d4ccd71 100644 --- a/test/commontests.js +++ b/test/commontests.js @@ -1,7 +1,7 @@ -// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Copyright IBM Corp. 2013,2018. All Rights Reserved. // Node module: loopback-connector-mssql -// US Government Users Restricted Rights - Use, duplication or disclosure -// restricted by GSA ADP Schedule Contract with IBM Corp. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT 'use strict'; var jdb = require('loopback-datasource-juggler'); diff --git a/test/connection.js b/test/connection.js index afb03b2..9a6229d 100644 --- a/test/connection.js +++ b/test/connection.js @@ -1,7 +1,7 @@ -// Copyright IBM Corp. 2016. All Rights Reserved. +// Copyright IBM Corp. 2016,2018. All Rights Reserved. // Node module: loopback-connector-mssql -// US Government Users Restricted Rights - Use, duplication or disclosure -// restricted by GSA ADP Schedule Contract with IBM Corp. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT /* eslint-env node, mocha */ 'use strict'; diff --git a/test/discover.test.js b/test/discover.test.js index 3b5f5f9..3662ef4 100644 --- a/test/discover.test.js +++ b/test/discover.test.js @@ -1,7 +1,7 @@ -// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Copyright IBM Corp. 2014,2018. All Rights Reserved. // Node module: loopback-connector-mssql -// US Government Users Restricted Rights - Use, duplication or disclosure -// restricted by GSA ADP Schedule Contract with IBM Corp. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT 'use strict'; process.env.NODE_ENV = 'test'; diff --git a/test/id.test.js b/test/id.test.js index 26c700a..a71803c 100644 --- a/test/id.test.js +++ b/test/id.test.js @@ -1,7 +1,7 @@ -// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Copyright IBM Corp. 2015,2018. All Rights Reserved. // Node module: loopback-connector-mssql -// US Government Users Restricted Rights - Use, duplication or disclosure -// restricted by GSA ADP Schedule Contract with IBM Corp. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT 'use strict'; require('./init.js'); diff --git a/test/imported.test.js b/test/imported.test.js index 634db62..6f59402 100644 --- a/test/imported.test.js +++ b/test/imported.test.js @@ -1,7 +1,7 @@ -// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Copyright IBM Corp. 2014,2018. All Rights Reserved. // Node module: loopback-connector-mssql -// US Government Users Restricted Rights - Use, duplication or disclosure -// restricted by GSA ADP Schedule Contract with IBM Corp. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT 'use strict'; describe('mssql imported features', function() { diff --git a/test/init.js b/test/init.js index 5f4ee74..f51e8f2 100644 --- a/test/init.js +++ b/test/init.js @@ -1,7 +1,7 @@ -// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Copyright IBM Corp. 2014,2018. All Rights Reserved. // Node module: loopback-connector-mssql -// US Government Users Restricted Rights - Use, duplication or disclosure -// restricted by GSA ADP Schedule Contract with IBM Corp. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT 'use strict'; module.exports = require('should'); diff --git a/test/mapping.test.js b/test/mapping.test.js index 97dc179..d0e0d5c 100644 --- a/test/mapping.test.js +++ b/test/mapping.test.js @@ -1,7 +1,7 @@ -// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Copyright IBM Corp. 2015,2018. All Rights Reserved. // Node module: loopback-connector-mssql -// US Government Users Restricted Rights - Use, duplication or disclosure -// restricted by GSA ADP Schedule Contract with IBM Corp. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT 'use strict'; var should = require('should'); diff --git a/test/mssql.test.js b/test/mssql.test.js index 88ecd88..861dd90 100644 --- a/test/mssql.test.js +++ b/test/mssql.test.js @@ -1,7 +1,7 @@ -// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Copyright IBM Corp. 2015,2018. All Rights Reserved. // Node module: loopback-connector-mssql -// US Government Users Restricted Rights - Use, duplication or disclosure -// restricted by GSA ADP Schedule Contract with IBM Corp. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT 'use strict'; require('./init'); diff --git a/test/transaction.test.js b/test/transaction.test.js index 5eb721c..c7f8abb 100644 --- a/test/transaction.test.js +++ b/test/transaction.test.js @@ -1,7 +1,7 @@ -// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Copyright IBM Corp. 2015,2018. All Rights Reserved. // Node module: loopback-connector-mssql -// US Government Users Restricted Rights - Use, duplication or disclosure -// restricted by GSA ADP Schedule Contract with IBM Corp. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT 'use strict'; require('./init.js'); From e20349fce0ea1bea047e1e4c57292154e36058d9 Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Tue, 7 Aug 2018 14:24:26 -0400 Subject: [PATCH 207/239] 3.3.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update to MIT license (Diana Lau) * chore: update node + dependencies (virkt25) * Increase test limit from 9.9 to 30 seconds (Miroslav Bajtoš) * Move Mocha config to `test/mocha.opts` (Miroslav Bajtoš) * [WebFM] cs/pl/ru translation (candytangnb) --- CHANGES.md | 14 ++++++++++++++ package.json | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 2a75628..77b0788 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,17 @@ +2018-08-07, Version 3.3.0 +========================= + + * update to MIT license (Diana Lau) + + * chore: update node + dependencies (virkt25) + + * Increase test limit from 9.9 to 30 seconds (Miroslav Bajtoš) + + * Move Mocha config to `test/mocha.opts` (Miroslav Bajtoš) + + * [WebFM] cs/pl/ru translation (candytangnb) + + 2018-05-04, Version 3.2.1 ========================= diff --git a/package.json b/package.json index 1f7a295..a2e45ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "3.2.1", + "version": "3.3.0", "description": "Microsoft SQL Server connector for LoopBack", "engines": { "node": ">=6" From 8041fd59c1f5e1a869b510752750d7deb073b338 Mon Sep 17 00:00:00 2001 From: Nora Date: Fri, 12 Jul 2019 14:06:38 -0400 Subject: [PATCH 208/239] drop support for node.js 6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a2e45ad..9e367e9 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "3.3.0", "description": "Microsoft SQL Server connector for LoopBack", "engines": { - "node": ">=6" + "node": ">=8" }, "keywords": [ "StrongLoop", From 3a9475f1ffb2aeff8a99e19864b96949c30bf3ba Mon Sep 17 00:00:00 2001 From: Nora Date: Fri, 12 Jul 2019 14:39:12 -0400 Subject: [PATCH 209/239] update CODEOWNERS --- CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 0c736ba..7d0b5d5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,7 +3,7 @@ # the last matching pattern has the most precendence. # Alumni members -# @kjdelisle @loay @ssh24 +# @kjdelisle @loay @ssh24 @virkt25 # Core team members from IBM -* @jannyHou @b-admike @virkt25 @dhmlau +* @jannyHou @b-admike @dhmlau @emonddr From e0297c14f243da0edc2bd3c5126229be9c0536c1 Mon Sep 17 00:00:00 2001 From: Nora Date: Fri, 12 Jul 2019 14:16:22 -0400 Subject: [PATCH 210/239] update dependencies --- package.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 9e367e9..101b62c 100644 --- a/package.json +++ b/package.json @@ -16,24 +16,24 @@ ], "main": "index.js", "dependencies": { - "async": "^2.6.1", - "debug": "^3.1.0", - "loopback-connector": "^4.5.0", + "async": "^3.1.0", + "debug": "^4.1.1", + "loopback-connector": "^4.8.0", "mssql": "^4.1.0", - "strong-globalize": "^4.1.1", + "strong-globalize": "^5.0.0", "sqlcmdjs": "^1.5.0" }, "bundledDependencies": [ "mssql" ], "devDependencies": { - "bluebird": "^3.5.1", - "eslint": "^5.2.0", - "eslint-config-loopback": "^11.0.0", + "bluebird": "^3.5.5", + "eslint": "^6.0.1", + "eslint-config-loopback": "^13.1.0", "loopback-datasource-juggler": "^3.0.0", - "mocha": "^5.2.0", - "rc": "^1.0.0", - "should": "^13.2.1", + "mocha": "^6.1.4", + "rc": "^1.2.8", + "should": "^13.2.3", "sinon": "^6.1.3" }, "scripts": { From cfde698abcf31f0d1da9501e77213ddf0ed5a8a3 Mon Sep 17 00:00:00 2001 From: Nora Date: Fri, 12 Jul 2019 14:16:38 -0400 Subject: [PATCH 211/239] fix eslint violations --- index.js | 2 +- lib/discovery.js | 44 ++++----- lib/migration.js | 202 +++++++++++++++++++-------------------- lib/mssql.js | 103 ++++++++++---------- lib/transaction.js | 4 +- pretest.js | 14 +-- remove-regenerator.js | 8 +- test/autoupdate.test.js | 28 +++--- test/commontests.js | 20 ++-- test/connection.js | 38 ++++---- test/discover.test.js | 10 +- test/id.test.js | 28 +++--- test/init.js | 10 +- test/mapping.test.js | 12 +-- test/mssql.test.js | 12 +-- test/transaction.test.js | 12 +-- 16 files changed, 274 insertions(+), 273 deletions(-) diff --git a/index.js b/index.js index 8361bf6..2efb593 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,7 @@ // License text available at https://opensource.org/licenses/MIT 'use strict'; -var SG = require('strong-globalize'); +const SG = require('strong-globalize'); SG.SetRootDir(__dirname); module.exports = require('./lib/mssql.js'); diff --git a/lib/discovery.js b/lib/discovery.js index f7ddff6..29f8205 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -4,20 +4,20 @@ // License text available at https://opensource.org/licenses/MIT 'use strict'; -var g = require('strong-globalize')(); +const g = require('strong-globalize')(); module.exports = mixinDiscovery; function mixinDiscovery(MsSQL) { - var async = require('async'); + const async = require('async'); function paginateSQL2012(sql, orderBy, options) { options = options || {}; - var offset = options.offset || options.skip; + let offset = options.offset || options.skip; if (isNaN(offset)) { offset = 0; } - var limit = options.limit; + let limit = options.limit; if (isNaN(limit)) { limit = -1; } @@ -25,7 +25,7 @@ function mixinDiscovery(MsSQL) { return sql; } - var fetch = ''; + let fetch = ''; if (options.offset || options.skip || options.limit) { fetch = ' OFFSET ' + offset + ' ROWS'; // Offset starts from 0 if (options.limit) { @@ -43,26 +43,26 @@ function mixinDiscovery(MsSQL) { MsSQL.prototype.paginateSQL = function(sql, orderBy, options) { options = options || {}; - var offset = options.offset || options.skip; + let offset = options.offset || options.skip; if (isNaN(offset)) { offset = 0; } - var limit = options.limit; + let limit = options.limit; if (isNaN(limit)) { limit = -1; } if (offset === 0 && limit === -1) { return sql; } - var index = sql.indexOf(' FROM'); - var select = sql.substring(0, index); - var from = sql.substring(index); + const index = sql.indexOf(' FROM'); + const select = sql.substring(0, index); + const from = sql.substring(index); if (orderBy) { orderBy = 'ORDER BY ' + orderBy; } else { orderBy = 'ORDER BY 1'; } - var paginatedSQL = 'SELECT *' + MsSQL.newline + + let paginatedSQL = 'SELECT *' + MsSQL.newline + 'FROM (' + MsSQL.newline + select + ', ROW_NUMBER() OVER' + ' (' + orderBy + ') AS rowNum' + MsSQL.newline + @@ -83,8 +83,8 @@ function mixinDiscovery(MsSQL) { * @returns {string} The sql statement */ MsSQL.prototype.buildQueryTables = function(options) { - var sqlTables = null; - var owner = options.owner || options.schema; + let sqlTables = null; + const owner = options.owner || options.schema; if (options.all && !owner) { sqlTables = this.paginateSQL('SELECT \'table\' AS "type", TABLE_NAME AS "name", TABLE_SCHEMA AS "owner"' + @@ -106,9 +106,9 @@ function mixinDiscovery(MsSQL) { * @returns {string} The sql statement */ MsSQL.prototype.buildQueryViews = function(options) { - var sqlViews = null; + let sqlViews = null; if (options.views) { - var owner = options.owner || options.schema; + const owner = options.owner || options.schema; if (options.all && !owner) { sqlViews = this.paginateSQL('SELECT \'view\' AS "type", TABLE_NAME AS "name",' + @@ -168,7 +168,7 @@ function mixinDiscovery(MsSQL) { * @returns {String} The sql statement */ MsSQL.prototype.buildQueryColumns = function(owner, table) { - var sql = null; + let sql = null; if (owner) { sql = this.paginateSQL('SELECT TABLE_SCHEMA AS "owner", TABLE_NAME AS "tableName", COLUMN_NAME' + ' AS "columnName", DATA_TYPE AS "dataType",' + @@ -223,7 +223,7 @@ function mixinDiscovery(MsSQL) { */ MsSQL.prototype.buildQueryPrimaryKeys = function(owner, table) { - var sql = 'SELECT kc.TABLE_SCHEMA AS "owner", ' + + let sql = 'SELECT kc.TABLE_SCHEMA AS "owner", ' + 'kc.TABLE_NAME AS "tableName", kc.COLUMN_NAME AS "columnName", kc.ORDINAL_POSITION' + ' AS "keySeq", kc.CONSTRAINT_NAME AS "pkName" FROM' + ' INFORMATION_SCHEMA.KEY_COLUMN_USAGE kc' + @@ -279,7 +279,7 @@ function mixinDiscovery(MsSQL) { */ MsSQL.prototype.buildQueryForeignKeys = function(owner, table) { - var sql = + let sql = 'SELECT tc.TABLE_SCHEMA AS "fkOwner", tc.CONSTRAINT_NAME AS "fkName", tc.TABLE_NAME AS "fkTableName",' + ' kcu.COLUMN_NAME AS "fkColumnName", kcu.ORDINAL_POSITION AS "keySeq",' + ' ccu.TABLE_SCHEMA AS "pkOwner", \'PK\' AS "pkName", ' + @@ -314,7 +314,7 @@ function mixinDiscovery(MsSQL) { * @returns {string} */ MsSQL.prototype.buildQueryExportedForeignKeys = function(owner, table) { - var sql = 'SELECT kcu.CONSTRAINT_NAME AS "fkName", kcu.TABLE_SCHEMA AS "fkOwner", kcu.TABLE_NAME AS "fkTableName",' + + let sql = 'SELECT kcu.CONSTRAINT_NAME AS "fkName", kcu.TABLE_SCHEMA AS "fkOwner", kcu.TABLE_NAME AS "fkTableName",' + ' kcu.COLUMN_NAME AS "fkColumnName", kcu.ORDINAL_POSITION AS "keySeq",' + ' \'PK\' AS "pkName", ccu.TABLE_SCHEMA AS "pkOwner",' + ' ccu.TABLE_NAME AS "pkTableName", ccu.COLUMN_NAME AS "pkColumnName"' + @@ -342,9 +342,9 @@ function mixinDiscovery(MsSQL) { // */ MsSQL.prototype.buildPropertyType = function(columnDefinition, options) { - var mysqlType = columnDefinition.dataType; - var dataLength = options.dataLength || columnDefinition.dataLength; - var type = mysqlType.toUpperCase(); + const mysqlType = columnDefinition.dataType; + const dataLength = options.dataLength || columnDefinition.dataLength; + const type = mysqlType.toUpperCase(); switch (type) { case 'BIT': return 'Boolean'; diff --git a/lib/migration.js b/lib/migration.js index 9e40c46..6823f16 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -4,15 +4,15 @@ // License text available at https://opensource.org/licenses/MIT 'use strict'; -var g = require('strong-globalize')(); +const g = require('strong-globalize')(); -var async = require('async'); +const async = require('async'); module.exports = mixinMigration; function mixinMigration(MsSQL) { MsSQL.prototype.showFields = function(model, cb) { - var sql = 'select [COLUMN_NAME] as [Field], ' + + const sql = 'select [COLUMN_NAME] as [Field], ' + ' [IS_NULLABLE] as [Null], [DATA_TYPE] as [Type],' + ' [CHARACTER_MAXIMUM_LENGTH] as [Length],' + ' [NUMERIC_PRECISION] as [Precision], NUMERIC_SCALE as [Scale]' + @@ -39,9 +39,9 @@ function mixinMigration(MsSQL) { }; MsSQL.prototype.showIndexes = function(model, cb) { - var schema = "'" + this.schema(model) + "'"; - var table = "'" + this.table(model) + "'"; - var sql = 'SELECT OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) AS [table_schema],' + + const schema = "'" + this.schema(model) + "'"; + const table = "'" + this.table(model) + "'"; + const sql = 'SELECT OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) AS [table_schema],' + ' T.[name] AS [Table], I.[name] AS [Key_name], AC.[name] AS [Column_name],' + ' I.[type_desc], I.[is_unique], I.[data_space_id], I.[ignore_dup_key], I.[is_primary_key],' + ' I.[is_unique_constraint], I.[fill_factor], I.[is_padded], I.[is_disabled], I.[is_hypothetical],' + @@ -60,8 +60,8 @@ function mixinMigration(MsSQL) { }; MsSQL.prototype.isActual = function(models, cb) { - var ok = false; - var self = this; + let ok = false; + const self = this; if ((!cb) && ('function' === typeof models)) { cb = models; @@ -94,21 +94,21 @@ function mixinMigration(MsSQL) { }; MsSQL.prototype.getColumnsToAdd = function(model, actualFields) { - var self = this; - var m = self._models[model]; - var propNames = Object.keys(m.properties).filter(function(name) { + const self = this; + const m = self._models[model]; + const propNames = Object.keys(m.properties).filter(function(name) { return !!m.properties[name]; }); - var idName = this.idName(model); + const idName = this.idName(model); - var statements = []; - var columnsToAdd = []; - var columnsToAlter = []; + const statements = []; + const columnsToAdd = []; + const columnsToAlter = []; // change/add new fields propNames.forEach(function(propName) { if (propName === idName) return; - var found; + let found; if (actualFields) { actualFields.forEach(function(f) { if (f.Field === propName) { @@ -137,7 +137,7 @@ function mixinMigration(MsSQL) { } function actualize(propName, oldSettings) { - var newSettings = m.properties[propName]; + const newSettings = m.properties[propName]; if (newSettings && changed(newSettings, oldSettings)) { columnsToAlter.push(self.columnEscaped(model, propName) + ' ' + self.propertySettingsSQL(model, propName)); @@ -162,20 +162,20 @@ function mixinMigration(MsSQL) { }; MsSQL.prototype.getColumnsToDrop = function(model, actualFields) { - var self = this; - var m = this._models[model]; - var propNames = Object.keys(m.properties).filter(function(name) { + const self = this; + const m = this._models[model]; + const propNames = Object.keys(m.properties).filter(function(name) { return !!m.properties[name]; }); - var idName = this.idName(model); + const idName = this.idName(model); - var statements = []; - var columnsToDrop = []; + const statements = []; + const columnsToDrop = []; if (actualFields) { // drop columns actualFields.forEach(function(f) { - var notFound = !~propNames.indexOf(f.Field); + const notFound = !~propNames.indexOf(f.Field); if (f.Field === idName) return; if (notFound || !m.properties[f.Field]) { columnsToDrop.push(self.columnEscaped(model, f.Field)); @@ -185,28 +185,28 @@ function mixinMigration(MsSQL) { if (columnsToDrop.length) { statements.push('DROP COLUMN' + columnsToDrop.join(',' + MsSQL.newline)); } - }; + } return statements; }; MsSQL.prototype.addIndexes = function(model, actualIndexes) { - var self = this; - var m = this._models[model]; - var idName = this.idName(model); + const self = this; + const m = this._models[model]; + const idName = this.idName(model); - var indexNames = m.settings.indexes ? Object.keys(m.settings.indexes).filter(function(name) { + const indexNames = m.settings.indexes ? Object.keys(m.settings.indexes).filter(function(name) { return !!m.settings.indexes[name]; }) : []; - var propNames = Object.keys(m.properties).filter(function(name) { + const propNames = Object.keys(m.properties).filter(function(name) { return !!m.properties[name]; }); - var ai = {}; - var sql = []; + const ai = {}; + const sql = []; if (actualIndexes) { actualIndexes.forEach(function(i) { - var name = i.Key_name; + const name = i.Key_name; if (!ai[name]) { ai[name] = { info: i, @@ -217,7 +217,7 @@ function mixinMigration(MsSQL) { }); } - var aiNames = Object.keys(ai); + const aiNames = Object.keys(ai); // remove indexes aiNames.forEach(function(indexName) { @@ -234,7 +234,7 @@ function mixinMigration(MsSQL) { return; } // second: check multiple indexes - var orderMatched = true; + let orderMatched = true; if (indexNames.indexOf(indexName) !== -1) { m.settings.indexes[indexName].columns.split(/,\s*/).forEach(function(columnName, i) { if (ai[indexName].columns[i] !== columnName) { @@ -251,16 +251,16 @@ function mixinMigration(MsSQL) { // add single-column indexes propNames.forEach(function(propName) { - var found = ai[propName] && ai[propName].info; + const found = ai[propName] && ai[propName].info; if (!found) { - var tblName = self.tableEscaped(model); - var i = m.properties[propName].index; + const tblName = self.tableEscaped(model); + const i = m.properties[propName].index; if (!i) { return; } - var type = 'ASC'; - var kind = 'NONCLUSTERED'; - var unique = false; + let type = 'ASC'; + let kind = 'NONCLUSTERED'; + let unique = false; if (i.type) { type = i.type; } @@ -270,12 +270,12 @@ function mixinMigration(MsSQL) { if (i.unique) { unique = true; } - var name = propName + '_' + kind + '_' + type + '_idx'; + let name = propName + '_' + kind + '_' + type + '_idx'; if (i.name) { name = i.name; } self._idxNames[model].push(name); - var cmd = 'CREATE ' + (unique ? 'UNIQUE ' : '') + kind + ' INDEX [' + name + '] ON ' + + let cmd = 'CREATE ' + (unique ? 'UNIQUE ' : '') + kind + ' INDEX [' + name + '] ON ' + tblName + MsSQL.newline; cmd += '(' + MsSQL.newline; cmd += ' [' + propName + '] ' + type; @@ -289,13 +289,13 @@ function mixinMigration(MsSQL) { // add multi-column indexes indexNames.forEach(function(indexName) { - var found = ai[indexName] && ai[indexName].info; + const found = ai[indexName] && ai[indexName].info; if (!found) { - var tblName = self.tableEscaped(model); - var i = m.settings.indexes[indexName]; - var type = 'ASC'; - var kind = 'NONCLUSTERED'; - var unique = false; + const tblName = self.tableEscaped(model); + const i = m.settings.indexes[indexName]; + let type = 'ASC'; + let kind = 'NONCLUSTERED'; + let unique = false; if (i.type) { type = i.type; } @@ -305,12 +305,12 @@ function mixinMigration(MsSQL) { if (i.unique) { unique = true; } - var splitcolumns = i.columns.split(','); - var columns = []; - var name = ''; + const splitcolumns = i.columns.split(','); + const columns = []; + let name = ''; splitcolumns.forEach(function(elem, ind) { - var trimmed = elem.trim(); + let trimmed = elem.trim(); name += trimmed + '_'; trimmed = '[' + trimmed + '] ' + type; columns.push(trimmed); @@ -319,7 +319,7 @@ function mixinMigration(MsSQL) { name += kind + '_' + type + '_idx'; self._idxNames[model].push(name); - var cmd = 'CREATE ' + (unique ? 'UNIQUE ' : '') + kind + ' INDEX [' + name + '] ON ' + + let cmd = 'CREATE ' + (unique ? 'UNIQUE ' : '') + kind + ' INDEX [' + name + '] ON ' + tblName + MsSQL.newline; cmd += '(' + MsSQL.newline; cmd += columns.join(',' + MsSQL.newline); @@ -334,9 +334,9 @@ function mixinMigration(MsSQL) { }; MsSQL.prototype.alterTable = function(model, actualFields, actualIndexes, done, checkOnly) { - var self = this; + const self = this; - var statements = self.getAddModifyColumns(model, actualFields); + let statements = self.getAddModifyColumns(model, actualFields); statements = statements.concat(self.getDropColumns(model, actualFields)); statements = statements.concat(self.addIndexes(model, actualIndexes)); @@ -353,16 +353,16 @@ function mixinMigration(MsSQL) { MsSQL.prototype.propertiesSQL = function(model) { // debugger; - var self = this; - var objModel = this._models[model]; - var modelPKID = this.idName(model); - - var sql = []; - var props = Object.keys(objModel.properties); - for (var i = 0, n = props.length; i < n; i++) { - var prop = props[i]; + const self = this; + const objModel = this._models[model]; + const modelPKID = this.idName(model); + + const sql = []; + const props = Object.keys(objModel.properties); + for (let i = 0, n = props.length; i < n; i++) { + const prop = props[i]; if (prop === modelPKID) { - var idProp = objModel.properties[modelPKID]; + const idProp = objModel.properties[modelPKID]; if (idProp.type === Number) { if (idProp.generated !== false) { sql.push(self.columnEscaped(model, modelPKID) + @@ -385,8 +385,8 @@ function mixinMigration(MsSQL) { } sql.push(self.columnEscaped(model, prop) + ' ' + self.propertySettingsSQL(model, prop)); } - var joinedSql = sql.join(',' + MsSQL.newline + ' '); - var cmd = ''; + let joinedSql = sql.join(',' + MsSQL.newline + ' '); + let cmd = ''; if (modelPKID) { cmd = 'PRIMARY KEY CLUSTERED' + MsSQL.newline + '(' + MsSQL.newline; cmd += ' ' + self.columnEscaped(model, modelPKID) + ' ASC' + MsSQL.newline; @@ -401,11 +401,11 @@ function mixinMigration(MsSQL) { MsSQL.prototype.singleIndexSettingsSQL = function(model, prop, add) { // Recycled from alterTable single indexes above, more or less. - var tblName = this.tableEscaped(model); - var i = this._models[model].properties[prop].index; - var type = 'ASC'; - var kind = 'NONCLUSTERED'; - var unique = false; + const tblName = this.tableEscaped(model); + const i = this._models[model].properties[prop].index; + let type = 'ASC'; + let kind = 'NONCLUSTERED'; + let unique = false; if (i.type) { type = i.type; } @@ -415,12 +415,12 @@ function mixinMigration(MsSQL) { if (i.unique) { unique = true; } - var name = prop + '_' + kind + '_' + type + '_idx'; + let name = prop + '_' + kind + '_' + type + '_idx'; if (i.name) { name = i.name; } this._idxNames[model].push(name); - var cmd = 'CREATE ' + (unique ? 'UNIQUE ' : '') + kind + ' INDEX [' + name + '] ON ' + + let cmd = 'CREATE ' + (unique ? 'UNIQUE ' : '') + kind + ' INDEX [' + name + '] ON ' + tblName + MsSQL.newline; cmd += '(' + MsSQL.newline; cmd += ' [' + prop + '] ' + type; @@ -433,11 +433,11 @@ function mixinMigration(MsSQL) { MsSQL.prototype.indexSettingsSQL = function(model, prop) { // Recycled from alterTable multi-column indexes above, more or less. - var tblName = this.tableEscaped(model); - var i = this._models[model].settings.indexes[prop]; - var type = 'ASC'; - var kind = 'NONCLUSTERED'; - var unique = false; + const tblName = this.tableEscaped(model); + const i = this._models[model].settings.indexes[prop]; + let type = 'ASC'; + let kind = 'NONCLUSTERED'; + let unique = false; if (i.type) { type = i.type; } @@ -447,11 +447,11 @@ function mixinMigration(MsSQL) { if (i.unique) { unique = true; } - var splitcolumns = i.columns.split(','); - var columns = []; - var name = ''; + const splitcolumns = i.columns.split(','); + const columns = []; + let name = ''; splitcolumns.forEach(function(elem, ind) { - var trimmed = elem.trim(); + let trimmed = elem.trim(); name += trimmed + '_'; trimmed = '[' + trimmed + '] ' + type; columns.push(trimmed); @@ -460,7 +460,7 @@ function mixinMigration(MsSQL) { name += kind + '_' + type + '_idx'; this._idxNames[model].push(name); - var cmd = 'CREATE ' + (unique ? 'UNIQUE ' : '') + kind + ' INDEX [' + name + '] ON ' + + let cmd = 'CREATE ' + (unique ? 'UNIQUE ' : '') + kind + ' INDEX [' + name + '] ON ' + tblName + MsSQL.newline; cmd += '(' + MsSQL.newline; cmd += columns.join(',' + MsSQL.newline); @@ -477,13 +477,13 @@ function mixinMigration(MsSQL) { } MsSQL.prototype.propertySettingsSQL = function(model, prop) { - var p = this._models[model].properties[prop]; + const p = this._models[model].properties[prop]; return this.columnDataType(model, prop) + ' ' + (isNullable(p) ? 'NULL' : 'NOT NULL'); }; MsSQL.prototype.automigrate = function(models, cb) { - var self = this; + const self = this; if ((!cb) && ('function' === typeof models)) { cb = models; models = undefined; @@ -512,8 +512,8 @@ function mixinMigration(MsSQL) { }; MsSQL.prototype.dropTable = function(model, cb) { - var tblName = this.tableEscaped(model); - var cmd = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" + + const tblName = this.tableEscaped(model); + let cmd = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" + tblName + "') AND type in (N'U'))"; cmd += MsSQL.newline + 'BEGIN' + MsSQL.newline; cmd += ' DROP TABLE ' + tblName; @@ -522,8 +522,8 @@ function mixinMigration(MsSQL) { }; MsSQL.prototype.createTable = function(model, cb) { - var tblName = this.tableEscaped(model); - var cmd = 'SET ANSI_NULLS ON;' + MsSQL.newline + 'SET QUOTED_IDENTIFIER ON;' + + const tblName = this.tableEscaped(model); + let cmd = 'SET ANSI_NULLS ON;' + MsSQL.newline + 'SET QUOTED_IDENTIFIER ON;' + MsSQL.newline + 'SET ANSI_PADDING ON;' + MsSQL.newline; cmd += "IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" + tblName + "') AND type in (N'U'))" + MsSQL.newline + 'BEGIN' + MsSQL.newline; @@ -535,18 +535,18 @@ function mixinMigration(MsSQL) { }; MsSQL.prototype.createIndexes = function(model) { - var self = this; - var sql = []; + const self = this; + const sql = []; // Declared in model index property indexes. Object.keys(this._models[model].properties).forEach(function(prop) { - var i = self._models[model].properties[prop].index; + const i = self._models[model].properties[prop].index; if (i) { sql.push(self.singleIndexSettingsSQL(model, prop)); } }); // Settings might not have an indexes property. - var dxs = this._models[model].settings.indexes; + const dxs = this._models[model].settings.indexes; if (dxs) { Object.keys(this._models[model].settings.indexes).forEach(function(prop) { sql.push(self.indexSettingsSQL(model, prop)); @@ -557,19 +557,19 @@ function mixinMigration(MsSQL) { }; MsSQL.prototype.columnDataType = function(model, property) { - var columnMetadata = this.columnMetadata(model, property); - var colType = columnMetadata && columnMetadata.dataType; + const columnMetadata = this.columnMetadata(model, property); + let colType = columnMetadata && columnMetadata.dataType; if (colType) { colType = colType.toUpperCase(); } - var prop = this._models[model].properties[property]; + const prop = this._models[model].properties[property]; if (!prop) { return null; } - var colLength = columnMetadata && columnMetadata.dataLength || prop.length; + const colLength = columnMetadata && columnMetadata.dataLength || prop.length; if (colType) { - var dataPrecision = columnMetadata.dataPrecision; - var dataScale = columnMetadata.dataScale; + const dataPrecision = columnMetadata.dataPrecision; + const dataScale = columnMetadata.dataScale; if (dataPrecision && dataScale) { return colType + '(' + dataPrecision + ', ' + dataScale + ')'; } @@ -579,7 +579,7 @@ function mixinMigration(MsSQL) { }; function datatype(p) { - var dt = ''; + let dt = ''; switch (p.type.name) { default: case 'String': diff --git a/lib/mssql.js b/lib/mssql.js index f4eba89..47e8874 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -4,24 +4,24 @@ // License text available at https://opensource.org/licenses/MIT 'use strict'; -var g = require('strong-globalize')(); +const g = require('strong-globalize')(); /*! Module dependencies */ -var mssql = require('mssql'); -var SqlConnector = require('loopback-connector').SqlConnector; -var ParameterizedSQL = SqlConnector.ParameterizedSQL; -var util = require('util'); -var debug = require('debug')('loopback:connector:mssql'); +const mssql = require('mssql'); +const SqlConnector = require('loopback-connector').SqlConnector; +const ParameterizedSQL = SqlConnector.ParameterizedSQL; +const util = require('util'); +const debug = require('debug')('loopback:connector:mssql'); mssql.map.register(Number, mssql.BigInt); -var name = 'mssql'; +const name = 'mssql'; exports.name = name; exports.initialize = function initializeSchema(dataSource, callback) { - var settings = dataSource.settings || {}; + const settings = dataSource.settings || {}; debug('Settings: %j', settings); - var driver = new MsSQL(settings); + const driver = new MsSQL(settings); dataSource.connector = driver; dataSource.connector.dataSource = dataSource; dataSource.connector.tableNameID = dataSource.settings.tableNameID; @@ -63,8 +63,8 @@ MsSQL.newline = '\r\n'; */ function format(sql, params) { if (Array.isArray(params)) { - var count = 0; - var index = -1; + let count = 0; + let index = -1; while (count < params.length) { index = sql.indexOf('(?)'); if (index === -1) { @@ -79,11 +79,11 @@ function format(sql, params) { } MsSQL.prototype.connect = function(callback) { - var self = this; + const self = this; if (self.client) { return process.nextTick(callback); } - var connection = new mssql.ConnectionPool(this.connConfig, function(err) { + const connection = new mssql.ConnectionPool(this.connConfig, function(err) { if (err) { debug('Connection error: ', err); return callback(err); @@ -95,8 +95,8 @@ MsSQL.prototype.connect = function(callback) { }; function parameterizedSQL(sql) { - var count = 0; - var index = -1; + let count = 0; + let index = -1; while (true) { index = sql.indexOf('(?)'); if (index === -1) { @@ -115,14 +115,14 @@ MsSQL.prototype.executeSQL = function(sql, params, options, callback) { // Convert (?) to @paramX sql = parameterizedSQL(sql); - var connection = this.client; + let connection = this.client; - var transaction = options.transaction; + const transaction = options.transaction; if (transaction && transaction.connector === this && transaction.connection) { debug('Execute SQL in a transaction'); connection = transaction.connection; } - var innerCB = function(err, data) { + const innerCB = function(err, data) { debug('Result: %j %j', err, data); if (data) { data = data.recordset; @@ -130,14 +130,14 @@ MsSQL.prototype.executeSQL = function(sql, params, options, callback) { callback && callback(err, data); }; - var request = new mssql.Request(connection); + const request = new mssql.Request(connection); // Allow multiple result sets if (options.multipleResultSets) { request.multiple = true; } if (Array.isArray(params) && params.length > 0) { - for (var i = 0, n = params.length; i < n; i++) { + for (let i = 0, n = params.length; i < n; i++) { if (typeof params[i] === 'number' && params[i] % 1 !== 0) { // Float number @@ -158,7 +158,7 @@ function isBigInt(num) { if (num > 2147483647 && num <= 9223372036854775807) return true; if (num < -2147483648 && num >= -9223372036854775808) return true; return false; -}; +} MsSQL.prototype.disconnect = function disconnect(cb) { this.client.close(cb); @@ -194,8 +194,8 @@ MsSQL.prototype.buildInsertDefaultValues = function(model, data, options) { }; MsSQL.prototype.buildInsertInto = function(model, fields, options) { - var stmt = this.invokeSuper('buildInsertInto', model, fields, options); - var idName = this.idName(model); + const stmt = this.invokeSuper('buildInsertInto', model, fields, options); + const idName = this.idName(model); stmt.sql = idName ? (MsSQL.newline + 'DECLARE @insertedIds TABLE (id ' + this.columnDataType(model, idName) + ')' + @@ -212,9 +212,9 @@ MsSQL.prototype.buildInsertInto = function(model, fields, options) { }; MsSQL.prototype.buildInsert = function(model, data, options) { - var idName = this.idName(model); - var prop = this.getPropertyDefinition(model, idName); - var isIdentity = (prop && prop.type === Number && prop.generated !== false); + const idName = this.idName(model); + const prop = this.getPropertyDefinition(model, idName); + const isIdentity = (prop && prop.type === Number && prop.generated !== false); if (isIdentity && data[idName] == null) { // remove the pkid column if it's in the data, since we're going to insert a // new record, not update an existing one. @@ -223,8 +223,8 @@ MsSQL.prototype.buildInsert = function(model, data, options) { // delete data.id; } - var stmt = this.invokeSuper('buildInsert', model, data, options); - var tblName = this.tableEscaped(model); + const stmt = this.invokeSuper('buildInsert', model, data, options); + const tblName = this.tableEscaped(model); if (isIdentity && data[idName] != null) { stmt.sql = 'SET IDENTITY_INSERT ' + tblName + ' ON;' + MsSQL.newline + @@ -246,43 +246,43 @@ MsSQL.prototype.getInsertedId = function(model, info) { }; MsSQL.prototype.buildDelete = function(model, where, options) { - var stmt = this.invokeSuper('buildDelete', model, where, options); + const stmt = this.invokeSuper('buildDelete', model, where, options); stmt.merge(';SELECT @@ROWCOUNT as count', ''); return stmt; }; MsSQL.prototype.buildReplace = function(model, where, data, options) { - var stmt = this.invokeSuper('buildReplace', model, where, data, options); + const stmt = this.invokeSuper('buildReplace', model, where, data, options); stmt.merge(';SELECT @@ROWCOUNT as count', ''); return stmt; }; MsSQL.prototype.getCountForAffectedRows = function(model, info) { - var affectedCountQueryResult = info && info[0]; + const affectedCountQueryResult = info && info[0]; if (!affectedCountQueryResult) { return undefined; } - var affectedCount = typeof affectedCountQueryResult.count === 'number' ? + const affectedCount = typeof affectedCountQueryResult.count === 'number' ? affectedCountQueryResult.count : undefined; return affectedCount; }; MsSQL.prototype.buildUpdate = function(model, where, data, options) { - var stmt = this.invokeSuper('buildUpdate', model, where, data, options); + const stmt = this.invokeSuper('buildUpdate', model, where, data, options); stmt.merge(';SELECT @@ROWCOUNT as count', ''); return stmt; }; // Convert to ISO8601 format YYYY-MM-DDThh:mm:ss[.mmm] function dateToMsSql(val) { - var dateStr = val.getUTCFullYear() + '-' + + const dateStr = val.getUTCFullYear() + '-' + fillZeros(val.getUTCMonth() + 1) + '-' + fillZeros(val.getUTCDate()) + 'T' + fillZeros(val.getUTCHours()) + ':' + fillZeros(val.getUTCMinutes()) + ':' + fillZeros(val.getUTCSeconds()) + '.'; - var ms = val.getUTCMilliseconds(); + let ms = val.getUTCMilliseconds(); if (ms < 10) { ms = '00' + ms; } else if (ms < 100) { @@ -379,7 +379,7 @@ MsSQL.prototype.fromColumnValue = function(prop, val) { if (val == null) { return val; } - var type = prop && prop.type; + const type = prop && prop.type; if (type === Boolean) { val = !!val; // convert to a boolean type from number } @@ -405,11 +405,12 @@ MsSQL.prototype.tableEscaped = function(model) { }; MsSQL.prototype.applySqlChanges = function(model, pendingChanges, cb) { - var self = this; + const self = this; if (pendingChanges.length) { - var alterTable = (pendingChanges[0].substring(0, 10) !== 'DROP INDEX' && pendingChanges[0].substring(0, 6) !== 'CREATE'); - var thisQuery = alterTable ? 'ALTER TABLE ' + self.tableEscaped(model) : ''; - var ranOnce = false; + const alterTable = (pendingChanges[0].substring(0, 10) !== + 'DROP INDEX' && pendingChanges[0].substring(0, 6) !== 'CREATE'); + let thisQuery = alterTable ? 'ALTER TABLE ' + self.tableEscaped(model) : ''; + let ranOnce = false; pendingChanges.forEach(function(change) { if (ranOnce) { thisQuery = thisQuery + ' '; @@ -425,7 +426,7 @@ function buildLimit(limit, offset) { if (isNaN(offset)) { offset = 0; } - var sql = 'OFFSET ' + offset + ' ROWS'; + let sql = 'OFFSET ' + offset + ' ROWS'; if (limit >= 0) { sql += ' FETCH NEXT ' + limit + ' ROWS ONLY'; } @@ -433,11 +434,11 @@ function buildLimit(limit, offset) { } MsSQL.prototype.buildColumnNames = function(model, filter, options) { - var columnNames = this.invokeSuper('buildColumnNames', model, filter); + let columnNames = this.invokeSuper('buildColumnNames', model, filter); if (filter.limit || filter.offset || filter.skip) { - var orderBy = this.buildOrderBy(model, filter.order); - var orderClause = ''; - var partitionByClause = ''; + const orderBy = this.buildOrderBy(model, filter.order); + let orderClause = ''; + let partitionByClause = ''; if (options && options.partitionBy) { partitionByClause = 'PARTITION BY ' + this.columnEscaped(model, options.partitionBy); } @@ -453,19 +454,19 @@ MsSQL.prototype.buildColumnNames = function(model, filter, options) { MsSQL.prototype.buildSelect = function(model, filter, options) { if (!filter.order) { - var idNames = this.idNames(model); + const idNames = this.idNames(model); if (idNames && idNames.length) { filter.order = idNames; } } - var selectStmt = new ParameterizedSQL('SELECT ' + + let selectStmt = new ParameterizedSQL('SELECT ' + this.buildColumnNames(model, filter, options) + ' FROM ' + this.tableEscaped(model)); if (filter) { if (filter.where) { - var whereStmt = this.buildWhere(model, filter.where); + const whereStmt = this.buildWhere(model, filter.where); selectStmt.merge(whereStmt); } @@ -484,16 +485,16 @@ MsSQL.prototype.buildSelect = function(model, filter, options) { MsSQL.prototype.applyPagination = function(model, stmt, filter) { - var offset = filter.offset || filter.skip || 0; + const offset = filter.offset || filter.skip || 0; if (this.settings.supportsOffsetFetch) { // SQL 2012 or later // http://technet.microsoft.com/en-us/library/gg699618.aspx - var limitClause = buildLimit(filter.limit, filter.offset || filter.skip); + const limitClause = buildLimit(filter.limit, filter.offset || filter.skip); return stmt.merge(limitClause); } else { // SQL 2005/2008 // http://blog.sqlauthority.com/2013/04/14/sql-server-tricks-for-row-offset-and-paging-in-various-versions-of-sql-server/ - var paginatedSQL = 'SELECT * FROM (' + stmt.sql + MsSQL.newline + + let paginatedSQL = 'SELECT * FROM (' + stmt.sql + MsSQL.newline + ') AS S' + MsSQL.newline + ' WHERE S.RowNum > ' + offset; if (filter.limit !== -1) { diff --git a/lib/transaction.js b/lib/transaction.js index ef8b5a2..a4aec22 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -4,7 +4,7 @@ // License text available at https://opensource.org/licenses/MIT 'use strict'; -var debug = require('debug')('loopback:connector:mssql:transaction'); +const debug = require('debug')('loopback:connector:mssql:transaction'); module.exports = mixinTransaction; @@ -20,7 +20,7 @@ function mixinTransaction(MsSQL, mssql) { MsSQL.prototype.beginTransaction = function(isolationLevel, cb) { debug('Begin a transaction with isolation level: %s', isolationLevel); isolationLevel = mssql.ISOLATION_LEVEL[isolationLevel.replace(' ', '_')]; - var transaction = new mssql.Transaction(this.client); + const transaction = new mssql.Transaction(this.client); transaction.begin(isolationLevel, function(err) { cb(err, transaction); }); diff --git a/pretest.js b/pretest.js index d55527f..f973d0b 100644 --- a/pretest.js +++ b/pretest.js @@ -6,12 +6,12 @@ 'use strict'; require('./test/init'); -var exec = require('child_process').exec; -var path = require('path'); +const exec = require('child_process').exec; +const path = require('path'); -var isWin = (process.platform === 'win32'); -var sqlFileDir = path.resolve(__dirname, 'test', 'tables.sql'); -var sqlDependencyDir = path.resolve(__dirname, 'node_modules', '.bin', 'sqlcmd'); +const isWin = (process.platform === 'win32'); +const sqlFileDir = path.resolve(__dirname, 'test', 'tables.sql'); +const sqlDependencyDir = path.resolve(__dirname, 'node_modules', '.bin', 'sqlcmd'); if (!process.env.CI) { return console.log('not seeding DB with test db'); @@ -23,9 +23,9 @@ if (!process.env.MSSQL_USER) process.env.MSSQL_USER = global.getConfig().user; if (!process.env.MSSQL_PASSWORD) process.env.MSSQL_PASSWORD = global.getConfig().password; if (!process.env.MSSQL_DATABASE) process.env.MSSQL_DATABASE = global.getConfig().database; -var catFileCmd = (isWin ? 'type ' : 'cat ') + sqlFileDir; +const catFileCmd = (isWin ? 'type ' : 'cat ') + sqlFileDir; -var sqlcmd = catFileCmd + ' | ' + sqlDependencyDir + ' -s ' + process.env.MSSQL_HOST + ' -o ' + process.env.MSSQL_PORT + +const sqlcmd = catFileCmd + ' | ' + sqlDependencyDir + ' -s ' + process.env.MSSQL_HOST + ' -o ' + process.env.MSSQL_PORT + ' -u ' + process.env.MSSQL_USER + ' -p ' + process.env.MSSQL_PASSWORD + ' -d ' + process.env.MSSQL_DATABASE; exec(sqlcmd, function(err, result) { diff --git a/remove-regenerator.js b/remove-regenerator.js index c1cb2a4..deda59f 100644 --- a/remove-regenerator.js +++ b/remove-regenerator.js @@ -7,10 +7,10 @@ * This script removes regenerator bundled with babel-runtime */ 'use strict'; -var fs = require('fs'); +const fs = require('fs'); try { - var index = require.resolve('babel-runtime/regenerator/index.js'); - var runtime = require.resolve( + const index = require.resolve('babel-runtime/regenerator/index.js'); + const runtime = require.resolve( 'babel-runtime/regenerator/runtime.js' ); if (index) { @@ -18,7 +18,7 @@ try { if (err) console.error(err); if (runtime) fs.unlink(runtime, function(err) { if (err) console.error(err); - }); ; + }); }); } } catch (err) { diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js index ac04099..42298c4 100644 --- a/test/autoupdate.test.js +++ b/test/autoupdate.test.js @@ -5,8 +5,8 @@ 'use strict'; require('./init.js'); -var assert = require('assert'); -var ds; +const assert = require('assert'); +let ds; before(function() { /* global getDataSource */ @@ -18,7 +18,7 @@ describe('MS SQL server connector', function() { this.timeout(30000); /* eslint-disable camelcase */ - var schema_v1 = + const schema_v1 = { name: 'CustomerTest', options: { @@ -68,7 +68,7 @@ describe('MS SQL server connector', function() { }, }; - var schema_v2 = + const schema_v2 = { name: 'CustomerTest', options: { @@ -137,7 +137,7 @@ describe('MS SQL server connector', function() { ds.discoverModelProperties('CUSTOMER_TEST', function(err, props) { assert(!err); assert.equal(props.length, 5); - var names = props.map(function(p) { + const names = props.map(function(p) { return p.columnName; }); assert.equal(props[0].nullable, 'NO'); @@ -155,7 +155,7 @@ describe('MS SQL server connector', function() { ds.autoupdate(function(err, result) { ds.discoverModelProperties('CUSTOMER_TEST', function(err, props) { assert.equal(props.length, 5); - var names = props.map(function(p) { + const names = props.map(function(p) { return p.columnName; }); assert.equal(names[0], 'id'); @@ -164,9 +164,9 @@ describe('MS SQL server connector', function() { assert.equal(names[3], 'lastName'); assert.equal(names[4], 'city'); - var schema = "'dbo'"; - var table = "'CUSTOMER_TEST'"; - var sql = 'SELECT OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) AS [table_schema],' + + const schema = "'dbo'"; + const table = "'CUSTOMER_TEST'"; + const sql = 'SELECT OBJECT_SCHEMA_NAME(T.[object_id],DB_ID()) AS [table_schema],' + ' T.[name] AS [Table], I.[name] AS [Key_name], AC.[name] AS [Column_name],' + ' I.[type_desc], I.[is_unique], I.[data_space_id], I.[ignore_dup_key], I.[is_primary_key],' + ' I.[is_unique_constraint], I.[fill_factor], I.[is_padded], I.[is_disabled], I.[is_hypothetical],' + @@ -180,11 +180,11 @@ describe('MS SQL server connector', function() { ' ORDER BY T.[name], I.[index_id], IC.[key_ordinal]'; ds.connector.execute(sql, function(err, indexes) { - var countIdEmailIndex = 0; - var countIdCityIndex = 0; - var countCityIndex = 0; - var countAgeIndex = 0; - for (var i = 0; i < indexes.length; i++) { + let countIdEmailIndex = 0; + let countIdCityIndex = 0; + let countCityIndex = 0; + let countAgeIndex = 0; + for (let i = 0; i < indexes.length; i++) { if (indexes[i].Key_name == 'id_email_unique_ASC_idx') { countIdEmailIndex++; } diff --git a/test/commontests.js b/test/commontests.js index d4ccd71..c2c31c3 100644 --- a/test/commontests.js +++ b/test/commontests.js @@ -4,13 +4,13 @@ // License text available at https://opensource.org/licenses/MIT 'use strict'; -var jdb = require('loopback-datasource-juggler'); -var commonTest = jdb.test; +const jdb = require('loopback-datasource-juggler'); +const commonTest = jdb.test; require('./init'); /* global getDataSource */ -var schema = getDataSource(); +const schema = getDataSource(); // run the tests exposed by jugglingdb commonTest(module.exports, schema); @@ -27,7 +27,7 @@ commonTest.it('should automigrate', function(test) { }); commonTest.it('should be able to ORDER results', function(test) { - var titles = [ + const titles = [ {title: 'Title A', subject: 'B'}, {title: 'Title Z', subject: 'A'}, {title: 'Title M', subject: 'C'}, @@ -36,7 +36,7 @@ commonTest.it('should be able to ORDER results', function(test) { {title: 'Title C', subject: 'D'}, ]; - var dates = [ + const dates = [ new Date(1000 * 5), new Date(1000 * 9), new Date(1000 * 0), @@ -48,8 +48,8 @@ commonTest.it('should be able to ORDER results', function(test) { titles.forEach(function(t, i) { schema.models.Post.create({title: t.title, subject: t.subject, date: dates[i]}, done); }); - var i = 0; - var tests = 0; + let i = 0; + let tests = 0; function done(err, obj) { if (++i === titles.length) { doFilterAndSortTest(); @@ -150,7 +150,7 @@ commonTest.it('should be able to ORDER results', function(test) { }); } - var fin = 0; + let fin = 0; function finished() { if (++fin === tests) { test.done(); @@ -181,7 +181,7 @@ commonTest.it('should delete a post', function(test) { }, function(err, posts) { test.ifError(err); test.equal(posts.length, 1); - var id = posts[0].id; + const id = posts[0].id; posts[0].destroy(function(err) { test.ifError(err); schema.models.Post.find(id, function(err, post) { @@ -208,7 +208,7 @@ commonTest.it('should delete all posts', function(test) { // custom primary keys not quite working :(, hopefully 1602 will implement that functionality in jugglingdb soon. commonTest.it('should support custom primary key', function(test) { test.expect(3); - var AppliesTo = schema.define('AppliesTo', { + const AppliesTo = schema.define('AppliesTo', { AppliesToID: { type: Number, primaryKey: true, diff --git a/test/connection.js b/test/connection.js index 9a6229d..99ca4af 100644 --- a/test/connection.js +++ b/test/connection.js @@ -6,12 +6,12 @@ /* eslint-env node, mocha */ 'use strict'; require('./init.js'); -var assert = require('assert'); -var DataSource = require('loopback-datasource-juggler').DataSource; -var url = require('url'); -var mssqlConnector = require('../'); +const assert = require('assert'); +const DataSource = require('loopback-datasource-juggler').DataSource; +const url = require('url'); +const mssqlConnector = require('../'); -var config; +let config; before(function() { config = global.getConfig(); @@ -19,13 +19,13 @@ before(function() { describe('testConnection', function() { it('should pass with valid settings', function(done) { - var db = new DataSource(mssqlConnector, config); + const db = new DataSource(mssqlConnector, config); db.ping(done); }); it('ignores all other settings when url is present', function(done) { - var formatedUrl = generateURL(config); - var dbConfig = { + const formatedUrl = generateURL(config); + const dbConfig = { url: formatedUrl, host: 'invalid-hostname', port: 80, @@ -34,13 +34,13 @@ describe('testConnection', function() { password: 'invalid-password', }; - var db = new DataSource(mssqlConnector, dbConfig); + const db = new DataSource(mssqlConnector, dbConfig); db.ping(done); }); }); function generateURL(config) { - var urlObj = { + const urlObj = { protocol: 'mssql', auth: config.user + ':' + config.password, hostname: config.host, @@ -49,25 +49,25 @@ function generateURL(config) { query: {encrypt: true}, slashes: true, }; - var formatedUrl = url.format(urlObj); + const formatedUrl = url.format(urlObj); return formatedUrl; -}; +} describe('lazyConnect', function() { - var getDS = function(myconfig) { - var db = new DataSource(mssqlConnector, myconfig); + const getDS = function(myconfig) { + const db = new DataSource(mssqlConnector, myconfig); return db; }; it('should skip connect phase (lazyConnect = true)', function(done) { - var dsConfig = { + const dsConfig = { host: 'invalid-hostname', port: 80, lazyConnect: true, }; - var ds = getDS(dsConfig); + const ds = getDS(dsConfig); - var errTimeout = setTimeout(function() { + const errTimeout = setTimeout(function() { done(); }, 2000); ds.on('error', function(err) { @@ -77,12 +77,12 @@ describe('lazyConnect', function() { }); it('should report connection error (lazyConnect = false)', function(done) { - var dsConfig = { + const dsConfig = { host: 'invalid-hostname', port: 80, lazyConnect: false, }; - var ds = getDS(dsConfig); + const ds = getDS(dsConfig); ds.on('error', function(err) { err.message.should.containEql('ENOTFOUND'); diff --git a/test/discover.test.js b/test/discover.test.js index 3662ef4..c8e97cb 100644 --- a/test/discover.test.js +++ b/test/discover.test.js @@ -8,10 +8,10 @@ process.env.NODE_ENV = 'test'; require('./init.js'); require('should'); -var assert = require('assert'); +const assert = require('assert'); /* global getDataSource */ -var db = getDataSource(); +const db = getDataSource(); describe('discoverModels', function() { describe('Discover database schemas', function() { @@ -32,7 +32,7 @@ describe('discoverModels', function() { limit: 3, }, function(err, models) { if (err) return done(err); - var views = false; + let views = false; models.forEach(function(m) { // console.dir(m); if (m.type === 'view') { @@ -52,7 +52,7 @@ describe('discoverModels', function() { limit: 3, }, function(err, models) { if (err) return done(err); - var views = false; + let views = false; models.forEach(function(m) { // console.dir(m); if (m.type === 'view') { @@ -74,7 +74,7 @@ describe('Discover models including other users', function() { limit: 100, }, function(err, models) { if (err) return done(err); - var others = false; + let others = false; models.forEach(function(m) { // console.dir(m); if (m.owner !== 'dbo') { diff --git a/test/id.test.js b/test/id.test.js index a71803c..43420ef 100644 --- a/test/id.test.js +++ b/test/id.test.js @@ -5,10 +5,10 @@ 'use strict'; require('./init.js'); -var should = require('should'); -var assert = require('assert'); -var async = require('async'); -var ds; +const should = require('should'); +const assert = require('assert'); +const async = require('async'); +let ds; before(function() { /* global getDataSource */ @@ -17,7 +17,7 @@ before(function() { describe('Manipulating id column', function() { it('should auto generate id', function(done) { - var schema = + const schema = { name: 'WarehouseTest', options: { @@ -39,8 +39,8 @@ describe('Manipulating id column', function() { }, }; - var models = ds.modelBuilder.buildModels(schema); - var Model = models.WarehouseTest; + const models = ds.modelBuilder.buildModels(schema); + const Model = models.WarehouseTest; Model.attachTo(ds); ds.automigrate(function(err) { @@ -66,7 +66,7 @@ describe('Manipulating id column', function() { function(err, results) { assert(!err); results.should.have.lengthOf(3); - for (var i = 0; i < results.length; i++) { + for (let i = 0; i < results.length; i++) { should.equal(results[i].id, i + 1); } callback(); @@ -77,7 +77,7 @@ describe('Manipulating id column', function() { }); it('should use manual id', function(done) { - var schema = + const schema = { name: 'WarehouseTest', options: { @@ -101,8 +101,8 @@ describe('Manipulating id column', function() { }, }; - var models = ds.modelBuilder.buildModels(schema); - var Model = models.WarehouseTest; + const models = ds.modelBuilder.buildModels(schema); + const Model = models.WarehouseTest; Model.attachTo(ds); ds.automigrate(function(err) { @@ -129,7 +129,7 @@ describe('Manipulating id column', function() { }); it('should use bigint id', function(done) { - var schema = + const schema = { name: 'WarehouseTest', options: { @@ -158,8 +158,8 @@ describe('Manipulating id column', function() { }, }; - var models = ds.modelBuilder.buildModels(schema); - var Model = models.WarehouseTest; + const models = ds.modelBuilder.buildModels(schema); + const Model = models.WarehouseTest; Model.attachTo(ds); ds.automigrate(function(err) { diff --git a/test/init.js b/test/init.js index f51e8f2..faf9a71 100644 --- a/test/init.js +++ b/test/init.js @@ -6,9 +6,9 @@ 'use strict'; module.exports = require('should'); -var DataSource = require('loopback-datasource-juggler').DataSource; +const DataSource = require('loopback-datasource-juggler').DataSource; -var config = {}; +let config = {}; try { config = require('rc')('loopback', {test: {mssql: {}}}).test.mssql; } catch (err) { @@ -22,7 +22,7 @@ try { } global.getConfig = function(options) { - var dbConf = { + const dbConf = { host: process.env.MSSQL_HOST || config.host || config.hostname || config.server || 'localhost', port: process.env.MSSQL_PORT || config.port || 1433, database: process.env.MSSQL_DATABASE || config.database || 'test', @@ -36,7 +36,7 @@ global.getConfig = function(options) { }; if (options) { - for (var el in options) { + for (const el in options) { dbConf[el] = options[el]; } } @@ -46,7 +46,7 @@ global.getConfig = function(options) { global.getDataSource = global.getSchema = function(options) { /* global getConfig */ - var db = new DataSource(require('../'), getConfig(options)); + const db = new DataSource(require('../'), getConfig(options)); return db; }; diff --git a/test/mapping.test.js b/test/mapping.test.js index d0e0d5c..f5d62e5 100644 --- a/test/mapping.test.js +++ b/test/mapping.test.js @@ -4,12 +4,12 @@ // License text available at https://opensource.org/licenses/MIT 'use strict'; -var should = require('should'); +const should = require('should'); require('./init'); -var async = require('async'); +const async = require('async'); -var db; +let db; before(function() { /* global getDataSource */ @@ -18,7 +18,7 @@ before(function() { describe('Mapping models', function() { it('should honor the mssql settings for table/column', function(done) { - var schema = { + const schema = { name: 'TestInventory', options: { idInjection: false, @@ -66,8 +66,8 @@ describe('Mapping models', function() { }, }, }; - var models = db.modelBuilder.buildModels(schema); - var Model = models.TestInventory; + const models = db.modelBuilder.buildModels(schema); + const Model = models.TestInventory; Model.attachTo(db); db.automigrate(function(err, data) { diff --git a/test/mssql.test.js b/test/mssql.test.js index 861dd90..f9d7dec 100644 --- a/test/mssql.test.js +++ b/test/mssql.test.js @@ -6,8 +6,8 @@ 'use strict'; require('./init'); -var should = require('should'); -var Post, PostWithUUID, PostWithStringId, db; +const should = require('should'); +let Post, PostWithUUID, PostWithStringId, db; describe('mssql connector', function() { before(function() { @@ -43,7 +43,7 @@ describe('mssql connector', function() { }); }); - var post; + let post; it('should support boolean types with true value', function(done) { Post.create({title: 'T1', content: 'C1', approved: true}, function(err, p) { should.not.exists(err); @@ -156,9 +156,9 @@ describe('mssql connector', function() { it('should avoid SQL injection for parameters containing (?)', function(done) { - var connector = db.connector; - var value1 = '(?)'; - var value2 = ', 1 ); INSERT INTO SQLI_TEST VALUES (1, 2); --'; + const connector = db.connector; + const value1 = '(?)'; + const value2 = ', 1 ); INSERT INTO SQLI_TEST VALUES (1, 2); --'; connector.execute('DROP TABLE SQLI_TEST;', function(err) { connector.execute('CREATE TABLE SQLI_TEST' + diff --git a/test/transaction.test.js b/test/transaction.test.js index c7f8abb..61b57c2 100644 --- a/test/transaction.test.js +++ b/test/transaction.test.js @@ -7,9 +7,9 @@ require('./init.js'); require('should'); -var Transaction = require('loopback-connector').Transaction; +const Transaction = require('loopback-connector').Transaction; -var db, Post; +let db, Post; describe('transactions', function() { before(function(done) { @@ -22,7 +22,7 @@ describe('transactions', function() { db.automigrate('PostTX', done); }); - var currentTx; + let currentTx; // Return an async function to start a transaction and create a post function createPostInTx(post) { return function(done) { @@ -46,7 +46,7 @@ describe('transactions', function() { // records to equal to the count function expectToFindPosts(where, count, inTx) { return function(done) { - var options = {}; + const options = {}; if (inTx) { options.transaction = currentTx; } @@ -60,7 +60,7 @@ describe('transactions', function() { } describe('commit', function() { - var post = {title: 't1', content: 'c1'}; + const post = {title: 't1', content: 'c1'}; before(createPostInTx(post)); // FIXME: [rfeng] SQL server creates LCK_M_S (Shared Lock on the table @@ -79,7 +79,7 @@ describe('transactions', function() { }); describe('rollback', function() { - var post = {title: 't2', content: 'c2'}; + const post = {title: 't2', content: 'c2'}; before(createPostInTx(post)); // FIXME: [rfeng] SQL server creates LCK_M_S (Shared Lock on the table From a8d56609f3050a48f5860d0e27b2c87782fab714 Mon Sep 17 00:00:00 2001 From: Nora Date: Fri, 12 Jul 2019 13:37:05 -0400 Subject: [PATCH 212/239] run shared tests from both v3 and v4 of juggler --- .eslintignore | 1 + deps/juggler-v3/package.json | 8 ++++++++ deps/juggler-v3/test.js | 24 ++++++++++++++++++++++++ deps/juggler-v4/package.json | 8 ++++++++ deps/juggler-v4/test.js | 24 ++++++++++++++++++++++++ package.json | 4 ++-- test/imported.test.js | 14 -------------- test/init.js | 14 ++++++++++++-- 8 files changed, 79 insertions(+), 18 deletions(-) create mode 100644 deps/juggler-v3/package.json create mode 100644 deps/juggler-v3/test.js create mode 100644 deps/juggler-v4/package.json create mode 100644 deps/juggler-v4/test.js delete mode 100644 test/imported.test.js diff --git a/.eslintignore b/.eslintignore index e69de29..7cd53fd 100644 --- a/.eslintignore +++ b/.eslintignore @@ -0,0 +1 @@ +**/node_modules/ diff --git a/deps/juggler-v3/package.json b/deps/juggler-v3/package.json new file mode 100644 index 0000000..a662daa --- /dev/null +++ b/deps/juggler-v3/package.json @@ -0,0 +1,8 @@ +{ + "name": "juggler-v3", + "version": "3.0.0", + "dependencies": { + "loopback-datasource-juggler":"3.x", + "should": "^13.2.3" + } +} diff --git a/deps/juggler-v3/test.js b/deps/juggler-v3/test.js new file mode 100644 index 0000000..778e621 --- /dev/null +++ b/deps/juggler-v3/test.js @@ -0,0 +1,24 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: loopback-connector-mssql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +'use strict'; + +const juggler = require('loopback-datasource-juggler'); +const name = require('./package.json').name; + +require('../../test/init'); + +describe(name, function() { + before(function() { + return global.resetDataSourceClass(juggler.DataSource); + }); + + after(function() { + return global.resetDataSourceClass(); + }); + + require('loopback-datasource-juggler/test/common.batch.js'); + require('loopback-datasource-juggler/test/include.test.js'); +}); diff --git a/deps/juggler-v4/package.json b/deps/juggler-v4/package.json new file mode 100644 index 0000000..e1d258f --- /dev/null +++ b/deps/juggler-v4/package.json @@ -0,0 +1,8 @@ +{ + "name": "juggler-v4", + "version": "4.0.0", + "dependencies": { + "loopback-datasource-juggler":"4.x", + "should": "^13.2.3" + } +} diff --git a/deps/juggler-v4/test.js b/deps/juggler-v4/test.js new file mode 100644 index 0000000..778e621 --- /dev/null +++ b/deps/juggler-v4/test.js @@ -0,0 +1,24 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: loopback-connector-mssql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +'use strict'; + +const juggler = require('loopback-datasource-juggler'); +const name = require('./package.json').name; + +require('../../test/init'); + +describe(name, function() { + before(function() { + return global.resetDataSourceClass(juggler.DataSource); + }); + + after(function() { + return global.resetDataSourceClass(); + }); + + require('loopback-datasource-juggler/test/common.batch.js'); + require('loopback-datasource-juggler/test/include.test.js'); +}); diff --git a/package.json b/package.json index 101b62c..7b0c410 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "bluebird": "^3.5.5", "eslint": "^6.0.1", "eslint-config-loopback": "^13.1.0", - "loopback-datasource-juggler": "^3.0.0", + "loopback-datasource-juggler": "^3.0.0 || ^4.0.0", "mocha": "^6.1.4", "rc": "^1.2.8", "should": "^13.2.3", @@ -39,7 +39,7 @@ "scripts": { "lint": "eslint .", "pretest": "node pretest.js", - "test": "mocha", + "test": "mocha test/*.test.js node_modules/juggler-v3/test.js node_modules/juggler-v4/test.js", "posttest": "npm run lint", "prepublish": "node remove-regenerator.js" }, diff --git a/test/imported.test.js b/test/imported.test.js deleted file mode 100644 index 6f59402..0000000 --- a/test/imported.test.js +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright IBM Corp. 2014,2018. All Rights Reserved. -// Node module: loopback-connector-mssql -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -'use strict'; -describe('mssql imported features', function() { - before(function() { - require('./init.js'); - }); - - require('loopback-datasource-juggler/test/common.batch.js'); - require('loopback-datasource-juggler/test/include.test.js'); -}); diff --git a/test/init.js b/test/init.js index faf9a71..b7d543e 100644 --- a/test/init.js +++ b/test/init.js @@ -6,7 +6,8 @@ 'use strict'; module.exports = require('should'); -const DataSource = require('loopback-datasource-juggler').DataSource; +const juggler = require('loopback-datasource-juggler'); +let DataSource = juggler.DataSource; let config = {}; try { @@ -44,12 +45,21 @@ global.getConfig = function(options) { return dbConf; }; +let db; + global.getDataSource = global.getSchema = function(options) { /* global getConfig */ - const db = new DataSource(require('../'), getConfig(options)); + db = new DataSource(require('../'), getConfig(options)); return db; }; +global.resetDataSourceClass = function(ctor) { + DataSource = ctor || juggler.DataSource; + const promise = db ? db.disconnect() : Promise.resolve(); + db = undefined; + return promise; +}; + global.connectorCapabilities = { ilike: false, nilike: false, From 75a9891ca0fc499bd3d2ab4c3631ce0b12881a81 Mon Sep 17 00:00:00 2001 From: Nora Date: Mon, 15 Jul 2019 15:07:17 -0400 Subject: [PATCH 213/239] add juggler-v3 and v4 to dev dependencies --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 7b0c410..e4d6523 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "bluebird": "^3.5.5", "eslint": "^6.0.1", "eslint-config-loopback": "^13.1.0", + "juggler-v3": "file:./deps/juggler-v3", + "juggler-v4": "file:./deps/juggler-v4", "loopback-datasource-juggler": "^3.0.0 || ^4.0.0", "mocha": "^6.1.4", "rc": "^1.2.8", From a24260ae57f978b2ad19d45dfbc068bbc94246d1 Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Thu, 19 Sep 2019 10:09:35 -0400 Subject: [PATCH 214/239] 3.4.0 * add juggler-v3 and v4 to dev dependencies (Nora) * run shared tests from both v3 and v4 of juggler (Nora) * fix eslint violations (Nora) * update dependencies (Nora) * update CODEOWNERS (Nora) * drop support for node.js 6 (Nora) --- CHANGES.md | 16 ++++++++++++++++ package.json | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 77b0788..62ae5c1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,19 @@ +2019-09-19, Version 3.4.0 +========================= + + * add juggler-v3 and v4 to dev dependencies (Nora) + + * run shared tests from both v3 and v4 of juggler (Nora) + + * fix eslint violations (Nora) + + * update dependencies (Nora) + + * update CODEOWNERS (Nora) + + * drop support for node.js 6 (Nora) + + 2018-08-07, Version 3.3.0 ========================= diff --git a/package.json b/package.json index e4d6523..4079aa8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "3.3.0", + "version": "3.4.0", "description": "Microsoft SQL Server connector for LoopBack", "engines": { "node": ">=8" From b54826df2c56e7d637b34c6bf3699547afa182fa Mon Sep 17 00:00:00 2001 From: Joost de Bruijn Date: Mon, 22 Jul 2019 13:57:34 +0200 Subject: [PATCH 215/239] update mssql 5.x and mark sqlcmdjs devDependency --- package.json | 8 ++++---- test/init.js | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 4079aa8..7484151 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,8 @@ "async": "^3.1.0", "debug": "^4.1.1", "loopback-connector": "^4.8.0", - "mssql": "^4.1.0", - "strong-globalize": "^5.0.0", - "sqlcmdjs": "^1.5.0" + "mssql": "^5.1.0", + "strong-globalize": "^5.0.0" }, "bundledDependencies": [ "mssql" @@ -36,7 +35,8 @@ "mocha": "^6.1.4", "rc": "^1.2.8", "should": "^13.2.3", - "sinon": "^6.1.3" + "sinon": "^6.1.3", + "sqlcmdjs": "^1.5.0" }, "scripts": { "lint": "eslint .", diff --git a/test/init.js b/test/init.js index b7d543e..a1e9bc1 100644 --- a/test/init.js +++ b/test/init.js @@ -42,6 +42,10 @@ global.getConfig = function(options) { } } + if (typeof dbConf.port === 'string') { + dbConf.port = parseInt(dbConf.port); + } + return dbConf; }; From fd7c13ee50aa5961d12801f92509b9154a90578b Mon Sep 17 00:00:00 2001 From: Joost de Bruijn Date: Tue, 5 Nov 2019 20:28:28 +0100 Subject: [PATCH 216/239] fix linter errors --- lib/mssql.js | 4 ++-- remove-regenerator.js | 2 +- test/mssql.test.js | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 47e8874..cdad976 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -205,7 +205,7 @@ MsSQL.prototype.buildInsertInto = function(model, fields, options) { stmt.merge( 'OUTPUT INSERTED.' + this.columnEscaped(model, idName) + - ' into @insertedIds' + ' into @insertedIds', ); } return stmt; @@ -472,7 +472,7 @@ MsSQL.prototype.buildSelect = function(model, filter, options) { if (filter.limit || filter.skip || filter.offset) { selectStmt = this.applyPagination( - model, selectStmt, filter + model, selectStmt, filter, ); } else { if (filter.order) { diff --git a/remove-regenerator.js b/remove-regenerator.js index deda59f..d4981f8 100644 --- a/remove-regenerator.js +++ b/remove-regenerator.js @@ -11,7 +11,7 @@ const fs = require('fs'); try { const index = require.resolve('babel-runtime/regenerator/index.js'); const runtime = require.resolve( - 'babel-runtime/regenerator/runtime.js' + 'babel-runtime/regenerator/runtime.js', ); if (index) { fs.unlink(index, function(err) { diff --git a/test/mssql.test.js b/test/mssql.test.js index f9d7dec..bbb1931 100644 --- a/test/mssql.test.js +++ b/test/mssql.test.js @@ -172,7 +172,7 @@ describe('mssql connector', function() { if (err) return done(err); data.should.be.eql( [{V1: '(?)', - V2: ', 1 ); INSERT INTO SQLI_TEST VALUES (1, 2); --'}] + V2: ', 1 ); INSERT INTO SQLI_TEST VALUES (1, 2); --'}], ); done(); }); @@ -238,7 +238,7 @@ describe('mssql connector', function() { p.should.have.property('rating', 3.5); done(); }); - } + }, ); }); @@ -291,7 +291,7 @@ describe('mssql connector', function() { console.warn.calledOnce.should.be.ok; should.exist(err); done(); - } + }, ); }); }); From 7707e07dc8a4f111efcba35fbe5a7bfd41dcdf41 Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Wed, 6 Nov 2019 22:39:35 -0500 Subject: [PATCH 217/239] 3.5.0 * fix linter errors (Joost de Bruijn) * update mssql 5.x and mark sqlcmdjs devDependency (Joost de Bruijn) --- CHANGES.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 62ae5c1..7b267d2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,11 @@ +2019-11-07, Version 3.5.0 +========================= + + * fix linter errors (Joost de Bruijn) + + * update mssql 5.x and mark sqlcmdjs devDependency (Joost de Bruijn) + + 2019-09-19, Version 3.4.0 ========================= diff --git a/package.json b/package.json index 7484151..659c92e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "3.4.0", + "version": "3.5.0", "description": "Microsoft SQL Server connector for LoopBack", "engines": { "node": ">=8" From 4f2c23b894ee368cda99d682ba4e9353075e5436 Mon Sep 17 00:00:00 2001 From: Joost de Bruijn Date: Thu, 7 Nov 2019 13:54:15 +0100 Subject: [PATCH 218/239] update mssql to 6.x --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 659c92e..e0153d1 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "async": "^3.1.0", "debug": "^4.1.1", "loopback-connector": "^4.8.0", - "mssql": "^5.1.0", + "mssql": "^6.0.0", "strong-globalize": "^5.0.0" }, "bundledDependencies": [ From 0035dcb836fd64bfb03ef09fcae1f655e3c336fd Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Thu, 14 Nov 2019 15:39:43 -0500 Subject: [PATCH 219/239] 3.6.0 * update mssql to 6.x (Joost de Bruijn) --- CHANGES.md | 8 +++++++- package.json | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7b267d2..4a7c7f9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,10 @@ -2019-11-07, Version 3.5.0 +2019-11-14, Version 3.6.0 +========================= + + * update mssql to 6.x (Joost de Bruijn) + + +2019-11-06, Version 3.5.0 ========================= * fix linter errors (Joost de Bruijn) diff --git a/package.json b/package.json index e0153d1..a65e854 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "3.5.0", + "version": "3.6.0", "description": "Microsoft SQL Server connector for LoopBack", "engines": { "node": ">=8" From 72d3a08eab2713fb746f3726e66be525d6bf0515 Mon Sep 17 00:00:00 2001 From: Nora Date: Fri, 15 Nov 2019 11:54:49 -0500 Subject: [PATCH 220/239] chore: improve issue and PR templates --- .github/ISSUE_TEMPLATE.md | 37 ----------------- .github/ISSUE_TEMPLATE/Bug_report.md | 50 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/Feature_request.md | 25 ++++++++++++ .github/ISSUE_TEMPLATE/Question.md | 27 ++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 11 +++++ .github/PULL_REQUEST_TEMPLATE.md | 29 +++++-------- 6 files changed, 124 insertions(+), 55 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/ISSUE_TEMPLATE/Bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/Feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/Question.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 795176c..0000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,37 +0,0 @@ - - -# Description/Steps to reproduce - - - -# Link to reproduction sandbox - - - -# Expected result - - - -# Additional information - - diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md new file mode 100644 index 0000000..6bc732a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -0,0 +1,50 @@ +--- +name: Bug report +about: Create a report to help us improve +labels: bug + +--- + + + +## Steps to reproduce + + + +## Current Behavior + + + +## Expected Behavior + + + +## Link to reproduction sandbox + + + +## Additional information + + + +## Related Issues + + + +_See [Reporting Issues](http://loopback.io/doc/en/contrib/Reporting-issues.html) for more tips on writing good issues_ diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md new file mode 100644 index 0000000..1fd76ba --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -0,0 +1,25 @@ +--- +name: Feature request +about: Suggest an idea for this project +labels: feature + +--- + +## Suggestion + + + +## Use Cases + + + +## Examples + + + +## Acceptance criteria + +TBD - will be filled by the team. diff --git a/.github/ISSUE_TEMPLATE/Question.md b/.github/ISSUE_TEMPLATE/Question.md new file mode 100644 index 0000000..eb25195 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Question.md @@ -0,0 +1,27 @@ +--- +name: Question +about: The issue tracker is not for questions. Please use Stack Overflow or other resources for help. +labels: question + +--- + + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..9204746 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Report a security vulnerability + url: https://loopback.io/doc/en/contrib/Reporting-issues.html#security-issues + about: Do not report security vulnerabilities using GitHub issues. Please send an email to `reachsl@us.ibm.com` instead. + - name: Get help on StackOverflow + url: https://stackoverflow.com/tags/loopbackjs + about: Please ask and answer questions on StackOverflow. + - name: Join our mailing list + url: https://groups.google.com/forum/#!forum/loopbackjs + about: You can also post your question to our mailing list. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 368cb4c..781e40b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,25 +1,18 @@ -### Description - - -#### Related issues - +Include references to all related GitHub issues and other pull requests, for example: -- connect to +Fixes #123 +Implements #254 +See also #23 +--> -### Checklist +## Checklist - +👉 [Read and sign the CLA (Contributor License Agreement)](https://cla.strongloop.com/agreements/strongloop/loopback-connector-mssql) 👈 +- [ ] `npm test` passes on your machine - [ ] New tests added or existing tests modified to cover all changes -- [ ] Code conforms with the [style - guide](http://loopback.io/doc/en/contrib/style-guide.html) +- [ ] Code conforms with the [style guide](https://loopback.io/doc/en/contrib/style-guide-es6.html) +- [ ] Commit messages are following our [guidelines](https://loopback.io/doc/en/contrib/git-commit-messages.html) From 499c1bb4de9a4e62c1c7f6161d441db3e8d832e8 Mon Sep 17 00:00:00 2001 From: Rifa Achrinza <25147899+achrinza@users.noreply.github.com> Date: Wed, 27 Nov 2019 00:00:00 +0000 Subject: [PATCH 221/239] add missing 'ORDER BY' before 'OFFSET' --- lib/mssql.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mssql.js b/lib/mssql.js index cdad976..3c258ff 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -426,7 +426,7 @@ function buildLimit(limit, offset) { if (isNaN(offset)) { offset = 0; } - let sql = 'OFFSET ' + offset + ' ROWS'; + let sql = 'ORDER BY RowNum OFFSET ' + offset + ' ROWS'; if (limit >= 0) { sql += ' FETCH NEXT ' + limit + ' ROWS ONLY'; } From 3f21d1dca5af0f04816b9b63917c8eaa47770995 Mon Sep 17 00:00:00 2001 From: Francisco Buceta Date: Sun, 19 Jan 2020 18:39:56 +0100 Subject: [PATCH 222/239] add tests for limit&limit with supportsOffsetFetch --- test/mssql.test.js | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/test/mssql.test.js b/test/mssql.test.js index bbb1931..040661e 100644 --- a/test/mssql.test.js +++ b/test/mssql.test.js @@ -7,12 +7,21 @@ require('./init'); const should = require('should'); -let Post, PostWithUUID, PostWithStringId, db; +let User, Post, PostWithUUID, PostWithStringId, db, dbOSF; describe('mssql connector', function() { before(function() { /* global getDataSource */ db = getDataSource(); + dbOSF = getDataSource({ + supportsOffSetFetch: true, + }); + + User = dbOSF.define('User', { + id: {type: Number, generated: true, id: true}, + name: String, + age: Number, + }); Post = db.define('PostWithBoolean', { title: {type: String, length: 255, index: true}, @@ -242,6 +251,28 @@ describe('mssql connector', function() { ); }); + it('should support limit', function(done) { + Post.find({ + limit: 3, + }, (err, result) => { + should.not.exist(err); + should.exist(result); + result.should.have.length(3); + done(); + }); + }); + + it('should support limit with \'supportsOffsetFetch\'', function(done) { + User.find({ + limit: 3, + }, (err, result) => { + should.not.exist(err); + should.exist(result); + result.should.have.length(3); + done(); + }); + }); + context('regexp operator', function() { beforeEach(function deleteExistingTestFixtures(done) { Post.destroyAll(done); From 063a7d0342f65011ad427be6f5eae4624d0a6dc3 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 22 Jan 2020 09:51:07 -0800 Subject: [PATCH 223/239] 3.7.0 * add tests for limit&limit with supportsOffsetFetch (Francisco Buceta) * add missing 'ORDER BY' before 'OFFSET' (Rifa Achrinza) * chore: improve issue and PR templates (Nora) --- CHANGES.md | 10 ++++++++++ package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 4a7c7f9..cf1273b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,13 @@ +2020-01-22, Version 3.7.0 +========================= + + * add tests for limit&limit with supportsOffsetFetch (Francisco Buceta) + + * add missing 'ORDER BY' before 'OFFSET' (Rifa Achrinza) + + * chore: improve issue and PR templates (Nora) + + 2019-11-14, Version 3.6.0 ========================= diff --git a/package.json b/package.json index a65e854..370c635 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "3.6.0", + "version": "3.7.0", "description": "Microsoft SQL Server connector for LoopBack", "engines": { "node": ">=8" From b5fae4b25d751c4efd06cfdd2b561c3447e97f20 Mon Sep 17 00:00:00 2001 From: Francisco Buceta Date: Tue, 21 Jan 2020 00:19:39 +0100 Subject: [PATCH 224/239] fix problem setting passwords with chars like '(' --- pretest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pretest.js b/pretest.js index f973d0b..9605088 100644 --- a/pretest.js +++ b/pretest.js @@ -25,8 +25,8 @@ if (!process.env.MSSQL_DATABASE) process.env.MSSQL_DATABASE = global.getConfig() const catFileCmd = (isWin ? 'type ' : 'cat ') + sqlFileDir; -const sqlcmd = catFileCmd + ' | ' + sqlDependencyDir + ' -s ' + process.env.MSSQL_HOST + ' -o ' + process.env.MSSQL_PORT + -' -u ' + process.env.MSSQL_USER + ' -p ' + process.env.MSSQL_PASSWORD + ' -d ' + process.env.MSSQL_DATABASE; +const sqlcmd = `${catFileCmd} | ${sqlDependencyDir} -s ${process.env.MSSQL_HOST} -o ${process.env.MSSQL_PORT} ` + + `-u "${process.env.MSSQL_USER}" -p "${process.env.MSSQL_PASSWORD}" -d "${process.env.MSSQL_DATABASE}"`; exec(sqlcmd, function(err, result) { if (err) return console.log('Database seeding failed.\n%s', err); From 85c5cde34ffb9e8e8711e543c17249c216318109 Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Sat, 8 Feb 2020 17:04:13 -0500 Subject: [PATCH 225/239] chore: update copyrights year --- index.js | 2 +- lib/discovery.js | 2 +- lib/migration.js | 2 +- lib/mssql.js | 2 +- lib/transaction.js | 2 +- pretest.js | 2 +- remove-regenerator.js | 2 +- test/autoupdate.test.js | 2 +- test/commontests.js | 2 +- test/connection.js | 2 +- test/discover.test.js | 2 +- test/id.test.js | 2 +- test/init.js | 2 +- test/mapping.test.js | 2 +- test/mssql.test.js | 2 +- test/transaction.test.js | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index 2efb593..5e006a9 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2013,2018. All Rights Reserved. +// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Node module: loopback-connector-mssql // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/lib/discovery.js b/lib/discovery.js index 29f8205..de42afc 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2014,2018. All Rights Reserved. +// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Node module: loopback-connector-mssql // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/lib/migration.js b/lib/migration.js index 6823f16..ad63b27 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2015,2018. All Rights Reserved. +// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Node module: loopback-connector-mssql // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/lib/mssql.js b/lib/mssql.js index 3c258ff..6f4372b 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2013,2018. All Rights Reserved. +// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Node module: loopback-connector-mssql // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/lib/transaction.js b/lib/transaction.js index a4aec22..a0662c2 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2015,2018. All Rights Reserved. +// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Node module: loopback-connector-mssql // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/pretest.js b/pretest.js index 9605088..7112056 100644 --- a/pretest.js +++ b/pretest.js @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. +// Copyright IBM Corp. 2017,2020. All Rights Reserved. // Node module: loopback-connector-mssql // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/remove-regenerator.js b/remove-regenerator.js index d4981f8..6a0f71a 100644 --- a/remove-regenerator.js +++ b/remove-regenerator.js @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2016,2018. All Rights Reserved. +// Copyright IBM Corp. 2016,2019. All Rights Reserved. // Node module: loopback-connector-mssql // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/test/autoupdate.test.js b/test/autoupdate.test.js index 42298c4..5955963 100644 --- a/test/autoupdate.test.js +++ b/test/autoupdate.test.js @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2014,2018. All Rights Reserved. +// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Node module: loopback-connector-mssql // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/test/commontests.js b/test/commontests.js index c2c31c3..16853b2 100644 --- a/test/commontests.js +++ b/test/commontests.js @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2013,2018. All Rights Reserved. +// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Node module: loopback-connector-mssql // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/test/connection.js b/test/connection.js index 99ca4af..a162010 100644 --- a/test/connection.js +++ b/test/connection.js @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2016,2018. All Rights Reserved. +// Copyright IBM Corp. 2016,2019. All Rights Reserved. // Node module: loopback-connector-mssql // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/test/discover.test.js b/test/discover.test.js index c8e97cb..026a248 100644 --- a/test/discover.test.js +++ b/test/discover.test.js @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2014,2018. All Rights Reserved. +// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Node module: loopback-connector-mssql // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/test/id.test.js b/test/id.test.js index 43420ef..b330edf 100644 --- a/test/id.test.js +++ b/test/id.test.js @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2015,2018. All Rights Reserved. +// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Node module: loopback-connector-mssql // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/test/init.js b/test/init.js index a1e9bc1..33e717f 100644 --- a/test/init.js +++ b/test/init.js @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2014,2018. All Rights Reserved. +// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Node module: loopback-connector-mssql // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/test/mapping.test.js b/test/mapping.test.js index f5d62e5..1f56ef7 100644 --- a/test/mapping.test.js +++ b/test/mapping.test.js @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2015,2018. All Rights Reserved. +// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Node module: loopback-connector-mssql // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/test/mssql.test.js b/test/mssql.test.js index 040661e..4868852 100644 --- a/test/mssql.test.js +++ b/test/mssql.test.js @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2015,2018. All Rights Reserved. +// Copyright IBM Corp. 2015,2020. All Rights Reserved. // Node module: loopback-connector-mssql // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/test/transaction.test.js b/test/transaction.test.js index 61b57c2..e891504 100644 --- a/test/transaction.test.js +++ b/test/transaction.test.js @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2015,2018. All Rights Reserved. +// Copyright IBM Corp. 2015,2019. All Rights Reserved. // Node module: loopback-connector-mssql // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT From 3380917c07eae1c858c46fcf9d62f984fd04e17b Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Sat, 8 Feb 2020 17:04:40 -0500 Subject: [PATCH 226/239] chore: update CODEOWNERS --- CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 7d0b5d5..2e0d143 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,7 +3,7 @@ # the last matching pattern has the most precendence. # Alumni members -# @kjdelisle @loay @ssh24 @virkt25 +# @kjdelisle @loay @ssh24 @virkt25 @b-admike # Core team members from IBM -* @jannyHou @b-admike @dhmlau @emonddr +* @jannyHou @dhmlau @emonddr From dfff87a77e4add6786f9f3cad5b703396939cef6 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 5 Mar 2020 09:39:11 -0800 Subject: [PATCH 227/239] Remove obsolete npm script to delete regenerator We had this in the past as facebook/regenerator was under a restrictive license and now it's under MIT. --- package.json | 6 +----- remove-regenerator.js | 26 -------------------------- 2 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 remove-regenerator.js diff --git a/package.json b/package.json index 370c635..dd913cc 100644 --- a/package.json +++ b/package.json @@ -22,9 +22,6 @@ "mssql": "^6.0.0", "strong-globalize": "^5.0.0" }, - "bundledDependencies": [ - "mssql" - ], "devDependencies": { "bluebird": "^3.5.5", "eslint": "^6.0.1", @@ -42,8 +39,7 @@ "lint": "eslint .", "pretest": "node pretest.js", "test": "mocha test/*.test.js node_modules/juggler-v3/test.js node_modules/juggler-v4/test.js", - "posttest": "npm run lint", - "prepublish": "node remove-regenerator.js" + "posttest": "npm run lint" }, "repository": { "type": "git", diff --git a/remove-regenerator.js b/remove-regenerator.js deleted file mode 100644 index 6a0f71a..0000000 --- a/remove-regenerator.js +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright IBM Corp. 2016,2019. All Rights Reserved. -// Node module: loopback-connector-mssql -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -/** - * This script removes regenerator bundled with babel-runtime - */ -'use strict'; -const fs = require('fs'); -try { - const index = require.resolve('babel-runtime/regenerator/index.js'); - const runtime = require.resolve( - 'babel-runtime/regenerator/runtime.js', - ); - if (index) { - fs.unlink(index, function(err) { - if (err) console.error(err); - if (runtime) fs.unlink(runtime, function(err) { - if (err) console.error(err); - }); - }); - } -} catch (err) { - // Ignore -} From efb2dacdcc1e83e85540390e5a8609baade2a842 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 5 Mar 2020 09:39:43 -0800 Subject: [PATCH 228/239] Exclude `deps` and `.github` from npm publish --- package.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/package.json b/package.json index dd913cc..2a02a8d 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,13 @@ "test": "mocha test/*.test.js node_modules/juggler-v3/test.js node_modules/juggler-v4/test.js", "posttest": "npm run lint" }, + "files": [ + "intl", + "lib", + "docs.json", + "index.js", + "setup.sh" + ], "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-connector-mssql.git" From 6bd276c57fe1f802d57056d186abefef3e6d7097 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 10 Mar 2020 10:04:58 -0700 Subject: [PATCH 229/239] 3.8.0 * Exclude `deps` and `.github` from npm publish (Raymond Feng) * Remove obsolete npm script to delete regenerator (Raymond Feng) * chore: update CODEOWNERS (Diana Lau) * chore: update copyrights year (Diana Lau) * fix problem setting passwords with chars like '(' (Francisco Buceta) --- CHANGES.md | 14 ++++++++++++++ package.json | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index cf1273b..f72c785 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,17 @@ +2020-03-10, Version 3.8.0 +========================= + + * Exclude `deps` and `.github` from npm publish (Raymond Feng) + + * Remove obsolete npm script to delete regenerator (Raymond Feng) + + * chore: update CODEOWNERS (Diana Lau) + + * chore: update copyrights year (Diana Lau) + + * fix problem setting passwords with chars like '(' (Francisco Buceta) + + 2020-01-22, Version 3.7.0 ========================= diff --git a/package.json b/package.json index 2a02a8d..545e463 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-mssql", - "version": "3.7.0", + "version": "3.8.0", "description": "Microsoft SQL Server connector for LoopBack", "engines": { "node": ">=8" From 313978e48d2e1bc0555e50fcff52b884ee84110b Mon Sep 17 00:00:00 2001 From: jannyHou Date: Thu, 7 May 2020 15:32:58 -0400 Subject: [PATCH 230/239] chore: drop node 8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 545e463..8bc714e 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "3.8.0", "description": "Microsoft SQL Server connector for LoopBack", "engines": { - "node": ">=8" + "node": ">=10" }, "keywords": [ "StrongLoop", From eb143b0acb466e7d146cb1a1f46dc404faf77556 Mon Sep 17 00:00:00 2001 From: ataft <11670864+ataft@users.noreply.github.com> Date: Fri, 27 Mar 2020 16:43:14 -0700 Subject: [PATCH 231/239] Update migration.js Execute alterTable statements in order --- lib/migration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/migration.js b/lib/migration.js index ad63b27..6778526 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -340,7 +340,7 @@ function mixinMigration(MsSQL) { statements = statements.concat(self.getDropColumns(model, actualFields)); statements = statements.concat(self.addIndexes(model, actualIndexes)); - async.each(statements, function(query, fn) { + async.eachSeries(statements, function(query, fn) { if (checkOnly) { fn(null, true, {statements: statements, query: query}); } else { From 2f3fa3c8f6478d9aaf58365cba2fb9cc1e7a5711 Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Tue, 18 Aug 2020 21:35:58 -0400 Subject: [PATCH 232/239] chore: switch to DCO Signed-off-by: Diana Lau --- .github/PULL_REQUEST_TEMPLATE.md | 3 +- CONTRIBUTING.md | 136 +++---------------------------- 2 files changed, 12 insertions(+), 127 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 781e40b..2cdc707 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -10,8 +10,7 @@ See also #23 ## Checklist -👉 [Read and sign the CLA (Contributor License Agreement)](https://cla.strongloop.com/agreements/strongloop/loopback-connector-mssql) 👈 - +- [ ] DCO (Developer Certificate of Origin) [signed in all commits](https://loopback.io/doc/en/contrib/code-contrib.html) - [ ] `npm test` passes on your machine - [ ] New tests added or existing tests modified to cover all changes - [ ] Code conforms with the [style guide](https://loopback.io/doc/en/contrib/style-guide-es6.html) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6aac285..8cc81cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -### Contributing ### +### Contributing Thank you for your interest in `loopback-connector-mssql`, an open source project administered by IBM. @@ -14,138 +14,24 @@ Contributing to `loopback-connector-mssql` is easy. In a few simple steps: * Adhere to code style outlined in the [Google C++ Style Guide][] and [Google Javascript Style Guide][]. - * Sign the [Contributor License Agreement](https://cla.strongloop.com/agreements/strongloop/loopback-connector-mssql) + * [Sign](https://loopback.io/doc/en/contrib/code-contrib.html) all commits with DCO. * Submit a pull request through Github. +### Developer Certificate of Origin -### Contributor License Agreement ### +This project uses [DCO](https://developercertificate.org/). Be sure to sign off +your commits using the `-s` flag or adding `Signed-off-By: Name` in the +commit message. -``` - Individual Contributor License Agreement - - By signing this Individual Contributor License Agreement - ("Agreement"), and making a Contribution (as defined below) to - IBM Corp. ("IBM"), You (as defined below) accept and - agree to the following terms and conditions for Your present and - future Contributions submitted to IBM. Except for the license - granted in this Agreement to IBM and recipients of software - distributed by IBM, You reserve all right, title, and interest - in and to Your Contributions. - - 1. Definitions - - "You" or "Your" shall mean the copyright owner or the individual - authorized by the copyright owner that is entering into this - Agreement with IBM. - - "Contribution" shall mean any original work of authorship, - including any modifications or additions to an existing work, that - is intentionally submitted by You to IBM for inclusion in, - or documentation of, any of the products owned or managed by - IBM ("Work"). For purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication - sent to IBM or its representatives, including but not - limited to communication or electronic mailing lists, source code - control systems, and issue tracking systems that are managed by, - or on behalf of, IBM for the purpose of discussing and - improving the Work, but excluding communication that is - conspicuously marked or otherwise designated in writing by You as - "Not a Contribution." - - 2. You Grant a Copyright License to IBM - - Subject to the terms and conditions of this Agreement, You hereby - grant to IBM and recipients of software distributed by - IBM, a perpetual, worldwide, non-exclusive, no-charge, - royalty-free, irrevocable copyright license to reproduce, prepare - derivative works of, publicly display, publicly perform, - sublicense, and distribute Your Contributions and such derivative - works under any license and without any restrictions. - - 3. You Grant a Patent License to IBM - - Subject to the terms and conditions of this Agreement, You hereby - grant to IBM and to recipients of software distributed by - IBM a perpetual, worldwide, non-exclusive, no-charge, - royalty-free, irrevocable (except as stated in this Section) - patent license to make, have made, use, offer to sell, sell, - import, and otherwise transfer the Work under any license and - without any restrictions. The patent license You grant to - IBM under this Section applies only to those patent claims - licensable by You that are necessarily infringed by Your - Contributions(s) alone or by combination of Your Contributions(s) - with the Work to which such Contribution(s) was submitted. If any - entity institutes a patent litigation against You or any other - entity (including a cross-claim or counterclaim in a lawsuit) - alleging that Your Contribution, or the Work to which You have - contributed, constitutes direct or contributory patent - infringement, any patent licenses granted to that entity under - this Agreement for that Contribution or Work shall terminate as - of the date such litigation is filed. - - 4. You Have the Right to Grant Licenses to IBM - - You represent that You are legally entitled to grant the licenses - in this Agreement. - - If Your employer(s) has rights to intellectual property that You - create, You represent that You have received permission to make - the Contributions on behalf of that employer, that Your employer - has waived such rights for Your Contributions, or that Your - employer has executed a separate Corporate Contributor License - Agreement with IBM. +**Example** - 5. The Contributions Are Your Original Work - - You represent that each of Your Contributions are Your original - works of authorship (see Section 8 (Submissions on Behalf of - Others) for submission on behalf of others). You represent that to - Your knowledge, no other person claims, or has the right to claim, - any right in any intellectual property right related to Your - Contributions. - - You also represent that You are not legally obligated, whether by - entering into an agreement or otherwise, in any way that conflicts - with the terms of this Agreement. - - You represent that Your Contribution submissions include complete - details of any third-party license or other restriction (including, - but not limited to, related patents and trademarks) of which You - are personally aware and which are associated with any part of - Your Contributions. - - 6. You Don't Have an Obligation to Provide Support for Your Contributions - - You are not expected to provide support for Your Contributions, - except to the extent You desire to provide support. You may provide - support for free, for a fee, or not at all. - - 6. No Warranties or Conditions - - IBM acknowledges that unless required by applicable law or - agreed to in writing, You provide Your Contributions on an "AS IS" - BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER - EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES - OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR - FITNESS FOR A PARTICULAR PURPOSE. - - 7. Submission on Behalf of Others - - If You wish to submit work that is not Your original creation, You - may submit it to IBM separately from any Contribution, - identifying the complete details of its source and of any license - or other restriction (including, but not limited to, related - patents, trademarks, and license agreements) of which You are - personally aware, and conspicuously marking the work as - "Submitted on Behalf of a Third-Party: [named here]". +``` +git commit -s -m "feat: my commit message" +``` - 8. Agree to Notify of Change of Circumstances +Also see the [Contributing to LoopBack](https://loopback.io/doc/en/contrib/code-contrib.html) to get you started. - You agree to notify IBM of any facts or circumstances of - which You become aware that would make these representations - inaccurate in any respect. Email us at callback@strongloop.com. -``` [Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html [Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml From 3852180ca0a60e00dc6210de6616333ab1247c7c Mon Sep 17 00:00:00 2001 From: ataft <11670864+ataft@users.noreply.github.com> Date: Tue, 5 Oct 2021 10:06:51 -0700 Subject: [PATCH 233/239] Allow composite key Fix migration to use idNames() instead of idName(), allowing for a composite primary key from multiple columns Signed-off-by: ataft <11670864+ataft@users.noreply.github.com> Composite Key Test Added test for composite key Signed-off-by: ataft <11670864+ataft@users.noreply.github.com> --- lib/migration.js | 10 +++++---- test/id.test.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/lib/migration.js b/lib/migration.js index 6778526..38f0193 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -355,13 +355,15 @@ function mixinMigration(MsSQL) { // debugger; const self = this; const objModel = this._models[model]; - const modelPKID = this.idName(model); + const modelPKIDs = this.idNames(model); + let j=0; const sql = []; const props = Object.keys(objModel.properties); for (let i = 0, n = props.length; i < n; i++) { const prop = props[i]; - if (prop === modelPKID) { + if (modelPKIDs && modelPKIDs.indexOf(prop) !== -1) { + const modelPKID = modelPKIDs[j++]; const idProp = objModel.properties[modelPKID]; if (idProp.type === Number) { if (idProp.generated !== false) { @@ -387,9 +389,9 @@ function mixinMigration(MsSQL) { } let joinedSql = sql.join(',' + MsSQL.newline + ' '); let cmd = ''; - if (modelPKID) { + if (modelPKIDs && modelPKIDs.length) { cmd = 'PRIMARY KEY CLUSTERED' + MsSQL.newline + '(' + MsSQL.newline; - cmd += ' ' + self.columnEscaped(model, modelPKID) + ' ASC' + MsSQL.newline; + cmd += ' ' + modelPKIDs.map(function(modelPKID) { return self.columnEscaped(model, modelPKID) + ' ASC'; }).join(', ') + MsSQL.newline; cmd += ') WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ' + 'IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)'; } diff --git a/test/id.test.js b/test/id.test.js index b330edf..af0dc40 100644 --- a/test/id.test.js +++ b/test/id.test.js @@ -128,6 +128,61 @@ describe('Manipulating id column', function() { }); }); + it('should create composite key', function(done) { + const schema = + { + name: 'CompositeKeyTest', + options: { + idInjection: false, + mssql: { + schema: 'dbo', + table: 'COMPOSITE_KEY_TEST', + }, + }, + properties: { + idOne: { + type: 'Number', + id: true, + generated: false, + }, + idTwo: { + type: 'Number', + id: true, + generated: false, + }, + name: { + type: 'String', + required: false, + length: 40, + }, + }, + }; + + const models = ds.modelBuilder.buildModels(schema); + const Model = models.CompositeKeyTest; + Model.attachTo(ds); + + ds.automigrate(function(err) { + assert(!err); + async.series([ + function(callback) { + Model.destroyAll(callback); + }, + function(callback) { + Model.create({idOne: 1, idTwo: 2, name: 'w1'}, + callback); + }, + function(callback) { + ds.discoverPrimaryKeys('COMPOSITE_KEY_TEST', function(err, models) { + if (err) return done(err); + assert(models.length === 2); + callback(); + }); + }, + ], done); + }); + }); + it('should use bigint id', function(done) { const schema = { From f0fb4f304d595196642888d3cdfc11c3187eecae Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Sun, 17 Oct 2021 14:18:28 -0400 Subject: [PATCH 234/239] chore: update docker image in setup.sh Signed-off-by: Diana Lau --- setup.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) mode change 100644 => 100755 setup.sh diff --git a/setup.sh b/setup.sh old mode 100644 new mode 100755 index b71a73d..ed0de4d --- a/setup.sh +++ b/setup.sh @@ -50,12 +50,12 @@ printf "\n${CYAN}Clean up complete.${PLAIN}\n" ## pull latest mssql image printf "\n${RED}>> Pulling latest mssql image${PLAIN} ${GREEN}...${PLAIN}" -docker pull microsoft/mssql-server-linux:latest > /dev/null 2>&1 +docker pull mcr.microsoft.com/mssql/server:2019-latest > /dev/null 2>&1 printf "\n${CYAN}Image successfully built.${PLAIN}\n" ## run the mssql container printf "\n${RED}>> Starting the mssql container${PLAIN} ${GREEN}...${PLAIN}" -CONTAINER_STATUS=$(docker run --name $MSSQL_CONTAINER -e ACCEPT_EULA=Y -e SA_PASSWORD=$PASSWORD -p $PORT:1433 -d microsoft/mssql-server-linux:latest 2>&1) +CONTAINER_STATUS=$(docker run --name $MSSQL_CONTAINER -e ACCEPT_EULA=Y -e SA_PASSWORD=$PASSWORD -p $PORT:1433 -d mcr.microsoft.com/mssql/server:2019-latest 2>&1) if [[ "$CONTAINER_STATUS" == *"Error"* ]]; then printf "\n\n${CYAN}Status: ${PLAIN}${RED}Error starting container. Terminating setup.${PLAIN}\n\n" exit 1 From 4b7bcaf8557ae5884728825796b5a05f64424054 Mon Sep 17 00:00:00 2001 From: uma mahesh Date: Thu, 25 Nov 2021 18:01:13 -0500 Subject: [PATCH 235/239] fix: upgrade libraries to fix CVE issues Fixes #239 Signed-off-by: uma mahesh --- .eslintrc | 2 +- lib/migration.js | 2 +- package.json | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.eslintrc b/.eslintrc index 6101320..bf8f245 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,7 +1,7 @@ { "extends": "loopback", "rules": { - "max-len": ["error", 125, 4, { + "max-len": ["error", 140, 4, { "ignoreComments": true, "ignoreUrls": true, "ignorePattern": "^\\s*var\\s.+=\\s*(require\\s*\\()|(/)" diff --git a/lib/migration.js b/lib/migration.js index 38f0193..5f68d96 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -356,7 +356,7 @@ function mixinMigration(MsSQL) { const self = this; const objModel = this._models[model]; const modelPKIDs = this.idNames(model); - let j=0; + let j = 0; const sql = []; const props = Object.keys(objModel.properties); diff --git a/package.json b/package.json index 8bc714e..b019eec 100644 --- a/package.json +++ b/package.json @@ -17,14 +17,14 @@ "main": "index.js", "dependencies": { "async": "^3.1.0", - "debug": "^4.1.1", - "loopback-connector": "^4.8.0", - "mssql": "^6.0.0", + "debug": "^4.3.1", + "loopback-connector": "^5.0.1", + "mssql": "^6.4.0", "strong-globalize": "^5.0.0" }, "devDependencies": { "bluebird": "^3.5.5", - "eslint": "^6.0.1", + "eslint": "^7.0.0", "eslint-config-loopback": "^13.1.0", "juggler-v3": "file:./deps/juggler-v3", "juggler-v4": "file:./deps/juggler-v4", From e3d1ada08c38e21309096d921e8dbbde79b7d648 Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Wed, 23 Mar 2022 21:36:39 -0400 Subject: [PATCH 236/239] docs: add code of conduct Signed-off-by: Diana Lau --- .github/ISSUE_TEMPLATE/config.yml | 2 +- CODE_OF_CONDUCT.md | 128 ++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 CODE_OF_CONDUCT.md diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 9204746..652dfad 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,7 +2,7 @@ blank_issues_enabled: false contact_links: - name: Report a security vulnerability url: https://loopback.io/doc/en/contrib/Reporting-issues.html#security-issues - about: Do not report security vulnerabilities using GitHub issues. Please send an email to `reachsl@us.ibm.com` instead. + about: Do not report security vulnerabilities using GitHub issues. Please send an email to `security@loopback.io` instead. - name: Get help on StackOverflow url: https://stackoverflow.com/tags/loopbackjs about: Please ask and answer questions on StackOverflow. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..7e01524 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[tsc@loopback.io](mailto:tsc@loopback.io). All complaints will be reviewed and +investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. \ No newline at end of file From 7c1530805d295eff355887a8fa9461b26fd57e2f Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Fri, 25 Mar 2022 15:36:20 -0400 Subject: [PATCH 237/239] docs: update coc Signed-off-by: Diana Lau --- CODE_OF_CONDUCT.md | 54 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 7e01524..0f79fa5 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,54 @@ -# Contributor Covenant Code of Conduct +# Code of Conduct + +LoopBack, as member project of the OpenJS Foundation, use +[Contributor Covenant v2.0](https://contributor-covenant.org/version/2/0/code_of_conduct) +as their code of conduct. The full text is included +[below](#contributor-covenant-code-of-conduct-v2.0) in English, and translations +are available from the Contributor Covenant organisation: + +- [contributor-covenant.org/translations](https://www.contributor-covenant.org/translations) +- [github.com/ContributorCovenant](https://github.com/ContributorCovenant/contributor_covenant/tree/release/content/version/2/0) + +Refer to the sections on reporting and escalation in this document for the +specific emails that can be used to report and escalate issues. + +## Reporting + +### Project Spaces + +For reporting issues in spaces related to LoopBack, please use the email +`tsc@loopback.io`. The LoopBack Technical Steering Committee (TSC) handles CoC issues related to the spaces that it +maintains. The project TSC commits to: + +- maintain the confidentiality with regard to the reporter of an incident +- to participate in the path for escalation as outlined in the section on + Escalation when required. + +### Foundation Spaces + +For reporting issues in spaces managed by the OpenJS Foundation, for example, +repositories within the OpenJS organization, use the email +`report@lists.openjsf.org`. The Cross Project Council (CPC) is responsible for +managing these reports and commits to: + +- maintain the confidentiality with regard to the reporter of an incident +- to participate in the path for escalation as outlined in the section on + Escalation when required. + +## Escalation + +The OpenJS Foundation maintains a Code of Conduct Panel (CoCP). This is a +foundation-wide team established to manage escalation when a reporter believes +that a report to a member project or the CPC has not been properly handled. In +order to escalate to the CoCP send an email to +`coc-escalation@lists.openjsf.org`. + +For more information, refer to the full +[Code of Conduct governance document](https://github.com/openjs-foundation/cross-project-council/blob/HEAD/CODE_OF_CONDUCT.md). + +--- + +## Contributor Covenant Code of Conduct v2.0 ## Our Pledge @@ -125,4 +175,4 @@ Community Impact Guidelines were inspired by For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. \ No newline at end of file +https://www.contributor-covenant.org/translations. From cfb97efa83e0684446a1ed617c3c6bca95279d92 Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Tue, 29 Mar 2022 10:19:51 -0400 Subject: [PATCH 238/239] docs: add SECURITY.md Signed-off-by: Diana Lau --- SECURITY.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..0961858 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +## Security advisories + +Security advisories can be found on the +[LoopBack website](https://loopback.io/doc/en/sec/index.html). + +## Reporting a vulnerability + +If you think you have discovered a new security issue with any LoopBack package, +**please do not report it on GitHub**. Instead, send an email to +[security@loopback.io](mailto:security@loopback.io) with the following details: + +- Full description of the vulnerability. +- Steps to reproduce the issue. +- Possible solutions. + +If you are sending us any logs as part of the report, then make sure to redact +any sensitive data from them. \ No newline at end of file From 4a53d6d5d9b728831d4f4ee7f7cce589b908b1f1 Mon Sep 17 00:00:00 2001 From: mdegraw Date: Mon, 15 Aug 2022 18:09:30 -0600 Subject: [PATCH 239/239] fix: use decimal instead of float type --- lib/mssql.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/mssql.js b/lib/mssql.js index 6f4372b..1beb5eb 100644 --- a/lib/mssql.js +++ b/lib/mssql.js @@ -140,8 +140,9 @@ MsSQL.prototype.executeSQL = function(sql, params, options, callback) { for (let i = 0, n = params.length; i < n; i++) { if (typeof params[i] === 'number' && params[i] % 1 !== 0) { - // Float number - request.input('param' + (i + 1), mssql.Real, params[i]); + // TODO: prefer decimal or numeric data + // until https://github.com/loopbackio/loopback-connector-mssql/issues/183 is resolved + request.input('param' + (i + 1), mssql.Decimal(38, 10), params[i]); } else if (typeof params[i] === 'number' && isBigInt(params[i])) { request.input('param' + (i + 1), mssql.BigInt, params[i]); } else {