diff --git a/.travis.yml b/.travis.yml index 05d299e..f04dabe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,6 @@ language: node_js node_js: + - "5.0" + - "4.2" - "0.10" - "0.11" diff --git a/lib/dialect/mssql.js b/lib/dialect/mssql.js index daf6493..48c1ad4 100644 --- a/lib/dialect/mssql.js +++ b/lib/dialect/mssql.js @@ -6,6 +6,14 @@ var util = require('util'); var assert = require('assert'); +/** + * Config can contain: + * + * questionMarkParameterPlaceholder:true which will use a "?" for the parameter placeholder instead of the @index. + * + * @param config + * @constructor + */ var Mssql = function(config) { this.output = []; this.params = []; @@ -23,6 +31,7 @@ Mssql.prototype._quoteCharacter = '['; Mssql.prototype._arrayAggFunctionName = ''; Mssql.prototype._getParameterPlaceholder = function(index, value) { + if (this.config.questionMarkParameterPlaceholder) return '?'; return '@' + index; }; @@ -95,7 +104,7 @@ Mssql.prototype.visitAlter = function(alter) { var table = self._queryNode.table; var result = ['EXEC sp_rename '+self.visit(table.toNode())+', '+self.visit(alter.nodes[0].nodes[0])]; self._visitingAlter = false; - return result + return result; } // Implement our own rename column: @@ -110,7 +119,7 @@ Mssql.prototype.visitAlter = function(alter) { "'COLUMN'" ]; self._visitingAlter = false; - return result + return result; } if (isAlterAddColumn(alter)) return _addColumn(); @@ -124,13 +133,13 @@ Mssql.prototype.visitAlter = function(alter) { // CASE WHEN true THEN xxx END // the "true" has to be a boolean expression like 1=1 Mssql.prototype.visitCase = function(caseExp) { - var _this=this + var _this=this; function _whenValue(node){ if (node.type!='PARAMETER') return _this.visit(node); // dealing with a true/false value var val=node.value(); - if (val==true) return '1=1'; else return '0=1'; + if (val===true) return '1=1'; else return '0=1'; } assert(caseExp.whenList.length == caseExp.thenList.length); @@ -146,7 +155,7 @@ Mssql.prototype.visitCase = function(caseExp) { text += whenExp + thenExp; } - if (null != caseExp.else && undefined != caseExp.else) { + if (null !== caseExp.else && undefined !== caseExp.else) { text += ' ELSE ' + this.visit(caseExp.else); } @@ -154,7 +163,7 @@ Mssql.prototype.visitCase = function(caseExp) { text += ' END)'; return [text]; -} +}; Mssql.prototype.visitColumn = function(columnNode) { var self=this; @@ -162,12 +171,12 @@ Mssql.prototype.visitColumn = function(columnNode) { var inSelectClause; function _arrayAgg(){ - throw new Error("SQL Server does not support array_agg.") + throw new Error("SQL Server does not support array_agg."); } function _countStar(){ // Implement our own since count(table.*) is invalid in Mssql - var result='COUNT(*)' + var result='COUNT(*)'; if(inSelectClause && columnNode.alias) { result += ' AS ' + self.quote(columnNode.alias); } @@ -184,8 +193,8 @@ Mssql.prototype.visitColumn = function(columnNode) { Mssql.prototype.visitCreate = function(create) { - var isNotExists=isCreateIfNotExists(create) - var isTemporary=isCreateTemporary(create) + var isNotExists=isCreateIfNotExists(create); + var isTemporary=isCreateTemporary(create); if (!isNotExists && !isTemporary) { return Mssql.super_.prototype.visitCreate.call(this, create); } @@ -210,7 +219,7 @@ Mssql.prototype.visitCreate = function(create) { // if (schema) { whereClause+=' AND TABLE_SCHEMA = schemaResult.join(' ')} // Add some tests for this as well - if (!isNotExists) return createResult + if (!isNotExists) return createResult; return ['IF NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES '+whereClause+') BEGIN '+createResult.join(' ')+' END']; }; @@ -237,24 +246,24 @@ Mssql.prototype.visitDrop = function(drop) { Mssql.prototype.visitFunctionCall = function(functionCall) { this._visitingFunctionCall = true; - var name=functionCall.name - // override the LENGTH function since mssql calls it LEN - if (name=="LENGTH") name="LEN" + var name=functionCall.name; + // override the LENGTH function since mssql calls it LEN + if (name=="LENGTH") name="LEN"; var txt = name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; this._visitingFunctionCall = false; return [txt]; }; Mssql.prototype.visitOrderBy = function(orderBy) { - var result=Mssql.super_.prototype.visitOrderBy.call(this, orderBy); - var offsetNode=orderBy.msSQLOffsetNode; - var limitNode=orderBy.msSQLLimitNode; - if (!offsetNode && !limitNode) return result; - assert(offsetNode,"Something bad happened, should have had an msSQLOffsetNode here."); - result.push("OFFSET "+getModifierValue(this,offsetNode)+" ROWS"); - if (!limitNode) return result; - result.push("FETCH NEXT "+getModifierValue(this,limitNode)+" ROWS ONLY"); - return result; + var result=Mssql.super_.prototype.visitOrderBy.call(this, orderBy); + var offsetNode=orderBy.msSQLOffsetNode; + var limitNode=orderBy.msSQLLimitNode; + if (!offsetNode && !limitNode) return result; + assert(offsetNode,"Something bad happened, should have had an msSQLOffsetNode here."); + result.push("OFFSET "+getModifierValue(this,offsetNode)+" ROWS"); + if (!limitNode) return result; + result.push("FETCH NEXT "+getModifierValue(this,limitNode)+" ROWS ONLY"); + return result; }; /** @@ -272,63 +281,63 @@ Mssql.prototype.visitOrderBy = function(orderBy) { * @returns {String[]} */ Mssql.prototype.visitQueryHelper=function(actions,targets,filters){ - function _handleLimitAndOffset(){ - var limitInfo=Mssql.super_.prototype.findNode.call(this, filters, "LIMIT"); - var offsetInfo=Mssql.super_.prototype.findNode.call(this, filters, "OFFSET"); - var orderByInfo=Mssql.super_.prototype.findNode.call(this, filters, "ORDER BY"); - - // no OFFSET or LIMIT then there's nothing special to do - if (!offsetInfo && !limitInfo) return; - // ORDER BY with OFFSET we have work to do, may consume LIMIT as well - if (orderByInfo && offsetInfo) _processOrderByOffsetLimit(orderByInfo,offsetInfo,limitInfo); - else if (offsetInfo) throw new Error("MS SQL Server does not allow OFFSET without ORDER BY"); - else if (limitInfo) _processLimit(limitInfo); - } - - /** - * We need to turn LIMIT into a TOP clause on the SELECT STATEMENT - * - * @param limitInfo - * @private - */ - function _processLimit(limitInfo){ - var selectInfo=Mssql.super_.prototype.findNode.call(this, actions, "SELECT"); - assert(selectInfo!=undefined,"MS SQL Server requires a SELECT clause when using LIMIT"); - // save the LIMIT node with the SELECT node - selectInfo.node.msSQLLimitNode=limitInfo.node; - // remove the LIMIT node from the filters so it doesn't get processed later. - filters.splice(limitInfo.index,1); - } - - /** - * We need to turn LIMIT into a TOP clause on the SELECT STATEMENT - * - * @param orderByInfo - * @param offsetInfo - * @param limitInfo - * @private - */ - function _processOrderByOffsetLimit(orderByInfo,offsetInfo,limitInfo){ - // save the OFFSET AND LIMIT nodes with the ORDER BY node - orderByInfo.node.msSQLOffsetNode=offsetInfo.node; - if (limitInfo) orderByInfo.node.msSQLLimitNode=limitInfo.node; - // remove the OFFSET and LIMIT nodes from the filters so they don't get processed later. - filters.splice(offsetInfo.index,1); - if (limitInfo) filters.splice(limitInfo.index,1); - } - - // MAIN + function _handleLimitAndOffset(){ + var limitInfo=Mssql.super_.prototype.findNode.call(this, filters, "LIMIT"); // jshint ignore:line + var offsetInfo=Mssql.super_.prototype.findNode.call(this, filters, "OFFSET"); // jshint ignore:line + var orderByInfo=Mssql.super_.prototype.findNode.call(this, filters, "ORDER BY"); // jshint ignore:line + + // no OFFSET or LIMIT then there's nothing special to do + if (!offsetInfo && !limitInfo) return; + // ORDER BY with OFFSET we have work to do, may consume LIMIT as well + if (orderByInfo && offsetInfo) _processOrderByOffsetLimit(orderByInfo,offsetInfo,limitInfo); + else if (offsetInfo) throw new Error("MS SQL Server does not allow OFFSET without ORDER BY"); + else if (limitInfo) _processLimit(limitInfo); + } + + /** + * We need to turn LIMIT into a TOP clause on the SELECT STATEMENT + * + * @param limitInfo + * @private + */ + function _processLimit(limitInfo){ + var selectInfo=Mssql.super_.prototype.findNode.call(this, actions, "SELECT"); // jshint ignore:line + assert(selectInfo!==undefined,"MS SQL Server requires a SELECT clause when using LIMIT"); + // save the LIMIT node with the SELECT node + selectInfo.node.msSQLLimitNode=limitInfo.node; + // remove the LIMIT node from the filters so it doesn't get processed later. + filters.splice(limitInfo.index,1); + } + + /** + * We need to turn LIMIT into a TOP clause on the SELECT STATEMENT + * + * @param orderByInfo + * @param offsetInfo + * @param limitInfo + * @private + */ + function _processOrderByOffsetLimit(orderByInfo,offsetInfo,limitInfo){ + // save the OFFSET AND LIMIT nodes with the ORDER BY node + orderByInfo.node.msSQLOffsetNode=offsetInfo.node; + if (limitInfo) orderByInfo.node.msSQLLimitNode=limitInfo.node; + // remove the OFFSET and LIMIT nodes from the filters so they don't get processed later. + filters.splice(offsetInfo.index,1); + if (limitInfo) filters.splice(limitInfo.index,1); + } + + // MAIN Mssql.super_.prototype.handleDistinct.call(this, actions, filters); - _handleLimitAndOffset(); - - // lazy-man sorting - var sortedNodes = actions.concat(targets).concat(filters); - for(var i = 0; i < sortedNodes.length; i++) { - var res = this.visit(sortedNodes[i]); - this.output = this.output.concat(res); - } - return this.output; + _handleLimitAndOffset(); + + // lazy-man sorting + var sortedNodes = actions.concat(targets).concat(filters); + for(var i = 0; i < sortedNodes.length; i++) { + var res = this.visit(sortedNodes[i]); + this.output = this.output.concat(res); + } + return this.output; }; //Mysql.prototype.visitRenameColumn = function(renameColumn) { @@ -364,71 +373,71 @@ Mssql.prototype.visitReturning = function() { // We deal with SELECT specially so we can add the TOP clause if needed Mssql.prototype.visitSelect = function(select) { - if (!select.msSQLLimitNode) return Mssql.super_.prototype.visitSelect.call(this, select); - var result=[ - 'SELECT', - 'TOP('+getModifierValue(this,select.msSQLLimitNode)+')', - select.nodes.map(this.visit.bind(this)).join(', ') - ]; - this._selectOrDeleteEndIndex = this.output.length + result.length; - return result; + if (!select.msSQLLimitNode) return Mssql.super_.prototype.visitSelect.call(this, select); + var result=[ + 'SELECT', + 'TOP('+getModifierValue(this,select.msSQLLimitNode)+')', + select.nodes.map(this.visit.bind(this)).join(', ') + ]; + this._selectOrDeleteEndIndex = this.output.length + result.length; + return result; }; // Node is either an OFFSET or LIMIT node function getModifierValue(dialect,node){ - return node.count.type ? dialect.visit(node.count) : node.count + return node.count.type ? dialect.visit(node.count) : node.count; } function isAlterAddColumn(alter){ - if (alter.nodes.length==0) return false; + if (alter.nodes.length===0) return false; if (alter.nodes[0].type!='ADD COLUMN') return false; return true; -}; +} function isAlterDropColumn(alter){ - if (alter.nodes.length==0) return false; + if (alter.nodes.length===0) return false; if (alter.nodes[0].type!='DROP COLUMN') return false; return true; -}; +} function isAlterRename(alter){ - if (alter.nodes.length==0) return false; + if (alter.nodes.length===0) return false; if (alter.nodes[0].type!='RENAME') return false; return true; -}; +} function isAlterRenameColumn(alter){ - if (alter.nodes.length==0) return false; + if (alter.nodes.length===0) return false; if (alter.nodes[0].type!='RENAME COLUMN') return false; return true; -}; +} function isCountStarExpression(columnNode){ if (!columnNode.aggregator) return false; if (columnNode.aggregator.toLowerCase()!='count') return false; if (!columnNode.star) return false; return true; -}; +} function isCreateIfNotExists(create){ - if (create.nodes.length==0) return false; + if (create.nodes.length===0) return false; if (create.nodes[0].type!='IF NOT EXISTS') return false; return true; -}; +} function isCreateTemporary(create){ - return create.options.isTemporary -}; + return create.options.isTemporary; +} function isDropIfExists(drop){ - if (drop.nodes.length==0) return false; + if (drop.nodes.length===0) return false; if (drop.nodes[0].type!='IF EXISTS') return false; return true; -}; +} // SQL Server does not support array expressions except in the IN clause. function isRightSideArray(binary){ return Array.isArray(binary.right); -}; +} module.exports = Mssql; diff --git a/lib/dialect/mysql.js b/lib/dialect/mysql.js index b4e8249..9d582ce 100644 --- a/lib/dialect/mysql.js +++ b/lib/dialect/mysql.js @@ -100,6 +100,6 @@ Mysql.prototype.visitBinary = function(binary) { return [text]; } return Mysql.super_.prototype.visitBinary.call(this, binary); -} +}; module.exports = Mysql; diff --git a/lib/dialect/oracle.js b/lib/dialect/oracle.js index ce912b2..6b59715 100644 --- a/lib/dialect/oracle.js +++ b/lib/dialect/oracle.js @@ -75,7 +75,7 @@ Oracle.prototype.visitDrop = function(drop) { }; Oracle.prototype.visitCreate = function(create) { - var isNotExists=isCreateIfNotExists(create) + var isNotExists=isCreateIfNotExists(create); //var isTemporary=isCreateTemporary(create) var createText = Oracle.super_.prototype.visitCreate.call(this, create); if (isNotExists) { @@ -144,8 +144,8 @@ Oracle.prototype.visitQueryHelper=function(actions,targets,filters){ output[limit+2] = temp[2]; } - return this.output; -} + return this.output; +}; Oracle.prototype.visitColumn = function(columnNode) { var self=this; @@ -153,12 +153,12 @@ Oracle.prototype.visitColumn = function(columnNode) { var inSelectClause; function _arrayAgg(){ - throw new Error("Oracle does not support array_agg.") + throw new Error("Oracle does not support array_agg."); } function _countStar(){ // Implement our own since count(table.*) is invalid in Oracle - var result='COUNT(*)' + var result='COUNT(*)'; if(inSelectClause && columnNode.alias) { result += self._aliasText + self.quote(columnNode.alias); } @@ -222,35 +222,35 @@ Oracle.prototype.visitDropIndex = function(node) { Oracle.prototype.visitCase = function(caseExp) { return Mssql.prototype.visitCase.call(this, caseExp); -} +}; function isCreateIfNotExists(create){ - if (create.nodes.length==0) return false; + if (create.nodes.length===0) return false; if (create.nodes[0].type!='IF NOT EXISTS') return false; return true; -}; +} function isCreateTemporary(create){ - return create.options.isTemporary -}; + return create.options.isTemporary; +} function isDropIfExists(drop){ - if (drop.nodes.length==0) return false; + if (drop.nodes.length===0) return false; if (drop.nodes[0].type!='IF EXISTS') return false; return true; -}; +} // SQL Server does not support array expressions except in the IN clause. function isRightSideArray(binary){ return Array.isArray(binary.right); -}; +} function isCountStarExpression(columnNode){ if (!columnNode.aggregator) return false; if (columnNode.aggregator.toLowerCase()!='count') return false; if (!columnNode.star) return false; return true; -}; +} module.exports = Oracle; diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index 295136f..e951cfc 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -26,7 +26,7 @@ Postgres.prototype._getParameterText = function(index, value) { } }; -Postgres.prototype._getParameterValue = function(value) { +Postgres.prototype._getParameterValue = function(value, quoteChar) { // handle primitives if (null === value) { value = 'NULL'; @@ -36,13 +36,29 @@ Postgres.prototype._getParameterValue = function(value) { // number is just number value = value; } else if ('string' === typeof value) { - // string uses single quote - value = this.quote(value, "'"); + // string uses single quote by default + value = this.quote(value, quoteChar || "'"); } else if ('object' === typeof value) { if (_.isArray(value)) { - // convert each element of the array - value = _.map(value, this._getParameterValue, this); - value = '(' + value.join(', ') + ')'; + if (this._myClass === Postgres) { + // naive check to see if this is an array of objects, which + // is handled differently than an array of primitives + if (value.length && 'object' === typeof value[0] && + !_.isFunction(value[0].toISOString) && + !_.isArray(value[0])) { + value = "'" + JSON.stringify(value) + "'"; + } else { + var self = this; + value = value.map(function (item) { + // In a Postgres array, strings must be double-quoted + return self._getParameterValue(item, '"'); + }); + value = '\'{' + value.join(',') + '}\''; + } + } else { + value = _.map(value, this._getParameterValue.bind(this)); + value = '(' + value.join(', ') + ')'; + } } else if (_.isFunction(value.toISOString)) { // Date object's default toString format does not get parsed well // Handle date like objects using toISOString @@ -51,7 +67,8 @@ Postgres.prototype._getParameterValue = function(value) { value = this._getParameterValue('\\x' + value.toString('hex')); } else { // rich object represent with string - value = this._getParameterValue(value.toString()); + var strValue = value.toString(); + value = strValue === '[object Object]' ? this._getParameterValue(JSON.stringify(value)) : this._getParameterValue(strValue); } } else { throw new Error('Unable to use ' + value + ' in query'); @@ -187,14 +204,14 @@ Postgres.prototype.quote = function(word, quoteCharacter) { } // handle square brackets specially if (q=='['){ - return '['+word+']' + return '['+word+']'; } else { return q + word.replace(new RegExp(q,'g'),q+q) + q; } }; Postgres.prototype.visitSelect = function(select) { - var result = ['SELECT'] + var result = ['SELECT']; if (select.isDistinct) result.push('DISTINCT'); @@ -281,7 +298,7 @@ Postgres.prototype.visitCreate = function(create) { var col_nodes = table.columns.map(function(col) { return col.toNode(); }); var result = ['CREATE TABLE']; - if (create.options.isTemporary) result=['CREATE TEMPORARY TABLE'] + if (create.options.isTemporary) result=['CREATE TEMPORARY TABLE']; result = result.concat(create.nodes.map(this.visit.bind(this))); result.push(this.visit(table.toNode())); var primary_col_nodes = col_nodes.filter(function(n) { @@ -539,7 +556,7 @@ Postgres.prototype.visitCase = function(caseExp) { text += whenExp + thenExp; } - if (null != caseExp.else && undefined != caseExp.else) { + if (null !== caseExp.else && undefined !== caseExp.else) { text += ' ELSE ' + this.visit(caseExp.else); } @@ -547,7 +564,7 @@ Postgres.prototype.visitCase = function(caseExp) { text += ' END)'; return [text]; -} +}; Postgres.prototype.visitAt = function(at) { var text = '(' + this.visit(at.value) + ')[' + this.visit(at.index) + ']'; @@ -594,7 +611,7 @@ Postgres.prototype.visitQuery = function(queryNode) { var node = queryNode.nodes[i]; switch(node.type) { case "SELECT": - isSelect = true; + isSelect = true; // jshint ignore:line case "DELETE": actions.push(node); break; @@ -637,7 +654,7 @@ Postgres.prototype.visitQuery = function(queryNode) { throw new Error('Create View requires a Select.'); } } - return this.visitQueryHelper(actions,targets,filters) + return this.visitQueryHelper(actions,targets,filters); }; /** @@ -649,16 +666,16 @@ Postgres.prototype.visitQuery = function(queryNode) { * @returns {String[]} */ Postgres.prototype.visitQueryHelper=function(actions,targets,filters){ - this.handleDistinct(actions, filters) - // lazy-man sorting - var sortedNodes = actions.concat(targets).concat(filters); - for(var i = 0; i < sortedNodes.length; i++) { - var res = this.visit(sortedNodes[i]); - this.output = this.output.concat(res); - } - // implicit 'from' - return this.output; -} + this.handleDistinct(actions, filters); + // lazy-man sorting + var sortedNodes = actions.concat(targets).concat(filters); + for(var i = 0; i < sortedNodes.length; i++) { + var res = this.visit(sortedNodes[i]); + this.output = this.output.concat(res); + } + // implicit 'from' + return this.output; +}; Postgres.prototype.visitSubquery = function(queryNode) { // create another query builder of the current class to build the subquery @@ -702,11 +719,11 @@ Postgres.prototype.visitColumn = function(columnNode) { var inSelectClause = this.visitingReturning || (!this._selectOrDeleteEndIndex - && !this._visitingWhere - && !inInsertUpdateClause - && !inDdlClause - && !this.visitingCase - && !this._visitingJoin + && !this._visitingWhere // jshint ignore:line + && !inInsertUpdateClause // jshint ignore:line + && !inDdlClause // jshint ignore:line + && !this.visitingCase // jshint ignore:line + && !this._visitingJoin // jshint ignore:line ); var inFunctionCall = this._visitingFunctionCall; var inCast = this._visitingCast; @@ -766,7 +783,7 @@ Postgres.prototype.visitColumn = function(columnNode) { txt.push(this.quote(columnNode.name)); } if(closeParen) { - for(var i = 0; i < closeParen; i++) { + for(var j = 0; j < closeParen; j++) { txt.push(')'); } } @@ -785,6 +802,9 @@ Postgres.prototype.visitColumn = function(columnNode) { } else if (columnNode.notNull) { txt.push(' NOT NULL'); } + if (!columnNode.primaryKey && columnNode.unique) { + txt.push(' UNIQUE'); + } } if (!!columnNode.references) { @@ -813,7 +833,7 @@ Postgres.prototype.visitColumn = function(columnNode) { var onDelete = columnNode.references.onDelete; if (onDelete) onDelete = onDelete.toUpperCase(); if (onDelete === 'CASCADE' || onDelete === 'RESTRICT') { - txt.push(' ON DELETE ' + onDelete) + txt.push(' ON DELETE ' + onDelete); } var constraint = columnNode.references.constraint; if (constraint) { @@ -1003,26 +1023,26 @@ Postgres.prototype.visitCreateView = function(createView) { * @returns {Object|undefined} {index:number, node:Node} */ Postgres.prototype.findNode=function(list,type) { - for (var i= 0, len=list.length; i", "name": "sql", "description": "sql builder", - "version": "0.64.1", + "version": "0.67.0", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { @@ -11,14 +11,16 @@ }, "main": "lib/", "scripts": { - "test": "node ./runtests" + "test": "node_modules/.bin/mocha", + "lint": "jshint lib test", + "posttest": "jshint lib test" }, "engines": { "node": "*" }, "dependencies": { "sliced": "0.0.x", - "lodash": "1.3.x" + "lodash": "4.1.x" }, "devDependencies": { "jshint": "*", diff --git a/runtests.js b/runtests.js index 8b722ef..ac89769 100644 --- a/runtests.js +++ b/runtests.js @@ -1,18 +1,18 @@ -var childProcess = require("child_process") -var path = require("path") +var childProcess = require("child_process"); +var path = require("path"); -var env = process.env -env.NODE_ENV = "test" +var env = process.env; +env.NODE_ENV = "test"; var options = { env: env -} +}; -var command = path.join(".", "node_modules", ".bin", "mocha") -if (process.platform == "win32") command += ".cmd" -var run = childProcess.spawn(command, [], options) -run.stdout.pipe(process.stdout) -run.stderr.pipe(process.stderr) +var command = path.join(".", "node_modules", ".bin", "mocha"); +if (process.platform == "win32") command += ".cmd"; +var run = childProcess.spawn(command, [], options); +run.stdout.pipe(process.stdout); +run.stderr.pipe(process.stderr); run.on('close', function(code) { - process.exit(code) -}) + process.exit(code); +}); diff --git a/test/column-tests.js b/test/column-tests.js index 6fb6155..d0b95a1 100644 --- a/test/column-tests.js +++ b/test/column-tests.js @@ -36,29 +36,29 @@ describe('column', function() { assert.equal(col, '"subTable"."subId"'); }); - describe('property', function() { - var table = sql.define({ - name: 'roundtrip', - columns: { - column_name: { property: 'propertyName' } - } - }); - it('used as alias when !== column name', function() { - assert.equal(table.propertyName.toQuery().text, '"roundtrip"."column_name" AS "propertyName"'); - }); - it('uses explicit alias when !== column name', function() { - assert.equal(table.propertyName.as('alias').toQuery().text, '"roundtrip"."column_name" AS "alias"'); - }); - it('maps to column name in insert', function() { - assert.equal(table.insert({propertyName:'propVal'}).toQuery().text, 'INSERT INTO "roundtrip" ("column_name") VALUES ($1)'); - }); - it('maps to column name in update', function() { - assert.equal(table.update({propertyName:'propVal'}).toQuery().text, 'UPDATE "roundtrip" SET "column_name" = $1'); - }); - it('explicitly selected by *', function() { - assert.equal(table.select(table.star()).from(table).toQuery().text, 'SELECT "roundtrip"."column_name" AS "propertyName" FROM "roundtrip"'); - }); - }); + describe('property', function() { + var table = sql.define({ + name: 'roundtrip', + columns: { + column_name: { property: 'propertyName' } + } + }); + it('used as alias when !== column name', function() { + assert.equal(table.propertyName.toQuery().text, '"roundtrip"."column_name" AS "propertyName"'); + }); + it('uses explicit alias when !== column name', function() { + assert.equal(table.propertyName.as('alias').toQuery().text, '"roundtrip"."column_name" AS "alias"'); + }); + it('maps to column name in insert', function() { + assert.equal(table.insert({propertyName:'propVal'}).toQuery().text, 'INSERT INTO "roundtrip" ("column_name") VALUES ($1)'); + }); + it('maps to column name in update', function() { + assert.equal(table.update({propertyName:'propVal'}).toQuery().text, 'UPDATE "roundtrip" SET "column_name" = $1'); + }); + it('explicitly selected by *', function() { + assert.equal(table.select(table.star()).from(table).toQuery().text, 'SELECT "roundtrip"."column_name" AS "propertyName" FROM "roundtrip"'); + }); + }); describe('autoGenerate', function() { var table = sql.define({ @@ -96,10 +96,10 @@ describe('column', function() { columns: ['id', 'name'] }); it('throws for insert properties that are not a column', function() { - assert.throws(function() { table.insert({id:0, _private:'_private', name:'name'}) }, Error); + assert.throws(function() { table.insert({id:0, _private:'_private', name:'name'}); }, Error); }); it('throws for update properties that are not a column', function() { - assert.throws(function() { table.update({id:0, _private:'_private', name:'name'}) }, Error); + assert.throws(function() { table.update({id:0, _private:'_private', name:'name'}); }, Error); }); }); diff --git a/test/dialects/case-tests.js b/test/dialects/case-tests.js index 046ee4b..f492a4f 100644 --- a/test/dialects/case-tests.js +++ b/test/dialects/case-tests.js @@ -12,7 +12,7 @@ Harness.test({ }, sqlite: { text : 'SELECT (CASE WHEN $1 THEN $2 WHEN $3 THEN $4 ELSE $5 END) FROM "customer"', - string: 'SELECT (CASE WHEN TRUE THEN 0 WHEN FALSE THEN 1 ELSE 2 END) FROM "customer"' + string: 'SELECT (CASE WHEN 1 THEN 0 WHEN 0 THEN 1 ELSE 2 END) FROM "customer"' }, mysql: { text : 'SELECT (CASE WHEN ? THEN ? WHEN ? THEN ? ELSE ? END) FROM `customer`', @@ -40,7 +40,7 @@ Harness.test({ }, sqlite: { text : 'SELECT ("customer"."age" + (CASE WHEN $1 THEN $2 WHEN $3 THEN $4 ELSE $5 END)) FROM "customer"', - string: 'SELECT ("customer"."age" + (CASE WHEN TRUE THEN 0 WHEN FALSE THEN 1 ELSE 2 END)) FROM "customer"' + string: 'SELECT ("customer"."age" + (CASE WHEN 1 THEN 0 WHEN 0 THEN 1 ELSE 2 END)) FROM "customer"' }, mysql: { text : 'SELECT (`customer`.`age` + (CASE WHEN ? THEN ? WHEN ? THEN ? ELSE ? END)) FROM `customer`', @@ -68,7 +68,7 @@ Harness.test({ }, sqlite: { text : 'SELECT ((CASE WHEN $1 THEN $2 WHEN $3 THEN $4 ELSE $5 END) + $6) FROM "customer"', - string: 'SELECT ((CASE WHEN TRUE THEN 0 WHEN FALSE THEN 1 ELSE 2 END) + 3) FROM "customer"' + string: 'SELECT ((CASE WHEN 1 THEN 0 WHEN 0 THEN 1 ELSE 2 END) + 3) FROM "customer"' }, mysql: { text : 'SELECT ((CASE WHEN ? THEN ? WHEN ? THEN ? ELSE ? END) + ?) FROM `customer`', @@ -96,7 +96,7 @@ Harness.test({ }, sqlite: { text : 'SELECT (CASE WHEN $1 THEN $2 WHEN $3 THEN $4 ELSE ("customer"."age" BETWEEN $5 AND $6) END) FROM "customer"', - string: 'SELECT (CASE WHEN TRUE THEN 0 WHEN FALSE THEN 1 ELSE ("customer"."age" BETWEEN 10 AND 20) END) FROM "customer"' + string: 'SELECT (CASE WHEN 1 THEN 0 WHEN 0 THEN 1 ELSE ("customer"."age" BETWEEN 10 AND 20) END) FROM "customer"' }, mysql: { text : 'SELECT (CASE WHEN ? THEN ? WHEN ? THEN ? ELSE (`customer`.`age` BETWEEN ? AND ?) END) FROM `customer`', @@ -124,7 +124,7 @@ Harness.test({ }, sqlite: { text : 'SELECT (CASE WHEN $1 THEN $2 WHEN $3 THEN $4 END) FROM "customer"', - string: 'SELECT (CASE WHEN TRUE THEN 0 WHEN FALSE THEN 1 END) FROM "customer"' + string: 'SELECT (CASE WHEN 1 THEN 0 WHEN 0 THEN 1 END) FROM "customer"' }, mysql: { text : 'SELECT (CASE WHEN ? THEN ? WHEN ? THEN ? END) FROM `customer`', diff --git a/test/dialects/create-table-tests.js b/test/dialects/create-table-tests.js index 160e726..872d51f 100644 --- a/test/dialects/create-table-tests.js +++ b/test/dialects/create-table-tests.js @@ -479,28 +479,64 @@ var users = Table.define({ Harness.test({ query: users.create(), pg: { - text : 'CREATE TABLE "users" ("id" int PRIMARY KEY REFERENCES "entity"("id") DEFERRABLE INITIALLY DEFERRED)', + text: 'CREATE TABLE "users" ("id" int PRIMARY KEY REFERENCES "entity"("id") DEFERRABLE INITIALLY DEFERRED)', string: 'CREATE TABLE "users" ("id" int PRIMARY KEY REFERENCES "entity"("id") DEFERRABLE INITIALLY DEFERRED)' }, sqlite: { - text : 'CREATE TABLE "users" ("id" int PRIMARY KEY REFERENCES "entity"("id") DEFERRABLE INITIALLY DEFERRED)', + text: 'CREATE TABLE "users" ("id" int PRIMARY KEY REFERENCES "entity"("id") DEFERRABLE INITIALLY DEFERRED)', string: 'CREATE TABLE "users" ("id" int PRIMARY KEY REFERENCES "entity"("id") DEFERRABLE INITIALLY DEFERRED)' }, mysql: { - text : 'CREATE TABLE `users` (`id` int PRIMARY KEY REFERENCES `entity`(`id`) DEFERRABLE INITIALLY DEFERRED)', + text: 'CREATE TABLE `users` (`id` int PRIMARY KEY REFERENCES `entity`(`id`) DEFERRABLE INITIALLY DEFERRED)', string: 'CREATE TABLE `users` (`id` int PRIMARY KEY REFERENCES `entity`(`id`) DEFERRABLE INITIALLY DEFERRED)' }, mssql: { - text : 'CREATE TABLE [users] ([id] int PRIMARY KEY REFERENCES [entity]([id]) DEFERRABLE INITIALLY DEFERRED)', + text: 'CREATE TABLE [users] ([id] int PRIMARY KEY REFERENCES [entity]([id]) DEFERRABLE INITIALLY DEFERRED)', string: 'CREATE TABLE [users] ([id] int PRIMARY KEY REFERENCES [entity]([id]) DEFERRABLE INITIALLY DEFERRED)' }, oracle: { - text : 'CREATE TABLE "users" ("id" int PRIMARY KEY REFERENCES "entity"("id") DEFERRABLE INITIALLY DEFERRED)', + text: 'CREATE TABLE "users" ("id" int PRIMARY KEY REFERENCES "entity"("id") DEFERRABLE INITIALLY DEFERRED)', string: 'CREATE TABLE "users" ("id" int PRIMARY KEY REFERENCES "entity"("id") DEFERRABLE INITIALLY DEFERRED)' }, params: [] }); +// UNIQUE COLUMN TESTS +Harness.test({ + query: Table.define({ + name: 'post', + columns: [{ + name: 'id', + dataType: 'int', + //primaryKey: true, + //notNull: true, + unique: true + }] + }).create(), + pg: { + text : 'CREATE TABLE "post" ("id" int UNIQUE)', + string: 'CREATE TABLE "post" ("id" int UNIQUE)' + }, + sqlite: { + text : 'CREATE TABLE "post" ("id" int UNIQUE)', + string: 'CREATE TABLE "post" ("id" int UNIQUE)' + }, + mysql: { + text : 'CREATE TABLE `post` (`id` int UNIQUE)', + string: 'CREATE TABLE `post` (`id` int UNIQUE)' + }, + mssql: { + text : 'CREATE TABLE [post] ([id] int UNIQUE)', + string: 'CREATE TABLE [post] ([id] int UNIQUE)' + }, + oracle: { + text : 'CREATE TABLE "post" ("id" int UNIQUE)', + string: 'CREATE TABLE "post" ("id" int UNIQUE)' + }, + params: [] +}); + + var noUsers = Table.define({ name: 'no_users', columns: { @@ -539,4 +575,72 @@ Harness.test({ string: 'CREATE TABLE "no_users" ("id" int PRIMARY KEY REFERENCES "entity"("id"))' }, params: [] +}); + +Harness.test({ + query: Table.define({ + name: 'post', + columns: [{ + name: 'id', + dataType: 'int', + //primaryKey: true, + notNull: true, + unique: true + }] + }).create(), + pg: { + text : 'CREATE TABLE "post" ("id" int NOT NULL UNIQUE)', + string: 'CREATE TABLE "post" ("id" int NOT NULL UNIQUE)' + }, + sqlite: { + text : 'CREATE TABLE "post" ("id" int NOT NULL UNIQUE)', + string: 'CREATE TABLE "post" ("id" int NOT NULL UNIQUE)' + }, + mysql: { + text : 'CREATE TABLE `post` (`id` int NOT NULL UNIQUE)', + string: 'CREATE TABLE `post` (`id` int NOT NULL UNIQUE)' + }, + mssql: { + text : 'CREATE TABLE [post] ([id] int NOT NULL UNIQUE)', + string: 'CREATE TABLE [post] ([id] int NOT NULL UNIQUE)' + }, + oracle: { + text : 'CREATE TABLE "post" ("id" int NOT NULL UNIQUE)', + string: 'CREATE TABLE "post" ("id" int NOT NULL UNIQUE)' + }, + params: [] +}); + +Harness.test({ + query: Table.define({ + name: 'post', + columns: [{ + name: 'id', + dataType: 'int', + primaryKey: true, + //notNull: true, + unique: true + }] + }).create(), + pg: { + text : 'CREATE TABLE "post" ("id" int PRIMARY KEY)', + string: 'CREATE TABLE "post" ("id" int PRIMARY KEY)' + }, + sqlite: { + text : 'CREATE TABLE "post" ("id" int PRIMARY KEY)', + string: 'CREATE TABLE "post" ("id" int PRIMARY KEY)' + }, + mysql: { + text : 'CREATE TABLE `post` (`id` int PRIMARY KEY)', + string: 'CREATE TABLE `post` (`id` int PRIMARY KEY)' + }, + mssql: { + text : 'CREATE TABLE [post] ([id] int PRIMARY KEY)', + string: 'CREATE TABLE [post] ([id] int PRIMARY KEY)' + }, + oracle: { + text : 'CREATE TABLE "post" ("id" int PRIMARY KEY)', + string: 'CREATE TABLE "post" ("id" int PRIMARY KEY)' + }, + params: [] }); \ No newline at end of file diff --git a/test/dialects/insert-tests.js b/test/dialects/insert-tests.js index b472f51..804daa0 100644 --- a/test/dialects/insert-tests.js +++ b/test/dialects/insert-tests.js @@ -4,6 +4,11 @@ var Harness = require('./support'); var post = Harness.definePostTable(); var user = Harness.defineUserTable(); +var arrayTable = require('../../lib/table').define({ + name: 'arraytest', + columns: ['id', 'numbers'] +}); + Harness.test({ query: post.insert(post.content.value('test'), post.userId.value(1)), pg: { @@ -630,3 +635,43 @@ Harness.test({ }, params: [] }); + +Harness.test({ + query: arrayTable.insert(arrayTable.id.value(1), arrayTable.numbers.value([2, 3, 4])), + pg: { + text : 'INSERT INTO "arraytest" ("id", "numbers") VALUES ($1, $2)', + string: 'INSERT INTO "arraytest" ("id", "numbers") VALUES (1, \'{2,3,4}\')' + }, + sqlite: { + text : 'INSERT INTO "arraytest" ("id", "numbers") VALUES ($1, $2)', + string: 'INSERT INTO "arraytest" ("id", "numbers") VALUES (1, \'[2,3,4]\')' + }, + mysql: { + text : 'INSERT INTO `arraytest` (`id`, `numbers`) VALUES (?, ?)', + string: 'INSERT INTO `arraytest` (`id`, `numbers`) VALUES (1, (2, 3, 4))' + }, + oracle: { + text : 'INSERT INTO "arraytest" ("id", "numbers") VALUES (:1, :2)', + string: 'INSERT INTO "arraytest" ("id", "numbers") VALUES (1, (2, 3, 4))' + } +}); + +Harness.test({ + query: arrayTable.insert(arrayTable.id.value(1), arrayTable.numbers.value(["one", "two", "three"])), + pg: { + text : 'INSERT INTO "arraytest" ("id", "numbers") VALUES ($1, $2)', + string: 'INSERT INTO "arraytest" ("id", "numbers") VALUES (1, \'{"one","two","three"}\')' + }, + sqlite: { + text : 'INSERT INTO "arraytest" ("id", "numbers") VALUES ($1, $2)', + string: 'INSERT INTO "arraytest" ("id", "numbers") VALUES (1, \'["one","two","three"]\')' + }, + mysql: { + text : 'INSERT INTO `arraytest` (`id`, `numbers`) VALUES (?, ?)', + string: 'INSERT INTO `arraytest` (`id`, `numbers`) VALUES (1, (\'one\', \'two\', \'three\'))' + }, + oracle: { + text : 'INSERT INTO "arraytest" ("id", "numbers") VALUES (:1, :2)', + string: 'INSERT INTO "arraytest" ("id", "numbers") VALUES (1, (\'one\', \'two\', \'three\'))' + } +}); diff --git a/test/dialects/subquery-tests.js b/test/dialects/subquery-tests.js index 1c79c28..fe5b2aa 100644 --- a/test/dialects/subquery-tests.js +++ b/test/dialects/subquery-tests.js @@ -29,7 +29,7 @@ Harness.test({ string: 'SELECT "user"."name" FROM "user" WHERE ("user"."id" IN (SELECT "post"."userId" FROM "post"))' }, params: [] -}) +}); Harness.test({ query: user.name.in( diff --git a/test/dialects/update-tests.js b/test/dialects/update-tests.js index 5639c3a..eb3d0b3 100644 --- a/test/dialects/update-tests.js +++ b/test/dialects/update-tests.js @@ -3,6 +3,7 @@ var Harness = require('./support'); var post = Harness.definePostTable(); var user = Harness.defineUserTable(); +var variable = Harness.defineVariableTable(); Harness.test({ query: post.update({ @@ -193,3 +194,53 @@ Harness.test({ }, params: [new Buffer('test')] }); + +// Boolean updates +Harness.test({ + query: variable.update({ + a: true, + b: false + }), + pg: { + text : 'UPDATE "variable" SET "a" = $1, "b" = $2', + string: 'UPDATE "variable" SET "a" = TRUE, "b" = FALSE' + }, + sqlite: { + text : 'UPDATE "variable" SET "a" = $1, "b" = $2', + string: 'UPDATE "variable" SET "a" = 1, "b" = 0' + }, + mysql: { + text : 'UPDATE `variable` SET `a` = ?, `b` = ?', + string: 'UPDATE `variable` SET `a` = TRUE, `b` = FALSE' + }, + oracle: { + text : 'UPDATE "variable" SET "a" = :1, "b" = :2', + string: 'UPDATE "variable" SET "a" = TRUE, "b" = FALSE' + }, + params: [true, false] +}); + +// Object updates +Harness.test({ + query: variable.update({ + a: {"id": 1, "value": 2}, + b: [{"id": 2, "value": 3}, {"id": 3, "value": 4}] + }), + pg: { + text : 'UPDATE "variable" SET "a" = $1, "b" = $2', + string: 'UPDATE "variable" SET "a" = \'{"id":1,"value":2}\', "b" = \'[{"id":2,"value":3},{"id":3,"value":4}]\'' + }, + sqlite: { + text : 'UPDATE "variable" SET "a" = $1, "b" = $2', + string: 'UPDATE "variable" SET "a" = \'{"id":1,"value":2}\', "b" = \'[{"id":2,"value":3},{"id":3,"value":4}]\'' + }, + mysql: { + text : 'UPDATE `variable` SET `a` = ?, `b` = ?', + string: 'UPDATE `variable` SET `a` = \'{"id":1,"value":2}\', `b` = (\'{"id":2,"value":3}\', \'{"id":3,"value":4}\')' + }, + oracle: { + text : 'UPDATE "variable" SET "a" = :1, "b" = :2', + string: 'UPDATE "variable" SET "a" = \'{"id":1,"value":2}\', "b" = (\'{"id":2,"value":3}\', \'{"id":3,"value":4}\')' + }, + params: [{"id": 1, "value": 2}, [{"id": 2, "value": 3}, {"id": 3, "value": 4}]] +}); \ No newline at end of file diff --git a/test/index-tests.js b/test/index-tests.js index 0893a7e..f25839e 100644 --- a/test/index-tests.js +++ b/test/index-tests.js @@ -195,4 +195,20 @@ suite('index', function() { }); }); + test('mssql default parameter place holder is @index', function() { + var Sql = sql.Sql; + var mssql = new Sql('mssql'); + var query = mssql.select(user.id).from(user).where(user.email.equals('x@y.com')).toQuery(); + assert.equal(query.text, 'SELECT [user].[id] FROM [user] WHERE ([user].[email] = @1)'); + assert.equal(query.values[0], 'x@y.com'); + }); + + test('mssql override default parameter placeholder with ?', function() { + var Sql = sql.Sql; + var mssql = new Sql('mssql',{questionMarkParameterPlaceholder:true}); + var query = mssql.select(user.id).from(user).where(user.email.equals('x@y.com')).toQuery(); + assert.equal(query.text, 'SELECT [user].[id] FROM [user] WHERE ([user].[email] = ?)'); + assert.equal(query.values[0], 'x@y.com'); + }); + });