Skip to content

Commit a9b8ea9

Browse files
committed
first clickhouse test case
1 parent 95d775a commit a9b8ea9

4 files changed

Lines changed: 229 additions & 1 deletion

File tree

lib/dialect/clickhouse.js

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
'use strict';
2+
3+
var util = require('util');
4+
var assert = require('assert');
5+
var _ = require('lodash');
6+
7+
var Clickhouse = function(config) {
8+
this.output = [];
9+
this.params = [];
10+
this.config = config || {};
11+
};
12+
13+
var Postgres = require('./postgres');
14+
15+
util.inherits(Clickhouse, Postgres);
16+
17+
Clickhouse.prototype._myClass = Clickhouse;
18+
19+
Clickhouse.prototype._quoteCharacter = '';
20+
21+
Clickhouse.prototype._arrayAggFunctionName = 'GROUP_CONCAT';
22+
23+
Clickhouse.prototype.visitReplace = function(replace) {
24+
var self = this;
25+
// don't use table.column for replaces
26+
this._visitedReplace = true;
27+
28+
var result = ['REPLACE'];
29+
result = result.concat(replace.nodes.map(this.visit.bind(this)));
30+
result.push('INTO ' + this.visit(this._queryNode.table.toNode()));
31+
result.push('(' + replace.columns.map(this.visit.bind(this)).join(', ') + ')');
32+
33+
var paramNodes = replace.getParameters();
34+
35+
if (paramNodes.length > 0) {
36+
var paramText = paramNodes.map(function (paramSet) {
37+
return paramSet.map(function (param) {
38+
return self.visit(param);
39+
}).join(', ');
40+
}).map(function (param) {
41+
return '('+param+')';
42+
}).join(', ');
43+
44+
result.push('VALUES', paramText);
45+
46+
if (result.slice(2, 5).join(' ') === '() VALUES ()') {
47+
result.splice(2, 3, 'DEFAULT VALUES');
48+
}
49+
}
50+
51+
this._visitedReplace = false;
52+
53+
if (result[2] === 'DEFAULT VALUES') {
54+
result[2] = '() VALUES ()';
55+
}
56+
return result;
57+
};
58+
59+
Clickhouse.prototype._getParameterPlaceholder = function() {
60+
return '?';
61+
};
62+
63+
Clickhouse.prototype._getParameterValue = function(value) {
64+
if (Buffer.isBuffer(value)) {
65+
value = 'x' + this._getParameterValue(value.toString('hex'));
66+
} else {
67+
value = Postgres.prototype._getParameterValue.call(this, value);
68+
}
69+
return value;
70+
};
71+
72+
Clickhouse.prototype.visitOnDuplicate = function(onDuplicate) {
73+
var params = [];
74+
/* jshint boss: true */
75+
for(var i = 0, node; node = onDuplicate.nodes[i]; i++) {
76+
var target_col = this.visit(node);
77+
params = params.concat(target_col + ' = ' + this.visit(node.value));
78+
}
79+
var result = [
80+
'ON DUPLICATE KEY UPDATE',
81+
params.join(', ')
82+
];
83+
return result;
84+
};
85+
86+
Clickhouse.prototype.visitOnConflict = function(onConflict) {
87+
throw new Error('Clickhouse does not allow onConflict clause.');
88+
};
89+
90+
Clickhouse.prototype.visitReturning = function() {
91+
throw new Error('Clickhouse does not allow returning clause.');
92+
};
93+
94+
Clickhouse.prototype.visitForShare = function() {
95+
throw new Error('Clickhouse does not allow FOR SHARE clause.');
96+
};
97+
98+
Clickhouse.prototype.visitCreate = function(create) {
99+
var result = Clickhouse.super_.prototype.visitCreate.call(this, create);
100+
var engine = this._queryNode.table._initialConfig.engine;
101+
var charset = this._queryNode.table._initialConfig.charset;
102+
103+
if ( !! engine) {
104+
result.push('ENGINE=' + engine);
105+
}
106+
107+
if ( !! charset) {
108+
result.push('DEFAULT CHARSET=' + charset);
109+
}
110+
111+
return result;
112+
};
113+
114+
Clickhouse.prototype.visitRenameColumn = function(renameColumn) {
115+
var dataType = renameColumn.nodes[1].dataType || renameColumn.nodes[0].dataType;
116+
assert(dataType, 'dataType missing for column ' + (renameColumn.nodes[1].name || renameColumn.nodes[0].name || '') +
117+
' (CHANGE COLUMN statements require a dataType)');
118+
return ['CHANGE COLUMN ' + this.visit(renameColumn.nodes[0]) + ' ' + this.visit(renameColumn.nodes[1]) + ' ' + dataType];
119+
};
120+
121+
Clickhouse.prototype.visitInsert = function(insert) {
122+
var result = Postgres.prototype.visitInsert.call(this, insert);
123+
if (result[2] === 'DEFAULT VALUES') {
124+
result[2] = '() VALUES ()';
125+
}
126+
return result;
127+
};
128+
129+
Clickhouse.prototype.visitIndexes = function(node) {
130+
var tableName = this.visit(this._queryNode.table.toNode())[0];
131+
132+
return "SHOW INDEX FROM " + tableName;
133+
};
134+
135+
Clickhouse.prototype.visitBinary = function(binary) {
136+
if (binary.operator === '@@') {
137+
var self = this;
138+
var text = '(MATCH ' + this.visit(binary.left) + ' AGAINST ';
139+
text += this.visit(binary.right);
140+
text += ')';
141+
return [text];
142+
}
143+
return Clickhouse.super_.prototype.visitBinary.call(this, binary);
144+
};
145+
146+
Clickhouse.prototype.visitFunctionCall = function(functionCall) {
147+
var _this=this;
148+
149+
this._visitingFunctionCall = true;
150+
151+
function _extract() {
152+
var nodes = functionCall.nodes.map(_this.visit.bind(_this));
153+
if (nodes.length != 1) throw new Error('Not enough parameters passed to ' + functionCall.name + ' function');
154+
var txt = functionCall.name + '(' + (nodes[0]+'') + ')';
155+
return txt;
156+
}
157+
158+
var txt="";
159+
var name = functionCall.name;
160+
// Override date functions since Clickhouse is different than postgres
161+
if (['YEAR', 'MONTH', 'DAY', 'HOUR'].indexOf(functionCall.name) >= 0) txt = _extract();
162+
// Override CURRENT_TIMESTAMP function to remove parens
163+
else if ('CURRENT_TIMESTAMP' == functionCall.name) txt = functionCall.name;
164+
else txt = name + '(' + functionCall.nodes.map(this.visit.bind(this)).join(', ') + ')';
165+
166+
this._visitingFunctionCall = false;
167+
return [txt];
168+
};
169+
170+
Clickhouse.prototype.visitColumn = function(columnNode) {
171+
var self = this;
172+
var inSelectClause;
173+
174+
function isCountStarExpression(columnNode){
175+
if (!columnNode.aggregator) return false;
176+
if (columnNode.aggregator.toLowerCase()!='count') return false;
177+
if (!columnNode.star) return false;
178+
return true;
179+
}
180+
181+
function _countStar(){
182+
// Implement our own
183+
var result='COUNT()';
184+
if(inSelectClause && columnNode.alias) {
185+
result += ' AS ' + self.quote(columnNode.alias);
186+
}
187+
return result;
188+
}
189+
190+
inSelectClause = !this._selectOrDeleteEndIndex;
191+
if(isCountStarExpression(columnNode)) return _countStar();
192+
return Clickhouse.super_.prototype.visitColumn.call(this, columnNode);
193+
};
194+
195+
Clickhouse.prototype.visitInterval = function(interval) {
196+
var parameter;
197+
if(_.isNumber(interval.years)) {
198+
if(_.isNumber(interval.months)) {
199+
parameter = "'" + interval.years + '-' + interval.months + "' YEAR_MONTH";
200+
} else {
201+
parameter = interval.years + ' YEAR';
202+
}
203+
} else if(_.isNumber(interval.months)) {
204+
parameter = interval.months + ' MONTH';
205+
} else if(_.isNumber(interval.days)) {
206+
parameter = "'" + interval.days + ' ' +
207+
(_.isNumber(interval.hours)?interval.hours:0) + ':' +
208+
(_.isNumber(interval.minutes)?interval.minutes:0) + ':' +
209+
(_.isNumber(interval.seconds)?interval.seconds:0) + "' DAY_SECOND";
210+
} else {
211+
parameter = "'" + (_.isNumber(interval.hours)?interval.hours:0) + ':' +
212+
(_.isNumber(interval.minutes)?interval.minutes:0) + ':' +
213+
(_.isNumber(interval.seconds)?interval.seconds:0) + "' HOUR_SECOND";
214+
}
215+
var result = "INTERVAL " + parameter;
216+
return result;
217+
};
218+
219+
220+
221+
module.exports = Clickhouse;

lib/dialect/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ var getDialect = function(dialect) {
1313
return require('./mssql');
1414
case 'oracle':
1515
return require('./oracle');
16+
case 'clickhouse':
17+
return require('./clickhouse');
1618
default:
1719
throw new Error(dialect + ' is unsupported');
1820
}

test/dialects/aggregate-tests.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ Harness.test({
2727
text : 'SELECT COUNT(*) "post_count" FROM "post"',
2828
string: 'SELECT COUNT(*) "post_count" FROM "post"'
2929
},
30+
clickhouse: {
31+
text : 'SELECT COUNT() AS post_count FROM post',
32+
string: 'SELECT COUNT() AS post_count FROM post'
33+
},
3034
params: []
3135
});
3236

test/dialects/support.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ var dialects = {
99
sqlite : require('../../lib/dialect/sqlite'),
1010
mysql : require('../../lib/dialect/mysql'),
1111
mssql : require('../../lib/dialect/mssql'),
12-
oracle : require('../../lib/dialect/oracle')
12+
oracle : require('../../lib/dialect/oracle'),
13+
clickhouse : require('../../lib/dialect/clickhouse')
1314
};
1415

1516
module.exports = {

0 commit comments

Comments
 (0)