diff --git a/.travis.yml b/.travis.yml index f04dabe6..07becfb2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,8 @@ node_js: - "4.2" - "0.10" - "0.11" + +matrix: + allow_failures: + - node_js: "0.10" + - node_js: "0.11" \ No newline at end of file diff --git a/README.md b/README.md index 193ab2be..e1a3af8c 100644 --- a/README.md +++ b/README.md @@ -183,5 +183,5 @@ Usually after a few high-quality pull requests and friendly interactions we will After all, open source belongs to everyone. -##license +## license MIT diff --git a/lib/column.js b/lib/column.js index 3c5e70f5..55a3da18 100644 --- a/lib/column.js +++ b/lib/column.js @@ -20,6 +20,7 @@ var Column = function(config) { direction : new TextNode('DESC') }); this.dataType = config.dataType; + this.defaultValue = config.defaultValue; }; // mix in value expression diff --git a/lib/dialect/mssql.js b/lib/dialect/mssql.js index 48c1ad45..6e71f2e1 100644 --- a/lib/dialect/mssql.js +++ b/lib/dialect/mssql.js @@ -20,7 +20,7 @@ var Mssql = function(config) { this.config = config || {}; }; -var Postgres = require(__dirname + '/postgres'); +var Postgres = require('./postgres'); util.inherits(Mssql, Postgres); @@ -30,6 +30,10 @@ Mssql.prototype._quoteCharacter = '['; Mssql.prototype._arrayAggFunctionName = ''; +Mssql.prototype.visitReplace = function(replace) { + throw new Error('Mssql does not support REPLACE.'); +}; + Mssql.prototype._getParameterPlaceholder = function(index, value) { if (this.config.questionMarkParameterPlaceholder) return '?'; return '@' + index; @@ -44,6 +48,10 @@ Mssql.prototype.visitBinary = function(binary) { return [text]; } + if(binary.operator === '||'){ + return ['(' + this.visit(binary.left) + ' + ' + this.visit(binary.right) + ')']; + } + if (!isRightSideArray(binary)){ return Mssql.super_.prototype.visitBinary.call(this, binary); } @@ -186,7 +194,7 @@ Mssql.prototype.visitColumn = function(columnNode) { table = columnNode.table; inSelectClause = !this._selectOrDeleteEndIndex; if (isCountStarExpression(columnNode)) return _countStar(); - if (inSelectClause && !table.alias && columnNode.asArray) return _arrayAgg(); + if (inSelectClause && table && !table.alias && columnNode.asArray) return _arrayAgg(); return Mssql.super_.prototype.visitColumn.call(this, columnNode); }; @@ -246,14 +254,40 @@ 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 txt = name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; + var _this = this; + + function _extract() { + var nodes = functionCall.nodes.map(_this.visit.bind(_this)); + if (nodes.length != 1) throw new Error('Not enough parameters passed to ' + functionCall.name + ' function'); + var txt = 'DATEPART(' + functionCall.name.toLowerCase() + ', ' + (nodes[0]+'') + ')'; + return txt; + } + + var txt; + // Override date functions since mssql uses datepart + if (['YEAR', 'MONTH', 'DAY', 'HOUR'].indexOf(functionCall.name) >= 0) txt = _extract(); + // Override CURRENT_TIMESTAMP function to remove parens + else if ('CURRENT_TIMESTAMP' == functionCall.name) txt = functionCall.name; + else { + var name = functionCall.name; + // override the LENGTH function since mssql calls it LEN + if (name == "LENGTH") name = "LEN"; + txt = name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; + } this._visitingFunctionCall = false; return [txt]; }; +Mssql.prototype.visitJoin = function(join) { + if (join.subType !== 'LEFT LATERAL') return Mssql.super_.prototype.visitJoin.call(this, join); + var result = []; + this._visitingJoin = true; + result = result.concat(this.visit(join.from)); + result = result.concat('OUTER APPLY'); + result = result.concat(this.visit(join.to)); + return result; +}; + Mssql.prototype.visitOrderBy = function(orderBy) { var result=Mssql.super_.prototype.visitOrderBy.call(this, orderBy); var offsetNode=orderBy.msSQLOffsetNode; @@ -365,6 +399,10 @@ Mssql.prototype.visitOnDuplicate = function(onDuplicate) { throw new Error('MSSQL does not allow onDuplicate clause.'); }; +Mssql.prototype.visitOnConflict = function(onConflict) { + throw new Error('MSSQL does not allow onConflict clause.'); +}; + Mssql.prototype.visitReturning = function() { // TODO: need to add some code to the INSERT clause to support this since its the equivalent of the OUTPUT clause // in MS SQL which appears before the values, not at the end of the statement. diff --git a/lib/dialect/mysql.js b/lib/dialect/mysql.js index 9d582ce7..0a63f3be 100644 --- a/lib/dialect/mysql.js +++ b/lib/dialect/mysql.js @@ -2,6 +2,7 @@ var util = require('util'); var assert = require('assert'); +var _ = require('lodash'); var Mysql = function(config) { this.output = []; @@ -9,7 +10,7 @@ var Mysql = function(config) { this.config = config || {}; }; -var Postgres = require(__dirname + '/postgres'); +var Postgres = require('./postgres'); util.inherits(Mysql, Postgres); @@ -19,6 +20,42 @@ Mysql.prototype._quoteCharacter = '`'; Mysql.prototype._arrayAggFunctionName = 'GROUP_CONCAT'; +Mysql.prototype.visitReplace = function(replace) { + var self = this; + // don't use table.column for replaces + this._visitedReplace = true; + + var result = ['REPLACE']; + result = result.concat(replace.nodes.map(this.visit.bind(this))); + result.push('INTO ' + this.visit(this._queryNode.table.toNode())); + result.push('(' + replace.columns.map(this.visit.bind(this)).join(', ') + ')'); + + var paramNodes = replace.getParameters(); + + if (paramNodes.length > 0) { + var paramText = paramNodes.map(function (paramSet) { + return paramSet.map(function (param) { + return self.visit(param); + }).join(', '); + }).map(function (param) { + return '('+param+')'; + }).join(', '); + + result.push('VALUES', paramText); + + if (result.slice(2, 5).join(' ') === '() VALUES ()') { + result.splice(2, 3, 'DEFAULT VALUES'); + } + } + + this._visitedReplace = false; + + if (result[2] === 'DEFAULT VALUES') { + result[2] = '() VALUES ()'; + } + return result; +}; + Mysql.prototype._getParameterPlaceholder = function() { return '?'; }; @@ -46,6 +83,10 @@ Mysql.prototype.visitOnDuplicate = function(onDuplicate) { return result; }; +Mysql.prototype.visitOnConflict = function(onConflict) { + throw new Error('Mysql does not allow onConflict clause.'); +}; + Mysql.prototype.visitReturning = function() { throw new Error('MySQL does not allow returning clause.'); }; @@ -102,4 +143,79 @@ Mysql.prototype.visitBinary = function(binary) { return Mysql.super_.prototype.visitBinary.call(this, binary); }; +Mysql.prototype.visitFunctionCall = function(functionCall) { + var _this=this; + + this._visitingFunctionCall = true; + + function _extract() { + var nodes = functionCall.nodes.map(_this.visit.bind(_this)); + if (nodes.length != 1) throw new Error('Not enough parameters passed to ' + functionCall.name + ' function'); + var txt = functionCall.name + '(' + (nodes[0]+'') + ')'; + return txt; + } + + var txt=""; + var name = functionCall.name; + // Override date functions since mysql is different than postgres + if (['YEAR', 'MONTH', 'DAY', 'HOUR'].indexOf(functionCall.name) >= 0) txt = _extract(); + // Override CURRENT_TIMESTAMP function to remove parens + else if ('CURRENT_TIMESTAMP' == functionCall.name) txt = functionCall.name; + else txt = name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; + + this._visitingFunctionCall = false; + return [txt]; +}; + +Mysql.prototype.visitColumn = function(columnNode) { + var self = this; + var inSelectClause; + + function isCountStarExpression(columnNode){ + if (!columnNode.aggregator) return false; + if (columnNode.aggregator.toLowerCase()!='count') return false; + if (!columnNode.star) return false; + return true; + } + + function _countStar(){ + // Implement our own since count(table.*) is invalid in Mysql + var result='COUNT(*)'; + if(inSelectClause && columnNode.alias) { + result += ' AS ' + self.quote(columnNode.alias); + } + return result; + } + + inSelectClause = !this._selectOrDeleteEndIndex; + if(isCountStarExpression(columnNode)) return _countStar(); + return Mysql.super_.prototype.visitColumn.call(this, columnNode); +}; + +Mysql.prototype.visitInterval = function(interval) { + var parameter; + if(_.isNumber(interval.years)) { + if(_.isNumber(interval.months)) { + parameter = "'" + interval.years + '-' + interval.months + "' YEAR_MONTH"; + } else { + parameter = interval.years + ' YEAR'; + } + } else if(_.isNumber(interval.months)) { + parameter = interval.months + ' MONTH'; + } else if(_.isNumber(interval.days)) { + parameter = "'" + interval.days + ' ' + + (_.isNumber(interval.hours)?interval.hours:0) + ':' + + (_.isNumber(interval.minutes)?interval.minutes:0) + ':' + + (_.isNumber(interval.seconds)?interval.seconds:0) + "' DAY_SECOND"; + } else { + parameter = "'" + (_.isNumber(interval.hours)?interval.hours:0) + ':' + + (_.isNumber(interval.minutes)?interval.minutes:0) + ':' + + (_.isNumber(interval.seconds)?interval.seconds:0) + "' HOUR_SECOND"; + } + var result = "INTERVAL " + parameter; + return result; +}; + + + module.exports = Mysql; diff --git a/lib/dialect/oracle.js b/lib/dialect/oracle.js index 6b597154..a55e7aca 100644 --- a/lib/dialect/oracle.js +++ b/lib/dialect/oracle.js @@ -9,14 +9,18 @@ var Oracle = function(config) { this.config = config || {}; }; -var Postgres = require(__dirname + '/postgres'); +var Postgres = require('./postgres'); -var Mssql = require(__dirname + '/mssql'); +var Mssql = require('./mssql'); util.inherits(Oracle, Postgres); Oracle.prototype._myClass = Oracle; +Oracle.prototype.visitReplace = function(replace) { + throw new Error('Oracle does not support REPLACE.'); +}; + Oracle.prototype._aliasText = ' '; Oracle.prototype._getParameterPlaceholder = function(index, value) { /* jshint unused: false */ @@ -28,6 +32,56 @@ Oracle.prototype.visitAlias = function(alias) { return result; }; +Oracle.prototype.visitAlter = function(alter) { + var self=this; + var errMsg='ALTER TABLE cannot be used to perform multiple different operations in the same statement.'; + + // Implement our own add column: + // PostgreSQL: ALTER TABLE "name" ADD COLUMN "col1", ADD COLUMN "col2" + // Oracle: ALTER TABLE "name" ADD ("col1", "col2") + function _addColumn(){ + self._visitingAlter = true; + var table = self._queryNode.table; + self._visitingAddColumn = true; + var result='ALTER TABLE '+self.visit(table.toNode())+' ADD ('+self.visit(alter.nodes[0].nodes[0]); + for (var i= 1,len=alter.nodes.length; i 0) { + colspec += ', ' + foreign_key_nodes.map(this.visit.bind(this)).join(', '); + } colspec += ')'; result.push(colspec); this._visitCreateCompoundPrimaryKey = false; @@ -580,7 +608,7 @@ Postgres.prototype.visitOverlap = function(overlap) { }; Postgres.prototype.visitQuery = function(queryNode) { - if (this._queryNode) return this.visitSubquery(queryNode); + if (this._queryNode) return this.visitSubquery(queryNode,dontParenthesizeSubQuery(this._queryNode)); this._queryNode = queryNode; // need to sort the top level query nodes on visitation priority // so select/insert/update/delete comes before from comes before where @@ -601,6 +629,7 @@ Postgres.prototype.visitQuery = function(queryNode) { break; case "INDEXES": case "INSERT": + case "REPLACE": case "UPDATE": case "CREATE": case "DROP": @@ -628,7 +657,8 @@ Postgres.prototype.visitQuery = function(queryNode) { actions.push(new Select().add('*')); isSelect = true; } - if(missingFrom) { + if(missingFrom && queryNode.table instanceof Table) { + // the instanceof handles the situation where a sql.select(some expression) is used and there should be no FROM clause targets.push(new From().add(queryNode.table)); } if (createView) { @@ -661,7 +691,7 @@ Postgres.prototype.visitQueryHelper=function(actions,targets,filters){ return this.output; }; -Postgres.prototype.visitSubquery = function(queryNode) { +Postgres.prototype.visitSubquery = function(queryNode,dontParenthesize) { // create another query builder of the current class to build the subquery var subQuery = new this._myClass(this.config); @@ -679,6 +709,9 @@ Postgres.prototype.visitSubquery = function(queryNode) { } var alias = queryNode.alias; + if (dontParenthesize) { + return [subQuery.output.join(' ') + (alias ? ' ' + this.quote(alias) : '')]; + } return ['(' + subQuery.output.join(' ') + ')' + (alias ? ' ' + this.quote(alias) : '')]; }; @@ -698,7 +731,7 @@ Postgres.prototype.visitTable = function(tableNode) { Postgres.prototype.visitColumn = function(columnNode) { var table = columnNode.table; - var inInsertUpdateClause = this._visitedInsert || this._visitingUpdateTargetColumn; + var inInsertUpdateClause = this._visitedInsert || this._visitedReplace || this._visitingUpdateTargetColumn; var inDdlClause = this._visitingAddColumn || this._visitingAlter || this._visitingCreate; var inSelectClause = this.visitingReturning || @@ -713,7 +746,7 @@ Postgres.prototype.visitColumn = function(columnNode) { var inCast = this._visitingCast; var txt = []; var closeParen = 0; - if(inSelectClause && (!table.alias || !!columnNode.alias)) { + if(inSelectClause && (table && !table.alias || !!columnNode.alias)) { if (columnNode.asArray) { closeParen++; txt.push(this._arrayAggFunctionName+'('); @@ -730,16 +763,18 @@ Postgres.prototype.visitColumn = function(columnNode) { } } if(!inInsertUpdateClause && !this.visitingReturning && !this._visitingCreate && !this._visitingAlter && !columnNode.subfieldContainer) { - if(typeof table.alias === 'string') { - txt.push(this.quote(table.alias)); - } else { - if(table.getSchema()) { - txt.push(this.quote(table.getSchema())); - txt.push('.'); + if (table) { + if (typeof table.alias === 'string') { + txt.push(this.quote(table.alias)); + } else { + if (table.getSchema()) { + txt.push(this.quote(table.getSchema())); + txt.push('.'); + } + txt.push(this.quote(table.getName())); } - txt.push(this.quote(table.getName())); + txt.push('.'); } - txt.push('.'); } if (columnNode.star) { var allCols = []; @@ -760,6 +795,14 @@ Postgres.prototype.visitColumn = function(columnNode) { txt.push('*'); } } + else if (columnNode.isConstant) { + // this injects directly into SELECT statement rather than creating a parameter + // txt.push(this._getParameterValue(columnNode.literalValue)) + // currently thinking it is better to generate a parameter + var value = columnNode.constantValue; + this.params.push(value); + txt.push(this._getParameterText(this.params.length, value)); + } else { if (columnNode.subfieldContainer) { txt.push('(' + this.visitColumn(columnNode.subfieldContainer) + ').'); @@ -789,6 +832,9 @@ Postgres.prototype.visitColumn = function(columnNode) { if (!columnNode.primaryKey && columnNode.unique) { txt.push(' UNIQUE'); } + if (columnNode.defaultValue !== undefined) { + txt.push(' DEFAULT ' + this._getParameterValue(columnNode.defaultValue)); + } } if (!!columnNode.references) { @@ -807,7 +853,6 @@ Postgres.prototype.visitColumn = function(columnNode) { columnNode.name + ' (REFERENCES statements within CREATE TABLE and ADD COLUMN statements' + ' require a table and column)'); - txt.push(' REFERENCES '); if(columnNode.references.schema) { txt.push(this.quote(columnNode.references.schema) + '.'); @@ -817,18 +862,97 @@ Postgres.prototype.visitColumn = function(columnNode) { var onDelete = columnNode.references.onDelete; if (onDelete) onDelete = onDelete.toUpperCase(); - if (onDelete === 'CASCADE' || onDelete === 'RESTRICT') { + if (onDelete === 'CASCADE' || onDelete === 'RESTRICT' || onDelete === 'SET NULL' || onDelete === 'SET DEFAULT' || onDelete === 'NO ACTION') { txt.push(' ON DELETE ' + onDelete); } + var onUpdate = columnNode.references.onUpdate; + if (onUpdate) onUpdate = onUpdate.toUpperCase(); + if (onUpdate === 'CASCADE' || onUpdate === 'RESTRICT' || onUpdate === 'SET NULL' || onUpdate === 'SET DEFAULT' || onUpdate === 'NO ACTION') { + txt.push(' ON UPDATE ' + onUpdate); + } + var constraint = columnNode.references.constraint; + if (constraint) { + constraint = ' ' + constraint.toUpperCase(); + txt.push(constraint); + } + } + } + } + return [txt.join('')]; +}; + +Postgres.prototype.visitForeignKey = function(foreignKeyNode) +{ + var txt = []; + if(this._visitingCreate) { + assert(foreignKeyNode.table, 'Foreign table missing for table reference'); + assert(foreignKeyNode.columns, 'Columns missing for table reference'); + if(foreignKeyNode.refColumns !== undefined) { + assert.equal(foreignKeyNode.columns.length, foreignKeyNode.refColumns.length, 'Number of local columns and foreign columns differ in table reference'); + } + if(foreignKeyNode.name !== undefined) { + txt.push('CONSTRAINT ' + this.quote(foreignKeyNode.name) + ' '); + } + txt.push('FOREIGN KEY ( '); + for(var i = 0; i < foreignKeyNode.columns.length; i++) { + if(i>0) { + txt.push(', '); + } + txt.push(this.quote(foreignKeyNode.columns[i])); + } + txt.push(' ) REFERENCES '); + if(foreignKeyNode.schema !== undefined) { + txt.push(this.quote(foreignKeyNode.schema) + '.'); + } + txt.push(this.quote(foreignKeyNode.table)); + if(foreignKeyNode.refColumns !== undefined) { + txt.push(' ( '); + for(i = 0; i < foreignKeyNode.refColumns.length; i++) { + if(i>0) { + txt.push(', '); + } + txt.push(this.quote(foreignKeyNode.refColumns[i])); + } + txt.push(' )'); + } + var onDelete = foreignKeyNode.onDelete; + if(onDelete) { + onDelete = onDelete.toUpperCase(); + if(onDelete === 'CASCADE' || onDelete === 'RESTRICT' || onDelete === 'SET NULL' || onDelete === 'SET DEFAULT' || onDelete === 'NO ACTION') { + txt.push(' ON DELETE ' + onDelete); } } + var onUpdate = foreignKeyNode.onUpdate; + if(onUpdate) { + onUpdate = onUpdate.toUpperCase(); + if(onUpdate === 'CASCADE' || onUpdate === 'RESTRICT' || onUpdate === 'SET NULL' || onUpdate === 'SET DEFAULT' || onUpdate === 'NO ACTION') { + txt.push(' ON UPDATE ' + onUpdate); + } + } + if(foreignKeyNode.constraint) { + txt.push(' ' + foreignKeyNode.constraint.toUpperCase()); + } } return [txt.join('')]; }; Postgres.prototype.visitFunctionCall = function(functionCall) { this._visitingFunctionCall = true; - var txt = functionCall.name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; + var _this = this; + + function _extract() { + var nodes = functionCall.nodes.map(_this.visit.bind(_this)); + if (nodes.length != 1) throw new Error('Not enough parameters passed to ' + functionCall.name + ' function'); + var txt = 'EXTRACT(' + functionCall.name + ' FROM ' + (nodes[0]+'') + ')'; + return txt; + } + + var txt = ""; + // Override date functions since postgres (and others) uses extract + if (['YEAR', 'MONTH', 'DAY', 'HOUR'].indexOf(functionCall.name) >= 0) txt = _extract(); + // Override CURRENT_TIMESTAMP function to remove parens + else if ('CURRENT_TIMESTAMP' == functionCall.name) txt = functionCall.name; + else txt = functionCall.name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; this._visitingFunctionCall = false; return [txt]; }; @@ -877,6 +1001,10 @@ Postgres.prototype.visitIfNotExists = function() { return ['IF NOT EXISTS']; }; +Postgres.prototype.visitOrIgnore = function() { + throw new Error('PostgreSQL does not allow orIgnore clause.'); +}; + Postgres.prototype.visitCascade = function() { return ['CASCADE']; }; @@ -894,6 +1022,7 @@ Postgres.prototype.visitForShare = function() { }; Postgres.prototype.visitJoin = function(join) { + if (join.subType === 'LEFT LATERAL') return this.visitLeftJoinLateral(join); var result = []; this._visitingJoin = true; result = result.concat(this.visit(join.from)); @@ -904,6 +1033,16 @@ Postgres.prototype.visitJoin = function(join) { return result; }; +Postgres.prototype.visitLeftJoinLateral = function(join) { + var result = []; + this._visitingJoin = true; + result = result.concat(this.visit(join.from)); + result = result.concat('LEFT JOIN LATERAL'); + result = result.concat(this.visit(join.to)); + result = result.concat('ON true'); + return result; +}; + Postgres.prototype.visitLiteral = function(node) { var txt = [node.literal]; if(node.alias) { @@ -924,6 +1063,37 @@ Postgres.prototype.visitOnDuplicate = function(onDuplicate) { throw new Error('PostgreSQL does not allow onDuplicate clause.'); }; +Postgres.prototype.visitOnConflict = function(onConflict) { + var result = ['ON CONFLICT']; + var columns = []; + var updateClause = [], i, col; + var table = this._queryNode.table; + if(onConflict.constraint) + result.push(['ON CONSTRAINT', this.quote(onConflict.constraint)].join(' ')); + else if(onConflict.columns) { + for(i=0; i < onConflict.columns.length; i++) { + columns.push(this.quote(table.getColumn(onConflict.columns[i]).name)); + } + result.push( '(' + columns.join(', ') + ')' ); + } + + if(onConflict.update){ + updateClause.push("DO UPDATE SET"); + var update = onConflict.update; + var setClause = []; + for(i=0; i 0) { + var paramText = paramNodes.map(function (paramSet) { + return paramSet.map(function (param) { + return self.visit(param); + }).join(', '); + }).map(function (param) { + return '('+param+')'; + }).join(', '); + + result.push('VALUES', paramText); + + if (result.slice(2, 5).join(' ') === '() VALUES ()') { + result.splice(2, 3, 'DEFAULT VALUES'); + } + } + + this._visitedReplace = false; + + return result; +}; + Sqlite.prototype._getParameterValue = function(value) { if (Buffer.isBuffer(value)) { value = 'x' + this._getParameterValue(value.toString('hex')); @@ -42,16 +75,16 @@ Sqlite.prototype.visitDropColumn = function() { throw new Error('SQLite does not allow dropping columns.'); }; -Sqlite.prototype.visitFunctionCall = function(functionCall) { - var _this=this; +Sqlite.prototype.visitFunctionCall = function (functionCall) { + var _this = this; - this._visitingFunctionCall = true; + this._visitingFunctionCall = true; function _left() { // convert LEFT(column,4) to SUBSTR(column,1,4) var nodes = functionCall.nodes.map(_this.visit.bind(_this)); if (nodes.length != 2) throw new Error('Not enough parameters passed to LEFT function.'); - var txt = "SUBSTR(" + (nodes[0]+'') + ', 1, ' + (nodes[1]+'') + ')'; + var txt = "SUBSTR(" + (nodes[0] + '') + ', 1, ' + (nodes[1] + '') + ')'; return txt; } @@ -59,18 +92,49 @@ Sqlite.prototype.visitFunctionCall = function(functionCall) { // convert RIGHT(column,4) to SUBSTR(column,-4) var nodes = functionCall.nodes.map(_this.visit.bind(_this)); if (nodes.length != 2) throw new Error('Not enough parameters passed to RIGHT function.'); - var txt = "SUBSTR(" + (nodes[0]+'') + ', -' + (nodes[1]+'') + ')'; + var txt = "SUBSTR(" + (nodes[0] + '') + ', -' + (nodes[1] + '') + ')'; + return txt; + } + + function _extract() { + var nodes = functionCall.nodes.map(_this.visit.bind(_this)); + if (nodes.length != 1) throw new Error('Not enough parameters passed to ' + functionCall.name + ' function'); + var format; + switch (functionCall.name) { + case 'YEAR': + format = "'%Y'"; + break; + case 'MONTH': + format = "'%m'"; + break; + case 'DAY': + format = "'%d'"; + break; + case 'HOUR': + format = "'%H'"; + break; + } + var col = (nodes[0] + ''); + if (_this.config.dateTimeMillis) { + // Convert to a datetime before running the strftime function + // Sqlite unix epoch is in seconds, but javascript is milliseconds. + col = 'datetime(' + col + '/1000, "unixepoch")'; + } + var txt = 'strftime(' + format + ', ' + col + ')'; return txt; } - var txt=""; - var name=functionCall.name; + var txt = ""; + var name = functionCall.name; // Override LEFT and RIGHT and convert to SUBSTR if (name == "LEFT") txt = _left(); else if (name == "RIGHT") txt = _right(); + // Override date functions since sqlite uses strftime + else if (['YEAR', 'MONTH', 'DAY', 'HOUR'].indexOf(functionCall.name) >= 0) txt = _extract(); + else if ('CURRENT_TIMESTAMP' == functionCall.name) txt = functionCall.name; else txt = name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; - this._visitingFunctionCall = false; + this._visitingFunctionCall = false; return [txt]; }; @@ -88,6 +152,10 @@ Sqlite.prototype.visitOnDuplicate = function() { throw new Error('SQLite does not allow onDuplicate clause.'); }; +Sqlite.prototype.visitOnConflict = function(onConflict) { + throw new Error('Sqlite does not allow onConflict clause.'); +}; + Sqlite.prototype.visitReturning = function() { throw new Error('SQLite does not allow returning clause.'); }; @@ -130,4 +198,8 @@ Sqlite.prototype.visitBinary = function(binary) { return Sqlite.super_.prototype.visitBinary.call(this, binary); }; +Sqlite.prototype.visitOrIgnore = function() { + return ['OR IGNORE']; +}; + module.exports = Sqlite; diff --git a/lib/functions.js b/lib/functions.js index ef9b6f81..fe9cdaaf 100644 --- a/lib/functions.js +++ b/lib/functions.js @@ -1,7 +1,7 @@ 'use strict'; var _ = require('lodash'); var sliced = require('sliced'); -var FunctionCall = require(__dirname + '/node/functionCall'); +var FunctionCall = require('./node/functionCall'); // create a function that creates a function call of the specific name, using the specified sql instance var getFunctionCallCreator = function(name) { @@ -13,6 +13,9 @@ var getFunctionCallCreator = function(name) { // creates a hash of functions for a sql instance var getFunctions = function(functionNames) { + if (typeof functionNames === 'string') + return getFunctionCallCreator(functionNames); + var functions = _.reduce(functionNames, function(reducer, name) { reducer[name] = getFunctionCallCreator(name); return reducer; @@ -47,17 +50,26 @@ var scalarFunctions = [ 'UPPER' ]; +var dateFunctions = [ + 'YEAR', + 'MONTH', + 'DAY', + 'HOUR', + 'CURRENT_TIMESTAMP' +]; + // hstore function available to Postgres var hstoreFunction = 'HSTORE'; //text search functions available to Postgres var textsearchFunctions = ['TS_RANK','TS_RANK_CD', 'PLAINTO_TSQUERY', 'TO_TSQUERY', 'TO_TSVECTOR', 'SETWEIGHT']; -var standardFunctionNames = aggregateFunctions.concat(scalarFunctions).concat(hstoreFunction).concat(textsearchFunctions); +var standardFunctionNames = aggregateFunctions.concat(scalarFunctions).concat(hstoreFunction).concat(textsearchFunctions).concat(dateFunctions); // creates a hash of standard functions for a sql instance var getStandardFunctions = function() { return getFunctions(standardFunctionNames); }; +module.exports.getFunctions = getFunctions; module.exports.getStandardFunctions = getStandardFunctions; diff --git a/lib/index.js b/lib/index.js index 6f609570..f26ad827 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,13 +1,16 @@ 'use strict'; var _ = require('lodash'); +var Column = require("./column"); var FunctionCall = require('./node/functionCall'); var ArrayCall = require('./node/arrayCall'); var functions = require('./functions'); var getDialect = require('./dialect'); +var JoinNode = require('./node/join'); var Query = require('./node/query'); var sliced = require('sliced'); var Table = require('./table'); +var Interval = require('./node/interval'); // default dialect is postgres var DEFAULT_DIALECT = 'postgres'; @@ -17,6 +20,7 @@ var Sql = function(dialect, config) { // attach the standard SQL functions to this instance this.functions = functions.getStandardFunctions(); + this.function = functions.getFunctions; }; // Define a table @@ -49,6 +53,24 @@ Sql.prototype.select = function() { return query; }; +// Returns a subQuery clause +Sql.prototype.subQuery = function(alias) { + // create the query and pass it off + var query = new Query(this); + query.type = 'SUBQUERY'; + query.alias = alias; + query.join = function(other) { + return new JoinNode('INNER', this.toNode(), other.toNode(), other); + }; + return query; +}; + +// Returns an interval clause +Sql.prototype.interval = function() { + var interval = new Interval(sliced(arguments)); + return interval; +}; + // Set the dialect Sql.prototype.setDialect = function(dialect, config) { this.dialect = getDialect(dialect); @@ -58,6 +80,19 @@ Sql.prototype.setDialect = function(dialect, config) { return this; }; +// Create a constant Column (for use in SELECT) +Sql.prototype.constant = function(value) { + var config={ + name:"constant", + property:"constant", + isConstant:true, + constantValue:value, + }; + var cn = new Column(config); + return cn; +}; + + // back compat shim for the Sql class constructor var create = function(dialect, config) { return new Sql(dialect, {}); diff --git a/lib/node/addColumn.js b/lib/node/addColumn.js index 273d8836..3d7d4066 100644 --- a/lib/node/addColumn.js +++ b/lib/node/addColumn.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'ADD COLUMN' diff --git a/lib/node/alias.js b/lib/node/alias.js index 78f01323..9f9855f5 100644 --- a/lib/node/alias.js +++ b/lib/node/alias.js @@ -1,7 +1,7 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); +var Node = require('./index'); var AliasNode = Node.define({ type: 'ALIAS', diff --git a/lib/node/alter.js b/lib/node/alter.js index ce53623c..4aae4d83 100644 --- a/lib/node/alter.js +++ b/lib/node/alter.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'ALTER' diff --git a/lib/node/arrayCall.js b/lib/node/arrayCall.js index 4f2f657b..7533190d 100644 --- a/lib/node/arrayCall.js +++ b/lib/node/arrayCall.js @@ -1,9 +1,9 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var ParameterNode = require(__dirname + '/parameter'); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var ParameterNode = require('./parameter'); +var valueExpressionMixin = require('./valueExpression'); var ArrayCallNode = Node.define({ type: 'ARRAY CALL', @@ -18,7 +18,7 @@ var ArrayCallNode = Node.define({ _.extend(ArrayCallNode.prototype, valueExpressionMixin()); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(ArrayCallNode.prototype, AliasNode.AliasMixin); module.exports = ArrayCallNode; diff --git a/lib/node/at.js b/lib/node/at.js index 2ded9362..5e8ca47b 100644 --- a/lib/node/at.js +++ b/lib/node/at.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var AtNode = Node.define({ @@ -21,7 +21,7 @@ var AtNode = Node.define({ }); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(AtNode.prototype, AliasNode.AliasMixin); module.exports = AtNode; diff --git a/lib/node/binary.js b/lib/node/binary.js index 7b721650..da8eb8bd 100644 --- a/lib/node/binary.js +++ b/lib/node/binary.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var BinaryNode = Node.define(_.extend({ @@ -23,7 +23,7 @@ var BinaryNode = Node.define(_.extend({ })); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(BinaryNode.prototype, AliasNode.AliasMixin); module.exports = BinaryNode; diff --git a/lib/node/cascade.js b/lib/node/cascade.js index 1dea1eef..4b6646fd 100644 --- a/lib/node/cascade.js +++ b/lib/node/cascade.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'CASCADE' diff --git a/lib/node/case.js b/lib/node/case.js index b8729314..ac3bf214 100644 --- a/lib/node/case.js +++ b/lib/node/case.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var CaseNode = Node.define(_.extend({ @@ -23,7 +23,7 @@ var CaseNode = Node.define(_.extend({ })); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(CaseNode.prototype, AliasNode.AliasMixin); module.exports = CaseNode; diff --git a/lib/node/cast.js b/lib/node/cast.js index 60966ea9..be915b1e 100644 --- a/lib/node/cast.js +++ b/lib/node/cast.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var CastNode = Node.define({ @@ -21,7 +21,7 @@ var CastNode = Node.define({ }); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(CastNode.prototype, AliasNode.AliasMixin); module.exports = CastNode; diff --git a/lib/node/column.js b/lib/node/column.js index 6fa999fb..6b145d74 100644 --- a/lib/node/column.js +++ b/lib/node/column.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'COLUMN', @@ -10,6 +10,8 @@ module.exports = Node.define({ this.property = config.property || config.name; this.alias = config.alias; this.star = config.star; + this.isConstant = config.isConstant; + this.constantValue = config.constantValue; this.asArray = config.asArray; this.aggregator = config.aggregator; this.table = config.table; @@ -18,6 +20,7 @@ module.exports = Node.define({ this.distinct = config.distinct; this.primaryKey = config.primaryKey; this.notNull = config.notNull; + this.defaultValue = config.defaultValue; this.references = config.references; // If subfieldContainer is present, this is a subfield and subfieldContainer // is the parent Column diff --git a/lib/node/create.js b/lib/node/create.js index c048c3ce..67cd9b3d 100644 --- a/lib/node/create.js +++ b/lib/node/create.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'CREATE', diff --git a/lib/node/createView.js b/lib/node/createView.js index bc52684f..7d456d90 100644 --- a/lib/node/createView.js +++ b/lib/node/createView.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'CREATE VIEW', diff --git a/lib/node/default.js b/lib/node/default.js index 49bd7ebd..b5adc894 100644 --- a/lib/node/default.js +++ b/lib/node/default.js @@ -1,6 +1,6 @@ 'use strict'; -module.exports = require(__dirname).define({ +module.exports = require('./index').define({ type: 'DEFAULT', value: function() { return; diff --git a/lib/node/delete.js b/lib/node/delete.js index 2acb5872..0d02ebab 100644 --- a/lib/node/delete.js +++ b/lib/node/delete.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'DELETE' diff --git a/lib/node/distinct.js b/lib/node/distinct.js index a115f148..8c681551 100644 --- a/lib/node/distinct.js +++ b/lib/node/distinct.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'DISTINCT', diff --git a/lib/node/distinctOn.js b/lib/node/distinctOn.js index a208dfde..72c430e8 100644 --- a/lib/node/distinctOn.js +++ b/lib/node/distinctOn.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'DISTINCT ON', diff --git a/lib/node/drop.js b/lib/node/drop.js index 177b6d1a..b0428347 100644 --- a/lib/node/drop.js +++ b/lib/node/drop.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'DROP', diff --git a/lib/node/dropColumn.js b/lib/node/dropColumn.js index 60ad4219..01fe36c5 100644 --- a/lib/node/dropColumn.js +++ b/lib/node/dropColumn.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'DROP COLUMN' diff --git a/lib/node/forShare.js b/lib/node/forShare.js index 328a4ebe..68a83678 100644 --- a/lib/node/forShare.js +++ b/lib/node/forShare.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'FOR SHARE' diff --git a/lib/node/forUpdate.js b/lib/node/forUpdate.js index c869f981..da4a2518 100644 --- a/lib/node/forUpdate.js +++ b/lib/node/forUpdate.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'FOR UPDATE' diff --git a/lib/node/foreignKey.js b/lib/node/foreignKey.js new file mode 100644 index 00000000..a0ad99c1 --- /dev/null +++ b/lib/node/foreignKey.js @@ -0,0 +1,19 @@ +'use strict'; + +var Node = require('./index'); + +module.exports = Node.define({ + type: 'FOREIGN KEY', + constructor: function(config) { + Node.call(this); + this.name = config.name; + this.columns = config.columns; + this.schema = config.schema; + this.table = config.table; + this.refColumns = config.refColumns; + this.onUpdate = config.onUpdate; + this.onDelete = config.onDelete; + this.constraint = config.constraint; + } +}); + diff --git a/lib/node/from.js b/lib/node/from.js index 84a6010f..12b54565 100644 --- a/lib/node/from.js +++ b/lib/node/from.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); var From = Node.define({ type: 'FROM' diff --git a/lib/node/functionCall.js b/lib/node/functionCall.js index 6d43c279..608a3c1e 100644 --- a/lib/node/functionCall.js +++ b/lib/node/functionCall.js @@ -1,9 +1,9 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var ParameterNode = require(__dirname + '/parameter'); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var ParameterNode = require('./parameter'); +var valueExpressionMixin = require('./valueExpression'); var FunctionCallNode = Node.define({ type: 'FUNCTION CALL', @@ -18,7 +18,7 @@ var FunctionCallNode = Node.define({ _.extend(FunctionCallNode.prototype, valueExpressionMixin()); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(FunctionCallNode.prototype, AliasNode.AliasMixin); module.exports = FunctionCallNode; diff --git a/lib/node/groupBy.js b/lib/node/groupBy.js index 6e5888d1..53f63e94 100644 --- a/lib/node/groupBy.js +++ b/lib/node/groupBy.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'GROUP BY' diff --git a/lib/node/having.js b/lib/node/having.js index 7dcf3434..6f1523ac 100644 --- a/lib/node/having.js +++ b/lib/node/having.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'HAVING' diff --git a/lib/node/ifExists.js b/lib/node/ifExists.js index 9b759779..c26df660 100644 --- a/lib/node/ifExists.js +++ b/lib/node/ifExists.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'IF EXISTS' diff --git a/lib/node/ifNotExists.js b/lib/node/ifNotExists.js index ef414551..d731ccb2 100644 --- a/lib/node/ifNotExists.js +++ b/lib/node/ifNotExists.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'IF NOT EXISTS' diff --git a/lib/node/in.js b/lib/node/in.js index 4c807331..233b7c28 100644 --- a/lib/node/in.js +++ b/lib/node/in.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var InNode = Node.define(_.extend({ @@ -22,7 +22,7 @@ var InNode = Node.define(_.extend({ })); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(InNode.prototype, AliasNode.AliasMixin); module.exports = InNode; diff --git a/lib/node/index.js b/lib/node/index.js index b500ab39..66c0e481 100644 --- a/lib/node/index.js +++ b/lib/node/index.js @@ -15,7 +15,11 @@ Node.prototype.toNode = function() { Node.prototype.add = function(node) { assert(node, 'Error while trying to add a non-existant node to a query'); - this.nodes.push(typeof node === 'string' ? new TextNode(node) : node.toNode()); + var newNode; + if (typeof node === 'string') newNode = new TextNode(node); + else if (node.toNode) newNode = node.toNode(); + else newNode = node; + this.nodes.push(newNode); return this; }; @@ -94,4 +98,4 @@ Node.define = function(def) { }; module.exports = Node; -var TextNode = require(__dirname + '/text'); +var TextNode = require('./text'); diff --git a/lib/node/interval.js b/lib/node/interval.js new file mode 100644 index 00000000..9f46f9ab --- /dev/null +++ b/lib/node/interval.js @@ -0,0 +1,21 @@ +'use strict'; + +var Node = require('./index'); +var ParameterNode = require('./parameter'); + +var IntervalNode = Node.define({ + type: 'INTERVAL', + constructor: function(args) { + Node.call(this); + var interval = args[0] || {}; + this.years = interval.years; + this.months = interval.months; + this.days = interval.days; + this.hours = interval.hours; + this.minutes = interval.minutes; + this.seconds = interval.seconds; + } +}); + +module.exports = IntervalNode; + diff --git a/lib/node/join.js b/lib/node/join.js index 7186f694..f569997e 100644 --- a/lib/node/join.js +++ b/lib/node/join.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); var JoinNode = module.exports = Node.define({ type: 'JOIN', constructor: function(subType, from, to) { @@ -19,5 +19,8 @@ var JoinNode = module.exports = Node.define({ }, leftJoin: function(other) { return new JoinNode('LEFT', this, other); + }, + leftJoinLateral: function(other) { + return new JoinNode('LEFT LATERAL', this, other); } }); diff --git a/lib/node/literal.js b/lib/node/literal.js index c4f539bb..f5a1ea58 100644 --- a/lib/node/literal.js +++ b/lib/node/literal.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'LITERAL', diff --git a/lib/node/notIn.js b/lib/node/notIn.js index e363f7f3..c50976c3 100644 --- a/lib/node/notIn.js +++ b/lib/node/notIn.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var NotInNode = Node.define(_.extend({ @@ -22,7 +22,7 @@ var NotInNode = Node.define(_.extend({ })); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(NotInNode.prototype, AliasNode.AliasMixin); module.exports = NotInNode; diff --git a/lib/node/onConflict.js b/lib/node/onConflict.js new file mode 100644 index 00000000..cc71ac47 --- /dev/null +++ b/lib/node/onConflict.js @@ -0,0 +1,7 @@ +'use strict'; + +var Node = require('./index'); + +module.exports = Node.define({ + type: 'ONCONFLICT' +}); diff --git a/lib/node/onDuplicate.js b/lib/node/onDuplicate.js index 38146793..cd653cb3 100644 --- a/lib/node/onDuplicate.js +++ b/lib/node/onDuplicate.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'ONDUPLICATE' diff --git a/lib/node/orIgnore.js b/lib/node/orIgnore.js new file mode 100644 index 00000000..e23c9d6d --- /dev/null +++ b/lib/node/orIgnore.js @@ -0,0 +1,7 @@ +'use strict'; + +var Node = require('./index'); + +module.exports = Node.define({ + type: 'OR IGNORE' +}); diff --git a/lib/node/orderBy.js b/lib/node/orderBy.js index 6db2a978..b97fb891 100644 --- a/lib/node/orderBy.js +++ b/lib/node/orderBy.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'ORDER BY' diff --git a/lib/node/orderByValue.js b/lib/node/orderByValue.js index 51d44567..47a8d5c0 100644 --- a/lib/node/orderByValue.js +++ b/lib/node/orderByValue.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); var OrderByColumn = Node.define({ type: 'ORDER BY VALUE', diff --git a/lib/node/parameter.js b/lib/node/parameter.js index c2276710..051ed852 100644 --- a/lib/node/parameter.js +++ b/lib/node/parameter.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); var ParameterNode = module.exports = Node.define({ type: 'PARAMETER', diff --git a/lib/node/postfixUnary.js b/lib/node/postfixUnary.js index 0c132f8a..8c66b89f 100644 --- a/lib/node/postfixUnary.js +++ b/lib/node/postfixUnary.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var PostfixUnaryNode = Node.define({ @@ -22,7 +22,7 @@ var PostfixUnaryNode = Node.define({ }); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(PostfixUnaryNode.prototype, AliasNode.AliasMixin); module.exports = PostfixUnaryNode; diff --git a/lib/node/prefixUnary.js b/lib/node/prefixUnary.js index 67a59fa1..fbde69c0 100644 --- a/lib/node/prefixUnary.js +++ b/lib/node/prefixUnary.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var PrefixUnaryNode = Node.define({ @@ -22,7 +22,7 @@ var PrefixUnaryNode = Node.define({ }); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(PrefixUnaryNode.prototype, AliasNode.AliasMixin); module.exports = PrefixUnaryNode; diff --git a/lib/node/query.js b/lib/node/query.js index 4f13ebd1..ebb3ecfa 100644 --- a/lib/node/query.js +++ b/lib/node/query.js @@ -1,8 +1,11 @@ 'use strict'; +var _ = require('lodash'); +var alias = require('./alias'); var assert = require('assert'); var sliced = require('sliced'); var util = require('util'); +var valueExpressionMixin = require('./valueExpression'); var Node = require('./'); var Select = require('./select'); @@ -12,10 +15,12 @@ var OrderBy = require('./orderBy'); var GroupBy = require('./groupBy'); var Having = require('./having'); var Insert = require('./insert'); +var Replace = require('./replace'); var Update = require('./update'); var Delete = require('./delete'); var Returning = require('./returning'); var OnDuplicate = require('./onDuplicate'); +var OnConflict = require('./onConflict'); var ForUpdate = require('./forUpdate'); var ForShare = require('./forShare'); var Create = require('./create'); @@ -33,6 +38,7 @@ var ParameterNode = require('./parameter'); var PrefixUnaryNode = require('./prefixUnary'); var IfExists = require('./ifExists'); var IfNotExists = require('./ifNotExists'); +var OrIgnore = require('./orIgnore'); var Cascade = require('./cascade'); var Restrict = require('./restrict'); var Indexes = require('./indexes'); @@ -167,6 +173,9 @@ var Query = Node.define({ order: function() { var args = getArrayOrArgsAsArray(arguments); var orderBy; + if (args.length === 0) { + return this; + } if (this._orderBy) { orderBy = this._orderBy; } else { @@ -219,6 +228,36 @@ var Query = Node.define({ }, + replace: function(o) { + var self = this; + + var args = sliced(arguments); + // object literal + if (arguments.length === 1 && !o.toNode && !o.forEach) { + args = []; + Object.keys(o).forEach(function(key) { + var col = self.table.get(key); + if(col && !col.autoGenerated) + args.push(col.value(o[key])); + }); + } else if (o.forEach) { + o.forEach(function(arg) { + return self.replace.call(self, arg); + }); + return self; + } + + if (self.replaceClause) { + self.replaceClause.add(args); + return self; + } else { + self.replaceClause = new Replace(); + self.replaceClause.add(args); + return self.add(self.replaceClause); + } + + }, + update: function(o) { var self = this; var update = new Update(); @@ -286,6 +325,15 @@ var Query = Node.define({ return self.add(onDuplicate); }, + onConflict: function(o) { + var self = this; + + var onConflict = new OnConflict(); + Object.keys(o).forEach(function(key) { + onConflict[key] = o[key]; + }); + return self.add(onConflict); + }, forUpdate: function() { assert(typeof this._select !== 'undefined', 'FOR UPDATE can be used only in a select statement'); @@ -455,6 +503,11 @@ var Query = Node.define({ return this; }, + orIgnore: function() { + this.nodes[0].unshift(new OrIgnore()); + return this; + }, + cascade: function() { this.nodes[0].add(new Cascade()); return this; @@ -478,4 +531,22 @@ var Query = Node.define({ } }); +// Here we are extending query with valueExpressions so that it's possible to write queries like +// var query=sql.select(a.select(a.x.sum()).plus(b.select(b.y.sum())) +// which generates: +// SELECT (SELECT SUM(a.x) FROM a) + (SELECT SUM(b.y) FROM b) +// We need to remove "or" and "and" from here because it conflicts with the already existing functionality of appending +// to the where clause like so: +// var query=a.select().where(a.name.equals("joe")).or(a.name.equals("sam")) +var valueExpressions=valueExpressionMixin(); +delete valueExpressions.or; +delete valueExpressions.and; +_.extend(Query.prototype, valueExpressions); + +// Extend the query with the aliasMixin so that it's possible to write queries like +// var query=sql.select(a.select(a.count()).as("column1")) +// which generates: +// SELECT (SELECT COUNT(*) FROM a) AS "column1" +_.extend(Query.prototype, alias.AliasMixin); + module.exports = Query; diff --git a/lib/node/rename.js b/lib/node/rename.js index 9767a528..c87bcfb3 100644 --- a/lib/node/rename.js +++ b/lib/node/rename.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'RENAME' diff --git a/lib/node/renameColumn.js b/lib/node/renameColumn.js index f886a11d..c3c66865 100644 --- a/lib/node/renameColumn.js +++ b/lib/node/renameColumn.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'RENAME COLUMN' diff --git a/lib/node/replace.js b/lib/node/replace.js new file mode 100644 index 00000000..d17099d2 --- /dev/null +++ b/lib/node/replace.js @@ -0,0 +1,70 @@ +'use strict'; + +var DefaultNode = require('./default'); +var Node = require('./'); +var ParameterNode = require('./parameter'); + +var Replace = Node.define({ + type: 'REPLACE', + constructor: function () { + Node.call(this); + this.names = []; + this.columns = []; + this.valueSets = []; + } +}); + +module.exports = Replace; + +Replace.prototype.add = function (nodes) { + var hasColumns = false; + var hasValues = false; + var self = this; + var values = {}; + nodes.forEach(function (node) { + var column = node.toNode(); + var name = column.name; + var idx = self.names.indexOf(name); + if (idx < 0) { + self.names.push(name); + self.columns.push(column); + } + hasColumns = true; + hasValues = hasValues || column.value !== undefined; + values[name] = column; + }); + + // When none of the columns have a value, it's ambiguous whether the user + // intends to replace a row of default values or append a SELECT statement + // later. Resolve the ambiguity by assuming that if no columns are specified + // it is a row of default values, otherwise a SELECT will be added. + if (hasValues || !hasColumns) { + this.valueSets.push(values); + } + + return self; +}; + +/* + * Get parameters for all values to be replaced. This function + * handles handles bulk replaces, where keys may be present + * in some objects and not others. When keys are not present, + * the replace should refer to the column value as DEFAULT. + */ +Replace.prototype.getParameters = function () { + var self = this; + return this.valueSets + .map(function (nodeDict) { + var set = []; + self.names.forEach(function (name) { + var node = nodeDict[name]; + if (node) { + set.push(ParameterNode.getNodeOrParameterNode(node.value)); + } + else { + set.push(new DefaultNode()); + } + }); + return set; + }); +}; diff --git a/lib/node/restrict.js b/lib/node/restrict.js index f7c63c15..942f4112 100644 --- a/lib/node/restrict.js +++ b/lib/node/restrict.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'RESTRICT' diff --git a/lib/node/returning.js b/lib/node/returning.js index 35ea0a18..2f27d067 100644 --- a/lib/node/returning.js +++ b/lib/node/returning.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'RETURNING' diff --git a/lib/node/select.js b/lib/node/select.js index a485b814..93e22538 100644 --- a/lib/node/select.js +++ b/lib/node/select.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'SELECT', diff --git a/lib/node/slice.js b/lib/node/slice.js index ab32f314..86c1ce21 100644 --- a/lib/node/slice.js +++ b/lib/node/slice.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var SliceNode = Node.define({ @@ -22,7 +22,7 @@ var SliceNode = Node.define({ }); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(SliceNode.prototype, AliasNode.AliasMixin); module.exports = SliceNode; diff --git a/lib/node/table.js b/lib/node/table.js index b1257105..4fca70d1 100644 --- a/lib/node/table.js +++ b/lib/node/table.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'TABLE', constructor: function(table) { diff --git a/lib/node/ternary.js b/lib/node/ternary.js index 1ef5ce7b..3f688ba9 100644 --- a/lib/node/ternary.js +++ b/lib/node/ternary.js @@ -1,8 +1,8 @@ 'use strict'; var _ = require('lodash'); -var Node = require(__dirname); -var valueExpressionMixin = require(__dirname + '/valueExpression'); +var Node = require('./index'); +var valueExpressionMixin = require('./valueExpression'); var valueExpressionMixed = false; var TernaryNode = Node.define(_.extend({ @@ -25,7 +25,7 @@ var TernaryNode = Node.define(_.extend({ })); // allow aliasing -var AliasNode = require(__dirname + '/alias'); +var AliasNode = require('./alias'); _.extend(TernaryNode.prototype, AliasNode.AliasMixin); module.exports = TernaryNode; diff --git a/lib/node/text.js b/lib/node/text.js index ef1194b2..bf026151 100644 --- a/lib/node/text.js +++ b/lib/node/text.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'TEXT', diff --git a/lib/node/truncate.js b/lib/node/truncate.js index 790c26ca..165e0d9e 100644 --- a/lib/node/truncate.js +++ b/lib/node/truncate.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'TRUNCATE', diff --git a/lib/node/update.js b/lib/node/update.js index 32a29ba9..890fbee9 100644 --- a/lib/node/update.js +++ b/lib/node/update.js @@ -1,6 +1,6 @@ 'use strict'; -var Node = require(__dirname); +var Node = require('./index'); module.exports = Node.define({ type: 'UPDATE' diff --git a/lib/node/valueExpression.js b/lib/node/valueExpression.js index faad8033..2fa741a9 100644 --- a/lib/node/valueExpression.js +++ b/lib/node/valueExpression.js @@ -148,6 +148,7 @@ var ValueExpressionMixin = function() { at : atMethod, contains : binaryMethod('@>'), containedBy : binaryMethod('<@'), + containsKey : binaryMethod('?'), overlap : binaryMethod('&&'), slice : sliceMethod, cast : castMethod, diff --git a/lib/node/where.js b/lib/node/where.js index b542807d..5c0465fc 100644 --- a/lib/node/where.js +++ b/lib/node/where.js @@ -1,8 +1,8 @@ 'use strict'; -var Node = require(__dirname); -var BinaryNode = require(__dirname + '/binary'); -var TextNode = require(__dirname + '/text'); +var Node = require('./index'); +var BinaryNode = require('./binary'); +var TextNode = require('./text'); var normalizeNode = function(table, node) { var result = node; diff --git a/lib/table.js b/lib/table.js index 86e8adbe..1408542b 100644 --- a/lib/table.js +++ b/lib/table.js @@ -3,12 +3,13 @@ var util = require('util'); var lodash = require('lodash'); -var Query = require(__dirname + '/node/query'); -var Column = require(__dirname + '/column'); -var TableNode = require(__dirname + '/node/table'); -var JoinNode = require(__dirname + '/node/join'); -var LiteralNode = require(__dirname + '/node/literal'); -var Joiner = require(__dirname + '/joiner'); +var Query = require('./node/query'); +var Column = require('./column'); +var TableNode = require('./node/table'); +var JoinNode = require('./node/join'); +var LiteralNode = require('./node/literal'); +var Joiner = require('./joiner'); +var ForeignKeyNode = require('./node/foreignKey'); var Table = function(config) { this._schema = config.schema; @@ -18,6 +19,7 @@ var Table = function(config) { this.isTemporary=!!config.isTemporary; this.snakeToCamel = !!config.snakeToCamel; this.columns = []; + this.foreignKeys = []; this.table = this; if (!config.sql) { config.sql = require('./index'); @@ -45,6 +47,16 @@ Table.define = function(config) { for (var i = 0; i < config.columns.length; i++) { table.addColumn(config.columns[i]); } + + if(config.foreignKeys !== undefined) { + if(util.isArray(config.foreignKeys)) { + for(i = 0; i < config.foreignKeys.length; i++) { + table.foreignKeys.push(new ForeignKeyNode(config.foreignKeys[i])); + } + } else { + table.foreignKeys.push(new ForeignKeyNode(config.foreignKeys)); + } + } return table; }; @@ -55,7 +67,8 @@ Table.prototype.clone = function(config) { sql: this.sql, columnWhiteList: !!this.columnWhiteList, snakeToCamel: !!this.snakeToCamel, - columns: this.columns + columns: this.columns, + foreignKeys: this.foreignKeys }, config || {})); }; @@ -78,7 +91,7 @@ Table.prototype.createColumn = function(col) { name: subfield })]; }, this)) - .object() + .fromPairs() .value(); } } @@ -98,7 +111,7 @@ Table.prototype.addColumn = function(col, options) { } else { return this; } - } else if(!!this[col.name] && (process.env.NODE_ENV !== 'test')) { + } else if(!!this[col.name] && (process.env.NODE_ENV === 'debug')) { console.log('Please notice that you have just defined the column "' + col.name + '". In order to access it, you need to use "table.getColumn(\'' + col.name + '\');"!'); } this.columns.push(col); @@ -113,13 +126,10 @@ Table.prototype.addColumn = function(col, options) { }; Table.prototype.hasColumn = function(col) { - col = this.createColumn(col); - - var cols = this.columns.filter(function(column) { - return column.property === col.property || column.name === col.name; + var columnName = col instanceof Column ? col.name : col; + return this.columns.some(function(column) { + return column.property === columnName || column.name === columnName; }); - - return cols.length > 0; }; Table.prototype.getColumn = @@ -195,7 +205,7 @@ Table.prototype.subQuery = function(alias) { Table.prototype.insert = function() { var query = new Query(this); - if(arguments[0].length === 0){ + if(!arguments[0] || (util.isArray(arguments[0]) && arguments[0].length === 0)){ query.select.call(query, this.star()); query.where.apply(query,["1=2"]); } else { @@ -204,6 +214,17 @@ Table.prototype.insert = function() { return query; }; +Table.prototype.replace = function() { + var query = new Query(this); + if(!arguments[0] || (util.isArray(arguments[0]) && arguments[0].length === 0)){ + query.select.call(query, this.star()); + query.where.apply(query,["1=2"]); + } else { + query.replace.apply(query, arguments); + } + return query; +}; + Table.prototype.toNode = function() { return new TableNode(this); }; @@ -216,6 +237,10 @@ Table.prototype.leftJoin = function(other) { return new JoinNode('LEFT', this.toNode(), other.toNode()); }; +Table.prototype.leftJoinLateral = function(other) { + return new JoinNode('LEFT LATERAL', this.toNode(), other.toNode()); +}; + // auto-join tables based on column intropsection Table.prototype.joinTo = function(other) { return Joiner.leftJoin(this, other); diff --git a/lib/types.d.ts b/lib/types.d.ts new file mode 100644 index 00000000..a7011f99 --- /dev/null +++ b/lib/types.d.ts @@ -0,0 +1,221 @@ + +/** + * This is an adaptation of https://github.com/doxout/anydb-sql/blob/4e4c0ff4a7f2efb7f820baaafea1f624f1ae0399/d.ts/anydb-sql.d.ts + * Whole project is MIT licensed, so, we can use it. We also feed back any + * improvements, questions, concerns. + */ +declare module "sql" { + + type SQLDialects = + | "mssql" + | "mysql" + | "oracle" + | "postgres" + | "sqlite" + ; + + interface OrderByValueNode {} + + interface Named { + name?: Name; + } + interface ColumnDefinition extends Named { + jsType?: Type; + dataType: string; + primaryKey?: boolean; + references?: { + table:string; + column: string; + onDelete?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; + onUpdate?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; + }; + notNull?: boolean; + unique?: boolean; + defaultValue?: Type; + } + + interface TableDefinition { + name: Name; + schema: string; + columns: {[CName in ((keyof Row) & string)]: ColumnDefinition}; + dialect?: SQLDialects; + isTemporary?: boolean; + foreignKeys?: { + table: string, + columns: (keyof Row)[], + refColumns: string[], + onDelete?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; + onUpdate?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; + } + } + + interface QueryLike { + values: any[] + text:string + } + + interface Executable { + toQuery():QueryLike; + } + + interface Queryable extends Executable { + where(...nodes:any[]):Query + delete():ModifyingQuery + select(star: Column): Query; + select(n1: Column):Query<{[N in N1]: T1}>; + select( + n1: Column, + n2: Column):Query<{[N in N1]: T1} & {[N in N2]: T2}> + select( + n1: Column, + n2: Column, + n3: Column):Query<{[N in N1]: T1} & {[N in N2]: T2} & {[N in N3]: T3}> + select(...nodesOrTables:any[]):Query + + } + + interface Query extends Executable, Queryable { + resultType: T; + + from(table:TableNode):Query + from(statement:string):Query + update(o:{[key: string]:any}):ModifyingQuery + update(o:{}):ModifyingQuery + group(...nodes:any[]):Query + order(...criteria:OrderByValueNode[]):Query + limit(l:number):Query + offset(o:number):Query + } + + interface SubQuery { + select(node:Column):SubQuery + select(...nodes: any[]):SubQuery + where(...nodes:any[]):SubQuery + from(table:TableNode):SubQuery + from(statement:string):SubQuery + group(...nodes:any[]):SubQuery + order(criteria:OrderByValueNode):SubQuery + exists():BinaryNode + notExists(): BinaryNode; + notExists(subQuery:SubQuery):BinaryNode + } + + + interface ModifyingQuery extends Executable { + returning(...nodes:any[]):Query + where(...nodes:any[]):ModifyingQuery + } + + interface TableNode { + join(table:TableNode):JoinTableNode + leftJoin(table:TableNode):JoinTableNode + } + + interface JoinTableNode extends TableNode { + on(filter:BinaryNode):TableNode + on(filter:string):TableNode + } + + interface CreateQuery extends Executable { + ifNotExists():Executable + } + interface DropQuery extends Executable { + ifExists():Executable + } + + type Columns = { + [Name in keyof T]: Column + } + type Table = TableNode & Queryable & Named & Columns & { + getName(): string; + getSchema(): string; + + literal(statement: string): any; + + create():CreateQuery + drop():DropQuery + as(name:OtherName):Table + update(o: Partial):ModifyingQuery + insert(row:T):ModifyingQuery + insert(rows:T[]):ModifyingQuery + select():Query + select(...nodes:any[]):Query + from(table:TableNode):Query + from(statement:string):Query + star():Column + subQuery():SubQuery + columns:Column[] + sql: SQL; + alter():AlterQuery; + indexes(): IndexQuery; + } + + interface AlterQuery extends Executable { + addColumn(column:Column): AlterQuery; + addColumn(name: string, options:string): AlterQuery; + dropColumn(column: Column|string): AlterQuery; + renameColumn(column: Column, newColumn: Column):AlterQuery; + renameColumn(column: Column, newName: string):AlterQuery; + renameColumn(name: string, newName: string):AlterQuery; + rename(newName: string): AlterQuery + } + interface IndexQuery { + create(): IndexCreationQuery; + create(indexName: string): IndexCreationQuery; + drop(indexName: string): Executable; + drop(...columns: Column[]): Executable + } + interface IndexCreationQuery extends Executable { + unique(): IndexCreationQuery; + using(name: string): IndexCreationQuery; + on(...columns: (Column|OrderByValueNode)[]): IndexCreationQuery; + withParser(parserName: string): IndexCreationQuery; + fulltext(): IndexCreationQuery; + spatial(): IndexCreationQuery; + } + + interface SQL { + functions: { + LOWER(c:Column):Column + } + } + + interface BinaryNode { + and(node:BinaryNode):BinaryNode + or(node:BinaryNode):BinaryNode + } + + interface Column { + name: Name + in(arr:T[]):BinaryNode + in(subQuery:SubQuery):BinaryNode + notIn(arr:T[]):BinaryNode + equals(node: T|Column):BinaryNode + notEquals(node: T|Column):BinaryNode + gte(node: T|Column):BinaryNode + lte(node: T|Column):BinaryNode + gt(node:T|Column):BinaryNode + lt(node: T|Column):BinaryNode + like(str:string):BinaryNode + multiply:{ + (node:Column):Column + (n:number):Column //todo check column names + } + isNull():BinaryNode + isNotNull():BinaryNode + //todo check column names + sum():Column + count():Column + count(name:string):Column + distinct():Column + as(name:OtherName):Column + ascending:OrderByValueNode + descending:OrderByValueNode + asc:OrderByValueNode + desc:OrderByValueNode + } + + function define(map:TableDefinition): Table; + function setDialect(dialect: SQLDialects): void; + +} diff --git a/package.json b/package.json index 2f63aac7..dc250c01 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "brianc ", "name": "sql", "description": "sql builder", - "version": "0.65.0", + "version": "0.78.0", "homepage": "https://github.com/brianc/node-sql", "license": "MIT", "repository": { @@ -10,6 +10,7 @@ "url": "git://github.com/brianc/node-sql.git" }, "main": "lib/", + "types": "lib/types.d.ts", "scripts": { "test": "node_modules/.bin/mocha", "lint": "jshint lib test", @@ -20,7 +21,7 @@ }, "dependencies": { "sliced": "0.0.x", - "lodash": "1.3.x" + "lodash": "4.1.x" }, "devDependencies": { "jshint": "*", diff --git a/test/dialects/aggregate-tests.js b/test/dialects/aggregate-tests.js index d67d775a..59d48d57 100644 --- a/test/dialects/aggregate-tests.js +++ b/test/dialects/aggregate-tests.js @@ -16,8 +16,8 @@ Harness.test({ string: 'SELECT COUNT("post".*) AS "post_count" FROM "post"' }, mysql: { - text : 'SELECT COUNT(`post`.*) AS `post_count` FROM `post`', - string: 'SELECT COUNT(`post`.*) AS `post_count` FROM `post`' + text : 'SELECT COUNT(*) AS `post_count` FROM `post`', + string: 'SELECT COUNT(*) AS `post_count` FROM `post`' }, mssql: { text : 'SELECT COUNT(*) AS [post_count] FROM [post]', @@ -41,8 +41,8 @@ Harness.test({ string: 'SELECT COUNT("post".*) AS "post_count" FROM "post"' }, msyql: { - text : 'SELECT COUNT(`post`.*) AS `post_count` FROM `post`', - string: 'SELECT COUNT(`post`.*) AS `post_count` FROM `post`' + text : 'SELECT COUNT(*) AS `post_count` FROM `post`', + string: 'SELECT COUNT(*) AS `post_count` FROM `post`' }, mssql: { text : 'SELECT COUNT(*) AS [post_count] FROM [post]', @@ -66,8 +66,8 @@ Harness.test({ string: 'SELECT COUNT("post".*) AS "post_amount" FROM "post"' }, mysql: { - text : 'SELECT COUNT(`post`.*) AS `post_amount` FROM `post`', - string: 'SELECT COUNT(`post`.*) AS `post_amount` FROM `post`' + text : 'SELECT COUNT(*) AS `post_amount` FROM `post`', + string: 'SELECT COUNT(*) AS `post_amount` FROM `post`' }, mssql: { text : 'SELECT COUNT(*) AS [post_amount] FROM [post]', @@ -166,8 +166,8 @@ Harness.test({ string: 'SELECT COUNT("customer".*) AS "customer_count" FROM "customer"' }, mysql: { - text : 'SELECT COUNT(`customer`.*) AS `customer_count` FROM `customer`', - string: 'SELECT COUNT(`customer`.*) AS `customer_count` FROM `customer`' + text : 'SELECT COUNT(*) AS `customer_count` FROM `customer`', + string: 'SELECT COUNT(*) AS `customer_count` FROM `customer`' }, oracle: { text : 'SELECT COUNT(*) "customer_count" FROM "customer"', diff --git a/test/dialects/alter-table-tests.js b/test/dialects/alter-table-tests.js index f733b2f2..d050da3d 100644 --- a/test/dialects/alter-table-tests.js +++ b/test/dialects/alter-table-tests.js @@ -22,6 +22,10 @@ Harness.test({ text : 'ALTER TABLE [post] DROP COLUMN [content]', string: 'ALTER TABLE [post] DROP COLUMN [content]' }, + oracle: { + text : 'ALTER TABLE "post" DROP ("content")', + string: 'ALTER TABLE "post" DROP ("content")' + }, params: [] }); @@ -43,6 +47,10 @@ Harness.test({ text : 'ALTER TABLE [post] DROP COLUMN [content], [userId]', string: 'ALTER TABLE [post] DROP COLUMN [content], [userId]' }, + oracle: { + text : 'ALTER TABLE "post" DROP ("content", "userId")', + string: 'ALTER TABLE "post" DROP ("content", "userId")' + }, params: [] }); @@ -64,6 +72,10 @@ Harness.test({ text : 'ALTER TABLE [post] DROP COLUMN [content], [userId]', string: 'ALTER TABLE [post] DROP COLUMN [content], [userId]' }, + oracle: { + text : 'ALTER TABLE "post" DROP ("content", "userId")', + string: 'ALTER TABLE "post" DROP ("content", "userId")' + }, params: [] }); @@ -118,6 +130,10 @@ Harness.test({ text : 'ALTER TABLE [group] ADD [id] varchar(100)', string: 'ALTER TABLE [group] ADD [id] varchar(100)' }, + oracle: { + text : 'ALTER TABLE "group" ADD ("id" varchar(100))', + string: 'ALTER TABLE "group" ADD ("id" varchar(100))' + }, params: [] }); @@ -139,6 +155,10 @@ Harness.test({ text : 'ALTER TABLE [group] ADD [id] varchar(100), [userId] varchar(100)', string: 'ALTER TABLE [group] ADD [id] varchar(100), [userId] varchar(100)' }, + oracle: { + text : 'ALTER TABLE "group" ADD ("id" varchar(100), "userId" varchar(100))', + string: 'ALTER TABLE "group" ADD ("id" varchar(100), "userId" varchar(100))' + }, params: [] }); @@ -160,6 +180,10 @@ Harness.test({ text : 'ALTER TABLE [group] ADD [id] varchar(100), [userId] varchar(100)', string: 'ALTER TABLE [group] ADD [id] varchar(100), [userId] varchar(100)' }, + oracle: { + text : 'ALTER TABLE "group" ADD ("id" varchar(100), "userId" varchar(100))', + string: 'ALTER TABLE "group" ADD ("id" varchar(100), "userId" varchar(100))' + }, params: [] }); @@ -286,8 +310,8 @@ Harness.test({ string: 'ALTER TABLE `post` ADD COLUMN `userId` int REFERENCES `user`(`id`)' }, oracle: { - text : 'ALTER TABLE "post" ADD COLUMN "userId" int REFERENCES "user"("id")', - string: 'ALTER TABLE "post" ADD COLUMN "userId" int REFERENCES "user"("id")' + text : 'ALTER TABLE "post" ADD ("userId" int REFERENCES "user"("id"))', + string: 'ALTER TABLE "post" ADD ("userId" int REFERENCES "user"("id"))' }, params: [] }); @@ -307,8 +331,8 @@ Harness.test({ string: 'ALTER TABLE `post` ADD COLUMN `picture` varchar(100)' }, oracle: { - text : 'ALTER TABLE "post" ADD COLUMN "picture" varchar(100)', - string: 'ALTER TABLE "post" ADD COLUMN "picture" varchar(100)' + text : 'ALTER TABLE "post" ADD ("picture" varchar(100))', + string: 'ALTER TABLE "post" ADD ("picture" varchar(100))' }, params: [] }); diff --git a/test/dialects/create-table-tests.js b/test/dialects/create-table-tests.js index 80d65fbc..b1150617 100644 --- a/test/dialects/create-table-tests.js +++ b/test/dialects/create-table-tests.js @@ -248,6 +248,39 @@ Harness.test({ } }); +Harness.test({ + query: Table.define({ + name: 'user', + columns: [{ + name: 'id', + dataType: 'int', + primaryKey: true, + notNull: true + }, { + name: 'posts', + dataType: 'int', + notNull: true, + defaultValue: 0 + }] + }).create(), + pg: { + text : 'CREATE TABLE "user" ("id" int PRIMARY KEY, "posts" int NOT NULL DEFAULT 0)', + string: 'CREATE TABLE "user" ("id" int PRIMARY KEY, "posts" int NOT NULL DEFAULT 0)' + }, + sqlite: { + text : 'CREATE TABLE "user" ("id" int PRIMARY KEY, "posts" int NOT NULL DEFAULT 0)', + string: 'CREATE TABLE "user" ("id" int PRIMARY KEY, "posts" int NOT NULL DEFAULT 0)' + }, + mysql: { + text : 'CREATE TABLE `user` (`id` int PRIMARY KEY, `posts` int NOT NULL DEFAULT 0)', + string: 'CREATE TABLE `user` (`id` int PRIMARY KEY, `posts` int NOT NULL DEFAULT 0)' + }, + oracle: { + text : 'CREATE TABLE "user" ("id" int PRIMARY KEY, "posts" int NOT NULL DEFAULT 0)', + string: 'CREATE TABLE "user" ("id" int PRIMARY KEY, "posts" int NOT NULL DEFAULT 0)' + } +}); + Harness.test({ query: Table.define({ name: 'post', @@ -256,25 +289,27 @@ Harness.test({ dataType: 'int', references: { table: 'user', - column: 'id' + column: 'id', + onDelete: 'restrict', + onUpdate: 'set null' } }] }).create(), pg: { - text : 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id"))', - string: 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id"))' + text : 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE SET NULL)', + string: 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE SET NULL)' }, sqlite: { - text : 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id"))', - string: 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id"))' + text : 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE SET NULL)', + string: 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE SET NULL)' }, mysql: { - text : 'CREATE TABLE `post` (`userId` int REFERENCES `user`(`id`))', - string: 'CREATE TABLE `post` (`userId` int REFERENCES `user`(`id`))' + text : 'CREATE TABLE `post` (`userId` int REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE SET NULL)', + string: 'CREATE TABLE `post` (`userId` int REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE SET NULL)' }, oracle: { - text : 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id"))', - string: 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id"))' + text : 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE SET NULL)', + string: 'CREATE TABLE "post" ("userId" int REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE SET NULL)' }, params: [] }); @@ -461,6 +496,46 @@ Harness.test({ params: [] }); +var users = Table.define({ + name: 'users', + columns: { + id: { + primaryKey: true, + dataType: 'int', + references: { + table: "entity", + column: "id", + constraint: "DEFERRABLE INITIALLY DEFERRED" + } + } + } +}); + +Harness.test({ + query: users.create(), + pg: { + 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)', + 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)', + 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)', + 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)', + string: 'CREATE TABLE "users" ("id" int PRIMARY KEY REFERENCES "entity"("id") DEFERRABLE INITIALLY DEFERRED)' + }, + params: [] +}); + // UNIQUE COLUMN TESTS Harness.test({ query: Table.define({ @@ -496,6 +571,47 @@ Harness.test({ params: [] }); + +var noUsers = Table.define({ + name: 'no_users', + columns: { + id: { + primaryKey: true, + dataType: 'int', + references: { + table: "entity", + column: "id", + constraint: "" + } + } + } +}); + +Harness.test({ + query: noUsers.create(), + pg: { + text : 'CREATE TABLE "no_users" ("id" int PRIMARY KEY REFERENCES "entity"("id"))', + string: 'CREATE TABLE "no_users" ("id" int PRIMARY KEY REFERENCES "entity"("id"))' + }, + sqlite: { + text : 'CREATE TABLE "no_users" ("id" int PRIMARY KEY REFERENCES "entity"("id"))', + string: 'CREATE TABLE "no_users" ("id" int PRIMARY KEY REFERENCES "entity"("id"))' + }, + mysql: { + text : 'CREATE TABLE `no_users` (`id` int PRIMARY KEY REFERENCES `entity`(`id`))', + string: 'CREATE TABLE `no_users` (`id` int PRIMARY KEY REFERENCES `entity`(`id`))' + }, + mssql: { + text : 'CREATE TABLE [no_users] ([id] int PRIMARY KEY REFERENCES [entity]([id]))', + string: 'CREATE TABLE [no_users] ([id] int PRIMARY KEY REFERENCES [entity]([id]))' + }, + oracle: { + text : 'CREATE TABLE "no_users" ("id" int PRIMARY KEY REFERENCES "entity"("id"))', + string: 'CREATE TABLE "no_users" ("id" int PRIMARY KEY REFERENCES "entity"("id"))' + }, + params: [] +}); + Harness.test({ query: Table.define({ name: 'post', @@ -563,3 +679,83 @@ Harness.test({ }, params: [] }); + +Harness.test({ + query: Table.define({ + name: 'post', + columns: [{ + name: 'id', + dataType: 'int', + primaryKey: true + }, { + name: 'blog_id', + dataType: 'int' + }, { + name: 'user_id', + dataType: 'int' + }], + foreignKeys: { + table: 'users', + columns: [ 'blog_id', 'user_id' ], + refColumns: [ 'id', 'user_id' ] + } + }).create(), + pg: { + text : 'CREATE TABLE "post" ("id" int PRIMARY KEY, "blog_id" int, "user_id" int, FOREIGN KEY ( "blog_id", "user_id" ) REFERENCES "users" ( "id", "user_id" ))', + string: 'CREATE TABLE "post" ("id" int PRIMARY KEY, "blog_id" int, "user_id" int, FOREIGN KEY ( "blog_id", "user_id" ) REFERENCES "users" ( "id", "user_id" ))' + }, + sqlite: { + text : 'CREATE TABLE "post" ("id" int PRIMARY KEY, "blog_id" int, "user_id" int, FOREIGN KEY ( "blog_id", "user_id" ) REFERENCES "users" ( "id", "user_id" ))', + string: 'CREATE TABLE "post" ("id" int PRIMARY KEY, "blog_id" int, "user_id" int, FOREIGN KEY ( "blog_id", "user_id" ) REFERENCES "users" ( "id", "user_id" ))' + }, + mysql: { + text : 'CREATE TABLE `post` (`id` int PRIMARY KEY, `blog_id` int, `user_id` int, FOREIGN KEY ( `blog_id`, `user_id` ) REFERENCES `users` ( `id`, `user_id` ))', + string: 'CREATE TABLE `post` (`id` int PRIMARY KEY, `blog_id` int, `user_id` int, FOREIGN KEY ( `blog_id`, `user_id` ) REFERENCES `users` ( `id`, `user_id` ))' + }, + params: [] +}); + +Harness.test({ + query: Table.define({ + name: 'replies', + columns: [{ + name: 'id', + dataType: 'int', + primaryKey: true + }, { + name: 'blog_id', + dataType: 'int' + }, { + name: 'post_id', + dataType: 'int' + }, { + name: 'user_id', + dataType: 'int' + }], + foreignKeys: [{ + table: 'users', + columns: [ 'blog_id', 'user_id' ], + onDelete: 'no action' + }, { + name: 'posts_idx', + table: 'posts', + columns: [ 'blog_id', 'post_id' ], + refColumns: [ 'blog_id', 'id' ], + onDelete: 'cascade', + onUpdate: 'set default' + }] + }).create(), + pg: { + text : 'CREATE TABLE "replies" ("id" int PRIMARY KEY, "blog_id" int, "post_id" int, "user_id" int, FOREIGN KEY ( "blog_id", "user_id" ) REFERENCES "users" ON DELETE NO ACTION, CONSTRAINT "posts_idx" FOREIGN KEY ( "blog_id", "post_id" ) REFERENCES "posts" ( "blog_id", "id" ) ON DELETE CASCADE ON UPDATE SET DEFAULT)', + string: 'CREATE TABLE "replies" ("id" int PRIMARY KEY, "blog_id" int, "post_id" int, "user_id" int, FOREIGN KEY ( "blog_id", "user_id" ) REFERENCES "users" ON DELETE NO ACTION, CONSTRAINT "posts_idx" FOREIGN KEY ( "blog_id", "post_id" ) REFERENCES "posts" ( "blog_id", "id" ) ON DELETE CASCADE ON UPDATE SET DEFAULT)' + }, + sqlite: { + text : 'CREATE TABLE "replies" ("id" int PRIMARY KEY, "blog_id" int, "post_id" int, "user_id" int, FOREIGN KEY ( "blog_id", "user_id" ) REFERENCES "users" ON DELETE NO ACTION, CONSTRAINT "posts_idx" FOREIGN KEY ( "blog_id", "post_id" ) REFERENCES "posts" ( "blog_id", "id" ) ON DELETE CASCADE ON UPDATE SET DEFAULT)', + string: 'CREATE TABLE "replies" ("id" int PRIMARY KEY, "blog_id" int, "post_id" int, "user_id" int, FOREIGN KEY ( "blog_id", "user_id" ) REFERENCES "users" ON DELETE NO ACTION, CONSTRAINT "posts_idx" FOREIGN KEY ( "blog_id", "post_id" ) REFERENCES "posts" ( "blog_id", "id" ) ON DELETE CASCADE ON UPDATE SET DEFAULT)' + }, + mysql: { + text : 'CREATE TABLE `replies` (`id` int PRIMARY KEY, `blog_id` int, `post_id` int, `user_id` int, FOREIGN KEY ( `blog_id`, `user_id` ) REFERENCES `users` ON DELETE NO ACTION, CONSTRAINT `posts_idx` FOREIGN KEY ( `blog_id`, `post_id` ) REFERENCES `posts` ( `blog_id`, `id` ) ON DELETE CASCADE ON UPDATE SET DEFAULT)', + string: 'CREATE TABLE `replies` (`id` int PRIMARY KEY, `blog_id` int, `post_id` int, `user_id` int, FOREIGN KEY ( `blog_id`, `user_id` ) REFERENCES `users` ON DELETE NO ACTION, CONSTRAINT `posts_idx` FOREIGN KEY ( `blog_id`, `post_id` ) REFERENCES `posts` ( `blog_id`, `id` ) ON DELETE CASCADE ON UPDATE SET DEFAULT)' + }, + params: [] +}); diff --git a/test/dialects/date-tests.js b/test/dialects/date-tests.js new file mode 100644 index 00000000..dd2c4d64 --- /dev/null +++ b/test/dialects/date-tests.js @@ -0,0 +1,228 @@ +'use strict'; + +var Harness = require('./support'); +var customer = Harness.defineCustomerTable(); +var Sql = require('../../lib'); + +Harness.test({ + query: customer.select(Sql.functions.YEAR(customer.metadata)), + pg: { + text : 'SELECT EXTRACT(YEAR FROM "customer"."metadata") FROM "customer"', + string: 'SELECT EXTRACT(YEAR FROM "customer"."metadata") FROM "customer"' + }, + sqlite: { + text : 'SELECT strftime(\'%Y\', "customer"."metadata") FROM "customer"', + string: 'SELECT strftime(\'%Y\', "customer"."metadata") FROM "customer"' + }, + mysql: { + text : 'SELECT YEAR(`customer`.`metadata`) FROM `customer`', + string: 'SELECT YEAR(`customer`.`metadata`) FROM `customer`' + }, + mssql: { + text : 'SELECT DATEPART(year, [customer].[metadata]) FROM [customer]', + string: 'SELECT DATEPART(year, [customer].[metadata]) FROM [customer]' + }, + oracle: { + text : 'SELECT EXTRACT(YEAR FROM "customer"."metadata") FROM "customer"', + string: 'SELECT EXTRACT(YEAR FROM "customer"."metadata") FROM "customer"' + }, + params: [] +}); + +Harness.test({ + query: customer.select(Sql.functions.MONTH(customer.metadata)), + pg: { + text : 'SELECT EXTRACT(MONTH FROM "customer"."metadata") FROM "customer"', + string: 'SELECT EXTRACT(MONTH FROM "customer"."metadata") FROM "customer"' + }, + sqlite: { + text: 'SELECT strftime(\'%m\', datetime("customer"."metadata"/1000, "unixepoch")) FROM "customer"', + string: 'SELECT strftime(\'%m\', datetime("customer"."metadata"/1000, "unixepoch")) FROM "customer"', + config: { + dateTimeMillis: true + } + }, + mysql: { + text : 'SELECT MONTH(`customer`.`metadata`) FROM `customer`', + string: 'SELECT MONTH(`customer`.`metadata`) FROM `customer`' + }, + mssql: { + text : 'SELECT DATEPART(month, [customer].[metadata]) FROM [customer]', + string: 'SELECT DATEPART(month, [customer].[metadata]) FROM [customer]' + }, + oracle: { + text : 'SELECT EXTRACT(MONTH FROM "customer"."metadata") FROM "customer"', + string: 'SELECT EXTRACT(MONTH FROM "customer"."metadata") FROM "customer"' + }, + params: [] +}); + +Harness.test({ + query: customer.select(Sql.functions.DAY(customer.metadata)), + pg: { + text : 'SELECT EXTRACT(DAY FROM "customer"."metadata") FROM "customer"', + string: 'SELECT EXTRACT(DAY FROM "customer"."metadata") FROM "customer"' + }, + sqlite: { + text : 'SELECT strftime(\'%d\', "customer"."metadata") FROM "customer"', + string: 'SELECT strftime(\'%d\', "customer"."metadata") FROM "customer"' + }, + mysql: { + text : 'SELECT DAY(`customer`.`metadata`) FROM `customer`', + string: 'SELECT DAY(`customer`.`metadata`) FROM `customer`' + }, + mssql: { + text : 'SELECT DATEPART(day, [customer].[metadata]) FROM [customer]', + string: 'SELECT DATEPART(day, [customer].[metadata]) FROM [customer]' + }, + oracle: { + text : 'SELECT EXTRACT(DAY FROM "customer"."metadata") FROM "customer"', + string: 'SELECT EXTRACT(DAY FROM "customer"."metadata") FROM "customer"' + }, + params: [] +}); + +Harness.test({ + query: customer.select(Sql.functions.HOUR(customer.metadata)), + pg: { + text : 'SELECT EXTRACT(HOUR FROM "customer"."metadata") FROM "customer"', + string: 'SELECT EXTRACT(HOUR FROM "customer"."metadata") FROM "customer"' + }, + sqlite: { + text: 'SELECT strftime(\'%H\', datetime("customer"."metadata"/1000, "unixepoch")) FROM "customer"', + string: 'SELECT strftime(\'%H\', datetime("customer"."metadata"/1000, "unixepoch")) FROM "customer"', + config: { + dateTimeMillis: true + } + }, + mysql: { + text : 'SELECT HOUR(`customer`.`metadata`) FROM `customer`', + string: 'SELECT HOUR(`customer`.`metadata`) FROM `customer`' + }, + mssql: { + text : 'SELECT DATEPART(hour, [customer].[metadata]) FROM [customer]', + string: 'SELECT DATEPART(hour, [customer].[metadata]) FROM [customer]' + }, + oracle: { + text : 'SELECT EXTRACT(HOUR FROM "customer"."metadata") FROM "customer"', + string: 'SELECT EXTRACT(HOUR FROM "customer"."metadata") FROM "customer"' + }, + params: [] +}); + +Harness.test({ + query: customer.select(Sql.functions.CURRENT_TIMESTAMP()), + pg: { + text : 'SELECT CURRENT_TIMESTAMP FROM "customer"', + string: 'SELECT CURRENT_TIMESTAMP FROM "customer"' + }, + sqlite: { + text : 'SELECT CURRENT_TIMESTAMP FROM "customer"', + string: 'SELECT CURRENT_TIMESTAMP FROM "customer"' + }, + mysql: { + text : 'SELECT CURRENT_TIMESTAMP FROM `customer`', + string: 'SELECT CURRENT_TIMESTAMP FROM `customer`' + }, + mssql: { + text : 'SELECT CURRENT_TIMESTAMP FROM [customer]', + string: 'SELECT CURRENT_TIMESTAMP FROM [customer]' + }, + oracle: { + text : 'SELECT CURRENT_TIMESTAMP FROM "customer"', + string: 'SELECT CURRENT_TIMESTAMP FROM "customer"' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(Sql.functions.CURRENT_TIMESTAMP().plus(Sql.interval({hours:1}))), + pg: { + text : 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'1 HOUR\')', + string: 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'1 HOUR\')' + }, + mysql: { + text : 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'1:0:0\' HOUR_SECOND)', + string: 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'1:0:0\' HOUR_SECOND)' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(Sql.functions.CURRENT_TIMESTAMP().minus(Sql.interval({years:3}))), + pg: { + text : 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'3 YEAR\')', + string: 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'3 YEAR\')' + }, + mysql: { + text : 'SELECT (CURRENT_TIMESTAMP - INTERVAL 3 YEAR)', + string: 'SELECT (CURRENT_TIMESTAMP - INTERVAL 3 YEAR)' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(Sql.functions.CURRENT_TIMESTAMP().minus(Sql.interval({years:3, months:2}))), + pg: { + text : 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'3 YEAR 2 MONTH\')', + string: 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'3 YEAR 2 MONTH\')' + }, + mysql: { + text : 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'3-2\' YEAR_MONTH)', + string: 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'3-2\' YEAR_MONTH)' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(Sql.functions.CURRENT_TIMESTAMP().plus(Sql.interval({hours:1, minutes:20}))), + pg: { + text : 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'1 HOUR 20 MINUTE\')', + string: 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'1 HOUR 20 MINUTE\')' + }, + mysql: { + text : 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'1:20:0\' HOUR_SECOND)', + string: 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'1:20:0\' HOUR_SECOND)' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(Sql.functions.CURRENT_TIMESTAMP().plus(Sql.interval({hours:'sql\'injection', minutes:20}))), + pg: { + text : 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'20 MINUTE\')', + string: 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'20 MINUTE\')' + }, + mysql: { + text : 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'0:20:0\' HOUR_SECOND)', + string: 'SELECT (CURRENT_TIMESTAMP + INTERVAL \'0:20:0\' HOUR_SECOND)' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(Sql.functions.CURRENT_TIMESTAMP().minus(Sql.interval({days: 1, hours:5, minutes: 'sql\'injection'}))), + pg: { + text : 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'1 DAY 5 HOUR\')', + string: 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'1 DAY 5 HOUR\')' + }, + mysql: { + text : 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'1 5:0:0\' DAY_SECOND)', + string: 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'1 5:0:0\' DAY_SECOND)' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(Sql.functions.CURRENT_TIMESTAMP().minus(Sql.interval({years: 2, months: 5}))), + pg: { + text : 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'2 YEAR 5 MONTH\')', + string: 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'2 YEAR 5 MONTH\')' + }, + mysql: { + text : 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'2-5\' YEAR_MONTH)', + string: 'SELECT (CURRENT_TIMESTAMP - INTERVAL \'2-5\' YEAR_MONTH)' + }, + params: [] +}); + diff --git a/test/dialects/insert-tests.js b/test/dialects/insert-tests.js index b472f51d..60ba6a62 100644 --- a/test/dialects/insert-tests.js +++ b/test/dialects/insert-tests.js @@ -3,6 +3,13 @@ var Harness = require('./support'); var post = Harness.definePostTable(); var user = Harness.defineUserTable(); +var contentTable = Harness.defineContentTable(); +var customerAliasTable = Harness.defineCustomerAliasTable(); + +var arrayTable = require('../../lib/table').define({ + name: 'arraytest', + columns: ['id', 'numbers'] +}); Harness.test({ query: post.insert(post.content.value('test'), post.userId.value(1)), @@ -50,6 +57,31 @@ Harness.test({ params: ['whoah'] }); +Harness.test({ + query: post.insert({length: 0}), + pg: { + text : 'INSERT INTO "post" ("length") VALUES ($1)', + string: 'INSERT INTO "post" ("length") VALUES (0)' + }, + sqlite: { + text : 'INSERT INTO "post" ("length") VALUES ($1)', + string: 'INSERT INTO "post" ("length") VALUES (0)' + }, + mysql: { + text : 'INSERT INTO `post` (`length`) VALUES (?)', + string: 'INSERT INTO `post` (`length`) VALUES (0)' + }, + mssql: { + text : 'INSERT INTO [post] ([length]) VALUES (@1)', + string: 'INSERT INTO [post] ([length]) VALUES (0)' + }, + oracle: { + text : 'INSERT INTO "post" ("length") VALUES (:1)', + string: 'INSERT INTO "post" ("length") VALUES (0)' + }, + params: [0] +}); + Harness.test({ query: post.insert({ content: 'test', @@ -621,6 +653,271 @@ Harness.test({ params: ['test', 2, 'testupdate'] }); +Harness.test({ + query: customerAliasTable.insert({ + id : 2, + name : 'test' + }).onConflict({ + columns: ['id'], + update: ['name'] + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + text : 'INSERT INTO "customer" ("id", "name") VALUES ($1, $2) ON CONFLICT ("id") DO UPDATE SET "name" = EXCLUDED."name"', + string: 'INSERT INTO "customer" ("id", "name") VALUES (2, \'test\') ON CONFLICT ("id") DO UPDATE SET "name" = EXCLUDED."name"' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [2, 'test'] +}); + +Harness.test({ + query: customerAliasTable.insert({ + id : 2, + name : 'test' + }).orIgnore(), + mysql: { + throws: true + }, + sqlite: { + text : 'INSERT OR IGNORE INTO "customer" ("id", "name") VALUES ($1, $2)', + string: 'INSERT OR IGNORE INTO "customer" ("id", "name") VALUES (2, \'test\')' + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [2, 'test'] +}); + +Harness.test({ + query: post.insert({ + content: 'test', + userId: 2 + }).onConflict({ + columns: ['userId'], + update: ['content'] + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + text : 'INSERT INTO "post" ("content", "userId") VALUES ($1, $2) ON CONFLICT ("userId") DO UPDATE SET "content" = EXCLUDED."content"', + string: 'INSERT INTO "post" ("content", "userId") VALUES (\'test\', 2) ON CONFLICT ("userId") DO UPDATE SET "content" = EXCLUDED."content"' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2] +}); + +Harness.test({ + query: post.insert({ + content: 'test', + userId: 2 + }).onConflict({ + columns: ['userId','content'], + update: ['content','userId'] + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + text : 'INSERT INTO "post" ("content", "userId") VALUES ($1, $2) ON CONFLICT ("userId", "content") DO UPDATE SET "content" = EXCLUDED."content", "userId" = EXCLUDED."userId"', + string: 'INSERT INTO "post" ("content", "userId") VALUES (\'test\', 2) ON CONFLICT ("userId", "content") DO UPDATE SET "content" = EXCLUDED."content", "userId" = EXCLUDED."userId"' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2] +}); + +Harness.test({ + query: post.insert({ + content: 'test', + userId: 2 + }).onConflict({ + columns: ['userId'], + update: ['content'] + }).where(post.userId.equals(2)), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + text : 'INSERT INTO "post" ("content", "userId") VALUES ($1, $2) ON CONFLICT ("userId") DO UPDATE SET "content" = EXCLUDED."content" WHERE ("post"."userId" = $3)', + string: 'INSERT INTO "post" ("content", "userId") VALUES (\'test\', 2) ON CONFLICT ("userId") DO UPDATE SET "content" = EXCLUDED."content" WHERE ("post"."userId" = 2)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2, 2] +}); + +Harness.test({ + query: post.insert({ + content: 'test', + userId: 2 + }).onConflict({ + constraint: 'conc_userId', + update: ['content'] + }).where(post.userId.equals(2)), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + text : 'INSERT INTO "post" ("content", "userId") VALUES ($1, $2) ON CONFLICT ON CONSTRAINT "conc_userId" DO UPDATE SET "content" = EXCLUDED."content" WHERE ("post"."userId" = $3)', + string: 'INSERT INTO "post" ("content", "userId") VALUES (\'test\', 2) ON CONFLICT ON CONSTRAINT "conc_userId" DO UPDATE SET "content" = EXCLUDED."content" WHERE ("post"."userId" = 2)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2, 2] +}); + +Harness.test({ + query: post.insert({ + content: 'test', + userId: 2 + }).onConflict({ + columns: ['userId'], + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + text : 'INSERT INTO "post" ("content", "userId") VALUES ($1, $2) ON CONFLICT ("userId") DO NOTHING', + string: 'INSERT INTO "post" ("content", "userId") VALUES (\'test\', 2) ON CONFLICT ("userId") DO NOTHING' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2] +}); + +Harness.test({ + query: post.insert({ + content: 'test', + userId: 2 + }).onConflict({ + constraint: 'conc_userId', + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + text : 'INSERT INTO "post" ("content", "userId") VALUES ($1, $2) ON CONFLICT ON CONSTRAINT "conc_userId" DO NOTHING', + string: 'INSERT INTO "post" ("content", "userId") VALUES (\'test\', 2) ON CONFLICT ON CONSTRAINT "conc_userId" DO NOTHING' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2] +}); + +Harness.test({ + query: contentTable.insert({ + contentId: 20, + text : "something" + }).onConflict({ + columns: ['contentId'], + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + text : 'INSERT INTO "content" ("content_id", "text") VALUES ($1, $2) ON CONFLICT ("content_id") DO NOTHING', + string: 'INSERT INTO "content" ("content_id", "text") VALUES (20, \'something\') ON CONFLICT ("content_id") DO NOTHING' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [20, "something"] +}); + +Harness.test({ + query: contentTable.insert({ + contentId: 20, + text : "something", + contentPosts : "another thing", + }).onConflict({ + columns: ['contentId'], + update: ['contentPosts'] + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + text : 'INSERT INTO "content" ("content_id", "text", "content_posts") VALUES ($1, $2, $3) ON CONFLICT ("content_id") DO UPDATE SET "content_posts" = EXCLUDED."content_posts"', + string: 'INSERT INTO "content" ("content_id", "text", "content_posts") VALUES (20, \'something\', \'another thing\') ON CONFLICT ("content_id") DO UPDATE SET "content_posts" = EXCLUDED."content_posts"' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [20, "something", "another thing"] +}); + Harness.test({ query: post.insert([]), @@ -630,3 +927,168 @@ 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\'))' + } +}); + +Harness.test({ + query: post.insert(post.userId).select(user.id).from(user), + pg: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + sqlite: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + mysql: { + text : 'INSERT INTO `post` (`userId`) SELECT `user`.`id` FROM `user`', + string: 'INSERT INTO `post` (`userId`) SELECT `user`.`id` FROM `user`' + }, + mssql: { + text : 'INSERT INTO [post] ([userId]) SELECT [user].[id] FROM [user]', + string: 'INSERT INTO [post] ([userId]) SELECT [user].[id] FROM [user]' + }, + oracle: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + params: [] +}); + +Harness.test({ + query: post.insert(post.userId).select(user.id).from(user).onConflict({ + columns: ['userId'], + update: ['content'] + }), + pg: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" ON CONFLICT ("userId") DO UPDATE SET "content" = EXCLUDED."content"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" ON CONFLICT ("userId") DO UPDATE SET "content" = EXCLUDED."content"' + }, + sqlite: { + throws: true + }, + mysql: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.insert(post.userId).add(user.select(user.id)), + pg: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + sqlite: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + mysql: { + text : 'INSERT INTO `post` (`userId`) SELECT `user`.`id` FROM `user`', + string: 'INSERT INTO `post` (`userId`) SELECT `user`.`id` FROM `user`' + }, + mssql: { + text : 'INSERT INTO [post] ([userId]) SELECT [user].[id] FROM [user]', + string: 'INSERT INTO [post] ([userId]) SELECT [user].[id] FROM [user]' + }, + oracle: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + params: [] +}); + +Harness.test({ + query: post.insert(post.userId).add(user.select(user.id).from(user)), + pg: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + sqlite: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + mysql: { + text : 'INSERT INTO `post` (`userId`) SELECT `user`.`id` FROM `user`', + string: 'INSERT INTO `post` (`userId`) SELECT `user`.`id` FROM `user`' + }, + mssql: { + text : 'INSERT INTO [post] ([userId]) SELECT [user].[id] FROM [user]', + string: 'INSERT INTO [post] ([userId]) SELECT [user].[id] FROM [user]' + }, + oracle: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + params: [] +}); + +Harness.test({ + query: post.insert(post.userId).add(user.select(user.id).order(user.id)), + pg: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" ORDER BY "user"."id"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" ORDER BY "user"."id"' + }, + sqlite: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" ORDER BY "user"."id"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" ORDER BY "user"."id"' + }, + mysql: { + text : 'INSERT INTO `post` (`userId`) SELECT `user`.`id` FROM `user` ORDER BY `user`.`id`', + string: 'INSERT INTO `post` (`userId`) SELECT `user`.`id` FROM `user` ORDER BY `user`.`id`' + }, + mssql: { + text : 'INSERT INTO [post] ([userId]) SELECT [user].[id] FROM [user] ORDER BY [user].[id]', + string: 'INSERT INTO [post] ([userId]) SELECT [user].[id] FROM [user] ORDER BY [user].[id]' + }, + oracle: { + text : 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" ORDER BY "user"."id"', + string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" ORDER BY "user"."id"' + }, + params: [] +}); + diff --git a/test/dialects/join-tests.js b/test/dialects/join-tests.js index abebdb01..2e0c6882 100644 --- a/test/dialects/join-tests.js +++ b/test/dialects/join-tests.js @@ -174,3 +174,57 @@ Harness.test({ }, params: [] }); + +Harness.test({ + query: user.select().from(user.leftJoinLateral(post.subQuery().select(post.userId))), + pg: { + text : 'SELECT "user".* FROM "user" LEFT JOIN LATERAL (SELECT "post"."userId" FROM "post") ON true', + string: 'SELECT "user".* FROM "user" LEFT JOIN LATERAL (SELECT "post"."userId" FROM "post") ON true' + }, + mssql: { + text : 'SELECT [user].* FROM [user] OUTER APPLY (SELECT [post].[userId] FROM [post])', + string: 'SELECT [user].* FROM [user] OUTER APPLY (SELECT [post].[userId] FROM [post])' + }, + oracle: { + text : 'SELECT "user".* FROM "user" OUTER APPLY (SELECT "post"."userId" FROM "post")', + string: 'SELECT "user".* FROM "user" OUTER APPLY (SELECT "post"."userId" FROM "post")' + }, + params: [] +}); + +Harness.test({ + query: user.select().from(user.leftJoinLateral(post.subQuery().select(post.userId).where(user.id.equals(post.userId)))), + pg: { + text : 'SELECT "user".* FROM "user" LEFT JOIN LATERAL (SELECT "post"."userId" FROM "post" WHERE ("user"."id" = "post"."userId")) ON true', + string: 'SELECT "user".* FROM "user" LEFT JOIN LATERAL (SELECT "post"."userId" FROM "post" WHERE ("user"."id" = "post"."userId")) ON true' + }, + mssql: { + text : 'SELECT [user].* FROM [user] OUTER APPLY (SELECT [post].[userId] FROM [post] WHERE ([user].[id] = [post].[userId]))', + string: 'SELECT [user].* FROM [user] OUTER APPLY (SELECT [post].[userId] FROM [post] WHERE ([user].[id] = [post].[userId]))' + }, + oracle: { + text : 'SELECT "user".* FROM "user" OUTER APPLY (SELECT "post"."userId" FROM "post" WHERE ("user"."id" = "post"."userId"))', + string: 'SELECT "user".* FROM "user" OUTER APPLY (SELECT "post"."userId" FROM "post" WHERE ("user"."id" = "post"."userId"))' + }, + params: [] +}); + +Harness.test({ + query: user.select().from(user + .leftJoinLateral(post.subQuery().select(post.userId)) + .leftJoinLateral(comment.subQuery().select(comment.postId))), + pg: { + text : 'SELECT "user".* FROM "user" LEFT JOIN LATERAL (SELECT "post"."userId" FROM "post") ON true LEFT JOIN LATERAL (SELECT "comment"."postId" FROM "comment") ON true', + string: 'SELECT "user".* FROM "user" LEFT JOIN LATERAL (SELECT "post"."userId" FROM "post") ON true LEFT JOIN LATERAL (SELECT "comment"."postId" FROM "comment") ON true' + }, + mssql: { + text : 'SELECT [user].* FROM [user] OUTER APPLY (SELECT [post].[userId] FROM [post]) OUTER APPLY (SELECT [comment].[postId] FROM [comment])', + string: 'SELECT [user].* FROM [user] OUTER APPLY (SELECT [post].[userId] FROM [post]) OUTER APPLY (SELECT [comment].[postId] FROM [comment])' + }, + oracle: { + text : 'SELECT "user".* FROM "user" OUTER APPLY (SELECT "post"."userId" FROM "post") OUTER APPLY (SELECT "comment"."postId" FROM "comment")', + string: 'SELECT "user".* FROM "user" OUTER APPLY (SELECT "post"."userId" FROM "post") OUTER APPLY (SELECT "comment"."postId" FROM "comment")' + }, + params: [] +}); + diff --git a/test/dialects/order-tests.js b/test/dialects/order-tests.js index 2c4803f5..a0813d8e 100644 --- a/test/dialects/order-tests.js +++ b/test/dialects/order-tests.js @@ -336,4 +336,30 @@ Harness.test({ string: 'SELECT "post"."content" FROM "post" ORDER BY "post"."content"' }, params: [] -}); \ No newline at end of file +}); + +Harness.test({ + query: post.select(post.content).order([]), + pg: { + text : 'SELECT "post"."content" FROM "post"', + string: 'SELECT "post"."content" FROM "post"' + }, + sqlite: { + text : 'SELECT "post"."content" FROM "post"', + string: 'SELECT "post"."content" FROM "post"' + }, + mysql: { + text : 'SELECT `post`.`content` FROM `post`', + string: 'SELECT `post`.`content` FROM `post`' + }, + mssql: { + text : 'SELECT [post].[content] FROM [post]', + string: 'SELECT [post].[content] FROM [post]' + }, + oracle: { + text : 'SELECT "post"."content" FROM "post"', + string: 'SELECT "post"."content" FROM "post"' + }, + params: [] +}); + diff --git a/test/dialects/replace-tests.js b/test/dialects/replace-tests.js new file mode 100644 index 00000000..72af0e4b --- /dev/null +++ b/test/dialects/replace-tests.js @@ -0,0 +1,1000 @@ +'use strict'; + +var Harness = require('./support'); +var post = Harness.definePostTable(); +var user = Harness.defineUserTable(); +var contentTable = Harness.defineContentTable(); +var customerAliasTable = Harness.defineCustomerAliasTable(); + +var arrayTable = require('../../lib/table').define({ + name: 'arraytest', + columns: ['id', 'numbers'] +}); + +Harness.test({ + query: post.replace(post.content.value('test'), post.userId.value(1)), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") VALUES ($1, $2)', + string: 'REPLACE INTO "post" ("content", "userId") VALUES (\'test\', 1)' + }, + mysql: { + text : 'REPLACE INTO `post` (`content`, `userId`) VALUES (?, ?)', + string: 'REPLACE INTO `post` (`content`, `userId`) VALUES (\'test\', 1)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 1] +}); + +Harness.test({ + query: post.replace(post.content.value('whoah')), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content") VALUES ($1)', + string: 'REPLACE INTO "post" ("content") VALUES (\'whoah\')' + }, + mysql: { + text : 'REPLACE INTO `post` (`content`) VALUES (?)', + string: 'REPLACE INTO `post` (`content`) VALUES (\'whoah\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['whoah'] +}); + +Harness.test({ + query: post.replace({length: 0}), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("length") VALUES ($1)', + string: 'REPLACE INTO "post" ("length") VALUES (0)' + }, + mysql: { + text : 'REPLACE INTO `post` (`length`) VALUES (?)', + string: 'REPLACE INTO `post` (`length`) VALUES (0)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [0] +}); + +Harness.test({ + query: post.replace({ + content: 'test', + userId: 2 + }), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") VALUES ($1, $2)', + string: 'REPLACE INTO "post" ("content", "userId") VALUES (\'test\', 2)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2] +}); + +Harness.test({ + query: post.replace({ + content: post.sql.functions.LOWER('TEST'), + userId: 2 + }), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") VALUES (LOWER($1), $2)', + string: 'REPLACE INTO "post" ("content", "userId") VALUES (LOWER(\'TEST\'), 2)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['TEST', 2] +}); + +// allow bulk replace +Harness.test({ + query: post.replace([{ + content: 'whoah' + }, { + content: 'hey' + } + ]), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content") VALUES ($1), ($2)', + string: 'REPLACE INTO "post" ("content") VALUES (\'whoah\'), (\'hey\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['whoah', 'hey'] +}); + +Harness.test({ + query: post.replace([{ + content: 'whoah', + userId: 1 + }, { + content: 'hey', + userId: 2 + } + ]), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") VALUES ($1, $2), ($3, $4)', + string: 'REPLACE INTO "post" ("content", "userId") VALUES (\'whoah\', 1), (\'hey\', 2)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['whoah', 1, 'hey', 2] +}); + +// consistent order +Harness.test({ + query: post.replace([{ + content: 'whoah', + userId: 1 + }, { + userId: 2, + content: 'hey' + } + ]), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") VALUES ($1, $2), ($3, $4)', + string: 'REPLACE INTO "post" ("content", "userId") VALUES (\'whoah\', 1), (\'hey\', 2)' + }, + mysql: { + text : 'REPLACE INTO `post` (`content`, `userId`) VALUES (?, ?), (?, ?)', + string: 'REPLACE INTO `post` (`content`, `userId`) VALUES (\'whoah\', 1), (\'hey\', 2)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['whoah', 1, 'hey', 2] +}); + +Harness.test({ + query: post.replace({}), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" DEFAULT VALUES', + string: 'REPLACE INTO "post" DEFAULT VALUES' + }, + mysql: { + text : 'REPLACE INTO `post` () VALUES ()', + string: 'REPLACE INTO `post` () VALUES ()' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace({}).returning('*'), + pg: { + throws: true + }, + sqlite: { + throws: true + }, + mysql: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace({}).returning(post.star()), + pg: { + throws: true + }, + sqlite: { + throws: true + }, + mysql: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace({}).returning(post.id), + pg: { + throws: true + }, + sqlite: { + throws: true + }, + mysql: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace({}).returning(post.id, post.content), + pg: { + throws: true + }, + sqlite: { + throws: true + }, + mysql: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace({}).returning([post.id, post.content]), + pg: { + throws: true + }, + sqlite: { + throws: true + }, + mysql: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +// handle missing columns +Harness.test({ + query: post.replace([{ + content: 'whoah', + userId: 1 + }, { + content: 'hey' + } + ]), + pg: { + throws: true + }, + sqlite: { + text : 'Sqlite requires the same number of columns in each replace row', + throws: true + }, + mysql: { + text : 'REPLACE INTO `post` (`content`, `userId`) VALUES (?, ?), (?, DEFAULT)', + string: 'REPLACE INTO `post` (`content`, `userId`) VALUES (\'whoah\', 1), (\'hey\', DEFAULT)', + params: ['whoah', 1, 'hey'] + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, +}); + +Harness.test({ + query: post.replace([{ + userId: 1 + }, { + content: 'hey', + userId: 2 + } + ]), + pg: { + throws: true + }, + sqlite: { + text : 'Sqlite requires the same number of columns in each replace row', + throws: true + }, + mysql: { + text : 'REPLACE INTO `post` (`userId`, `content`) VALUES (?, DEFAULT), (?, ?)', + string: 'REPLACE INTO `post` (`userId`, `content`) VALUES (1, DEFAULT), (2, \'hey\')', + params: [1, 2, 'hey'] + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, +}); + +Harness.test({ + query: post.replace(post.content, post.userId) + .select('\'test\'', user.id).from(user).where(user.name.like('A%')), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") SELECT \'test\', "user"."id" FROM "user" WHERE ("user"."name" LIKE $1)', + string: 'REPLACE INTO "post" ("content", "userId") SELECT \'test\', "user"."id" FROM "user" WHERE ("user"."name" LIKE \'A%\')' + }, + mysql: { + text : 'REPLACE INTO `post` (`content`, `userId`) SELECT \'test\', `user`.`id` FROM `user` WHERE (`user`.`name` LIKE ?)', + string: 'REPLACE INTO `post` (`content`, `userId`) SELECT \'test\', `user`.`id` FROM `user` WHERE (`user`.`name` LIKE \'A%\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['A%'] +}); + +Harness.test({ + query: post.replace([post.content, post.userId]) + .select('\'test\'', user.id).from(user).where(user.name.like('A%')), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") SELECT \'test\', "user"."id" FROM "user" WHERE ("user"."name" LIKE $1)', + string: 'REPLACE INTO "post" ("content", "userId") SELECT \'test\', "user"."id" FROM "user" WHERE ("user"."name" LIKE \'A%\')' + }, + mysql: { + text : 'REPLACE INTO `post` (`content`, `userId`) SELECT \'test\', `user`.`id` FROM `user` WHERE (`user`.`name` LIKE ?)', + string: 'REPLACE INTO `post` (`content`, `userId`) SELECT \'test\', `user`.`id` FROM `user` WHERE (`user`.`name` LIKE \'A%\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['A%'] +}); + +Harness.test({ + query: post.replace(post.userId) + .select(user.id).from(user).where(user.name.like('A%')), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user" WHERE ("user"."name" LIKE $1)', + string: 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user" WHERE ("user"."name" LIKE \'A%\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['A%'] +}); + +Harness.test({ + query: post.replace(post.userId) + .select(post.userId).from(user.join(post).on(user.id.equals(post.userId))).where(post.tags.like('A%')), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("userId") SELECT "post"."userId" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId") WHERE ("post"."tags" LIKE $1)', + string: 'REPLACE INTO "post" ("userId") SELECT "post"."userId" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId") WHERE ("post"."tags" LIKE \'A%\')' + }, + mysql: { + text : 'REPLACE INTO `post` (`userId`) SELECT `post`.`userId` FROM `user` INNER JOIN `post` ON (`user`.`id` = `post`.`userId`) WHERE (`post`.`tags` LIKE ?)', + string: 'REPLACE INTO `post` (`userId`) SELECT `post`.`userId` FROM `user` INNER JOIN `post` ON (`user`.`id` = `post`.`userId`) WHERE (`post`.`tags` LIKE \'A%\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['A%'] +}); + +Harness.test({ + query: post.replace(post.userId).select(user.id).distinct().from(user), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("userId") SELECT DISTINCT "user"."id" FROM "user"', + string: 'REPLACE INTO "post" ("userId") SELECT DISTINCT "user"."id" FROM "user"' + }, + mysql: { + text : 'REPLACE INTO `post` (`userId`) SELECT DISTINCT `user`.`id` FROM `user`', + string: 'REPLACE INTO `post` (`userId`) SELECT DISTINCT `user`.`id` FROM `user`' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +// Binary replaces +Harness.test({ + query: post.replace(post.content.value(new Buffer('test')), post.userId.value(2)), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") VALUES ($1, $2)', + string: 'REPLACE INTO "post" ("content", "userId") VALUES (x\'74657374\', 2)' + }, + mysql: { + text : 'REPLACE INTO `post` (`content`, `userId`) VALUES (?, ?)', + string: 'REPLACE INTO `post` (`content`, `userId`) VALUES (x\'74657374\', 2)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [new Buffer('test'), 2] +}); + +Harness.test({ + query: post.replace({ + content: new Buffer('test'), + userId: 2 + }), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content", "userId") VALUES ($1, $2)', + string: 'REPLACE INTO "post" ("content", "userId") VALUES (x\'74657374\', 2)' + }, + mysql: { + text : 'REPLACE INTO `post` (`content`, `userId`) VALUES (?, ?)', + string: 'REPLACE INTO `post` (`content`, `userId`) VALUES (x\'74657374\', 2)' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [new Buffer('test'), 2] +}); + +Harness.test({ + query: post.replace([{ + content: new Buffer('whoah') + }, { + content: new Buffer('hey') + } + ]), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("content") VALUES ($1), ($2)', + string: 'REPLACE INTO "post" ("content") VALUES (x\'77686f6168\'), (x\'686579\')' + }, + mysql: { + text : 'REPLACE INTO `post` (`content`) VALUES (?), (?)', + string: 'REPLACE INTO `post` (`content`) VALUES (x\'77686f6168\'), (x\'686579\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [new Buffer('whoah'), new Buffer('hey')] +}); + +Harness.test({ + query: post.replace({ + content: 'test', + userId: 2 + }).onDuplicate({ + content: 'testupdate', + }), + pg: { + throws: true + }, + sqlite: { + throws: true + }, + mysql: { + text : 'REPLACE INTO `post` (`content`, `userId`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `post`.`content` = ?', + string: 'REPLACE INTO `post` (`content`, `userId`) VALUES (\'test\', 2) ON DUPLICATE KEY UPDATE `post`.`content` = \'testupdate\'' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2, 'testupdate'] +}); + +Harness.test({ + query: customerAliasTable.replace({ + id : 2, + name : 'test' + }).onConflict({ + columns: ['id'], + update: ['name'] + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [2, 'test'] +}); + +Harness.test({ + query: customerAliasTable.replace({ + id : 2, + name : 'test' + }).orIgnore(), + mysql: { + throws: true + }, + sqlite: { + text : 'REPLACE OR IGNORE INTO "customer" ("id", "name") VALUES ($1, $2)', + string: 'REPLACE OR IGNORE INTO "customer" ("id", "name") VALUES (2, \'test\')' + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [2, 'test'] +}); + +Harness.test({ + query: post.replace({ + content: 'test', + userId: 2 + }).onConflict({ + columns: ['userId'], + update: ['content'] + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2] +}); + +Harness.test({ + query: post.replace({ + content: 'test', + userId: 2 + }).onConflict({ + columns: ['userId','content'], + update: ['content','userId'] + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2] +}); + +Harness.test({ + query: post.replace({ + content: 'test', + userId: 2 + }).onConflict({ + columns: ['userId'], + update: ['content'] + }).where(post.userId.equals(2)), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2, 2] +}); + +Harness.test({ + query: post.replace({ + content: 'test', + userId: 2 + }).onConflict({ + constraint: 'conc_userId', + update: ['content'] + }).where(post.userId.equals(2)), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2, 2] +}); + +Harness.test({ + query: post.replace({ + content: 'test', + userId: 2 + }).onConflict({ + columns: ['userId'], + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2] +}); + +Harness.test({ + query: post.replace({ + content: 'test', + userId: 2 + }).onConflict({ + constraint: 'conc_userId', + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: ['test', 2] +}); + +Harness.test({ + query: contentTable.replace({ + contentId: 20, + text : "something" + }).onConflict({ + columns: ['contentId'], + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [20, "something"] +}); + +Harness.test({ + query: contentTable.replace({ + contentId: 20, + text : "something", + contentPosts : "another thing", + }).onConflict({ + columns: ['contentId'], + update: ['contentPosts'] + }), + mysql: { + throws: true + }, + sqlite: { + throws: true + }, + pg: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [20, "something", "another thing"] +}); + +Harness.test({ + query: post.replace([]), + + mysql: { + text : 'SELECT `post`.* FROM `post` WHERE (1=2)', + string: 'SELECT `post`.* FROM `post` WHERE (1=2)' + }, + params: [] +}); + +Harness.test({ + query: arrayTable.replace(arrayTable.id.value(1), arrayTable.numbers.value([2, 3, 4])), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "arraytest" ("id", "numbers") VALUES ($1, $2)', + string: 'REPLACE INTO "arraytest" ("id", "numbers") VALUES (1, \'[2,3,4]\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + } +}); + +Harness.test({ + query: arrayTable.replace(arrayTable.id.value(1), arrayTable.numbers.value(["one", "two", "three"])), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "arraytest" ("id", "numbers") VALUES ($1, $2)', + string: 'REPLACE INTO "arraytest" ("id", "numbers") VALUES (1, \'["one","two","three"]\')' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + } +}); + +Harness.test({ + query: post.replace(post.userId).select(user.id).from(user), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + mysql: { + text : 'REPLACE INTO `post` (`userId`) SELECT `user`.`id` FROM `user`', + string: 'REPLACE INTO `post` (`userId`) SELECT `user`.`id` FROM `user`' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace(post.userId).select(user.id).from(user).onConflict({ + columns: ['userId'], + update: ['content'] + }), + pg: { + throws: true + }, + sqlite: { + throws: true + }, + mysql: { + throws: true + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace(post.userId).add(user.select(user.id)), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + mysql: { + text : 'REPLACE INTO `post` (`userId`) SELECT `user`.`id` FROM `user`', + string: 'REPLACE INTO `post` (`userId`) SELECT `user`.`id` FROM `user`' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace(post.userId).add(user.select(user.id).from(user)), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user"', + string: 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user"' + }, + mysql: { + text : 'REPLACE INTO `post` (`userId`) SELECT `user`.`id` FROM `user`', + string: 'REPLACE INTO `post` (`userId`) SELECT `user`.`id` FROM `user`' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); + +Harness.test({ + query: post.replace(post.userId).add(user.select(user.id).order(user.id)), + pg: { + throws: true + }, + sqlite: { + text : 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user" ORDER BY "user"."id"', + string: 'REPLACE INTO "post" ("userId") SELECT "user"."id" FROM "user" ORDER BY "user"."id"' + }, + mysql: { + text : 'REPLACE INTO `post` (`userId`) SELECT `user`.`id` FROM `user` ORDER BY `user`.`id`', + string: 'REPLACE INTO `post` (`userId`) SELECT `user`.`id` FROM `user` ORDER BY `user`.`id`' + }, + mssql: { + throws: true + }, + oracle: { + throws: true + }, + params: [] +}); diff --git a/test/dialects/select-tests.js b/test/dialects/select-tests.js index 59fdd786..2892c8ae 100644 --- a/test/dialects/select-tests.js +++ b/test/dialects/select-tests.js @@ -2,7 +2,9 @@ var Harness = require('./support'); var post = Harness.definePostTable(); +var user = Harness.defineUserTable(); var customerAlias = Harness.defineCustomerAliasTable(); +var Sql = require('../../lib'); Harness.test({ query: post.select(post.id).select(post.content), @@ -52,4 +54,408 @@ Harness.test({ string: 'SELECT "customer"."id" "id_alias", "customer"."name" "name_alias", "customer"."age" "age_alias", "customer"."income" "income_alias", "customer"."metadata" "metadata_alias" FROM "customer"' }, params: [] -}); \ No newline at end of file +}); + +// Test that we can generate a SELECT claus without a FROM clause +Harness.test({ + query: Sql.select(), + pg: { + text : 'SELECT ', + string: 'SELECT ' + }, + sqlite: { + text : 'SELECT ', + string: 'SELECT ' + }, + mysql: { + text : 'SELECT ', + string: 'SELECT ' + }, + mssql: { + text : 'SELECT ', + string: 'SELECT ' + }, + oracle: { + text : 'SELECT ', + string: 'SELECT ' + }, + params: [] +}); + +// Test that we can generate a SELECT claus without a FROM clause +Harness.test({ + query: Sql.select("1"), + pg: { + text : 'SELECT 1', + string: 'SELECT 1' + }, + sqlite: { + text : 'SELECT 1', + string: 'SELECT 1' + }, + mysql: { + text : 'SELECT 1', + string: 'SELECT 1' + }, + mssql: { + text : 'SELECT 1', + string: 'SELECT 1' + }, + oracle: { + text : 'SELECT 1', + string: 'SELECT 1' + }, + params: [] +}); + +Harness.test({ + query: Sql.select("1").where("1=1"), + pg: { + text : 'SELECT 1 WHERE (1=1)', + string: 'SELECT 1 WHERE (1=1)' + }, + sqlite: { + text : 'SELECT 1 WHERE (1=1)', + string: 'SELECT 1 WHERE (1=1)' + }, + mysql: { + text : 'SELECT 1 WHERE (1=1)', + string: 'SELECT 1 WHERE (1=1)' + }, + mssql: { + text : 'SELECT 1 WHERE (1=1)', + string: 'SELECT 1 WHERE (1=1)' + }, + oracle: { + text : 'SELECT 1 WHERE (1=1)', + string: 'SELECT 1 WHERE (1=1)' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(post.select(post.id)), + pg: { + text : 'SELECT (SELECT "post"."id" FROM "post")', + string: 'SELECT (SELECT "post"."id" FROM "post")' + }, + sqlite: { + text : 'SELECT (SELECT "post"."id" FROM "post")', + string: 'SELECT (SELECT "post"."id" FROM "post")' + }, + mysql: { + text : 'SELECT (SELECT `post`.`id` FROM `post`)', + string: 'SELECT (SELECT `post`.`id` FROM `post`)' + }, + mssql: { + text : 'SELECT (SELECT [post].[id] FROM [post])', + string: 'SELECT (SELECT [post].[id] FROM [post])' + }, + oracle: { + text : 'SELECT (SELECT "post"."id" FROM "post")', + string: 'SELECT (SELECT "post"."id" FROM "post")' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(post.select(post.content).plus(post.select(post.content))), + pg: { + text : 'SELECT ((SELECT "post"."content" FROM "post") + (SELECT "post"."content" FROM "post"))', + string: 'SELECT ((SELECT "post"."content" FROM "post") + (SELECT "post"."content" FROM "post"))' + }, + sqlite: { + text : 'SELECT ((SELECT "post"."content" FROM "post") + (SELECT "post"."content" FROM "post"))', + string: 'SELECT ((SELECT "post"."content" FROM "post") + (SELECT "post"."content" FROM "post"))' + }, + mysql: { + text : 'SELECT ((SELECT `post`.`content` FROM `post`) + (SELECT `post`.`content` FROM `post`))', + string: 'SELECT ((SELECT `post`.`content` FROM `post`) + (SELECT `post`.`content` FROM `post`))' + }, + mssql: { + text : 'SELECT ((SELECT [post].[content] FROM [post]) + (SELECT [post].[content] FROM [post]))', + string: 'SELECT ((SELECT [post].[content] FROM [post]) + (SELECT [post].[content] FROM [post]))' + }, + oracle: { + text : 'SELECT ((SELECT "post"."content" FROM "post") + (SELECT "post"."content" FROM "post"))', + string: 'SELECT ((SELECT "post"."content" FROM "post") + (SELECT "post"."content" FROM "post"))' + }, + params: [] +}); + +Harness.test({ + query: post.select(post.id.as('col1')), + pg: { + text : 'SELECT "post"."id" AS "col1" FROM "post"', + string: 'SELECT "post"."id" AS "col1" FROM "post"' + }, + sqlite: { + text : 'SELECT "post"."id" AS "col1" FROM "post"', + string: 'SELECT "post"."id" AS "col1" FROM "post"' + }, + mysql: { + text : 'SELECT `post`.`id` AS `col1` FROM `post`', + string: 'SELECT `post`.`id` AS `col1` FROM `post`' + }, + mssql: { + text : 'SELECT [post].[id] AS [col1] FROM [post]', + string: 'SELECT [post].[id] AS [col1] FROM [post]' + }, + oracle: { + text : 'SELECT "post"."id" "col1" FROM "post"', + string: 'SELECT "post"."id" "col1" FROM "post"' + }, + params: [] +}); + +Harness.test({ + query: post.select(Sql.constant(4)), + pg: { + text : 'SELECT $1 FROM "post"', + string: 'SELECT 4 FROM "post"' + }, + sqlite: { + text : 'SELECT $1 FROM "post"', + string: 'SELECT 4 FROM "post"' + }, + mysql: { + text : 'SELECT ? FROM `post`', + string: 'SELECT 4 FROM `post`' + }, + mssql: { + text : 'SELECT @1 FROM [post]', + string: 'SELECT 4 FROM [post]' + }, + oracle: { + text : 'SELECT :1 FROM "post"', + string: 'SELECT 4 FROM "post"' + }, + params: [4] +}); + +Harness.test({ + query: post.select(post.id,Sql.constant(4)), + pg: { + text : 'SELECT "post"."id", $1 FROM "post"', + string: 'SELECT "post"."id", 4 FROM "post"' + }, + sqlite: { + text : 'SELECT "post"."id", $1 FROM "post"', + string: 'SELECT "post"."id", 4 FROM "post"' + }, + mysql: { + text : 'SELECT `post`.`id`, ? FROM `post`', + string: 'SELECT `post`.`id`, 4 FROM `post`' + }, + mssql: { + text : 'SELECT [post].[id], @1 FROM [post]', + string: 'SELECT [post].[id], 4 FROM [post]' + }, + oracle: { + text : 'SELECT "post"."id", :1 FROM "post"', + string: 'SELECT "post"."id", 4 FROM "post"' + }, + params: [4] +}); + +Harness.test({ + query: post.select(Sql.constant(4).as('col1')), + pg: { + text : 'SELECT $1 AS "col1" FROM "post"', + string: 'SELECT 4 AS "col1" FROM "post"' + }, + sqlite: { + text : 'SELECT $1 AS "col1" FROM "post"', + string: 'SELECT 4 AS "col1" FROM "post"' + }, + mysql: { + text : 'SELECT ? AS `col1` FROM `post`', + string: 'SELECT 4 AS `col1` FROM `post`' + }, + mssql: { + text : 'SELECT @1 AS [col1] FROM [post]', + string: 'SELECT 4 AS [col1] FROM [post]' + }, + oracle: { + text : 'SELECT :1 "col1" FROM "post"', + string: 'SELECT 4 "col1" FROM "post"' + }, + params: [4] +}); + +Harness.test({ + query: post.select(Sql.constant(4).plus(5)), + pg: { + text : 'SELECT ($1 + $2) FROM "post"', + string: 'SELECT (4 + 5) FROM "post"' + }, + sqlite: { + text : 'SELECT ($1 + $2) FROM "post"', + string: 'SELECT (4 + 5) FROM "post"' + }, + mysql: { + text : 'SELECT (? + ?) FROM `post`', + string: 'SELECT (4 + 5) FROM `post`' + }, + mssql: { + text : 'SELECT (@1 + @2) FROM [post]', + string: 'SELECT (4 + 5) FROM [post]' + }, + oracle: { + text : 'SELECT (:1 + :2) FROM "post"', + string: 'SELECT (4 + 5) FROM "post"' + }, + params: [4,5] +}); + +Harness.test({ + query: post.select(Sql.constant(4).plus(5).as('col1')), + pg: { + text : 'SELECT ($1 + $2) AS "col1" FROM "post"', + string: 'SELECT (4 + 5) AS "col1" FROM "post"' + }, + sqlite: { + text : 'SELECT ($1 + $2) AS "col1" FROM "post"', + string: 'SELECT (4 + 5) AS "col1" FROM "post"' + }, + mysql: { + text : 'SELECT (? + ?) AS `col1` FROM `post`', + string: 'SELECT (4 + 5) AS `col1` FROM `post`' + }, + mssql: { + text : 'SELECT (@1 + @2) AS [col1] FROM [post]', + string: 'SELECT (4 + 5) AS [col1] FROM [post]' + }, + oracle: { + text : 'SELECT (:1 + :2) "col1" FROM "post"', + string: 'SELECT (4 + 5) "col1" FROM "post"' + }, + params: [4,5] +}); + +Harness.test({ + query: post.select(Sql.constant(4),Sql.constant("abc"),Sql.constant(true)), + pg: { + text : 'SELECT $1, $2, $3 FROM "post"', + string: 'SELECT 4, \'abc\', TRUE FROM "post"' + }, + sqlite: { + text : 'SELECT $1, $2, $3 FROM "post"', + string: 'SELECT 4, \'abc\', 1 FROM "post"' + }, + mysql: { + text : 'SELECT ?, ?, ? FROM `post`', + string: 'SELECT 4, \'abc\', TRUE FROM `post`' + }, + mssql: { + text : 'SELECT @1, @2, @3 FROM [post]', + string: 'SELECT 4, \'abc\', TRUE FROM [post]' + }, + oracle: { + text : 'SELECT :1, :2, :3 FROM "post"', + string: 'SELECT 4, \'abc\', TRUE FROM "post"' + }, + params: [4,'abc',true] +}); + +Harness.test({ + query: post.select(Sql.constant(1).sum()), + pg: { + text : 'SELECT SUM($1) AS "constant_sum" FROM "post"', + string: 'SELECT SUM(1) AS "constant_sum" FROM "post"' + }, + sqlite: { + text : 'SELECT SUM($1) AS "constant_sum" FROM "post"', + string: 'SELECT SUM(1) AS "constant_sum" FROM "post"' + }, + mysql: { + text : 'SELECT SUM(?) AS `constant_sum` FROM `post`', + string: 'SELECT SUM(1) AS `constant_sum` FROM `post`' + }, + mssql: { + text : 'SELECT SUM(@1) AS [constant_sum] FROM [post]', + string: 'SELECT SUM(1) AS [constant_sum] FROM [post]' + }, + oracle: { + text : 'SELECT SUM(:1) "constant_sum" FROM "post"', + string: 'SELECT SUM(1) "constant_sum" FROM "post"' + }, + params: [1] +}); + +Harness.test({ + query: Sql.select(post.select(post.id).as("column1")), + pg: { + text : 'SELECT (SELECT "post"."id" FROM "post") AS "column1"', + string: 'SELECT (SELECT "post"."id" FROM "post") AS "column1"' + }, + sqlite: { + text : 'SELECT (SELECT "post"."id" FROM "post") AS "column1"', + string: 'SELECT (SELECT "post"."id" FROM "post") AS "column1"' + }, + mysql: { + text : 'SELECT (SELECT `post`.`id` FROM `post`) AS `column1`', + string: 'SELECT (SELECT `post`.`id` FROM `post`) AS `column1`' + }, + mssql: { + text : 'SELECT (SELECT [post].[id] FROM [post]) AS [column1]', + string: 'SELECT (SELECT [post].[id] FROM [post]) AS [column1]' + }, + oracle: { + text : 'SELECT (SELECT "post"."id" FROM "post") "column1"', + string: 'SELECT (SELECT "post"."id" FROM "post") "column1"' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(post.select(post.count()).as("column1")), + pg: { + text : 'SELECT (SELECT COUNT("post".*) AS "post_count" FROM "post") AS "column1"', + string: 'SELECT (SELECT COUNT("post".*) AS "post_count" FROM "post") AS "column1"' + }, + sqlite: { + text : 'SELECT (SELECT COUNT("post".*) AS "post_count" FROM "post") AS "column1"', + string: 'SELECT (SELECT COUNT("post".*) AS "post_count" FROM "post") AS "column1"' + }, + mysql: { + text : 'SELECT (SELECT COUNT(*) AS `post_count` FROM `post`) AS `column1`', + string: 'SELECT (SELECT COUNT(*) AS `post_count` FROM `post`) AS `column1`' + }, + mssql: { + text : 'SELECT (SELECT COUNT(*) AS [post_count] FROM [post]) AS [column1]', + string: 'SELECT (SELECT COUNT(*) AS [post_count] FROM [post]) AS [column1]' + }, + oracle: { + text : 'SELECT (SELECT COUNT(*) "post_count" FROM "post") "column1"', + string: 'SELECT (SELECT COUNT(*) "post_count" FROM "post") "column1"' + }, + params: [] +}); + +Harness.test({ + query: Sql.select(post.select(post.id).as("column1"),user.select(user.id).as("column2")), + pg: { + text : 'SELECT (SELECT "post"."id" FROM "post") AS "column1", (SELECT "user"."id" FROM "user") AS "column2"', + string: 'SELECT (SELECT "post"."id" FROM "post") AS "column1", (SELECT "user"."id" FROM "user") AS "column2"' + }, + sqlite: { + text : 'SELECT (SELECT "post"."id" FROM "post") AS "column1", (SELECT "user"."id" FROM "user") AS "column2"', + string: 'SELECT (SELECT "post"."id" FROM "post") AS "column1", (SELECT "user"."id" FROM "user") AS "column2"' + }, + mysql: { + text : 'SELECT (SELECT `post`.`id` FROM `post`) AS `column1`, (SELECT `user`.`id` FROM `user`) AS `column2`', + string: 'SELECT (SELECT `post`.`id` FROM `post`) AS `column1`, (SELECT `user`.`id` FROM `user`) AS `column2`' + }, + mssql: { + text : 'SELECT (SELECT [post].[id] FROM [post]) AS [column1], (SELECT [user].[id] FROM [user]) AS [column2]', + string: 'SELECT (SELECT [post].[id] FROM [post]) AS [column1], (SELECT [user].[id] FROM [user]) AS [column2]' + }, + oracle: { + text : 'SELECT (SELECT "post"."id" FROM "post") "column1", (SELECT "user"."id" FROM "user") "column2"', + string: 'SELECT (SELECT "post"."id" FROM "post") "column1", (SELECT "user"."id" FROM "user") "column2"' + }, + params: [] +}); + + diff --git a/test/dialects/subquery-tests.js b/test/dialects/subquery-tests.js index fe5b2aad..05d76f1f 100644 --- a/test/dialects/subquery-tests.js +++ b/test/dialects/subquery-tests.js @@ -219,3 +219,55 @@ Harness.test({ params: [] }); +// Top-level subQuery +Harness.test({ + query: Sql.subQuery().select(user.id).from(user), + pg: { + text : '(SELECT "user"."id" FROM "user")', + string: '(SELECT "user"."id" FROM "user")' + }, + sqlite: { + text : '(SELECT "user"."id" FROM "user")', + string: '(SELECT "user"."id" FROM "user")' + }, + mysql: { + text : '(SELECT `user`.`id` FROM `user`)', + string: '(SELECT `user`.`id` FROM `user`)' + }, + mssql: { + text : '(SELECT [user].[id] FROM [user])', + string: '(SELECT [user].[id] FROM [user])' + }, + oracle: { + text : '(SELECT "user"."id" FROM "user")', + string: '(SELECT "user"."id" FROM "user")' + }, + params: [] +}); + +// Top-level subQuery with alias +Harness.test({ + query: Sql.subQuery("x").select(user.id).from(user), + pg: { + text : '(SELECT "user"."id" FROM "user") "x"', + string: '(SELECT "user"."id" FROM "user") "x"' + }, + sqlite: { + text : '(SELECT "user"."id" FROM "user") "x"', + string: '(SELECT "user"."id" FROM "user") "x"' + }, + mysql: { + text : '(SELECT `user`.`id` FROM `user`) `x`', + string: '(SELECT `user`.`id` FROM `user`) `x`' + }, + mssql: { + text : '(SELECT [user].[id] FROM [user]) [x]', + string: '(SELECT [user].[id] FROM [user]) [x]' + }, + oracle: { + text : '(SELECT "user"."id" FROM "user") "x"', + string: '(SELECT "user"."id" FROM "user") "x"' + }, + params: [] +}); + diff --git a/test/dialects/support.js b/test/dialects/support.js index f1d926e9..4a9fa427 100644 --- a/test/dialects/support.js +++ b/test/dialects/support.js @@ -76,7 +76,7 @@ module.exports = { definePostTable: function() { return Table.define({ name: 'post', - columns: ['id', 'userId', 'content', 'tags'] + columns: ['id', 'userId', 'content', 'tags', 'length'] }); }, @@ -124,5 +124,14 @@ module.exports = { name: 'variable', columns: ['a', 'b', 'c', 'd', 't', 'u', 'v', 'x', 'y', 'z'] }); + }, + +// this table is for testing snakeName related stuff + defineContentTable: function() { + return Table.define({ + name: 'content', + columns: ['content_id', 'text', 'content_posts'], + snakeToCamel: true + }); } }; diff --git a/test/dialects/update-tests.js b/test/dialects/update-tests.js index decfb4a5..eb3d0b3e 100644 --- a/test/dialects/update-tests.js +++ b/test/dialects/update-tests.js @@ -228,7 +228,7 @@ Harness.test({ }), 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}\')' + 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', diff --git a/test/dialects/value-expression-tests.js b/test/dialects/value-expression-tests.js index 5eaae5ae..d5c7fcd4 100644 --- a/test/dialects/value-expression-tests.js +++ b/test/dialects/value-expression-tests.js @@ -114,20 +114,46 @@ Harness.test({ query: post.select(post.id).where(post.content.equals(new Buffer('test'))), pg: { text : 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = $1)', - string: 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = \'\\x74657374\')', + string: 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = \'\\x74657374\')' }, sqlite: { text : 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = $1)', - string: 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = x\'74657374\')', + string: 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = x\'74657374\')' }, mysql: { text : 'SELECT `post`.`id` FROM `post` WHERE (`post`.`content` = ?)', - string: 'SELECT `post`.`id` FROM `post` WHERE (`post`.`content` = x\'74657374\')', + string: 'SELECT `post`.`id` FROM `post` WHERE (`post`.`content` = x\'74657374\')' }, oracle: { text : 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = :1)', - string: 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = utl_raw.cast_to_varchar2(hextoraw(\'74657374\')))', + string: 'SELECT "post"."id" FROM "post" WHERE ("post"."content" = utl_raw.cast_to_varchar2(hextoraw(\'74657374\')))' }, params: [new Buffer('test')] }); +// concat tests +Harness.test({ + query: post.select(post.content.concat(post.tags)), + pg: { + text : 'SELECT ("post"."content" || "post"."tags") FROM "post"', + string: 'SELECT ("post"."content" || "post"."tags") FROM "post"' + }, + sqlite: { + text : 'SELECT ("post"."content" || "post"."tags") FROM "post"', + string: 'SELECT ("post"."content" || "post"."tags") FROM "post"' + }, + mysql: { + text : 'SELECT (`post`.`content` || `post`.`tags`) FROM `post`', + string: 'SELECT (`post`.`content` || `post`.`tags`) FROM `post`' + }, + mssql: { + text : 'SELECT ([post].[content] + [post].[tags]) FROM [post]', + string: 'SELECT ([post].[content] + [post].[tags]) FROM [post]' + }, + oracle: { + text : 'SELECT ("post"."content" || "post"."tags") FROM "post"', + string: 'SELECT ("post"."content" || "post"."tags") FROM "post"' + }, + params: [] +}); + diff --git a/test/function-tests.js b/test/function-tests.js index 77ef0087..6c53072d 100644 --- a/test/function-tests.js +++ b/test/function-tests.js @@ -84,4 +84,10 @@ suite('function', function() { assert.equal(query.text, 'SELECT (AVG((DISTINCT((COUNT("user"."id") + MAX("user"."id"))) - MIN("user"."id"))) * $1) FROM "user"'); assert.equal(query.values[0], 100); }); + + test('use custom function', function() { + var query = user.select(sql.function('PHRASE_TO_TSQUERY')('simple', user.name)).toQuery(); + assert.equal(query.text, 'SELECT PHRASE_TO_TSQUERY($1, "user"."name") FROM "user"'); + assert.equal(query.values[0], 'simple'); + }); }); diff --git a/test/table-tests.js b/test/table-tests.js index 66c3bdd3..73d43367 100644 --- a/test/table-tests.js +++ b/test/table-tests.js @@ -167,6 +167,19 @@ test('hasColumn', function() { assert.equal(table.hasColumn('baz'), true); }); +test('hasColumn with user-defined column property', function() { + var table = Table.define({ + name: 'blah', + columns: [{ + name: 'id', + property: 'theId' + }, {name: 'foo'}] + }); + + assert.equal(table.hasColumn('id'), true); + assert.equal(table.hasColumn('theId'), true); +}); + test('the column "from" does not overwrite the from method', function() { var table = Table.define({ name: 'foo', columns: [] }); table.addColumn('from');