click_house-client 0.4.1 → 0.5.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: 19bdf144d98f858d7e130442d8c16f4e33f5d65b07aba29dd4ae00f3adc91274
4
- data.tar.gz: 6bde1d020f70b3ce69a824b37dbd839bb3edf813863c12077e83c770c16efe1e
3
+ metadata.gz: 4fb73c384094387fbb53365e18a5378aca87148e07e501b25e03f9371cead01e
4
+ data.tar.gz: f5a179a229a5c9fe115ce03d84d22d14b01c56dfa99694cea5f75d9f9c034f37
5
5
  SHA512:
6
- metadata.gz: c4abe5f4d0ae9a7859ee7bfc040ac536cb5ce9c29ce2fb4255a4075b51067e938a265f18224a4245dae52fa31b292341bce8086cf6f577d814ce097328379af9
7
- data.tar.gz: 76246c442edb0a3eed5347b9d264e7635404211b5568705caad903edc9549f19cc96a94e38839e9f1486aac431fa628ecf6dd3331b7f514e7707a6c9443a8262
6
+ metadata.gz: 1b3055e3104e139834ac5e3538ac3d0e402c16b8f1456861ed0bca269c03870038c9aadc97ebb1c74e1486a273c6a44867f20a07f423075ab8e8b90f972c2acd
7
+ data.tar.gz: 9f1e7160de8c0d4ead180e10dfc40b36db5e155d08a1d7d2c62c6225636dc72751c7b1ee68d583ff8653409b8fdddb8e9ce0cd2ff920bd38995e5eab8408e04e
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- click_house-client (0.4.1)
4
+ click_house-client (0.5.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.4.1)
139
+ click_house-client (0.5.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
@@ -6,7 +6,7 @@ module ClickHouse
6
6
  module Client
7
7
  class QueryBuilder < QueryLike
8
8
  attr_reader :table
9
- attr_accessor :conditions, :manager
9
+ attr_accessor :manager
10
10
 
11
11
  VALID_NODES = [
12
12
  Arel::Nodes::In,
@@ -27,38 +27,39 @@ module ClickHouse
27
27
  def initialize(table_name)
28
28
  @table = Arel::Table.new(table_name)
29
29
  @manager = Arel::SelectManager.new(Arel::Table.engine).from(@table).project(Arel.star)
30
- @conditions = []
30
+ end
31
+
32
+ def initialize_copy(other)
33
+ super
34
+
35
+ @manager = other.manager.clone
31
36
  end
32
37
 
33
38
  # The `where` method currently only supports IN and equal to queries along
34
39
  # with above listed VALID_NODES.
35
40
  # For example, using a range (start_date..end_date) will result in incorrect SQL.
36
- # If you need to query a range, use greater than and less than conditions with Arel.
41
+ # If you need to query a range, use greater than and less than constraints with Arel.
37
42
  #
38
43
  # Correct usage:
39
44
  # query.where(query.table[:created_at].lteq(Date.today)).to_sql
40
45
  # "SELECT * FROM \"table\" WHERE \"table\".\"created_at\" <= '2023-08-01'"
41
46
  #
42
- # This also supports array conditions which will result in an IN query.
47
+ # This also supports array constraints which will result in an IN query.
43
48
  # query.where(entity_id: [1,2,3]).to_sql
44
49
  # "SELECT * FROM \"table\" WHERE \"table\".\"entity_id\" IN (1, 2, 3)"
45
50
  #
46
51
  # Range support and more `Arel::Nodes` could be considered for future iterations.
47
52
  # @return [ClickHouse::QueryBuilder] New instance of query builder.
48
- def where(conditions)
49
- validate_condition_type!(conditions)
53
+ def where(constraints)
54
+ validate_constraint_type!(constraints)
50
55
 
51
- deep_clone.tap do |new_instance|
52
- if conditions.is_a?(Arel::Nodes::Node)
53
- new_instance.conditions << conditions
54
- else
55
- add_conditions_to(new_instance, conditions)
56
- end
56
+ clone.tap do |new_instance|
57
+ add_constraints_to(new_instance, constraints)
57
58
  end
58
59
  end
59
60
 
60
61
  def select(*fields)
61
- deep_clone.tap do |new_instance|
62
+ clone.tap do |new_instance|
62
63
  existing_fields = new_instance.manager.projections.filter_map do |projection|
63
64
  if projection.respond_to?(:to_s) && projection.to_s == '*'
64
65
  nil
@@ -90,7 +91,7 @@ module ClickHouse
90
91
  def order(field, direction = :asc)
91
92
  validate_order_direction!(direction)
92
93
 
93
- deep_clone.tap do |new_instance|
94
+ clone.tap do |new_instance|
94
95
  order_node = case field
95
96
  when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
96
97
  field
@@ -104,7 +105,7 @@ module ClickHouse
104
105
  end
105
106
 
106
107
  def group(*columns)
107
- deep_clone.tap do |new_instance|
108
+ clone.tap do |new_instance|
108
109
  new_instance.manager.group(*columns)
109
110
  end
110
111
  end
@@ -120,7 +121,7 @@ module ClickHouse
120
121
  end
121
122
 
122
123
  def from(subquery, alias_name)
123
- deep_clone.tap do |new_instance|
124
+ clone.tap do |new_instance|
124
125
  if subquery.is_a?(self.class)
125
126
  new_instance.manager.from(subquery.to_arel.as(alias_name))
126
127
  else
@@ -129,9 +130,35 @@ module ClickHouse
129
130
  end
130
131
  end
131
132
 
132
- def to_sql
133
- apply_conditions!
133
+ def joins(table_name, constraint = nil)
134
+ clone.tap do |new_instance|
135
+ join_table = table_name.is_a?(Arel::Table) ? table_name : Arel::Table.new(table_name)
136
+
137
+ join_condition = case constraint
138
+ when Hash
139
+ # Handle hash based constraints like { table1.id: table2.ref_id } or {id: :ref_id}
140
+ constraint_conditions = constraint.map do |left, right|
141
+ left_field = left.is_a?(Arel::Attributes::Attribute) ? left : new_instance.table[left]
142
+ right_field = right.is_a?(Arel::Attributes::Attribute) ? right : join_table[right]
143
+ left_field.eq(right_field)
144
+ end
145
+
146
+ constraint_conditions.reduce(&:and)
147
+ when Proc
148
+ constraint.call(new_instance.table, join_table)
149
+ when Arel::Nodes::Node
150
+ constraint
151
+ end
152
+
153
+ if join_condition
154
+ new_instance.manager.join(join_table).on(join_condition)
155
+ else
156
+ new_instance.manager.join(join_table)
157
+ end
158
+ end
159
+ end
134
160
 
161
+ def to_sql
135
162
  visitor = Arel::Visitors::ToSql.new(ClickHouse::Client::ArelEngine.new)
136
163
  visitor.accept(manager.ast, Arel::Collectors::SQLString.new).value
137
164
  end
@@ -141,39 +168,43 @@ module ClickHouse
141
168
  end
142
169
 
143
170
  def to_arel
144
- apply_conditions!
145
-
146
171
  manager
147
172
  end
148
173
 
149
174
  private
150
175
 
151
- def validate_condition_type!(condition)
152
- return unless condition.is_a?(Arel::Nodes::Node) && VALID_NODES.exclude?(condition.class)
176
+ def validate_constraint_type!(constraint)
177
+ return unless constraint.is_a?(Arel::Nodes::Node) && VALID_NODES.exclude?(constraint.class)
153
178
 
154
- raise ArgumentError, "Unsupported Arel node type for QueryBuilder: #{condition.class.name}"
179
+ raise ArgumentError, "Unsupported Arel node type for QueryBuilder: #{constraint.class.name}"
155
180
  end
156
181
 
157
- def add_conditions_to(instance, conditions)
158
- conditions.each do |key, value|
159
- instance.conditions << if value.is_a?(Array)
160
- instance.table[key].in(value)
161
- else
162
- instance.table[key].eq(value)
163
- end
182
+ def add_constraints_to(instance, constraints)
183
+ if constraints.is_a?(Arel::Nodes::Node)
184
+ instance.manager.where(constraints)
185
+ else
186
+ constraints.each do |key, value|
187
+ if value.is_a?(Hash)
188
+ # Handle nested hash for joined tables
189
+ join_table = Arel::Table.new(key)
190
+ value.each do |nested_key, nested_value|
191
+ build_arel_constraint(instance, join_table, nested_key, nested_value)
192
+ end
193
+ else
194
+ build_arel_constraint(instance, instance.table, key, value)
195
+ end
196
+ end
164
197
  end
165
198
  end
166
199
 
167
- def deep_clone
168
- self.class.new(table.name).tap do |new_instance|
169
- new_instance.manager = manager.clone
170
- new_instance.conditions = conditions.map(&:clone)
171
- end
172
- end
200
+ def build_arel_constraint(instance, table, key, value)
201
+ constraint = if value.is_a?(Array)
202
+ table[key].in(value)
203
+ else
204
+ table[key].eq(value)
205
+ end
173
206
 
174
- def apply_conditions!
175
- manager.constraints.clear
176
- conditions.each { |condition| manager.where(condition) }
207
+ instance.manager.where(constraint)
177
208
  end
178
209
 
179
210
  def validate_order_direction!(direction)
@@ -3,66 +3,71 @@
3
3
  module ClickHouse
4
4
  module Client
5
5
  module Redactor
6
- # Redacts the SQL query represented by the query builder.
7
- #
8
- # @param query_builder [::ClickHouse::Querybuilder] The query builder object to be redacted.
9
- # @return [String] The redacted SQL query as a string.
10
- # @raise [ArgumentError] when the condition in the query is of an unsupported type.
11
- #
12
- # Example:
13
- # query_builder = ClickHouse::QueryBuilder.new('users').where(name: 'John Doe')
14
- # redacted_query = ClickHouse::Redactor.redact(query_builder)
15
- # # The redacted_query will contain the SQL query with values replaced by placeholders.
16
- # output: "SELECT * FROM \"users\" WHERE \"users\".\"name\" = $1"
17
- def self.redact(query_builder, bind_manager = ClickHouse::Client::BindIndexManager.new)
18
- cloned_query_builder = query_builder.clone
6
+ class << self
7
+ # Redacts the SQL query represented by the query builder.
8
+ #
9
+ # @param query_builder [::ClickHouse::Querybuilder] The query builder object to be redacted.
10
+ # @return [String] The redacted SQL query as a string.
11
+ # @raise [ArgumentError] when the condition in the query is of an unsupported type.
12
+ #
13
+ # Example:
14
+ # query_builder = ClickHouse::QueryBuilder.new('users').where(name: 'John Doe')
15
+ # redacted_query = ClickHouse::Redactor.redact(query_builder)
16
+ # # The redacted_query will contain the SQL query with values replaced by placeholders.
17
+ # output: "SELECT * FROM \"users\" WHERE \"users\".\"name\" = $1"
18
+ def redact(query_builder, bind_manager = ClickHouse::Client::BindIndexManager.new)
19
+ redacted_constraints = query_builder.manager.constraints.map do |constraint|
20
+ redact_constraint(constraint, bind_manager)
21
+ end
19
22
 
20
- cloned_query_builder.conditions = cloned_query_builder.conditions.map do |condition|
21
- redact_condition(condition, bind_manager)
22
- end
23
+ cloned_query_builder = query_builder.clone
24
+
25
+ cloned_query_builder.manager.constraints.clear
26
+ redacted_constraints.each do |constraint|
27
+ cloned_query_builder.manager.where(constraint)
28
+ end
23
29
 
24
- cloned_query_builder.manager.constraints.clear
25
- cloned_query_builder.conditions.each do |condition|
26
- cloned_query_builder.manager.where(condition)
30
+ cloned_query_builder.to_sql
27
31
  end
28
32
 
29
- visitor = Arel::Visitors::ToSql.new(ClickHouse::Client::ArelEngine.new)
30
- visitor.accept(cloned_query_builder.manager.ast, Arel::Collectors::SQLString.new).value
31
- end
33
+ private
32
34
 
33
- def self.redact_condition(condition, bind_manager)
34
- case condition
35
- when Arel::Nodes::In
36
- condition.left.in(Array.new(condition.right.size) { Arel.sql(bind_manager.next_bind_str) })
37
- when Arel::Nodes::Equality
38
- condition.left.eq(Arel.sql(bind_manager.next_bind_str))
39
- when Arel::Nodes::LessThan
40
- condition.left.lt(Arel.sql(bind_manager.next_bind_str))
41
- when Arel::Nodes::LessThanOrEqual
42
- condition.left.lteq(Arel.sql(bind_manager.next_bind_str))
43
- when Arel::Nodes::GreaterThan
44
- condition.left.gt(Arel.sql(bind_manager.next_bind_str))
45
- when Arel::Nodes::GreaterThanOrEqual
46
- condition.left.gteq(Arel.sql(bind_manager.next_bind_str))
47
- when Arel::Nodes::NamedFunction
48
- redact_named_function(condition, bind_manager)
49
- else
50
- raise ArgumentError, "Unsupported Arel node type for Redactor: #{condition.class}"
35
+ def redact_constraint(constraint, bind_manager)
36
+ case constraint
37
+ when Arel::Nodes::In
38
+ constraint.left.in(Array.new(constraint.right.size) { Arel.sql(bind_manager.next_bind_str) })
39
+ when Arel::Nodes::Equality
40
+ constraint.left.eq(Arel.sql(bind_manager.next_bind_str))
41
+ when Arel::Nodes::LessThan
42
+ constraint.left.lt(Arel.sql(bind_manager.next_bind_str))
43
+ when Arel::Nodes::LessThanOrEqual
44
+ constraint.left.lteq(Arel.sql(bind_manager.next_bind_str))
45
+ when Arel::Nodes::GreaterThan
46
+ constraint.left.gt(Arel.sql(bind_manager.next_bind_str))
47
+ when Arel::Nodes::GreaterThanOrEqual
48
+ constraint.left.gteq(Arel.sql(bind_manager.next_bind_str))
49
+ when Arel::Nodes::NamedFunction
50
+ redact_named_function(constraint, bind_manager)
51
+ else
52
+ raise ArgumentError, "Unsupported Arel node type for Redactor: #{constraint.class}"
53
+ end
51
54
  end
52
- end
53
55
 
54
- def self.redact_named_function(condition, bind_manager)
55
- redacted_condition =
56
- Arel::Nodes::NamedFunction.new(condition.name, condition.expressions.dup)
56
+ def redact_named_function(constraint, bind_manager)
57
+ redacted_constraint =
58
+ Arel::Nodes::NamedFunction.new(constraint.name, constraint.expressions.dup)
57
59
 
58
- case redacted_condition.name
59
- when 'startsWith'
60
- redacted_condition.expressions[1] = Arel.sql(bind_manager.next_bind_str)
61
- else
62
- redacted_condition.expressions = redacted_condition.expressions.map { Arel.sql(bind_manager.next_bind_str) }
63
- end
60
+ case redacted_constraint.name
61
+ when 'startsWith'
62
+ redacted_constraint.expressions[1] = Arel.sql(bind_manager.next_bind_str)
63
+ else
64
+ redacted_constraint.expressions = redacted_constraint.expressions.map do
65
+ Arel.sql(bind_manager.next_bind_str)
66
+ end
67
+ end
64
68
 
65
- redacted_condition
69
+ redacted_constraint
70
+ end
66
71
  end
67
72
  end
68
73
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ClickHouse
4
4
  module Client
5
- VERSION = "0.4.1"
5
+ VERSION = "0.5.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.4.1
4
+ version: 0.5.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: 2025-08-19 00:00:00.000000000 Z
11
+ date: 2025-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord