From a9b8ea9ec5829e7bfac53baceb1534cc0884f77e Mon Sep 17 00:00:00 2001 From: osbert Date: Sat, 19 Aug 2017 13:13:59 +0800 Subject: [PATCH 01/16] first clickhouse test case --- lib/dialect/clickhouse.js | 221 +++++++++++++++++++++++++++++++ lib/dialect/index.js | 2 + test/dialects/aggregate-tests.js | 4 + test/dialects/support.js | 3 +- 4 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 lib/dialect/clickhouse.js diff --git a/lib/dialect/clickhouse.js b/lib/dialect/clickhouse.js new file mode 100644 index 00000000..d2d2943c --- /dev/null +++ b/lib/dialect/clickhouse.js @@ -0,0 +1,221 @@ +'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 txt = functionCall.name + '(' + (nodes[0]+'') + ')'; + return txt; + } + + var txt=""; + var 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 = name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; + + this._visitingFunctionCall = false; + return [txt]; +}; + +Clickhouse.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 + 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; +}; + + + +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/test/dialects/aggregate-tests.js b/test/dialects/aggregate-tests.js index 59d48d57..ae27246b 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: [] }); 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 = { From 8d6c1a3df63775284cc0a7ba2708a0d7b6db27bc Mon Sep 17 00:00:00 2001 From: osbert Date: Sat, 19 Aug 2017 13:16:26 +0800 Subject: [PATCH 02/16] make identifiers quoted https://clickhouse.yandex/docs/en/query_language/syntax.html#identifiers --- lib/dialect/clickhouse.js | 2 +- test/dialects/aggregate-tests.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/dialect/clickhouse.js b/lib/dialect/clickhouse.js index d2d2943c..7ff0b206 100644 --- a/lib/dialect/clickhouse.js +++ b/lib/dialect/clickhouse.js @@ -16,7 +16,7 @@ util.inherits(Clickhouse, Postgres); Clickhouse.prototype._myClass = Clickhouse; -Clickhouse.prototype._quoteCharacter = ''; +Clickhouse.prototype._quoteCharacter = '`'; Clickhouse.prototype._arrayAggFunctionName = 'GROUP_CONCAT'; diff --git a/test/dialects/aggregate-tests.js b/test/dialects/aggregate-tests.js index ae27246b..5edeb4e8 100644 --- a/test/dialects/aggregate-tests.js +++ b/test/dialects/aggregate-tests.js @@ -28,8 +28,8 @@ Harness.test({ string: 'SELECT COUNT(*) "post_count" FROM "post"' }, clickhouse: { - text : 'SELECT COUNT() AS post_count FROM post', - string: 'SELECT COUNT() AS post_count FROM post' + text : 'SELECT COUNT() AS `post_count` FROM `post`', + string: 'SELECT COUNT() AS `post_count` FROM `post`' }, params: [] }); From 62a292b6714010e73092ac32a7aebb2c0b5248c9 Mon Sep 17 00:00:00 2001 From: osbert Date: Sat, 19 Aug 2017 13:36:51 +0800 Subject: [PATCH 03/16] clickhosue:aggregate-tests --- lib/dialect/clickhouse.js | 4 ++ test/dialects/aggregate-tests.js | 72 ++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/lib/dialect/clickhouse.js b/lib/dialect/clickhouse.js index 7ff0b206..3024f916 100644 --- a/lib/dialect/clickhouse.js +++ b/lib/dialect/clickhouse.js @@ -168,6 +168,10 @@ Clickhouse.prototype.visitFunctionCall = function(functionCall) { }; Clickhouse.prototype.visitColumn = function(columnNode) { + + // Clichouse does not support tablename.columnName + delete columnNode.table; + var self = this; var inSelectClause; diff --git a/test/dialects/aggregate-tests.js b/test/dialects/aggregate-tests.js index 5edeb4e8..b3934444 100644 --- a/test/dialects/aggregate-tests.js +++ b/test/dialects/aggregate-tests.js @@ -56,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: [] }); @@ -81,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: [] }); @@ -106,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: [] }); @@ -131,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: [] }); @@ -156,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: [] }); @@ -177,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: [] }); @@ -202,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: [] }); @@ -227,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: [] }); @@ -252,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: [] }); @@ -277,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: [] }); @@ -302,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: [] }); @@ -327,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: [] }); @@ -352,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: [] }); @@ -377,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: [] }); @@ -402,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: [] }); @@ -427,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: [] }); @@ -452,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: [] }); @@ -477,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: [] }); From 9ae5e3ad70b8ec7d9f45c69cb62dacedf1739864 Mon Sep 17 00:00:00 2001 From: osbert Date: Sat, 19 Aug 2017 14:23:38 +0800 Subject: [PATCH 04/16] round function case sensitive --- lib/dialect/clickhouse.js | 12 ++++++--- test/dialects/alias-tests.js | 47 ++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/lib/dialect/clickhouse.js b/lib/dialect/clickhouse.js index 3024f916..611d03c3 100644 --- a/lib/dialect/clickhouse.js +++ b/lib/dialect/clickhouse.js @@ -151,17 +151,19 @@ Clickhouse.prototype.visitFunctionCall = function(functionCall) { 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]+'') + ')'; + 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 = name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; + else txt = displayName + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')'; this._visitingFunctionCall = false; return [txt]; @@ -171,7 +173,7 @@ Clickhouse.prototype.visitColumn = function(columnNode) { // Clichouse does not support tablename.columnName delete columnNode.table; - + var self = this; var inSelectClause; @@ -220,6 +222,8 @@ Clickhouse.prototype.visitInterval = function(interval) { return result; }; - +Clickhouse.functionMap = { + "ROUND": "round" +}; module.exports = Clickhouse; diff --git a/test/dialects/alias-tests.js b/test/dialects/alias-tests.js index 32db9308..6f5a12d1 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] }); From 0b510c4ff9eeda6e5fe6bd6e3298ec8399c93e65 Mon Sep 17 00:00:00 2001 From: osbert Date: Sat, 19 Aug 2017 14:27:12 +0800 Subject: [PATCH 05/16] clickhouse-alias-tests --- test/dialects/alias-tests.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/dialects/alias-tests.js b/test/dialects/alias-tests.js index 6f5a12d1..8121016a 100644 --- a/test/dialects/alias-tests.js +++ b/test/dialects/alias-tests.js @@ -165,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] }); From 29059bbe220f4cb20cd1b3c60415b66abeb96c0b Mon Sep 17 00:00:00 2001 From: osbert Date: Sat, 19 Aug 2017 14:40:10 +0800 Subject: [PATCH 06/16] alter-table-not-implemented --- lib/dialect/clickhouse.js | 4 +++ test/dialects/alter-table-tests.js | 48 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/lib/dialect/clickhouse.js b/lib/dialect/clickhouse.js index 611d03c3..8340075a 100644 --- a/lib/dialect/clickhouse.js +++ b/lib/dialect/clickhouse.js @@ -222,6 +222,10 @@ Clickhouse.prototype.visitInterval = function(interval) { return result; }; +Clickhouse.prototype.visitAlter = function(alter) { + throw new Error('Not Implemented'); +}; + Clickhouse.functionMap = { "ROUND": "round" }; 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: [] }); From 5f8708b7ef81e920bcea0fde8a2a73b33230baa9 Mon Sep 17 00:00:00 2001 From: osbert Date: Sat, 19 Aug 2017 16:58:01 +0800 Subject: [PATCH 07/16] add clickhouse functions --- lib/dialect/clickhouse.js | 4 +++- lib/functions.js | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/dialect/clickhouse.js b/lib/dialect/clickhouse.js index 8340075a..400eea7a 100644 --- a/lib/dialect/clickhouse.js +++ b/lib/dialect/clickhouse.js @@ -227,7 +227,9 @@ Clickhouse.prototype.visitAlter = function(alter) { }; Clickhouse.functionMap = { - "ROUND": "round" + "ROUND": "round", + "TODAY": "today", + "NOW": "now" }; module.exports = Clickhouse; diff --git a/lib/functions.js b/lib/functions.js index fe9cdaaf..47026449 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']; + +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() { From 3366181c69c7c4d21599dd8c465291bd94899d0b Mon Sep 17 00:00:00 2001 From: osbert Date: Sat, 19 Aug 2017 19:28:13 +0800 Subject: [PATCH 08/16] clickhouse: add array function --- lib/dialect/clickhouse.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/dialect/clickhouse.js b/lib/dialect/clickhouse.js index 400eea7a..82300900 100644 --- a/lib/dialect/clickhouse.js +++ b/lib/dialect/clickhouse.js @@ -226,10 +226,16 @@ 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.functionMap = { "ROUND": "round", "TODAY": "today", - "NOW": "now" + "NOW": "now", + "ARRAY_JOIN": "arrayJoin" }; module.exports = Clickhouse; From 898ae4db8b833716dfdbda8f215b0772e2d9d9f0 Mon Sep 17 00:00:00 2001 From: osbert Date: Sat, 19 Aug 2017 19:36:41 +0800 Subject: [PATCH 09/16] add array_join --- lib/functions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/functions.js b/lib/functions.js index 47026449..75e79cad 100644 --- a/lib/functions.js +++ b/lib/functions.js @@ -65,7 +65,7 @@ var hstoreFunction = 'HSTORE'; var textsearchFunctions = ['TS_RANK','TS_RANK_CD', 'PLAINTO_TSQUERY', 'TO_TSQUERY', 'TO_TSVECTOR', 'SETWEIGHT']; //clickhouse exclusive functions -var clickhouseFunctions = ['TODAY', 'NOW']; +var clickhouseFunctions = ['TODAY', 'NOW', 'ARRAY_JOIN']; var standardFunctionNames = aggregateFunctions .concat(scalarFunctions) From 923c67f62ffe45448bd5472e02aadfa349b5fcf2 Mon Sep 17 00:00:00 2001 From: osbert Date: Mon, 21 Aug 2017 21:29:19 +0800 Subject: [PATCH 10/16] Clickhouse: add limit test case --- test/dialects/limit-and-offset-tests.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/test/dialects/limit-and-offset-tests.js b/test/dialects/limit-and-offset-tests.js index 6561c14b..d2a78e91 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,10 @@ 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' }, + clickhouse: { + text : 'SELECT * FROM `user` ORDER BY `name` LIMIT 6, 3', + string: 'SELECT * FROM `user` ORDER BY `name` LIMIT 6, 3' + }, params: [] }); @@ -70,6 +78,10 @@ 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' }, + clickhouse: { + text : 'Clickhouse does not support OFFSET without LIMIT.', + throws: true + }, params: [] }); @@ -99,7 +111,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 (`user`.`name` = ?)) LIMIT 1', + string: 'SELECT * FROM `user` WHERE (`name` = \'John\') OFFSET (SELECT FLOOR(RANDOM() * COUNT(*)) FROM `user` WHERE (`user`.`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 From 965b6bb1f583d8d65a3c351e350527574cc65403 Mon Sep 17 00:00:00 2001 From: osbert Date: Mon, 21 Aug 2017 21:30:12 +0800 Subject: [PATCH 11/16] Clickhouse: expose Column --- lib/index.js | 1 + 1 file changed, 1 insertion(+) 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; From 8ab0c81e6da06c98049cb1991fa927a428b46b2b Mon Sep 17 00:00:00 2001 From: osbert Date: Mon, 21 Aug 2017 21:31:00 +0800 Subject: [PATCH 12/16] Clickhouse: add full join --- lib/node/join.js | 3 +++ lib/node/query.js | 5 +++++ lib/table.js | 4 ++++ 3 files changed, 12 insertions(+) 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..d7631a10 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); From f9b66da24e0242b3934b7ca74693a1e6d3a69a3a Mon Sep 17 00:00:00 2001 From: osbert Date: Mon, 21 Aug 2017 21:51:21 +0800 Subject: [PATCH 13/16] Clickhouse: support for clickhouse join syntax --- lib/dialect/clickhouse.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/dialect/clickhouse.js b/lib/dialect/clickhouse.js index 82300900..59419c5d 100644 --- a/lib/dialect/clickhouse.js +++ b/lib/dialect/clickhouse.js @@ -231,6 +231,17 @@ Clickhouse.prototype.visitArrayCall = function(arrayCall) { return [txt]; }; +Clickhouse.prototype.visitJoin = function(join) { + var result = []; + this._visitingJoin = true; + result = result.concat(this.visit(join.from)); + 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)); + return result; +}; + Clickhouse.functionMap = { "ROUND": "round", "TODAY": "today", From 118d00d0899462b70d5f7facb3831f035d615b22 Mon Sep 17 00:00:00 2001 From: osbert Date: Fri, 25 Aug 2017 13:35:11 +0800 Subject: [PATCH 14/16] clickhouse: fix test case --- lib/dialect/postgres.js | 34 ++++++++++++++----------- test/dialects/limit-and-offset-tests.js | 8 ++++-- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index 2816c901..b69661a2 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -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) { diff --git a/test/dialects/limit-and-offset-tests.js b/test/dialects/limit-and-offset-tests.js index d2a78e91..76d48dce 100644 --- a/test/dialects/limit-and-offset-tests.js +++ b/test/dialects/limit-and-offset-tests.js @@ -49,10 +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: [] }); @@ -78,10 +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: [] }); @@ -112,8 +116,8 @@ Harness.test({ 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 (`user`.`name` = ?)) LIMIT 1', - string: 'SELECT * FROM `user` WHERE (`name` = \'John\') OFFSET (SELECT FLOOR(RANDOM() * COUNT(*)) FROM `user` WHERE (`user`.`name` = \'John\')) LIMIT 1' + 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'] }); From aafc595d113220520f19c53af974b2e31b46fd4a Mon Sep 17 00:00:00 2001 From: osbert Date: Fri, 25 Aug 2017 13:39:11 +0800 Subject: [PATCH 15/16] clickhouse:join from query --- lib/dialect/postgres.js | 14 +++++++------- lib/table.js | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index b69661a2..0949b75b 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); @@ -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 @@ -1028,7 +1028,7 @@ Postgres.prototype.visitForShare = function() { Postgres.prototype.visitJoin = function(join) { var result = []; this._visitingJoin = true; - result = result.concat(this.visit(join.from)); + result = result.concat(this.visit(join.from, {"joinFromQuery": true})); result = result.concat(join.subType + ' JOIN'); result = result.concat(this.visit(join.to)); result = result.concat('ON'); @@ -1218,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/table.js b/lib/table.js index d7631a10..ff029115 100644 --- a/lib/table.js +++ b/lib/table.js @@ -239,7 +239,7 @@ Table.prototype.leftJoin = function(other) { 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) { From 39bba419fdc50f61a08319533de77fc2ebeef57a Mon Sep 17 00:00:00 2001 From: osbert Date: Fri, 25 Aug 2017 14:23:35 +0800 Subject: [PATCH 16/16] fix clickhouse join issue --- lib/dialect/clickhouse.js | 27 +++++++++++++++++++++++++-- lib/dialect/postgres.js | 4 ++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/lib/dialect/clickhouse.js b/lib/dialect/clickhouse.js index 59419c5d..c30e2032 100644 --- a/lib/dialect/clickhouse.js +++ b/lib/dialect/clickhouse.js @@ -231,14 +231,37 @@ Clickhouse.prototype.visitArrayCall = function(arrayCall) { return [txt]; }; -Clickhouse.prototype.visitJoin = function(join) { +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; - result = result.concat(this.visit(join.from)); + 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; }; diff --git a/lib/dialect/postgres.js b/lib/dialect/postgres.js index 0949b75b..260a7dd4 100644 --- a/lib/dialect/postgres.js +++ b/lib/dialect/postgres.js @@ -156,7 +156,7 @@ Postgres.prototype.visit = function(node, config) { 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); @@ -1028,7 +1028,7 @@ Postgres.prototype.visitForShare = function() { Postgres.prototype.visitJoin = function(join) { var result = []; this._visitingJoin = true; - result = result.concat(this.visit(join.from, {"joinFromQuery": true})); + result = result.concat(this.visit(join.from)); result = result.concat(join.subType + ' JOIN'); result = result.concat(this.visit(join.to)); result = result.concat('ON');