click_house-client 0.8.8 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: efb07f40cb2cd6ebd768d0d216372f960e57af838c825f41dfa3356bcb76143c
4
- data.tar.gz: 3da016575fc8ff03342151fd60b13f8b01c9aaf3a73a635572031a6c75b6d929
3
+ metadata.gz: bef646c7c07c30559265378e9fec3f14f5e41aeadd86058120a31d532d77d10a
4
+ data.tar.gz: e969763ad335eda82d36f3d949207cfcb95ab238646ff0b3e4ec18e5a17bd445
5
5
  SHA512:
6
- metadata.gz: a7abdfef08371fcadccc3bb911742a983ed6773510b54166858b8b16e36f2c0b0b30ef910fc884dc3147a54699e321b899231b0eaa4224b3a224aae7fa8491a3
7
- data.tar.gz: 26d6897f5abbc5c0403e4e04362268ee0ec672bd2149e4714f2f0b77a225c13b225a85459faaaa65a9f5a9cf5eb08200c06f77d5911360f30b018b32a705f0a6
6
+ metadata.gz: 9f7883a21fba0ab5534c85ca43686a3490234252c2277e200a244a3bead56fa3a31d7597c8b23deecb0086b5ad3a90dde67c8425f8ce257c289cb29abb8b29ad
7
+ data.tar.gz: 175aaeaefe0f02f0ba1cd9e3828d9d4bb876c450f626765b78568297c0d35d7e2eb7de00f900ea88db84f7240fbb32790fededbd258dcad4afb4d9291bb16f7c
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- click_house-client (0.8.8)
4
+ click_house-client (0.10.0)
5
5
  activerecord (>= 7.0, < 9.0)
6
6
  activesupport (>= 7.0, < 9.0)
7
7
  addressable (~> 2.8)
@@ -136,7 +136,7 @@ CHECKSUMS
136
136
  benchmark (0.4.1) sha256=d4ef40037bba27f03b28013e219b950b82bace296549ec15a78016552f8d2cce
137
137
  bigdecimal (3.2.2) sha256=39085f76b495eb39a79ce07af716f3a6829bc35eb44f2195e2753749f2fa5adc
138
138
  byebug (12.0.0) sha256=d4a150d291cca40b66ec9ca31f754e93fed8aa266a17335f71bb0afa7fca1a1e
139
- click_house-client (0.8.8)
139
+ click_house-client (0.10.0)
140
140
  concurrent-ruby (1.3.5) sha256=813b3e37aca6df2a21a3b9f1d497f8cbab24a2b94cab325bffe65ee0f6cbebc6
141
141
  connection_pool (2.5.3) sha256=cfd74a82b9b094d1ce30c4f1a346da23ee19dc8a062a16a85f58eab1ced4305b
142
142
  diff-lcs (1.5.0) sha256=49b934001c8c6aedb37ba19daec5c634da27b318a7a3c654ae979d6ba1929b67
data/README.md CHANGED
@@ -182,9 +182,28 @@ query
182
182
  # => "SELECT * FROM `users` WHERE `users`.`active` = 'true' GROUP BY `users`.`department` HAVING `users`.`avg_salary` > 50000"
183
183
  ```
184
184
 
185
+ ### FINAL Modifier
186
+
187
+ ClickHouse's `FINAL` modifier forces merging of rows during query time for tables in the MergeTree family. Apply it via `.final`:
188
+
189
+ ```ruby
190
+ query.final.to_sql
191
+ # => "SELECT * FROM `users` FINAL"
192
+
193
+ query.final.where(active: true).to_sql
194
+ # => "SELECT * FROM `users` FINAL WHERE `users`.`active` = 'true'"
195
+ ```
196
+
197
+ > ⚠️ **Warning:** Using `FINAL` in production code can cause excessive I/O and affect ClickHouse availability. Prefer using it only in test environments or behind a feature flag.
198
+
199
+ `FINAL` is currently applied only to the main `FROM` table. When joining, joined tables are not marked `FINAL`. Calling `.final` multiple times is idempotent.
200
+
185
201
  ### Working with JOINs
186
202
 
187
- When using JOINs, you can apply conditions to joined tables: _(Supports only `INNER JOIN`)_
203
+ `#joins` supports `INNER JOIN` (default) and `LEFT OUTER JOIN` via
204
+ `type: :outer`. The join source can be a table name, an `Arel::Table`, or a
205
+ pre-aliased subquery (`QueryBuilder.new(sub, 'alias').table` or
206
+ `sub.to_arel.as('alias')`).
188
207
 
