diff --git a/lib/dialect/clickhouse.js b/lib/dialect/clickhouse.js new file mode 100644 index 00000000..c30e2032 --- /dev/null +++ b/lib/dialect/clickhouse.js @@ -0,0 +1,275 @@ +'use strict'; + +var util = require('util'); +var assert = require('assert'); +var _ = require('lodash'); + +var Clickhouse = function(config) { + this.output = []; + this.params = []; + this.config = config || {}; +}; + +var Postgres = require('./postgres'); + +util.inherits(Clickhouse, Postgres); + +Clickhouse.prototype._myClass = Clickhouse; + +Clickhouse.prototype._quoteCharacter = '`'; + +Clickhouse.prototype._arrayAggFunctionName = 'GROUP_CONCAT'; + +Clickhouse.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; +}; + +Clickhouse.prototype._getParameterPlaceholder = function() { + return '?'; +}; + +Clickhouse.prototype._getParameterValue = function(value) { + if (Buffer.isBuffer(value)) { + value = 'x' + this._getParameterValue(value.toString('hex')); + } else { + value = Postgres.prototype._getParameterValue.call(this, value); + } + return value; +}; + +Clickhouse.prototype.visitOnDuplicate = function(onDuplicate) { + var params = []; + /* jshint boss: true */ + for(var i = 0, node; node = onDuplicate.nodes[i]; i++) { + var target_col = this.visit(node); + params = params.concat(target_col + ' = ' + this.visit(node.value)); + } + var result = [ + 'ON DUPLICATE KEY UPDATE', + params.join(', ') + ]; + return result; +}; + +Clickhouse.prototype.visitOnConflict = function(onConflict) { + throw new Error('Clickhouse does not allow onConflict clause.'); +}; + +Clickhouse.prototype.visitReturning = function() { + throw new Error('Clickhouse does not allow returning clause.'); +}; + +Clickhouse.prototype.visitForShare = function() { + throw new Error('Clickhouse does not allow FOR SHARE clause.'); +}; + +Clickhouse.prototype.visitCreate = function(create) { + var result = Clickhouse.super_.prototype.visitCreate.call(this, create); + var engine = this._queryNode.table._initialConfig.engine; + var charset = this._queryNode.table._initialConfig.charset; + + if ( !! engine) { + result.push('ENGINE=' + engine); + } + + if ( !! charset) { + result.push('DEFAULT CHARSET=' + charset); + } + + return result; +}; + +Clickhouse.prototype.visitRenameColumn = function(renameColumn) { + var dataType = renameColumn.nodes[1].dataType || renameColumn.nodes[0].dataType; + assert(dataType, 'dataType missing for column ' + (renameColumn.nodes[1].name || renameColumn.nodes[0].name || '') + + ' (CHANGE COLUMN statements require a dataType)'); + return ['CHANGE COLUMN ' + this.visit(renameColumn.nodes[0]) + ' ' + this.visit(renameColumn.nodes[1]) + ' ' + dataType]; +}; + +Clickhouse.prototype.visitInsert = function(insert) { + var result = Postgres.prototype.visitInsert.call(this, insert); + if (result[2] === 'DEFAULT VALUES') { + result[2] = '() VALUES ()'; + } + return result; +}; + +Clickhouse.prototype.visitIndexes = function(node) { + var tableName = this.visit(this._queryNode.table.toNode())[0]; + + return "SHOW INDEX FROM " + tableName; +}; + +Clickhouse.prototype.visitBinary = function(binary) { + if (binary.operator === '@@') { + var self = this; + var text = '(MATCH ' + this.visit(binary.left) + ' AGAINST '; + text += this.visit(binary.right); + text += ')'; + return [text]; + } + return Clickhouse.super_.prototype.visitBinary.call(this, binary); +}; + +Clickhouse.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 displayName = Clickhouse.functionMap.hasOwnProperty(functionCall.name) ? Clickhouse.functionMap[functionCall.name] : functionCall.name; + var txt = displayName + '(' + (nodes[0]+'') + ')'; + return txt; + } + + var txt=""; + var name = functionCall.name; + var displayName = Clickhouse.functionMap.hasOwnProperty(functionCall.name) ? Clickhouse.functionMap[functionCall.name] : functionCall.name; + // Override date functions since Clickhouse 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 = displayName + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; + + this._visitingFunctionCall = false; + return [txt]; +}; + +Clickhouse.prototype.visitColumn = function(columnNode) { + + // Clichouse does not support tablename.columnName + delete columnNode.table; + + 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 + var result='COUNT()'; + if(inSelectClause && columnNode.alias) { + result += ' AS ' + self.quote(columnNode.alias); + } + return result; + } + + inSelectClause = !this._selectOrDeleteEndIndex; + if(isCountStarExpression(columnNode)) return _countStar(); + return Clickhouse.super_.prototype.visitColumn.call(this, columnNode); +}; + +Clickhouse.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; +}; + +Clickhouse.prototype.visitAlter = function(alter) { + throw new Error('Not Implemented'); +}; + +Clickhouse.prototype.visitArrayCall = function(arrayCall) { + var txt = '[' + arrayCall.nodes.map(this.visit.bind(this)).join(', ') + ']'; + return [txt]; +}; + +Clickhouse.prototype.visitFrom = function(from) { + var result = []; + if (from.skipFromStatement) { + result.push(','); + } else { + result.push('FROM'); + } + for(var i = 0; i < from.nodes.length; i++) { + // Currently clickhouse only support single table, but anyway + result = result.concat(this.visit(from.nodes[i], {"parentIsFrom": true})); + } + return result; +}; + +Clickhouse.prototype.visitJoin = function(join, config) { + var result = []; + var parentIsFrom = config && config.parentIsFrom; + this._visitingJoin = true; + if (parentIsFrom) { + result = result.concat('('); + } + + result = result.concat(this.visit(join.from, {"joinFromQuery": true})); + result = result.concat('ALL ' + join.subType + ' JOIN'); + result = result.concat(this.visit(join.to)); + result = result.concat('USING'); + result = result.concat(this.visit(join.on)); + if (parentIsFrom) { + result = result.concat(')'); + } + + return result; +}; + +Clickhouse.functionMap = { + "ROUND": "round", + "TODAY": "today", + "NOW": "now", + "ARRAY_JOIN": "arrayJoin" +}; + +module.exports = Clickhouse; diff --git a/lib/dialect/index.js b/lib/dialect/index.js index c26f15de..d34aedde 100644 --- a/lib/dialect/index.js +++ b/lib/dialect/index.js @@ -13,6 +13,8 @@ var getDialect = function(dialect) { return require('./mssql'); case 'oracle': return require('./oracle'); + case 'clickhouse': + return require('./clickhouse'); default: throw new Error(dialect + ' is unsupported'); } diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index 2816c901..260a7dd4 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -125,9 +125,9 @@ Postgres.prototype.getString = function(queryNode) { return query.text; }; -Postgres.prototype.visit = function(node) { +Postgres.prototype.visit = function(node, config) { switch(node.type) { - case 'QUERY' : return this.visitQuery(node); + case 'QUERY' : return this.visitQuery(node, config); case 'SUBQUERY' : return this.visitSubquery(node); case 'SELECT' : return this.visitSelect(node); case 'INSERT' : return this.visitInsert(node); @@ -156,7 +156,7 @@ Postgres.prototype.visit = function(node) { case 'TABLE' : return this.visitTable(node); case 'COLUMN' : return this.visitColumn(node); case 'FOREIGN KEY' : return this.visitForeignKey(node); - case 'JOIN' : return this.visitJoin(node); + case 'JOIN' : return this.visitJoin(node, config); case 'LITERAL' : return this.visitLiteral(node); case 'TEXT' : return node.text; case 'PARAMETER' : return this.visitParameter(node); @@ -607,8 +607,8 @@ Postgres.prototype.visitOverlap = function(overlap) { return [text]; }; -Postgres.prototype.visitQuery = function(queryNode) { - if (this._queryNode) return this.visitSubquery(queryNode,dontParenthesizeSubQuery(this._queryNode)); +Postgres.prototype.visitQuery = function(queryNode, config) { + if (this._queryNode) return this.visitSubquery(queryNode,dontParenthesizeSubQuery(this._queryNode, config)); 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 @@ -779,19 +779,23 @@ Postgres.prototype.visitColumn = function(columnNode) { if (columnNode.star) { var allCols = []; var hasAliases = false; - if(columnNode.aggregator !== 'COUNT') { - var tableName = txt.join(''); - for (var i = 0; i < table.columns.length; ++i) { - var col = table.columns[i]; - var aliased = col.name !== (col.alias || col.property); - hasAliases = hasAliases || aliased; - allCols.push(tableName + this.quote(col.name) + (aliased ? this._aliasText + this.quote(col.alias || col.property) : '')); + if(table) { + if(columnNode.aggregator !== 'COUNT') { + var tableName = txt.join(''); + for (var i = 0; i < table.columns.length; ++i) { + var col = table.columns[i]; + var aliased = col.name !== (col.alias || col.property); + hasAliases = hasAliases || aliased; + allCols.push(tableName + this.quote(col.name) + (aliased ? this._aliasText + this.quote(col.alias || col.property) : '')); + } } - } - if(hasAliases) { - txt = [allCols.join(', ')]; - } - else { + if(hasAliases) { + txt = [allCols.join(', ')]; + } + else { + txt.push('*'); + } + } else { txt.push('*'); } } @@ -1065,7 +1069,7 @@ Postgres.prototype.visitOnConflict = function(onConflict) { } result.push( '(' + columns.join(', ') + ')' ); } - + if(onConflict.update){ updateClause.push("DO UPDATE SET"); var update = onConflict.update; @@ -1076,11 +1080,11 @@ Postgres.prototype.visitOnConflict = function(onConflict) { } updateClause.push(setClause.join(', ')); } - else + else updateClause.push('DO NOTHING'); result.push(updateClause.join(' ')); - return result; + return result; }; Postgres.prototype.visitModifier = function(node) { @@ -1214,10 +1218,10 @@ Postgres.prototype.handleDistinct = function(actions,filters) { * @param parentQuery * @returns {boolean} */ -function dontParenthesizeSubQuery(parentQuery){ +function dontParenthesizeSubQuery(parentQuery, config){ if (!parentQuery) return false; if (parentQuery.nodes.length === 0) return false; - if (['INSERT', 'REPLACE'].indexOf(parentQuery.nodes[0].type) === -1) return false; + if (['INSERT', 'REPLACE'].indexOf(parentQuery.nodes[0].type) === -1 && !(config && config.joinFromQuery)) return false; return true; } diff --git a/lib/functions.js b/lib/functions.js index fe9cdaaf..75e79cad 100644 --- a/lib/functions.js +++ b/lib/functions.js @@ -64,7 +64,15 @@ 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).concat(dateFunctions); +//clickhouse exclusive functions +var clickhouseFunctions = ['TODAY', 'NOW', 'ARRAY_JOIN']; + +var standardFunctionNames = aggregateFunctions + .concat(scalarFunctions) + .concat(hstoreFunction) + .concat(textsearchFunctions) + .concat(dateFunctions) + .concat(clickhouseFunctions); // creates a hash of standard functions for a sql instance var getStandardFunctions = function() { diff --git a/lib/index.js b/lib/index.js index f6cf39e6..d0c4ecc8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -88,4 +88,5 @@ var create = function(dialect, config) { module.exports = new Sql(DEFAULT_DIALECT, {}); module.exports.create = create; module.exports.Sql = Sql; +module.exports.Column = Column; module.exports.Table = Table; diff --git a/lib/node/join.js b/lib/node/join.js index bbc1f93a..e0ab4293 100644 --- a/lib/node/join.js +++ b/lib/node/join.js @@ -19,5 +19,8 @@ var JoinNode = module.exports = Node.define({ }, leftJoin: function(other) { return new JoinNode('LEFT', this, other); + }, + fullJoin: function(other) { + return new JoinNode('FULL', this, other); } }); diff --git a/lib/node/query.js b/lib/node/query.js index ebb3ecfa..91107570 100644 --- a/lib/node/query.js +++ b/lib/node/query.js @@ -140,6 +140,11 @@ var Query = Node.define({ return new JoinNode('LEFT', this, other.toNode()); }, + fullJoin: function(other) { + // assert(this.type === 'SUBQUERY', 'fullJoin() can only be used on a subQuery'); + return new JoinNode('FULL', this, other.toNode()); + }, + where: function(node) { if (arguments.length > 1) { // allow multiple where clause arguments diff --git a/lib/table.js b/lib/table.js index 3fae2710..ff029115 100644 --- a/lib/table.js +++ b/lib/table.js @@ -237,6 +237,10 @@ Table.prototype.leftJoin = function(other) { return new JoinNode('LEFT', this.toNode(), other.toNode()); }; +Table.prototype.fullJoin = function(other) { + return new JoinNode('FULL', this.toNode(), other.toNode()); +}; + // auto-join tables based on column intropsection Table.prototype.joinTo = function(other) { return Joiner.leftJoin(this, other); diff --git a/test/dialects/aggregate-tests.js b/test/dialects/aggregate-tests.js index 59d48d57..b3934444 100644 --- a/test/dialects/aggregate-tests.js +++ b/test/dialects/aggregate-tests.js @@ -27,6 +27,10 @@ Harness.test({ text : 'SELECT COUNT(*) "post_count" FROM "post"', string: 'SELECT COUNT(*) "post_count" FROM "post"' }, + clickhouse: { + text : 'SELECT COUNT() AS `post_count` FROM `post`', + string: 'SELECT COUNT() AS `post_count` FROM `post`' + }, params: [] }); @@ -52,6 +56,10 @@ Harness.test({ text : 'SELECT COUNT(*) "post_count" FROM "post"', string: 'SELECT COUNT(*) "post_count" FROM "post"' }, + clickhouse: { + text : 'SELECT COUNT() AS `post_count` FROM `post`', + string: 'SELECT COUNT() AS `post_count` FROM `post`' + }, params: [] }); @@ -77,6 +85,10 @@ Harness.test({ text : 'SELECT COUNT(*) "post_amount" FROM "post"', string: 'SELECT COUNT(*) "post_amount" FROM "post"' }, + clickhouse: { + text : 'SELECT COUNT() AS `post_amount` FROM `post`', + string: 'SELECT COUNT() AS `post_amount` FROM `post`' + }, params: [] }); @@ -102,6 +114,10 @@ Harness.test({ text : 'SELECT COUNT("post"."content") "content_count" FROM "post"', string: 'SELECT COUNT("post"."content") "content_count" FROM "post"' }, + clickhouse: { + text : 'SELECT COUNT(`content`) AS `content_count` FROM `post`', + string: 'SELECT COUNT(`content`) AS `content_count` FROM `post`' + }, params: [] }); @@ -127,6 +143,10 @@ Harness.test({ text : 'SELECT COUNT("post"."content") "content_count" FROM "post"', string: 'SELECT COUNT("post"."content") "content_count" FROM "post"' }, + clickhouse: { + text : 'SELECT COUNT(`content`) AS `content_count` FROM `post`', + string: 'SELECT COUNT(`content`) AS `content_count` FROM `post`' + }, params: [] }); @@ -152,6 +172,10 @@ Harness.test({ text : 'SELECT COUNT("post"."content") "content_count" FROM "post"', string: 'SELECT COUNT("post"."content") "content_count" FROM "post"' }, + clickhouse: { + text : 'SELECT COUNT(`content`) AS `content_count` FROM `post`', + string: 'SELECT COUNT(`content`) AS `content_count` FROM `post`' + }, params: [] }); @@ -173,6 +197,10 @@ Harness.test({ text : 'SELECT COUNT(*) "customer_count" FROM "customer"', string: 'SELECT COUNT(*) "customer_count" FROM "customer"' }, + clickhouse: { + text : 'SELECT COUNT() AS `customer_count` FROM `customer`', + string: 'SELECT COUNT() AS `customer_count` FROM `customer`' + }, params: [] }); @@ -198,6 +226,10 @@ Harness.test({ text : 'SELECT MIN("post"."id") "id_min" FROM "post"', string: 'SELECT MIN("post"."id") "id_min" FROM "post"' }, + clickhouse: { + text : 'SELECT MIN(`id`) AS `id_min` FROM `post`', + string: 'SELECT MIN(`id`) AS `id_min` FROM `post`' + }, params: [] }); @@ -223,6 +255,10 @@ Harness.test({ text : 'SELECT MIN("post"."id") "min_id" FROM "post"', string: 'SELECT MIN("post"."id") "min_id" FROM "post"' }, + clickhouse: { + text : 'SELECT MIN(`id`) AS `min_id` FROM `post`', + string: 'SELECT MIN(`id`) AS `min_id` FROM `post`' + }, params: [] }); @@ -248,6 +284,10 @@ Harness.test({ text : 'SELECT MIN("post"."id") "min_id" FROM "post"', string: 'SELECT MIN("post"."id") "min_id" FROM "post"' }, + clickhouse: { + text : 'SELECT MIN(`id`) AS `min_id` FROM `post`', + string: 'SELECT MIN(`id`) AS `min_id` FROM `post`' + }, params: [] }); @@ -273,6 +313,10 @@ Harness.test({ text : 'SELECT MAX("post"."id") "id_max" FROM "post"', string: 'SELECT MAX("post"."id") "id_max" FROM "post"' }, + clickhouse: { + text : 'SELECT MAX(`id`) AS `id_max` FROM `post`', + string: 'SELECT MAX(`id`) AS `id_max` FROM `post`' + }, params: [] }); @@ -298,6 +342,10 @@ Harness.test({ text : 'SELECT MAX("post"."id") "max_id" FROM "post"', string: 'SELECT MAX("post"."id") "max_id" FROM "post"' }, + clickhouse: { + text : 'SELECT MAX(`id`) AS `max_id` FROM `post`', + string: 'SELECT MAX(`id`) AS `max_id` FROM `post`' + }, params: [] }); @@ -323,6 +371,10 @@ Harness.test({ text : 'SELECT MAX("post"."id") "max_id" FROM "post"', string: 'SELECT MAX("post"."id") "max_id" FROM "post"' }, + clickhouse: { + text : 'SELECT MAX(`id`) AS `max_id` FROM `post`', + string: 'SELECT MAX(`id`) AS `max_id` FROM `post`' + }, params: [] }); @@ -348,6 +400,10 @@ Harness.test({ text : 'SELECT SUM("post"."id") "id_sum" FROM "post"', string: 'SELECT SUM("post"."id") "id_sum" FROM "post"' }, + clickhouse: { + text : 'SELECT SUM(`id`) AS `id_sum` FROM `post`', + string: 'SELECT SUM(`id`) AS `id_sum` FROM `post`' + }, params: [] }); @@ -373,6 +429,10 @@ Harness.test({ text : 'SELECT SUM("post"."id") "sum_id" FROM "post"', string: 'SELECT SUM("post"."id") "sum_id" FROM "post"' }, + clickhouse: { + text : 'SELECT SUM(`id`) AS `sum_id` FROM `post`', + string: 'SELECT SUM(`id`) AS `sum_id` FROM `post`' + }, params: [] }); @@ -398,6 +458,10 @@ Harness.test({ text : 'SELECT SUM("post"."id") "sum_id" FROM "post"', string: 'SELECT SUM("post"."id") "sum_id" FROM "post"' }, + clickhouse: { + text : 'SELECT SUM(`id`) AS `sum_id` FROM `post`', + string: 'SELECT SUM(`id`) AS `sum_id` FROM `post`' + }, params: [] }); @@ -423,6 +487,10 @@ Harness.test({ text : 'SELECT AVG("post"."id") "id_avg" FROM "post"', string: 'SELECT AVG("post"."id") "id_avg" FROM "post"' }, + clickhouse: { + text : 'SELECT AVG(`id`) AS `id_avg` FROM `post`', + string: 'SELECT AVG(`id`) AS `id_avg` FROM `post`' + }, params: [] }); @@ -448,6 +516,10 @@ Harness.test({ text : 'SELECT AVG("post"."id") "avg_id" FROM "post"', string: 'SELECT AVG("post"."id") "avg_id" FROM "post"' }, + clickhouse: { + text : 'SELECT AVG(`id`) AS `avg_id` FROM `post`', + string: 'SELECT AVG(`id`) AS `avg_id` FROM `post`' + }, params: [] }); @@ -473,5 +545,9 @@ Harness.test({ text : 'SELECT AVG("post"."id") "avg_id" FROM "post"', string: 'SELECT AVG("post"."id") "avg_id" FROM "post"' }, + clickhouse: { + text : 'SELECT AVG(`id`) AS `avg_id` FROM `post`', + string: 'SELECT AVG(`id`) AS `avg_id` FROM `post`' + }, params: [] }); diff --git a/test/dialects/alias-tests.js b/test/dialects/alias-tests.js index 32db9308..8121016a 100644 --- a/test/dialects/alias-tests.js +++ b/test/dialects/alias-tests.js @@ -26,6 +26,15 @@ Harness.test({ text : 'SELECT ("customer"."name" IS NULL) "nameIsNull" FROM "customer"', string: 'SELECT ("customer"."name" IS NULL) "nameIsNull" FROM "customer"' }, + /* + SELECT (`name` IS NULL) AS `nameIsNull` + FROM + (SELECT NULL AS `name` FROM system.one) + */ + clickhouse: { + text : 'SELECT (`name` IS NULL) AS `nameIsNull` FROM `customer`', + string: 'SELECT (`name` IS NULL) AS `nameIsNull` FROM `customer`' + }, params: [] }); @@ -51,6 +60,17 @@ Harness.test({ text : 'SELECT ("customer"."name" + "customer"."age") "nameAndAge" FROM "customer" WHERE (("customer"."age" > :1) AND ("customer"."age" < :2))', string: 'SELECT ("customer"."name" + "customer"."age") "nameAndAge" FROM "customer" WHERE (("customer"."age" > 10) AND ("customer"."age" < 20))' }, + // Clickhouse does not support String + int, only String || String. So I'll not write a test case here. But checkout the query below. + /* + SELECT (`name` || CAST(`age` AS String)) AS `nameAndAge` + FROM + (SELECT + arrayJoin(arrayMap(x -> x + 1, range(3))) AS `index`, + arrayElement(['uno', 'dos', 'tres'], index) AS `name`, + arrayElement([5, 15, 25], index) AS `age` + FROM system.one) + WHERE ((`age` > 10) AND (`age` < 20)) + */ params: [10, 20] }); @@ -76,6 +96,19 @@ Harness.test({ text : 'SELECT ("customer"."age" BETWEEN :1 AND :2) "ageBetween" FROM "customer"', string: 'SELECT ("customer"."age" BETWEEN 10 AND 20) "ageBetween" FROM "customer"' }, + /* + SELECT (`age` BETWEEN 10 AND 20) AS `ageBetween` + FROM + (SELECT + arrayJoin(arrayMap(x -> x + 1, range(3))) AS `index`, + arrayElement(['uno', 'dos', 'tres'], index) AS `name`, + arrayElement([5, 15, 25], index) AS `age` + FROM system.one) + */ + clickhouse: { + text : 'SELECT (`age` BETWEEN ? AND ?) AS `ageBetween` FROM `customer`', + string: 'SELECT (`age` BETWEEN 10 AND 20) AS `ageBetween` FROM `customer`' + }, params: [10, 20] }); @@ -101,6 +134,20 @@ Harness.test({ text : 'SELECT ROUND("customer"."age", :1) FROM "customer"', string: 'SELECT ROUND("customer"."age", 2) FROM "customer"' }, + // Mind the case + /* + SELECT round(`age`, 2) + FROM + (SELECT + arrayJoin(arrayMap(x -> x + 1, range(3))) AS `index`, + arrayElement(['uno', 'dos', 'tres'], index) AS `name`, + arrayElement([5, 15, 25], index) AS `age` + FROM system.one) + */ + clickhouse: { + text : 'SELECT round(`age`, ?) FROM `customer`', + string: 'SELECT round(`age`, 2) FROM `customer`' + }, params: [2] }); @@ -118,5 +165,15 @@ Harness.test({ text : 'SELECT (`customer`.`age` NOT BETWEEN ? AND ?) AS `ageNotBetween` FROM `customer`', string: 'SELECT (`customer`.`age` NOT BETWEEN 10 AND 20) AS `ageNotBetween` FROM `customer`' }, + // Clickhouse: not supported. can use NOT the following way but won't be able to do it without introducing too much stuff + /* + SELECT (NOT `age` BETWEEN 10 AND 20) AS `ageNotBetween` + FROM + (SELECT + arrayJoin(arrayMap(x -> x + 1, range(3))) AS `index`, + arrayElement(['uno', 'dos', 'tres'], index) AS `name`, + arrayElement([5, 15, 25], index) AS `age` + FROM system.one) + */ params: [10, 20] }); diff --git a/test/dialects/alter-table-tests.js b/test/dialects/alter-table-tests.js index d050da3d..45e1c49c 100644 --- a/test/dialects/alter-table-tests.js +++ b/test/dialects/alter-table-tests.js @@ -26,6 +26,10 @@ Harness.test({ text : 'ALTER TABLE "post" DROP ("content")', string: 'ALTER TABLE "post" DROP ("content")' }, + clickhouse: { + text : 'Not Implemeneted', + throws: true + }, params: [] }); @@ -51,6 +55,10 @@ Harness.test({ text : 'ALTER TABLE "post" DROP ("content", "userId")', string: 'ALTER TABLE "post" DROP ("content", "userId")' }, + clickhouse: { + text : 'Not Implemeneted', + throws: true + }, params: [] }); @@ -76,6 +84,10 @@ Harness.test({ text : 'ALTER TABLE "post" DROP ("content", "userId")', string: 'ALTER TABLE "post" DROP ("content", "userId")' }, + clickhouse: { + text : 'Not Implemeneted', + throws: true + }, params: [] }); @@ -97,6 +109,10 @@ Harness.test({ text : 'EXEC sp_rename [post], [posts]', string: 'EXEC sp_rename [post], [posts]' }, + clickhouse: { + text : 'Not Implemeneted', + throws: true + }, params: [] }); @@ -134,6 +150,10 @@ Harness.test({ text : 'ALTER TABLE "group" ADD ("id" varchar(100))', string: 'ALTER TABLE "group" ADD ("id" varchar(100))' }, + clickhouse: { + text : 'Not Implemeneted', + throws: true + }, params: [] }); @@ -159,6 +179,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))' }, + clickhouse: { + text : 'Not Implemeneted', + throws: true + }, params: [] }); @@ -184,6 +208,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))' }, + clickhouse: { + text : 'Not Implemeneted', + throws: true + }, params: [] }); @@ -205,6 +233,10 @@ Harness.test({ text : 'EXEC sp_rename \'[group].[userId]\', [newUserId], \'COLUMN\'', string: 'EXEC sp_rename \'[group].[userId]\', [newUserId], \'COLUMN\'' }, + clickhouse: { + text : 'Not Implemeneted', + throws: true + }, params: [] }); @@ -247,6 +279,10 @@ Harness.test({ text : 'EXEC sp_rename \'[group].[userId]\', [id], \'COLUMN\'', string: 'EXEC sp_rename \'[group].[userId]\', [id], \'COLUMN\'' }, + clickhouse: { + text : 'Not Implemeneted', + throws: true + }, params: [] }); @@ -276,6 +312,10 @@ Harness.test({ mssql: { text : 'EXEC sp_rename \'[UserWithSignature].[Signature]\', [sig], \'COLUMN\'', string: 'EXEC sp_rename \'[UserWithSignature].[Signature]\', [sig], \'COLUMN\'' + }, + clickhouse: { + text : 'Not Implemeneted', + throws: true } }); @@ -313,6 +353,10 @@ Harness.test({ text : 'ALTER TABLE "post" ADD ("userId" int REFERENCES "user"("id"))', string: 'ALTER TABLE "post" ADD ("userId" int REFERENCES "user"("id"))' }, + clickhouse: { + text : 'Not Implemeneted', + throws: true + }, params: [] }); @@ -334,5 +378,9 @@ Harness.test({ text : 'ALTER TABLE "post" ADD ("picture" varchar(100))', string: 'ALTER TABLE "post" ADD ("picture" varchar(100))' }, + clickhouse: { + text : 'Not Implemeneted', + throws: true + }, params: [] }); diff --git a/test/dialects/limit-and-offset-tests.js b/test/dialects/limit-and-offset-tests.js index 6561c14b..76d48dce 100644 --- a/test/dialects/limit-and-offset-tests.js +++ b/test/dialects/limit-and-offset-tests.js @@ -24,6 +24,10 @@ Harness.test({ text : 'SELECT TOP(1) [user].* FROM [user] ORDER BY [user].[name]', string: 'SELECT TOP(1) [user].* FROM [user] ORDER BY [user].[name]' }, + clickhouse: { + text : 'SELECT * FROM `user` ORDER BY `name` LIMIT 1', + string: 'SELECT * FROM `user` ORDER BY `name` LIMIT 1' + }, params: [] }); @@ -45,6 +49,12 @@ Harness.test({ text : 'SELECT [user].* FROM [user] ORDER BY [user].[name] OFFSET 6 ROWS FETCH NEXT 3 ROWS ONLY', string: 'SELECT [user].* FROM [user] ORDER BY [user].[name] OFFSET 6 ROWS FETCH NEXT 3 ROWS ONLY' }, + /* TODO + clickhouse: { + text : 'SELECT * FROM `user` ORDER BY `name` LIMIT 6, 3', + string: 'SELECT * FROM `user` ORDER BY `name` LIMIT 6, 3' + }, + */ params: [] }); @@ -70,6 +80,12 @@ Harness.test({ text : 'SELECT "user".* FROM "user" ORDER BY "user"."name" OFFSET 10 ROWS', string: 'SELECT "user".* FROM "user" ORDER BY "user"."name" OFFSET 10 ROWS' }, + /* TODO + clickhouse: { + text : 'Clickhouse does not support OFFSET without LIMIT.', + throws: true + }, + */ params: [] }); @@ -99,7 +115,11 @@ Harness.test({ text : 'SELECT "user".* FROM "user" WHERE ("user"."name" = :1) OFFSET (SELECT FLOOR(RANDOM() * COUNT(*)) FROM "user" WHERE ("user"."name" = :2)) ROWS FETCH NEXT 1 ROWS ONLY', string: 'SELECT "user".* FROM "user" WHERE ("user"."name" = \'John\') OFFSET (SELECT FLOOR(RANDOM() * COUNT(*)) FROM "user" WHERE ("user"."name" = \'John\')) ROWS FETCH NEXT 1 ROWS ONLY' }, + clickhouse: { + text : 'SELECT * FROM `user` WHERE (`name` = ?) OFFSET (SELECT FLOOR(RANDOM() * COUNT(*)) FROM `user` WHERE (`name` = ?)) LIMIT 1', + string: 'SELECT * FROM `user` WHERE (`name` = \'John\') OFFSET (SELECT FLOOR(RANDOM() * COUNT(*)) FROM `user` WHERE (`name` = \'John\')) LIMIT 1' + }, values: ['John', 'John'] }); -// TODO: Should probably have a test case like the one above but including an ORDER BY clause so the mssql case can be tested \ No newline at end of file +// TODO: Should probably have a test case like the one above but including an ORDER BY clause so the mssql case can be tested diff --git a/test/dialects/support.js b/test/dialects/support.js index 4a9fa427..c0ef72aa 100644 --- a/test/dialects/support.js +++ b/test/dialects/support.js @@ -9,7 +9,8 @@ var dialects = { sqlite : require('../../lib/dialect/sqlite'), mysql : require('../../lib/dialect/mysql'), mssql : require('../../lib/dialect/mssql'), - oracle : require('../../lib/dialect/oracle') + oracle : require('../../lib/dialect/oracle'), + clickhouse : require('../../lib/dialect/clickhouse') }; module.exports = {