click_house-client 0.11.0 → 0.12.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/README.md +64 -7
- data/lib/click_house/client/arel_visitor.rb +5 -0
- data/lib/click_house/client/configuration.rb +18 -1
- data/lib/click_house/client/formatter.rb +11 -4
- data/lib/click_house/client/query.rb +3 -3
- data/lib/click_house/client/query_builder.rb +76 -4
- data/lib/click_house/client/query_like.rb +1 -1
- data/lib/click_house/client/to_redacted_sql_visitor.rb +9 -16
- data/lib/click_house/client/version.rb +1 -1
- data/lib/click_house/client.rb +1 -2
- metadata +2 -3
- data/lib/click_house/client/bind_index_manager.rb +0 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5513d15d8bf17295cf98d021b30650f46d0101a6d828b2620499f87a949f2e3d
|
|
4
|
+
data.tar.gz: 27fd28cc07d8b67d8de72031de4e3c7e343cef51a3230afd96ba0adf3d24c252
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b3d7bf7328dd7ab42b004e3e35e58c35b4869267e4a2e701f7d29f963c2c445c3d63f0bbd5e45db679cc4d2fda6880ef367aff18e0a04b1a030f54ede2317ead
|
|
7
|
+
data.tar.gz: 40125fc9d75f13be2f1230ea872b72c6d01e797384674064d3fb42c613d1792c827e58d6ccfcd894b783ff4d89b838c04e9f50b2e297f4b085bba5c33b95585e
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ClickHouse::Client
|
|
2
2
|
|
|
3
|
-
This Gem provides a simple way to query ClickHouse databases using the HTTP interface.
|
|
3
|
+
This Gem provides a simple way to query ClickHouse databases using the HTTP interface.
|
|
4
4
|
|
|
5
5
|
## Example usage
|
|
6
6
|
|
|
@@ -61,6 +61,21 @@ puts ClickHouse::Client.execute('CREATE TABLE IF NOT EXISTS t1 (id Int64) ENGINE
|
|
|
61
61
|
puts ClickHouse::Client.execute('DROP TABLE IF EXISTS t1', :main)
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
## Custom typecasters
|
|
65
|
+
|
|
66
|
+
Values returned by `ClickHouse::Client.select` are typecast based on the column type using built-in typecasters (integers, floats, dates, datetimes, intervals). You can register custom typecasters which take precedence over the built-in ones:
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
ClickHouse::Client.configure do |config|
|
|
70
|
+
# Parse the value of `*_json` String columns as JSON
|
|
71
|
+
config.register_typecaster('String', ->(meta, value) do
|
|
72
|
+
meta['name'].match?(/\w+_json\z/) ? JSON.parse(value) : value
|
|
73
|
+
end)
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The first argument is the column type: either a `String` for an exact match or a `Regexp` (for example `/\AU?Int\d+\z/`). Wrapper types such as `Nullable(...)`, `LowCardinality(...)` and `SimpleAggregateFunction(...)` are unwrapped before matching. The typecaster receives the column metadata Hash (for example `{ 'name' => 'title', 'type' => 'String' }`) and the raw value. It should handle its own edge cases (for example malformed JSON). `NULL` values are returned as `nil` without invoking any typecaster.
|
|
78
|
+
|
|
64
79
|
## ClickHouse::Client::QueryBuilder
|
|
65
80
|
|
|
66
81
|
The QueryBuilder provides an ActiveRecord-like interface for constructing ClickHouse queries programmatically. While similar to ActiveRecord's query interface, it has been tailored specifically for ClickHouse's SQL dialect and features.
|
|
@@ -182,6 +197,24 @@ query
|
|
|
182
197
|
# => "SELECT * FROM `users` WHERE `users`.`active` = 'true' GROUP BY `users`.`department` HAVING `users`.`avg_salary` > 50000"
|
|
183
198
|
```
|
|
184
199
|
|
|
200
|
+
### Reversing Order
|
|
201
|
+
|
|
202
|
+
The `reverse_order` method inverts the direction of all `ORDER BY` clauses (`ASC` becomes `DESC` and vice versa).
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
query.order(:name, :desc).order(:id).reverse_order.to_sql
|
|
206
|
+
# => "SELECT * FROM `users` ORDER BY `users`.`name` ASC, `users`.`id` DESC"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Empty Result Set
|
|
210
|
+
|
|
211
|
+
Use `.none` to get a query that yields no rows. It appends an always-false `1 = 0` condition to the `WHERE` clause and stays chainable with other query builder methods:
|
|
212
|
+
|
|
213
|
+
```ruby
|
|
214
|
+
query.none.where(active: true).to_sql
|
|
215
|
+
# => "SELECT * FROM `users` WHERE 1 = 0 AND `users`.`active` = 'true'"
|
|
216
|
+
```
|
|
217
|
+
|
|
185
218
|
### FINAL Modifier
|
|
186
219
|
|
|
187
220
|
ClickHouse's `FINAL` modifier forces merging of rows during query time for tables in the MergeTree family. Apply it via `.final`:
|
|
@@ -260,6 +293,30 @@ query_builder.with(inner.as_cte(:a)).with(inner.as_cte(:b)).to_sql
|
|
|
260
293
|
(build it via `#as_cte`), and raises if the same CTE name is declared more than
|
|
261
294
|
once.
|
|
262
295
|
|
|
296
|
+
### UNION
|
|
297
|
+
|
|
298
|
+
Use `#union` to combine the result sets of two or more queries. The combined
|
|
299
|
+
queries are wrapped as an aliased subquery, so further chaining (`where`,
|
|
300
|
+
`order`, `limit`, ...) applies to the outer `SELECT`.
|
|
301
|
+
|
|
302
|
+
```ruby
|
|
303
|
+
query1 = ClickHouse::Client::QueryBuilder.new('users_2023').select(:id)
|
|
304
|
+
query2 = ClickHouse::Client::QueryBuilder.new('users_2024').select(:id)
|
|
305
|
+
|
|
306
|
+
query1.union(query2).to_sql
|
|
307
|
+
# => "SELECT * FROM ( SELECT `users_2023`.`id` FROM `users_2023` UNION DISTINCT SELECT `users_2024`.`id` FROM `users_2024` ) `union_subquery`"
|
|
308
|
+
|
|
309
|
+
# UNION ALL with a custom subquery alias, chaining on the union result
|
|
310
|
+
query1.union(query2, type: :all, alias_name: 'users').where(id: [1, 2]).to_sql
|
|
311
|
+
# => "SELECT * FROM ( ... UNION ALL ... ) `users` WHERE `users`.`id` IN (1, 2)"
|
|
312
|
+
|
|
313
|
+
# More than two queries can be combined in a single call
|
|
314
|
+
query1.union(query2, query3, type: :all)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
`#union` returns a new `QueryBuilder` (immutable) and raises if given anything
|
|
318
|
+
other than `QueryBuilder` instances or an unknown `type`.
|
|
319
|
+
|
|
263
320
|
### Complete Example
|
|
264
321
|
|
|
265
322
|
Here's a comprehensive example combining multiple QueryBuilder features:
|
|
@@ -287,12 +344,12 @@ result = ClickHouse::Client::QueryBuilder.new('users')
|
|
|
287
344
|
.limit(10)
|
|
288
345
|
|
|
289
346
|
puts result.to_sql
|
|
290
|
-
"SELECT `users`.`department`, COUNT(*) AS user_count FROM `users` WHERE `users`.`active` = 'true'
|
|
291
|
-
AND `users`.`department` IN ('Sales', 'Marketing', 'Engineering')
|
|
292
|
-
AND `users`.`id` IN (SELECT `orders`.`user_id` FROM `orders` WHERE `orders`.`status` = 'completed'
|
|
293
|
-
AND `users`.`created_at` > '2025-08-12')
|
|
294
|
-
AND `users`.`email` ILIKE '%@company.com'
|
|
295
|
-
GROUP BY department HAVING COUNT(*) AS user_count > 5
|
|
347
|
+
"SELECT `users`.`department`, COUNT(*) AS user_count FROM `users` WHERE `users`.`active` = 'true'
|
|
348
|
+
AND `users`.`department` IN ('Sales', 'Marketing', 'Engineering')
|
|
349
|
+
AND `users`.`id` IN (SELECT `orders`.`user_id` FROM `orders` WHERE `orders`.`status` = 'completed'
|
|
350
|
+
AND `users`.`created_at` > '2025-08-12')
|
|
351
|
+
AND `users`.`email` ILIKE '%@company.com'
|
|
352
|
+
GROUP BY department HAVING COUNT(*) AS user_count > 5
|
|
296
353
|
ORDER BY user_count DESC LIMIT 10"
|
|
297
354
|
```
|
|
298
355
|
|
|
@@ -30,6 +30,11 @@ module ClickHouse
|
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
# ClickHouse rejects a bare `UNION`; the mode must be explicit.
|
|
34
|
+
def visit_Arel_Nodes_Union(object, collector)
|
|
35
|
+
infix_value_with_paren(object, collector, " UNION DISTINCT ")
|
|
36
|
+
end
|
|
37
|
+
|
|
33
38
|
def visit_ClickHouse_Client_ArelExtensions_Nodes_Final(object, collector)
|
|
34
39
|
collector = visit(object.expr, collector)
|
|
35
40
|
collector << " FINAL"
|
|
@@ -21,6 +21,12 @@ module ClickHouse
|
|
|
21
21
|
# *logger*: object for receiving logger commands. Default `$stdout`
|
|
22
22
|
# *log_proc*: any output (e.g. structure) to wrap around the query for every statement
|
|
23
23
|
#
|
|
24
|
+
# *register_typecaster* (method): registers a custom typecaster for a column type,
|
|
25
|
+
# taking precedence over the built-in typecasters. Arguments:
|
|
26
|
+
# - type: column type as a String (exact match) or a Regexp
|
|
27
|
+
# - typecaster: a callable object receiving the column metadata Hash
|
|
28
|
+
# (e.g. { 'name' => 'title', 'type' => 'String' }) and the raw value
|
|
29
|
+
#
|
|
24
30
|
# Example:
|
|
25
31
|
#
|
|
26
32
|
# ClickHouse::Client.configure do |c|
|
|
@@ -51,12 +57,17 @@ module ClickHouse
|
|
|
51
57
|
# end
|
|
52
58
|
#
|
|
53
59
|
# c.json_parser = JSON
|
|
60
|
+
#
|
|
61
|
+
# c.register_typecaster('String', ->(meta, value) do
|
|
62
|
+
# meta['name'].match?(/\w+_json\z/) ? JSON.parse(value) : value
|
|
63
|
+
# end)
|
|
54
64
|
# end
|
|
55
65
|
attr_accessor :http_post_proc, :json_parser, :logger, :log_proc
|
|
56
|
-
attr_reader :databases
|
|
66
|
+
attr_reader :databases, :typecasters
|
|
57
67
|
|
|
58
68
|
def initialize
|
|
59
69
|
@databases = {}
|
|
70
|
+
@typecasters = {}
|
|
60
71
|
@http_post_proc = nil
|
|
61
72
|
@json_parser = JSON
|
|
62
73
|
@logger = ::Logger.new($stdout)
|
|
@@ -69,6 +80,12 @@ module ClickHouse
|
|
|
69
80
|
@databases[name] = Database.new(**args)
|
|
70
81
|
end
|
|
71
82
|
|
|
83
|
+
def register_typecaster(type, typecaster)
|
|
84
|
+
raise ConfigurationError, "The typecaster for '#{type}' is already registered" if @typecasters.key?(type)
|
|
85
|
+
|
|
86
|
+
@typecasters[type] = typecaster
|
|
87
|
+
end
|
|
88
|
+
|
|
72
89
|
def validate!
|
|
73
90
|
raise ConfigurationError, "The 'http_post_proc' option is not configured" unless @http_post_proc
|
|
74
91
|
raise ConfigurationError, "The 'json_parser' option is not configured" unless @json_parser
|
|
@@ -19,9 +19,9 @@ module ClickHouse
|
|
|
19
19
|
"IntervalMillisecond" => ->(value) { ActiveSupport::Duration.build(value.to_i / 1000.0) }
|
|
20
20
|
}.freeze
|
|
21
21
|
|
|
22
|
-
def self.format(result)
|
|
22
|
+
def self.format(result, custom_typecasters = {})
|
|
23
23
|
column_typecasters = result['meta'].each_with_object({}) do |column, hash|
|
|
24
|
-
hash[column['name']] =
|
|
24
|
+
hash[column['name']] = build_typecaster(column, custom_typecasters)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
result['data'].map do |row|
|
|
@@ -31,7 +31,14 @@ module ClickHouse
|
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
def self.
|
|
34
|
+
def self.build_typecaster(column, custom_typecasters)
|
|
35
|
+
custom_caster = get_typecaster(column['type'], custom_typecasters)
|
|
36
|
+
return ->(value) { custom_caster.call(column, value) } if custom_caster
|
|
37
|
+
|
|
38
|
+
get_typecaster(column['type']) || DEFAULT
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.get_typecaster(column_type, typecasters = TYPE_CASTERS)
|
|
35
42
|
return unless column_type
|
|
36
43
|
|
|
37
44
|
inner_type = column_type
|
|
@@ -39,7 +46,7 @@ module ClickHouse
|
|
|
39
46
|
.sub(/\ALowCardinality\((.+)\)\z/, '\1') # e.g. LowCardinality(String)
|
|
40
47
|
.sub(/\ASimpleAggregateFunction\(.+,\s*(.+)\)\z/, '\1') # e.g. SimpleAggregateFunction(sum, UInt64)
|
|
41
48
|
|
|
42
|
-
|
|
49
|
+
typecasters.each do |key, caster|
|
|
43
50
|
return caster if key.is_a?(String) ? key == inner_type : key.match?(inner_type)
|
|
44
51
|
end
|
|
45
52
|
|
|
@@ -51,14 +51,14 @@ module ClickHouse
|
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
def to_redacted_sql
|
|
54
|
+
def to_redacted_sql
|
|
55
55
|
raw_query.gsub(PLACEHOLDER_REGEX) do |placeholder_in_query|
|
|
56
56
|
value = placeholder_value(placeholder_in_query)
|
|
57
57
|
|
|
58
58
|
if value.is_a?(QueryLike)
|
|
59
|
-
value.to_redacted_sql
|
|
59
|
+
value.to_redacted_sql
|
|
60
60
|
else
|
|
61
|
-
|
|
61
|
+
'?'
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
end
|
|
@@ -33,8 +33,11 @@ module ClickHouse
|
|
|
33
33
|
delegate :[], to: :table
|
|
34
34
|
|
|
35
35
|
def initialize(table, alias_name = nil)
|
|
36
|
-
@table =
|
|
36
|
+
@table = case table
|
|
37
|
+
when self.class # subquery
|
|
37
38
|
Arel::Nodes::TableAlias.new(table.to_arel, alias_name)
|
|
39
|
+
when Arel::Nodes::TableAlias
|
|
40
|
+
table
|
|
38
41
|
else
|
|
39
42
|
Arel::Table.new(table)
|
|
40
43
|
end
|
|
@@ -144,6 +147,30 @@ module ClickHouse
|
|
|
144
147
|
end
|
|
145
148
|
end
|
|
146
149
|
|
|
150
|
+
# Inverts the direction of all ORDER BY clauses: ASC becomes DESC and
|
|
151
|
+
# vice versa.
|
|
152
|
+
#
|
|
153
|
+
# @example
|
|
154
|
+
# query.order(:id).reverse_order.to_sql
|
|
155
|
+
# # => "SELECT * FROM `test_table` ORDER BY `test_table`.`id` DESC"
|
|
156
|
+
#
|
|
157
|
+
# @raise [ArgumentError] if any ordering node has no explicit direction
|
|
158
|
+
# @return [ClickHouse::Client::QueryBuilder] New instance of query builder.
|
|
159
|
+
def reverse_order
|
|
160
|
+
clone.tap do |new_instance|
|
|
161
|
+
new_instance.manager.ast.orders = new_instance.manager.ast.orders.map do |order|
|
|
162
|
+
case order
|
|
163
|
+
when Arel::Nodes::Ascending
|
|
164
|
+
Arel::Nodes::Descending.new(order.expr)
|
|
165
|
+
when Arel::Nodes::Descending
|
|
166
|
+
Arel::Nodes::Ascending.new(order.expr)
|
|
167
|
+
else
|
|
168
|
+
raise ArgumentError, "Cannot reverse ordering node without explicit direction: #{order.class.name}"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
147
174
|
def group(*columns)
|
|
148
175
|
clone.tap do |new_instance|
|
|
149
176
|
new_instance.manager.group(*columns)
|
|
@@ -182,6 +209,21 @@ module ClickHouse
|
|
|
182
209
|
end
|
|
183
210
|
end
|
|
184
211
|
|
|
212
|
+
# Returns a query that yields no rows.
|
|
213
|
+
#
|
|
214
|
+
# @example
|
|
215
|
+
# query.none.to_sql
|
|
216
|
+
# # => "SELECT * FROM `test_table` WHERE 1 = 0"
|
|
217
|
+
#
|
|
218
|
+
# @return [ClickHouse::Client::QueryBuilder] New instance of query builder.
|
|
219
|
+
def none
|
|
220
|
+
clone.tap do |new_instance|
|
|
221
|
+
new_instance.manager.where(
|
|
222
|
+
Arel.sql('1 = 0')
|
|
223
|
+
)
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
185
227
|
def from(subquery, alias_name)
|
|
186
228
|
clone.tap do |new_instance|
|
|
187
229
|
new_from = if subquery.is_a?(self.class)
|
|
@@ -238,6 +280,31 @@ module ClickHouse
|
|
|
238
280
|
end
|
|
239
281
|
end
|
|
240
282
|
|
|
283
|
+
# Combines this query with one or more other queries using UNION and
|
|
284
|
+
# returns a new builder that selects from the combined result as an
|
|
285
|
+
# aliased subquery, so further chaining (#where, #order, #limit, ...)
|
|
286
|
+
# applies to the outer SELECT.
|
|
287
|
+
#
|
|
288
|
+
# @param others [Array<ClickHouse::Client::QueryBuilder>] queries to combine with
|
|
289
|
+
# @param type [Symbol] :distinct or :all
|
|
290
|
+
# @param alias_name [String, Symbol] alias for the union subquery
|
|
291
|
+
# @return [ClickHouse::Client::QueryBuilder] New instance of query builder.
|
|
292
|
+
def union(*others, type: :distinct, alias_name: 'union_subquery')
|
|
293
|
+
validate_union_type!(type)
|
|
294
|
+
raise ArgumentError, 'at least one query to union with is required' if others.empty?
|
|
295
|
+
|
|
296
|
+
others.each do |other|
|
|
297
|
+
raise ArgumentError, "expected #{self.class}, got #{other.class}" unless other.is_a?(self.class)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
node_class = type == :all ? Arel::Nodes::UnionAll : Arel::Nodes::Union
|
|
301
|
+
union_node = others.reduce(to_arel.clone.ast) do |left, other|
|
|
302
|
+
node_class.new(left, other.to_arel.clone.ast)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
self.class.new(Arel::Nodes::TableAlias.new(union_node, alias_name.to_s))
|
|
306
|
+
end
|
|
307
|
+
|
|
241
308
|
# Adds a JOIN clause. Pass `type: :outer` for `LEFT OUTER JOIN`.
|
|
242
309
|
# To join a subquery, pre-alias it via `QueryBuilder.new(sub, 'x').table`
|
|
243
310
|
# or `sub.to_arel.as('x')` and pass that.
|
|
@@ -423,9 +490,8 @@ module ClickHouse
|
|
|
423
490
|
visitor.accept(manager.ast, Arel::Collectors::SQLString.new).value
|
|
424
491
|
end
|
|
425
492
|
|
|
426
|
-
def to_redacted_sql
|
|
427
|
-
visitor = ClickHouse::Client::ToRedactedSqlVisitor.new(ClickHouse::Client::ArelEngine.new
|
|
428
|
-
bind_manager: bind_index_manager)
|
|
493
|
+
def to_redacted_sql
|
|
494
|
+
visitor = ClickHouse::Client::ToRedactedSqlVisitor.new(ClickHouse::Client::ArelEngine.new)
|
|
429
495
|
visitor.accept(manager.ast, Arel::Collectors::SQLString.new).value
|
|
430
496
|
end
|
|
431
497
|
|
|
@@ -499,6 +565,12 @@ module ClickHouse
|
|
|
499
565
|
raise ArgumentError, "Invalid order direction '#{direction}'. Must be :asc or :desc"
|
|
500
566
|
end
|
|
501
567
|
|
|
568
|
+
def validate_union_type!(type)
|
|
569
|
+
return if %i[distinct all].include?(type)
|
|
570
|
+
|
|
571
|
+
raise ArgumentError, "Invalid union type '#{type}'. Must be :distinct or :all"
|
|
572
|
+
end
|
|
573
|
+
|
|
502
574
|
def validate_join_type!(type)
|
|
503
575
|
return if %i[inner outer].include?(type)
|
|
504
576
|
|
|
@@ -11,7 +11,7 @@ module ClickHouse
|
|
|
11
11
|
# Redacted version of the SQL query generated by the to_sql method where the
|
|
12
12
|
# placeholders are stripped. These queries are meant to be exported to external
|
|
13
13
|
# log aggregation systems.
|
|
14
|
-
def to_redacted_sql
|
|
14
|
+
def to_redacted_sql
|
|
15
15
|
raise NotImplementedError
|
|
16
16
|
end
|
|
17
17
|
|
|
@@ -8,15 +8,8 @@ module ClickHouse
|
|
|
8
8
|
# query_builder = ClickHouse::QueryBuilder.new('users').where(name: 'John Doe')
|
|
9
9
|
# redacted_query = query_builder.to_redacted_sql
|
|
10
10
|
# # The redacted_query will contain the SQL query with values replaced by placeholders.
|
|
11
|
-
# output: "SELECT * FROM \"users\" WHERE \"users\".\"name\" =
|
|
11
|
+
# output: "SELECT * FROM \"users\" WHERE \"users\".\"name\" = ?"
|
|
12
12
|
class ToRedactedSqlVisitor < ArelVisitor
|
|
13
|
-
attr_reader :bind_manager
|
|
14
|
-
|
|
15
|
-
def initialize(*args, bind_manager: ClickHouse::Client::BindIndexManager.new)
|
|
16
|
-
@bind_manager = bind_manager
|
|
17
|
-
super(*args)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
13
|
private
|
|
21
14
|
|
|
22
15
|
def redaction_enabled?
|
|
@@ -80,11 +73,9 @@ module ClickHouse
|
|
|
80
73
|
|
|
81
74
|
case redacted_o.name
|
|
82
75
|
when 'startsWith'
|
|
83
|
-
redacted_o.expressions[1] =
|
|
76
|
+
redacted_o.expressions[1] = bind_param
|
|
84
77
|
else
|
|
85
|
-
redacted_o.expressions = redacted_o.expressions.map
|
|
86
|
-
Arel.sql(bind_manager.next_bind_str)
|
|
87
|
-
end
|
|
78
|
+
redacted_o.expressions = redacted_o.expressions.map { bind_param }
|
|
88
79
|
end
|
|
89
80
|
|
|
90
81
|
super(redacted_o, collector)
|
|
@@ -100,17 +91,19 @@ module ClickHouse
|
|
|
100
91
|
cloned_o = o.clone
|
|
101
92
|
|
|
102
93
|
redacted_right = if o.right.is_a?(Array)
|
|
103
|
-
Array.new(o.right.size)
|
|
104
|
-
Arel.sql(bind_manager.next_bind_str)
|
|
105
|
-
end
|
|
94
|
+
Array.new(o.right.size) { bind_param }
|
|
106
95
|
else
|
|
107
|
-
|
|
96
|
+
bind_param
|
|
108
97
|
end
|
|
109
98
|
|
|
110
99
|
cloned_o.right = redacted_right
|
|
111
100
|
cloned_o
|
|
112
101
|
end
|
|
113
102
|
|
|
103
|
+
def bind_param
|
|
104
|
+
Arel::Nodes::BindParam.new('?')
|
|
105
|
+
end
|
|
106
|
+
|
|
114
107
|
# rubocop:enable Naming/MethodName
|
|
115
108
|
# rubocop:enable Naming/MethodParameterName
|
|
116
109
|
end
|
data/lib/click_house/client.rb
CHANGED
|
@@ -10,7 +10,6 @@ require 'active_support/core_ext/object/blank'
|
|
|
10
10
|
require_relative "client/version"
|
|
11
11
|
require_relative "client/database"
|
|
12
12
|
require_relative "client/configuration"
|
|
13
|
-
require_relative "client/bind_index_manager"
|
|
14
13
|
require_relative "client/quoting"
|
|
15
14
|
require_relative "client/arel_engine"
|
|
16
15
|
require_relative "client/query_like"
|
|
@@ -50,7 +49,7 @@ module ClickHouse
|
|
|
50
49
|
|
|
51
50
|
instrument[:statistics] = parsed_response['statistics']&.symbolize_keys
|
|
52
51
|
|
|
53
|
-
Formatter.format(parsed_response)
|
|
52
|
+
Formatter.format(parsed_response, configuration.typecasters)
|
|
54
53
|
end
|
|
55
54
|
end
|
|
56
55
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: click_house-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- group::optimize
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -187,7 +187,6 @@ files:
|
|
|
187
187
|
- lib/click_house/client/arel_engine.rb
|
|
188
188
|
- lib/click_house/client/arel_extensions/nodes/final.rb
|
|
189
189
|
- lib/click_house/client/arel_visitor.rb
|
|
190
|
-
- lib/click_house/client/bind_index_manager.rb
|
|
191
190
|
- lib/click_house/client/configuration.rb
|
|
192
191
|
- lib/click_house/client/database.rb
|
|
193
192
|
- lib/click_house/client/formatter.rb
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module ClickHouse
|
|
4
|
-
module Client
|
|
5
|
-
class BindIndexManager
|
|
6
|
-
def initialize(start_index = 1)
|
|
7
|
-
@current_index = start_index
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def next_bind_str
|
|
11
|
-
bind_str = "$#{@current_index}"
|
|
12
|
-
@current_index += 1
|
|
13
|
-
bind_str
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|