189
208
  ```ruby
190
209
  # Join with conditions on joined table
@@ -201,6 +220,17 @@ query
201
220
  .having(orders: { total: [100, 200, 300] })
202
221
  .to_sql
203
222
  # => "SELECT * FROM `users` INNER JOIN `orders` ON `users`.`id` = `orders`.`user_id` GROUP BY `users`.`department` HAVING `orders`.`total` IN (100, 200, 300)"
223
+
224
+ # LEFT OUTER JOIN against a pre-aliased subquery
225
+ orders_sub = ClickHouse::Client::QueryBuilder.new(
226
+ ClickHouse::Client::QueryBuilder.new('orders').select(:id, :user_id),
227
+ 'o'
228
+ )
229
+
230
+ query
231
+ .joins(orders_sub.table, { id: :user_id }, type: :outer)
232
+ .to_sql
233
+ # => "SELECT * FROM `users` LEFT OUTER JOIN (SELECT `orders`.`id`, `orders`.`user_id` FROM `orders`) `o` ON `users`.`id` = `o`.`user_id`"
204
234
  ```
205
235
 
206
236
  ### Complete Example
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'arel'
4
+
5
+ module ClickHouse
6
+ module Client
7
+ module ArelExtensions
8
+ module Nodes
9
+ class Final < ::Arel::Nodes::Unary
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -30,6 +30,11 @@ module ClickHouse
30
30
  end
31
31
  end
32
32
 
33
+ def visit_ClickHouse_Client_ArelExtensions_Nodes_Final(object, collector)
34
+ collector = visit(object.expr, collector)
35
+ collector << " FINAL"
36
+ end
37
+
33
38
  # rubocop:enable Naming/MethodName
34
39
  end
35
40
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_record'
4
+ require_relative 'arel_extensions/nodes/final'
4
5
 
5
6
  module ClickHouse
6
7
  module Client
@@ -159,40 +160,78 @@ module ClickHouse
159
160
  self
160
161
  end
161
162
 
163
+ # Applies the ClickHouse `FINAL` modifier to the main FROM table.
164
+ # See https://clickhouse.com/docs/en/sql-reference/statements/select/from#final-modifier
165
+ #
166
+ # @example
167
+ # query.final.to_sql
168
+ # # => "SELECT * FROM `test_table` FINAL"
169
+ #
170
+ # Note: `FINAL` only makes sense on a ClickHouse MergeTree-family table.
171
+ #
172
+ # WARNING: Using `FINAL` in production code can cause excessive I/O and
173
+ # affect ClickHouse availability. Prefer using it only in test environments
174
+ # or behind a feature flag.
175
+ #
176
+ # @return [ClickHouse::Client::QueryBuilder] New instance of query builder.
177
+ def final
178
+ clone.tap do |new_instance|
179
+ source = new_instance.manager.source.left
180
+ wrapped = source.is_a?(ArelExtensions::Nodes::Final) ? source : ArelExtensions::Nodes::Final.new(source)
181
+ new_instance.manager.from(wrapped)
182
+ end
183
+ end
184
+
162
185
  def from(subquery, alias_name)
163
186
  clone.tap do |new_instance|
