clickhouse-ruby 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +74 -1
- data/README.md +165 -79
- data/lib/clickhouse_ruby/active_record/arel_visitor.rb +205 -76
- data/lib/clickhouse_ruby/active_record/connection_adapter.rb +103 -98
- data/lib/clickhouse_ruby/active_record/railtie.rb +20 -15
- data/lib/clickhouse_ruby/active_record/relation_extensions.rb +398 -0
- data/lib/clickhouse_ruby/active_record/schema_statements.rb +90 -104
- data/lib/clickhouse_ruby/active_record.rb +24 -10
- data/lib/clickhouse_ruby/client.rb +181 -74
- data/lib/clickhouse_ruby/configuration.rb +51 -10
- data/lib/clickhouse_ruby/connection.rb +180 -64
- data/lib/clickhouse_ruby/connection_pool.rb +25 -19
- data/lib/clickhouse_ruby/errors.rb +13 -1
- data/lib/clickhouse_ruby/result.rb +11 -16
- data/lib/clickhouse_ruby/retry_handler.rb +172 -0
- data/lib/clickhouse_ruby/streaming_result.rb +309 -0
- data/lib/clickhouse_ruby/types/array.rb +11 -64
- data/lib/clickhouse_ruby/types/base.rb +59 -0
- data/lib/clickhouse_ruby/types/boolean.rb +28 -25
- data/lib/clickhouse_ruby/types/date_time.rb +10 -27
- data/lib/clickhouse_ruby/types/decimal.rb +173 -0
- data/lib/clickhouse_ruby/types/enum.rb +262 -0
- data/lib/clickhouse_ruby/types/float.rb +14 -28
- data/lib/clickhouse_ruby/types/integer.rb +21 -43
- data/lib/clickhouse_ruby/types/low_cardinality.rb +1 -1
- data/lib/clickhouse_ruby/types/map.rb +21 -36
- data/lib/clickhouse_ruby/types/null_safe.rb +81 -0
- data/lib/clickhouse_ruby/types/nullable.rb +2 -2
- data/lib/clickhouse_ruby/types/parser.rb +28 -18
- data/lib/clickhouse_ruby/types/registry.rb +40 -29
- data/lib/clickhouse_ruby/types/string.rb +9 -13
- data/lib/clickhouse_ruby/types/string_parser.rb +135 -0
- data/lib/clickhouse_ruby/types/tuple.rb +11 -68
- data/lib/clickhouse_ruby/types/uuid.rb +15 -22
- data/lib/clickhouse_ruby/types.rb +19 -15
- data/lib/clickhouse_ruby/version.rb +1 -1
- data/lib/clickhouse_ruby.rb +11 -11
- metadata +41 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "arel/visitors/to_sql"
|
|
4
4
|
|
|
5
5
|
module ClickhouseRuby
|
|
6
6
|
module ActiveRecord
|
|
@@ -42,18 +42,18 @@ module ClickhouseRuby
|
|
|
42
42
|
table = o.relation
|
|
43
43
|
|
|
44
44
|
# Build ClickHouse DELETE syntax
|
|
45
|
-
collector <<
|
|
45
|
+
collector << "ALTER TABLE "
|
|
46
46
|
collector = visit(table, collector)
|
|
47
|
-
collector <<
|
|
47
|
+
collector << " DELETE"
|
|
48
48
|
|
|
49
49
|
# Add WHERE clause (required for ClickHouse DELETE)
|
|
50
50
|
if o.wheres.any?
|
|
51
|
-
collector <<
|
|
52
|
-
collector = inject_join(o.wheres, collector,
|
|
51
|
+
collector << " WHERE "
|
|
52
|
+
collector = inject_join(o.wheres, collector, " AND ")
|
|
53
53
|
else
|
|
54
54
|
# ClickHouse requires WHERE clause for DELETE
|
|
55
55
|
# Use 1=1 to delete all rows
|
|
56
|
-
collector <<
|
|
56
|
+
collector << " WHERE 1=1"
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
collector
|
|
@@ -70,22 +70,79 @@ module ClickhouseRuby
|
|
|
70
70
|
table = o.relation
|
|
71
71
|
|
|
72
72
|
# Build ClickHouse UPDATE syntax
|
|
73
|
-
collector <<
|
|
73
|
+
collector << "ALTER TABLE "
|
|
74
74
|
collector = visit(table, collector)
|
|
75
|
-
collector <<
|
|
75
|
+
collector << " UPDATE "
|
|
76
76
|
|
|
77
77
|
# Add SET assignments
|
|
78
|
-
unless o.values.empty?
|
|
79
|
-
collector = inject_join(o.values, collector, ', ')
|
|
80
|
-
end
|
|
78
|
+
collector = inject_join(o.values, collector, ", ") unless o.values.empty?
|
|
81
79
|
|
|
82
80
|
# Add WHERE clause (required for ClickHouse UPDATE)
|
|
83
81
|
if o.wheres.any?
|
|
84
|
-
collector <<
|
|
85
|
-
collector = inject_join(o.wheres, collector,
|
|
82
|
+
collector << " WHERE "
|
|
83
|
+
collector = inject_join(o.wheres, collector, " AND ")
|
|
86
84
|
else
|
|
87
85
|
# ClickHouse requires WHERE clause for UPDATE
|
|
88
|
-
collector <<
|
|
86
|
+
collector << " WHERE 1=1"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
collector
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Visit a SELECT statement
|
|
93
|
+
# Ensures proper ordering of ClickHouse-specific clauses
|
|
94
|
+
#
|
|
95
|
+
# Clause ordering for ClickHouse:
|
|
96
|
+
# SELECT ... FROM table [FINAL] [SAMPLE n] [PREWHERE ...] [WHERE ...] [GROUP BY ...] [ORDER BY ...] [LIMIT n]
|
|
97
|
+
#
|
|
98
|
+
# @param o [Arel::Nodes::SelectStatement] the select node
|
|
99
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
100
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
101
|
+
def visit_Arel_Nodes_SelectStatement(o, collector)
|
|
102
|
+
collector = visit_Arel_Nodes_SelectCore(o.cores[0], collector)
|
|
103
|
+
|
|
104
|
+
# FROM clause
|
|
105
|
+
collector = visit(o.cores[0].source, collector) if o.cores[0].source
|
|
106
|
+
|
|
107
|
+
# Get ClickHouse-specific state from the Arel node (set by RelationExtensions#build_arel)
|
|
108
|
+
use_final = o.instance_variable_get(:@clickhouse_final)
|
|
109
|
+
sample_value = o.instance_variable_get(:@clickhouse_sample_value)
|
|
110
|
+
sample_offset = o.instance_variable_get(:@clickhouse_sample_offset)
|
|
111
|
+
prewhere_values = o.instance_variable_get(:@clickhouse_prewhere_values)
|
|
112
|
+
query_settings = o.instance_variable_get(:@clickhouse_query_settings)
|
|
113
|
+
|
|
114
|
+
# FINAL clause (if set)
|
|
115
|
+
collector << " FINAL" if use_final
|
|
116
|
+
|
|
117
|
+
# SAMPLE clause (if set)
|
|
118
|
+
if sample_value
|
|
119
|
+
collector << " SAMPLE "
|
|
120
|
+
collector << format_sample_value(sample_value)
|
|
121
|
+
if sample_offset
|
|
122
|
+
collector << " OFFSET "
|
|
123
|
+
collector << sample_offset.to_s
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# PREWHERE clause (if set)
|
|
128
|
+
if prewhere_values&.any?
|
|
129
|
+
collector << " PREWHERE "
|
|
130
|
+
collector = visit_prewhere_conditions(prewhere_values, collector)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# WHERE clause
|
|
134
|
+
if o.cores[0].wheres.any?
|
|
135
|
+
collector << " WHERE "
|
|
136
|
+
collector = inject_join(o.cores[0].wheres, collector, " AND ")
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET
|
|
140
|
+
collector = visit_orders_and_limits(o, collector)
|
|
141
|
+
|
|
142
|
+
# SETTINGS clause (at the very end)
|
|
143
|
+
if query_settings&.any?
|
|
144
|
+
collector << " "
|
|
145
|
+
collector << build_settings_clause(query_settings)
|
|
89
146
|
end
|
|
90
147
|
|
|
91
148
|
collector
|
|
@@ -98,7 +155,7 @@ module ClickhouseRuby
|
|
|
98
155
|
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
99
156
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
100
157
|
def visit_Arel_Nodes_Limit(o, collector)
|
|
101
|
-
collector <<
|
|
158
|
+
collector << "LIMIT "
|
|
102
159
|
visit(o.expr, collector)
|
|
103
160
|
end
|
|
104
161
|
|
|
@@ -109,7 +166,7 @@ module ClickhouseRuby
|
|
|
109
166
|
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
110
167
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
111
168
|
def visit_Arel_Nodes_Offset(o, collector)
|
|
112
|
-
collector <<
|
|
169
|
+
collector << "OFFSET "
|
|
113
170
|
visit(o.expr, collector)
|
|
114
171
|
end
|
|
115
172
|
|
|
@@ -119,10 +176,6 @@ module ClickhouseRuby
|
|
|
119
176
|
# @param o [Arel::Nodes::SelectStatement] the select node
|
|
120
177
|
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
121
178
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
122
|
-
def visit_Arel_Nodes_SelectStatement(o, collector)
|
|
123
|
-
# Use default behavior but ensure ClickHouse compatibility
|
|
124
|
-
super
|
|
125
|
-
end
|
|
126
179
|
|
|
127
180
|
# Visit a table alias
|
|
128
181
|
# ClickHouse uses AS keyword for table aliases
|
|
@@ -132,7 +185,7 @@ module ClickhouseRuby
|
|
|
132
185
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
133
186
|
def visit_Arel_Nodes_TableAlias(o, collector)
|
|
134
187
|
collector = visit(o.relation, collector)
|
|
135
|
-
collector <<
|
|
188
|
+
collector << " AS "
|
|
136
189
|
collector << quote_table_name(o.name)
|
|
137
190
|
end
|
|
138
191
|
|
|
@@ -157,8 +210,8 @@ module ClickhouseRuby
|
|
|
157
210
|
# @param o [Arel::Nodes::True] the true node
|
|
158
211
|
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
159
212
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
160
|
-
def visit_Arel_Nodes_True(
|
|
161
|
-
collector <<
|
|
213
|
+
def visit_Arel_Nodes_True(_o, collector)
|
|
214
|
+
collector << "1"
|
|
162
215
|
end
|
|
163
216
|
|
|
164
217
|
# Visit a False node
|
|
@@ -166,8 +219,8 @@ module ClickhouseRuby
|
|
|
166
219
|
# @param o [Arel::Nodes::False] the false node
|
|
167
220
|
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
168
221
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
169
|
-
def visit_Arel_Nodes_False(
|
|
170
|
-
collector <<
|
|
222
|
+
def visit_Arel_Nodes_False(_o, collector)
|
|
223
|
+
collector << "0"
|
|
171
224
|
end
|
|
172
225
|
|
|
173
226
|
# Visit a CASE statement
|
|
@@ -176,25 +229,25 @@ module ClickhouseRuby
|
|
|
176
229
|
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
177
230
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
178
231
|
def visit_Arel_Nodes_Case(o, collector)
|
|
179
|
-
collector <<
|
|
232
|
+
collector << "CASE "
|
|
180
233
|
|
|
181
234
|
if o.case
|
|
182
235
|
visit(o.case, collector)
|
|
183
|
-
collector <<
|
|
236
|
+
collector << " "
|
|
184
237
|
end
|
|
185
238
|
|
|
186
239
|
o.conditions.each do |condition|
|
|
187
240
|
visit(condition, collector)
|
|
188
|
-
collector <<
|
|
241
|
+
collector << " "
|
|
189
242
|
end
|
|
190
243
|
|
|
191
244
|
if o.default
|
|
192
|
-
collector <<
|
|
245
|
+
collector << "ELSE "
|
|
193
246
|
visit(o.default, collector)
|
|
194
|
-
collector <<
|
|
247
|
+
collector << " "
|
|
195
248
|
end
|
|
196
249
|
|
|
197
|
-
collector <<
|
|
250
|
+
collector << "END"
|
|
198
251
|
end
|
|
199
252
|
|
|
200
253
|
# Handle INSERT statements
|
|
@@ -204,23 +257,23 @@ module ClickhouseRuby
|
|
|
204
257
|
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
205
258
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
206
259
|
def visit_Arel_Nodes_InsertStatement(o, collector)
|
|
207
|
-
collector <<
|
|
260
|
+
collector << "INSERT INTO "
|
|
208
261
|
collector = visit(o.relation, collector)
|
|
209
262
|
|
|
210
263
|
if o.columns.any?
|
|
211
|
-
collector <<
|
|
264
|
+
collector << " ("
|
|
212
265
|
o.columns.each_with_index do |column, i|
|
|
213
|
-
collector <<
|
|
266
|
+
collector << ", " if i.positive?
|
|
214
267
|
collector << quote_column_name(column.name)
|
|
215
268
|
end
|
|
216
|
-
collector <<
|
|
269
|
+
collector << ")"
|
|
217
270
|
end
|
|
218
271
|
|
|
219
272
|
if o.values
|
|
220
|
-
collector <<
|
|
273
|
+
collector << " VALUES "
|
|
221
274
|
collector = visit(o.values, collector)
|
|
222
275
|
elsif o.select
|
|
223
|
-
collector <<
|
|
276
|
+
collector << " "
|
|
224
277
|
collector = visit(o.select, collector)
|
|
225
278
|
end
|
|
226
279
|
|
|
@@ -233,19 +286,19 @@ module ClickhouseRuby
|
|
|
233
286
|
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
234
287
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
235
288
|
def visit_Arel_Nodes_Values(o, collector)
|
|
236
|
-
collector <<
|
|
289
|
+
collector << "("
|
|
237
290
|
o.expressions.each_with_index do |expr, i|
|
|
238
|
-
collector <<
|
|
291
|
+
collector << ", " if i.positive?
|
|
239
292
|
case expr
|
|
240
293
|
when Arel::Nodes::SqlLiteral
|
|
241
294
|
collector << expr.to_s
|
|
242
295
|
when nil
|
|
243
|
-
collector <<
|
|
296
|
+
collector << "NULL"
|
|
244
297
|
else
|
|
245
298
|
collector = visit(expr, collector)
|
|
246
299
|
end
|
|
247
300
|
end
|
|
248
|
-
collector <<
|
|
301
|
+
collector << ")"
|
|
249
302
|
end
|
|
250
303
|
|
|
251
304
|
# Handle multiple VALUES rows for bulk insert
|
|
@@ -255,20 +308,20 @@ module ClickhouseRuby
|
|
|
255
308
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
256
309
|
def visit_Arel_Nodes_ValuesList(o, collector)
|
|
257
310
|
o.rows.each_with_index do |row, i|
|
|
258
|
-
collector <<
|
|
259
|
-
collector <<
|
|
311
|
+
collector << ", " if i.positive?
|
|
312
|
+
collector << "("
|
|
260
313
|
row.each_with_index do |value, j|
|
|
261
|
-
collector <<
|
|
314
|
+
collector << ", " if j.positive?
|
|
262
315
|
case value
|
|
263
316
|
when Arel::Nodes::SqlLiteral
|
|
264
317
|
collector << value.to_s
|
|
265
318
|
when nil
|
|
266
|
-
collector <<
|
|
319
|
+
collector << "NULL"
|
|
267
320
|
else
|
|
268
321
|
collector = visit(value, collector)
|
|
269
322
|
end
|
|
270
323
|
end
|
|
271
|
-
collector <<
|
|
324
|
+
collector << ")"
|
|
272
325
|
end
|
|
273
326
|
collector
|
|
274
327
|
end
|
|
@@ -280,19 +333,17 @@ module ClickhouseRuby
|
|
|
280
333
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
281
334
|
def visit_Arel_Nodes_Assignment(o, collector)
|
|
282
335
|
case o.left
|
|
283
|
-
when Arel::Nodes::UnqualifiedColumn
|
|
284
|
-
collector << quote_column_name(o.left.name)
|
|
285
|
-
when Arel::Attributes::Attribute
|
|
336
|
+
when Arel::Nodes::UnqualifiedColumn, Arel::Attributes::Attribute
|
|
286
337
|
collector << quote_column_name(o.left.name)
|
|
287
338
|
else
|
|
288
339
|
collector = visit(o.left, collector)
|
|
289
340
|
end
|
|
290
341
|
|
|
291
|
-
collector <<
|
|
342
|
+
collector << " = "
|
|
292
343
|
|
|
293
344
|
case o.right
|
|
294
345
|
when nil
|
|
295
|
-
collector <<
|
|
346
|
+
collector << "NULL"
|
|
296
347
|
else
|
|
297
348
|
collector = visit(o.right, collector)
|
|
298
349
|
end
|
|
@@ -307,21 +358,19 @@ module ClickhouseRuby
|
|
|
307
358
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
308
359
|
def visit_Arel_Nodes_NamedFunction(o, collector)
|
|
309
360
|
collector << o.name
|
|
310
|
-
collector <<
|
|
361
|
+
collector << "("
|
|
311
362
|
|
|
312
|
-
if o.distinct
|
|
313
|
-
collector << 'DISTINCT '
|
|
314
|
-
end
|
|
363
|
+
collector << "DISTINCT " if o.distinct
|
|
315
364
|
|
|
316
365
|
o.expressions.each_with_index do |expr, i|
|
|
317
|
-
collector <<
|
|
366
|
+
collector << ", " if i.positive?
|
|
318
367
|
collector = visit(expr, collector)
|
|
319
368
|
end
|
|
320
369
|
|
|
321
|
-
collector <<
|
|
370
|
+
collector << ")"
|
|
322
371
|
|
|
323
372
|
if o.alias
|
|
324
|
-
collector <<
|
|
373
|
+
collector << " AS "
|
|
325
374
|
collector << quote_column_name(o.alias)
|
|
326
375
|
end
|
|
327
376
|
|
|
@@ -333,8 +382,8 @@ module ClickhouseRuby
|
|
|
333
382
|
# @param o [Arel::Nodes::Distinct] the distinct node
|
|
334
383
|
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
335
384
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
336
|
-
def visit_Arel_Nodes_Distinct(
|
|
337
|
-
collector <<
|
|
385
|
+
def visit_Arel_Nodes_Distinct(_o, collector)
|
|
386
|
+
collector << "DISTINCT"
|
|
338
387
|
end
|
|
339
388
|
|
|
340
389
|
# Handle GROUP BY
|
|
@@ -352,7 +401,7 @@ module ClickhouseRuby
|
|
|
352
401
|
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
353
402
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
354
403
|
def visit_Arel_Nodes_Having(o, collector)
|
|
355
|
-
collector <<
|
|
404
|
+
collector << "HAVING "
|
|
356
405
|
visit(o.expr, collector)
|
|
357
406
|
end
|
|
358
407
|
|
|
@@ -363,7 +412,7 @@ module ClickhouseRuby
|
|
|
363
412
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
364
413
|
def visit_Arel_Nodes_Ascending(o, collector)
|
|
365
414
|
collector = visit(o.expr, collector)
|
|
366
|
-
collector <<
|
|
415
|
+
collector << " ASC"
|
|
367
416
|
end
|
|
368
417
|
|
|
369
418
|
# Handle descending order
|
|
@@ -373,7 +422,7 @@ module ClickhouseRuby
|
|
|
373
422
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
374
423
|
def visit_Arel_Nodes_Descending(o, collector)
|
|
375
424
|
collector = visit(o.expr, collector)
|
|
376
|
-
collector <<
|
|
425
|
+
collector << " DESC"
|
|
377
426
|
end
|
|
378
427
|
|
|
379
428
|
# Handle NULLS FIRST
|
|
@@ -383,7 +432,7 @@ module ClickhouseRuby
|
|
|
383
432
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
384
433
|
def visit_Arel_Nodes_NullsFirst(o, collector)
|
|
385
434
|
collector = visit(o.expr, collector)
|
|
386
|
-
collector <<
|
|
435
|
+
collector << " NULLS FIRST"
|
|
387
436
|
end
|
|
388
437
|
|
|
389
438
|
# Handle NULLS LAST
|
|
@@ -393,7 +442,7 @@ module ClickhouseRuby
|
|
|
393
442
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
394
443
|
def visit_Arel_Nodes_NullsLast(o, collector)
|
|
395
444
|
collector = visit(o.expr, collector)
|
|
396
|
-
collector <<
|
|
445
|
+
collector << " NULLS LAST"
|
|
397
446
|
end
|
|
398
447
|
|
|
399
448
|
# Handle COUNT function
|
|
@@ -402,7 +451,7 @@ module ClickhouseRuby
|
|
|
402
451
|
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
403
452
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
404
453
|
def visit_Arel_Nodes_Count(o, collector)
|
|
405
|
-
aggregate(
|
|
454
|
+
aggregate("count", o, collector)
|
|
406
455
|
end
|
|
407
456
|
|
|
408
457
|
# Handle SUM function
|
|
@@ -411,7 +460,7 @@ module ClickhouseRuby
|
|
|
411
460
|
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
412
461
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
413
462
|
def visit_Arel_Nodes_Sum(o, collector)
|
|
414
|
-
aggregate(
|
|
463
|
+
aggregate("sum", o, collector)
|
|
415
464
|
end
|
|
416
465
|
|
|
417
466
|
# Handle AVG function
|
|
@@ -420,7 +469,7 @@ module ClickhouseRuby
|
|
|
420
469
|
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
421
470
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
422
471
|
def visit_Arel_Nodes_Avg(o, collector)
|
|
423
|
-
aggregate(
|
|
472
|
+
aggregate("avg", o, collector)
|
|
424
473
|
end
|
|
425
474
|
|
|
426
475
|
# Handle MIN function
|
|
@@ -429,7 +478,7 @@ module ClickhouseRuby
|
|
|
429
478
|
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
430
479
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
431
480
|
def visit_Arel_Nodes_Min(o, collector)
|
|
432
|
-
aggregate(
|
|
481
|
+
aggregate("min", o, collector)
|
|
433
482
|
end
|
|
434
483
|
|
|
435
484
|
# Handle MAX function
|
|
@@ -438,7 +487,7 @@ module ClickhouseRuby
|
|
|
438
487
|
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
439
488
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
440
489
|
def visit_Arel_Nodes_Max(o, collector)
|
|
441
|
-
aggregate(
|
|
490
|
+
aggregate("max", o, collector)
|
|
442
491
|
end
|
|
443
492
|
|
|
444
493
|
# Helper to generate aggregate functions
|
|
@@ -449,20 +498,100 @@ module ClickhouseRuby
|
|
|
449
498
|
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
450
499
|
def aggregate(name, o, collector)
|
|
451
500
|
collector << "#{name}("
|
|
452
|
-
if o.distinct
|
|
453
|
-
collector << 'DISTINCT '
|
|
454
|
-
end
|
|
501
|
+
collector << "DISTINCT " if o.distinct
|
|
455
502
|
o.expressions.each_with_index do |expr, i|
|
|
456
|
-
collector <<
|
|
503
|
+
collector << ", " if i.positive?
|
|
457
504
|
collector = visit(expr, collector)
|
|
458
505
|
end
|
|
459
|
-
collector <<
|
|
506
|
+
collector << ")"
|
|
460
507
|
if o.alias
|
|
461
|
-
collector <<
|
|
508
|
+
collector << " AS "
|
|
462
509
|
collector << quote_column_name(o.alias)
|
|
463
510
|
end
|
|
464
511
|
collector
|
|
465
512
|
end
|
|
513
|
+
|
|
514
|
+
# Visit PREWHERE conditions
|
|
515
|
+
#
|
|
516
|
+
# @param conditions [Array] array of prewhere condition nodes
|
|
517
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
518
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
519
|
+
def visit_prewhere_conditions(conditions, collector)
|
|
520
|
+
conditions.each_with_index do |condition, i|
|
|
521
|
+
collector << " AND " if i.positive?
|
|
522
|
+
collector = visit(condition, collector)
|
|
523
|
+
end
|
|
524
|
+
collector
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
# Build SETTINGS clause for SQL generation
|
|
528
|
+
#
|
|
529
|
+
# @param settings [Hash] the settings hash
|
|
530
|
+
# @return [String] the SETTINGS clause
|
|
531
|
+
def build_settings_clause(settings)
|
|
532
|
+
pairs = settings.map do |key, value|
|
|
533
|
+
formatted = case value
|
|
534
|
+
when String then "'#{value}'"
|
|
535
|
+
when true then "1"
|
|
536
|
+
when false then "0"
|
|
537
|
+
else value.to_s
|
|
538
|
+
end
|
|
539
|
+
"#{key} = #{formatted}"
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
"SETTINGS #{pairs.join(", ")}"
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
# Visit orders and limits
|
|
546
|
+
#
|
|
547
|
+
# @param o [Arel::Nodes::SelectStatement] the select node
|
|
548
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
549
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
550
|
+
def visit_orders_and_limits(o, collector)
|
|
551
|
+
# GROUP BY
|
|
552
|
+
if o.cores[0].groups.any?
|
|
553
|
+
collector << " GROUP BY "
|
|
554
|
+
collector = inject_join(o.cores[0].groups, collector, ", ")
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
# HAVING
|
|
558
|
+
if o.cores[0].havings.any?
|
|
559
|
+
collector << " HAVING "
|
|
560
|
+
collector = inject_join(o.cores[0].havings, collector, " AND ")
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
# ORDER BY
|
|
564
|
+
if o.orders.any?
|
|
565
|
+
collector << " ORDER BY "
|
|
566
|
+
collector = inject_join(o.orders, collector, ", ")
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
# LIMIT
|
|
570
|
+
if o.limit
|
|
571
|
+
collector << " "
|
|
572
|
+
collector = visit(o.limit, collector)
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
# OFFSET
|
|
576
|
+
if o.offset
|
|
577
|
+
collector << " "
|
|
578
|
+
collector = visit(o.offset, collector)
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
collector
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
# Format a sample value for SQL generation
|
|
585
|
+
#
|
|
586
|
+
# Handles differentiation between Integer (absolute row count) and Float (fractional).
|
|
587
|
+
# Ruby's to_s preserves the distinction: Integer 1 becomes "1", Float 1.0 becomes "1.0"
|
|
588
|
+
# This matters because SAMPLE 1 means "at least 1 row" while SAMPLE 1.0 means "100% of data".
|
|
589
|
+
#
|
|
590
|
+
# @param value [Float, Integer] the sample value
|
|
591
|
+
# @return [String] the formatted sample value
|
|
592
|
+
def format_sample_value(value)
|
|
593
|
+
value.to_s
|
|
594
|
+
end
|
|
466
595
|
end
|
|
467
596
|
end
|
|
468
597
|
end
|