clickhouse-ruby 0.1.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 +7 -0
- data/CHANGELOG.md +80 -0
- data/LICENSE +21 -0
- data/README.md +251 -0
- data/lib/clickhouse_ruby/active_record/arel_visitor.rb +468 -0
- data/lib/clickhouse_ruby/active_record/connection_adapter.rb +723 -0
- data/lib/clickhouse_ruby/active_record/railtie.rb +192 -0
- data/lib/clickhouse_ruby/active_record/schema_statements.rb +693 -0
- data/lib/clickhouse_ruby/active_record.rb +121 -0
- data/lib/clickhouse_ruby/client.rb +471 -0
- data/lib/clickhouse_ruby/configuration.rb +145 -0
- data/lib/clickhouse_ruby/connection.rb +328 -0
- data/lib/clickhouse_ruby/connection_pool.rb +301 -0
- data/lib/clickhouse_ruby/errors.rb +144 -0
- data/lib/clickhouse_ruby/result.rb +189 -0
- data/lib/clickhouse_ruby/types/array.rb +183 -0
- data/lib/clickhouse_ruby/types/base.rb +77 -0
- data/lib/clickhouse_ruby/types/boolean.rb +68 -0
- data/lib/clickhouse_ruby/types/date_time.rb +163 -0
- data/lib/clickhouse_ruby/types/float.rb +115 -0
- data/lib/clickhouse_ruby/types/integer.rb +157 -0
- data/lib/clickhouse_ruby/types/low_cardinality.rb +58 -0
- data/lib/clickhouse_ruby/types/map.rb +249 -0
- data/lib/clickhouse_ruby/types/nullable.rb +73 -0
- data/lib/clickhouse_ruby/types/parser.rb +244 -0
- data/lib/clickhouse_ruby/types/registry.rb +148 -0
- data/lib/clickhouse_ruby/types/string.rb +83 -0
- data/lib/clickhouse_ruby/types/tuple.rb +206 -0
- data/lib/clickhouse_ruby/types/uuid.rb +84 -0
- data/lib/clickhouse_ruby/types.rb +69 -0
- data/lib/clickhouse_ruby/version.rb +5 -0
- data/lib/clickhouse_ruby.rb +101 -0
- metadata +150 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'arel/visitors/to_sql'
|
|
4
|
+
|
|
5
|
+
module ClickhouseRuby
|
|
6
|
+
module ActiveRecord
|
|
7
|
+
# Custom Arel visitor for generating ClickHouse-specific SQL
|
|
8
|
+
#
|
|
9
|
+
# ClickHouse has unique requirements for certain SQL operations:
|
|
10
|
+
# - DELETE: Uses ALTER TABLE ... DELETE WHERE syntax
|
|
11
|
+
# - UPDATE: Uses ALTER TABLE ... UPDATE ... WHERE syntax
|
|
12
|
+
# - LIMIT: Must come after ORDER BY
|
|
13
|
+
# - No OFFSET without LIMIT (use LIMIT n, m syntax)
|
|
14
|
+
#
|
|
15
|
+
# @example DELETE conversion
|
|
16
|
+
# # Standard SQL: DELETE FROM events WHERE id = 1
|
|
17
|
+
# # ClickHouse: ALTER TABLE events DELETE WHERE id = 1
|
|
18
|
+
#
|
|
19
|
+
# @example UPDATE conversion
|
|
20
|
+
# # Standard SQL: UPDATE events SET status = 'done' WHERE id = 1
|
|
21
|
+
# # ClickHouse: ALTER TABLE events UPDATE status = 'done' WHERE id = 1
|
|
22
|
+
#
|
|
23
|
+
class ArelVisitor < ::Arel::Visitors::ToSql
|
|
24
|
+
# Initialize the visitor
|
|
25
|
+
#
|
|
26
|
+
# @param connection [ConnectionAdapter] the database connection
|
|
27
|
+
def initialize(connection)
|
|
28
|
+
super(connection)
|
|
29
|
+
@connection = connection
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
# Visit a DELETE statement
|
|
35
|
+
# Converts to ClickHouse ALTER TABLE ... DELETE WHERE syntax
|
|
36
|
+
#
|
|
37
|
+
# @param o [Arel::Nodes::DeleteStatement] the delete node
|
|
38
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
39
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
40
|
+
def visit_Arel_Nodes_DeleteStatement(o, collector)
|
|
41
|
+
# Get table name
|
|
42
|
+
table = o.relation
|
|
43
|
+
|
|
44
|
+
# Build ClickHouse DELETE syntax
|
|
45
|
+
collector << 'ALTER TABLE '
|
|
46
|
+
collector = visit(table, collector)
|
|
47
|
+
collector << ' DELETE'
|
|
48
|
+
|
|
49
|
+
# Add WHERE clause (required for ClickHouse DELETE)
|
|
50
|
+
if o.wheres.any?
|
|
51
|
+
collector << ' WHERE '
|
|
52
|
+
collector = inject_join(o.wheres, collector, ' AND ')
|
|
53
|
+
else
|
|
54
|
+
# ClickHouse requires WHERE clause for DELETE
|
|
55
|
+
# Use 1=1 to delete all rows
|
|
56
|
+
collector << ' WHERE 1=1'
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
collector
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Visit an UPDATE statement
|
|
63
|
+
# Converts to ClickHouse ALTER TABLE ... UPDATE ... WHERE syntax
|
|
64
|
+
#
|
|
65
|
+
# @param o [Arel::Nodes::UpdateStatement] the update node
|
|
66
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
67
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
68
|
+
def visit_Arel_Nodes_UpdateStatement(o, collector)
|
|
69
|
+
# Get table name
|
|
70
|
+
table = o.relation
|
|
71
|
+
|
|
72
|
+
# Build ClickHouse UPDATE syntax
|
|
73
|
+
collector << 'ALTER TABLE '
|
|
74
|
+
collector = visit(table, collector)
|
|
75
|
+
collector << ' UPDATE '
|
|
76
|
+
|
|
77
|
+
# Add SET assignments
|
|
78
|
+
unless o.values.empty?
|
|
79
|
+
collector = inject_join(o.values, collector, ', ')
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Add WHERE clause (required for ClickHouse UPDATE)
|
|
83
|
+
if o.wheres.any?
|
|
84
|
+
collector << ' WHERE '
|
|
85
|
+
collector = inject_join(o.wheres, collector, ' AND ')
|
|
86
|
+
else
|
|
87
|
+
# ClickHouse requires WHERE clause for UPDATE
|
|
88
|
+
collector << ' WHERE 1=1'
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
collector
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Visit a LIMIT node
|
|
95
|
+
# ClickHouse supports LIMIT with optional OFFSET
|
|
96
|
+
#
|
|
97
|
+
# @param o [Arel::Nodes::Limit] the limit node
|
|
98
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
99
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
100
|
+
def visit_Arel_Nodes_Limit(o, collector)
|
|
101
|
+
collector << 'LIMIT '
|
|
102
|
+
visit(o.expr, collector)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Visit an OFFSET node
|
|
106
|
+
# ClickHouse uses OFFSET after LIMIT (LIMIT n OFFSET m)
|
|
107
|
+
#
|
|
108
|
+
# @param o [Arel::Nodes::Offset] the offset node
|
|
109
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
110
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
111
|
+
def visit_Arel_Nodes_Offset(o, collector)
|
|
112
|
+
collector << 'OFFSET '
|
|
113
|
+
visit(o.expr, collector)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Visit a SelectStatement
|
|
117
|
+
# Ensures proper ordering of clauses for ClickHouse
|
|
118
|
+
#
|
|
119
|
+
# @param o [Arel::Nodes::SelectStatement] the select node
|
|
120
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
121
|
+
# @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
|
+
|
|
127
|
+
# Visit a table alias
|
|
128
|
+
# ClickHouse uses AS keyword for table aliases
|
|
129
|
+
#
|
|
130
|
+
# @param o [Arel::Nodes::TableAlias] the table alias node
|
|
131
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
132
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
133
|
+
def visit_Arel_Nodes_TableAlias(o, collector)
|
|
134
|
+
collector = visit(o.relation, collector)
|
|
135
|
+
collector << ' AS '
|
|
136
|
+
collector << quote_table_name(o.name)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Quote a table name using the connection's quoting
|
|
140
|
+
#
|
|
141
|
+
# @param name [String] the table name
|
|
142
|
+
# @return [String] the quoted table name
|
|
143
|
+
def quote_table_name(name)
|
|
144
|
+
@connection.quote_table_name(name)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Quote a column name using the connection's quoting
|
|
148
|
+
#
|
|
149
|
+
# @param name [String] the column name
|
|
150
|
+
# @return [String] the quoted column name
|
|
151
|
+
def quote_column_name(name)
|
|
152
|
+
@connection.quote_column_name(name)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Visit a True node
|
|
156
|
+
#
|
|
157
|
+
# @param o [Arel::Nodes::True] the true node
|
|
158
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
159
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
160
|
+
def visit_Arel_Nodes_True(o, collector)
|
|
161
|
+
collector << '1'
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Visit a False node
|
|
165
|
+
#
|
|
166
|
+
# @param o [Arel::Nodes::False] the false node
|
|
167
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
168
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
169
|
+
def visit_Arel_Nodes_False(o, collector)
|
|
170
|
+
collector << '0'
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Visit a CASE statement
|
|
174
|
+
#
|
|
175
|
+
# @param o [Arel::Nodes::Case] the case node
|
|
176
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
177
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
178
|
+
def visit_Arel_Nodes_Case(o, collector)
|
|
179
|
+
collector << 'CASE '
|
|
180
|
+
|
|
181
|
+
if o.case
|
|
182
|
+
visit(o.case, collector)
|
|
183
|
+
collector << ' '
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
o.conditions.each do |condition|
|
|
187
|
+
visit(condition, collector)
|
|
188
|
+
collector << ' '
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
if o.default
|
|
192
|
+
collector << 'ELSE '
|
|
193
|
+
visit(o.default, collector)
|
|
194
|
+
collector << ' '
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
collector << 'END'
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Handle INSERT statements
|
|
201
|
+
# ClickHouse uses standard INSERT syntax but with some differences
|
|
202
|
+
#
|
|
203
|
+
# @param o [Arel::Nodes::InsertStatement] the insert node
|
|
204
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
205
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
206
|
+
def visit_Arel_Nodes_InsertStatement(o, collector)
|
|
207
|
+
collector << 'INSERT INTO '
|
|
208
|
+
collector = visit(o.relation, collector)
|
|
209
|
+
|
|
210
|
+
if o.columns.any?
|
|
211
|
+
collector << ' ('
|
|
212
|
+
o.columns.each_with_index do |column, i|
|
|
213
|
+
collector << ', ' if i > 0
|
|
214
|
+
collector << quote_column_name(column.name)
|
|
215
|
+
end
|
|
216
|
+
collector << ')'
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
if o.values
|
|
220
|
+
collector << ' VALUES '
|
|
221
|
+
collector = visit(o.values, collector)
|
|
222
|
+
elsif o.select
|
|
223
|
+
collector << ' '
|
|
224
|
+
collector = visit(o.select, collector)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
collector
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Handle VALUES list
|
|
231
|
+
#
|
|
232
|
+
# @param o [Arel::Nodes::Values] the values node
|
|
233
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
234
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
235
|
+
def visit_Arel_Nodes_Values(o, collector)
|
|
236
|
+
collector << '('
|
|
237
|
+
o.expressions.each_with_index do |expr, i|
|
|
238
|
+
collector << ', ' if i > 0
|
|
239
|
+
case expr
|
|
240
|
+
when Arel::Nodes::SqlLiteral
|
|
241
|
+
collector << expr.to_s
|
|
242
|
+
when nil
|
|
243
|
+
collector << 'NULL'
|
|
244
|
+
else
|
|
245
|
+
collector = visit(expr, collector)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
collector << ')'
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Handle multiple VALUES rows for bulk insert
|
|
252
|
+
#
|
|
253
|
+
# @param o [Arel::Nodes::ValuesList] the values list node
|
|
254
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
255
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
256
|
+
def visit_Arel_Nodes_ValuesList(o, collector)
|
|
257
|
+
o.rows.each_with_index do |row, i|
|
|
258
|
+
collector << ', ' if i > 0
|
|
259
|
+
collector << '('
|
|
260
|
+
row.each_with_index do |value, j|
|
|
261
|
+
collector << ', ' if j > 0
|
|
262
|
+
case value
|
|
263
|
+
when Arel::Nodes::SqlLiteral
|
|
264
|
+
collector << value.to_s
|
|
265
|
+
when nil
|
|
266
|
+
collector << 'NULL'
|
|
267
|
+
else
|
|
268
|
+
collector = visit(value, collector)
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
collector << ')'
|
|
272
|
+
end
|
|
273
|
+
collector
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Handle assignment for UPDATE statements
|
|
277
|
+
#
|
|
278
|
+
# @param o [Arel::Nodes::Assignment] the assignment node
|
|
279
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
280
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
281
|
+
def visit_Arel_Nodes_Assignment(o, collector)
|
|
282
|
+
case o.left
|
|
283
|
+
when Arel::Nodes::UnqualifiedColumn
|
|
284
|
+
collector << quote_column_name(o.left.name)
|
|
285
|
+
when Arel::Attributes::Attribute
|
|
286
|
+
collector << quote_column_name(o.left.name)
|
|
287
|
+
else
|
|
288
|
+
collector = visit(o.left, collector)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
collector << ' = '
|
|
292
|
+
|
|
293
|
+
case o.right
|
|
294
|
+
when nil
|
|
295
|
+
collector << 'NULL'
|
|
296
|
+
else
|
|
297
|
+
collector = visit(o.right, collector)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
collector
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Handle named functions
|
|
304
|
+
#
|
|
305
|
+
# @param o [Arel::Nodes::NamedFunction] the function node
|
|
306
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
307
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
308
|
+
def visit_Arel_Nodes_NamedFunction(o, collector)
|
|
309
|
+
collector << o.name
|
|
310
|
+
collector << '('
|
|
311
|
+
|
|
312
|
+
if o.distinct
|
|
313
|
+
collector << 'DISTINCT '
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
o.expressions.each_with_index do |expr, i|
|
|
317
|
+
collector << ', ' if i > 0
|
|
318
|
+
collector = visit(expr, collector)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
collector << ')'
|
|
322
|
+
|
|
323
|
+
if o.alias
|
|
324
|
+
collector << ' AS '
|
|
325
|
+
collector << quote_column_name(o.alias)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
collector
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Handle DISTINCT
|
|
332
|
+
#
|
|
333
|
+
# @param o [Arel::Nodes::Distinct] the distinct node
|
|
334
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
335
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
336
|
+
def visit_Arel_Nodes_Distinct(o, collector)
|
|
337
|
+
collector << 'DISTINCT'
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Handle GROUP BY
|
|
341
|
+
#
|
|
342
|
+
# @param o [Arel::Nodes::Group] the group node
|
|
343
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
344
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
345
|
+
def visit_Arel_Nodes_Group(o, collector)
|
|
346
|
+
visit(o.expr, collector)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Handle HAVING
|
|
350
|
+
#
|
|
351
|
+
# @param o [Arel::Nodes::Having] the having node
|
|
352
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
353
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
354
|
+
def visit_Arel_Nodes_Having(o, collector)
|
|
355
|
+
collector << 'HAVING '
|
|
356
|
+
visit(o.expr, collector)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# Handle ordering (ASC/DESC)
|
|
360
|
+
#
|
|
361
|
+
# @param o [Arel::Nodes::Ordering] the ordering node
|
|
362
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
363
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
364
|
+
def visit_Arel_Nodes_Ascending(o, collector)
|
|
365
|
+
collector = visit(o.expr, collector)
|
|
366
|
+
collector << ' ASC'
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# Handle descending order
|
|
370
|
+
#
|
|
371
|
+
# @param o [Arel::Nodes::Descending] the descending node
|
|
372
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
373
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
374
|
+
def visit_Arel_Nodes_Descending(o, collector)
|
|
375
|
+
collector = visit(o.expr, collector)
|
|
376
|
+
collector << ' DESC'
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# Handle NULLS FIRST
|
|
380
|
+
#
|
|
381
|
+
# @param o [Arel::Nodes::NullsFirst] the nulls first node
|
|
382
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
383
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
384
|
+
def visit_Arel_Nodes_NullsFirst(o, collector)
|
|
385
|
+
collector = visit(o.expr, collector)
|
|
386
|
+
collector << ' NULLS FIRST'
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# Handle NULLS LAST
|
|
390
|
+
#
|
|
391
|
+
# @param o [Arel::Nodes::NullsLast] the nulls last node
|
|
392
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
393
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
394
|
+
def visit_Arel_Nodes_NullsLast(o, collector)
|
|
395
|
+
collector = visit(o.expr, collector)
|
|
396
|
+
collector << ' NULLS LAST'
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Handle COUNT function
|
|
400
|
+
#
|
|
401
|
+
# @param o [Arel::Nodes::Count] the count node
|
|
402
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
403
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
404
|
+
def visit_Arel_Nodes_Count(o, collector)
|
|
405
|
+
aggregate('count', o, collector)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Handle SUM function
|
|
409
|
+
#
|
|
410
|
+
# @param o [Arel::Nodes::Sum] the sum node
|
|
411
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
412
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
413
|
+
def visit_Arel_Nodes_Sum(o, collector)
|
|
414
|
+
aggregate('sum', o, collector)
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Handle AVG function
|
|
418
|
+
#
|
|
419
|
+
# @param o [Arel::Nodes::Avg] the avg node
|
|
420
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
421
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
422
|
+
def visit_Arel_Nodes_Avg(o, collector)
|
|
423
|
+
aggregate('avg', o, collector)
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# Handle MIN function
|
|
427
|
+
#
|
|
428
|
+
# @param o [Arel::Nodes::Min] the min node
|
|
429
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
430
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
431
|
+
def visit_Arel_Nodes_Min(o, collector)
|
|
432
|
+
aggregate('min', o, collector)
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# Handle MAX function
|
|
436
|
+
#
|
|
437
|
+
# @param o [Arel::Nodes::Max] the max node
|
|
438
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
439
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
440
|
+
def visit_Arel_Nodes_Max(o, collector)
|
|
441
|
+
aggregate('max', o, collector)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
# Helper to generate aggregate functions
|
|
445
|
+
#
|
|
446
|
+
# @param name [String] function name
|
|
447
|
+
# @param o [Object] the node
|
|
448
|
+
# @param collector [Arel::Collectors::SQLString] SQL collector
|
|
449
|
+
# @return [Arel::Collectors::SQLString] the collector with SQL
|
|
450
|
+
def aggregate(name, o, collector)
|
|
451
|
+
collector << "#{name}("
|
|
452
|
+
if o.distinct
|
|
453
|
+
collector << 'DISTINCT '
|
|
454
|
+
end
|
|
455
|
+
o.expressions.each_with_index do |expr, i|
|
|
456
|
+
collector << ', ' if i > 0
|
|
457
|
+
collector = visit(expr, collector)
|
|
458
|
+
end
|
|
459
|
+
collector << ')'
|
|
460
|
+
if o.alias
|
|
461
|
+
collector << ' AS '
|
|
462
|
+
collector << quote_column_name(o.alias)
|
|
463
|
+
end
|
|
464
|
+
collector
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
end
|