164
- if subquery.is_a?(self.class)
165
- new_instance.manager.from(subquery.to_arel.as(alias_name))
166
- else
167
- new_instance.manager.from(Arel::Nodes::TableAlias.new(subquery, alias_name))
187
+ new_from = if subquery.is_a?(self.class)
188
+ subquery.to_arel.as(alias_name)
189
+ else
190
+ Arel::Nodes::TableAlias.new(subquery, alias_name)
191
+ end
192
+
193
+ if new_instance.manager.source.left.is_a?(ArelExtensions::Nodes::Final)
194
+ new_from = ArelExtensions::Nodes::Final.new(new_from)
168
195
  end
196
+
197
+ new_instance.manager.from(new_from)
169
198
  end
170
199
  end
171
200
 
172
- def joins(table_name, constraint = nil)
201
+ # Adds a JOIN clause. Pass `type: :outer` for `LEFT OUTER JOIN`.
202
+ # To join a subquery, pre-alias it via `QueryBuilder.new(sub, 'x').table`
203
+ # or `sub.to_arel.as('x')` and pass that.
204
+ # @return [ClickHouse::Client::QueryBuilder] New instance of query builder.
205
+ def joins(source, constraint = nil, type: :inner)
206
+ validate_join_type!(type)
207
+
173
208
  clone.tap do |new_instance|
174
- join_table = table_name.is_a?(Arel::Table) ? table_name : Arel::Table.new(table_name)
209
+ join_target = case source
210
+ when Arel::Table, Arel::Nodes::TableAlias then source
211
+ else Arel::Table.new(source)
212
+ end
213
+ join_class = type == :outer ? Arel::Nodes::OuterJoin : Arel::Nodes::InnerJoin
175
214
 
176
215
  join_condition = case constraint
177
216
  when Hash
178
217
  # Handle hash based constraints like { table1.id: table2.ref_id } or {id: :ref_id}
179
218
  constraint_conditions = constraint.map do |left, right|
180
219
  left_field = left.is_a?(Arel::Attributes::Attribute) ? left : new_instance.table[left]
181
- right_field = right.is_a?(Arel::Attributes::Attribute) ? right : join_table[right]
220
+ right_field = right.is_a?(Arel::Attributes::Attribute) ? right : join_target[right]
182
221
  left_field.eq(right_field)
183
222
  end
184
223
 
185
224
  constraint_conditions.reduce(&:and)
186
225
  when Proc
187
- constraint.call(new_instance.table, join_table)
188
- when Arel::Nodes::Node
226
+ constraint.call(new_instance.table, join_target)
227
+ when Arel::Nodes::Node, Arel::Nodes::SqlLiteral
189
228
  constraint
190
229
  end
191
230
 
192
231
  if join_condition
193
- new_instance.manager.join(join_table).on(join_condition)
232
+ new_instance.manager.join(join_target, join_class).on(join_condition)
194
233
  else
195
- new_instance.manager.join(join_table)
234
+ new_instance.manager.join(join_target, join_class)
196
235
  end
197
236
  end
198
237
  end
@@ -419,6 +458,12 @@ module ClickHouse
419
458
 
420
459
  raise ArgumentError, "Invalid order direction '#{direction}'. Must be :asc or :desc"
421
460
  end
461
+
462
+ def validate_join_type!(type)
463
+ return if %i[inner outer].include?(type)
464
+
465
+ raise ArgumentError, "Invalid join type '#{type}'. Must be :inner or :outer"
466
+ end
422
467
  end
423
468
  end
424
469
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ClickHouse
4
4
  module Client
5
- VERSION = "0.8.8"
5
+ VERSION = "0.10.0"
6
6
  end
7
7
  end
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.8.8
4
+ version: 0.10.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-03-02 00:00:00.000000000 Z
11
+ date: 2026-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -185,6 +185,7 @@ files:
185
185
  - gemfiles/Gemfile-rails-8.0
186
186
  - lib/click_house/client.rb
187
187
  - lib/click_house/client/arel_engine.rb
188
+ - lib/click_house/client/arel_extensions/nodes/final.rb
188
189
  - lib/click_house/client/arel_visitor.rb
189
190
  - lib/click_house/client/bind_index_manager.rb
190
191
  - lib/click_house/client/configuration.rb