click_house-client 0.3.6 → 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: 83fe3e04dc8f9d5a03f4fe17f2da28f6bcadc38b09189b556b46121da2d4ed83
4
- data.tar.gz: c1c41dc3f1addcf6ca4e3547789a83259a599b119386027958a512356000a6e9
3
+ metadata.gz: 4fb73c384094387fbb53365e18a5378aca87148e07e501b25e03f9371cead01e
4
+ data.tar.gz: f5a179a229a5c9fe115ce03d84d22d14b01c56dfa99694cea5f75d9f9c034f37
5
5
  SHA512:
6
- metadata.gz: 494b64ff402c478646f06e881b49999d6d701d42a9609ac00cd7e2e5c09a57c08a9e9764f350f77c3e155eeca99b0ba97b1210495c87b7dc62959a98ac2d5eaa
7
- data.tar.gz: 05e682159377bae54bb2556daa68f313ae4407b4103d41062a4e0544763e6a136163feb138fcf7a7cfff61a0ae8c06fff7e07178aa4b2c686d8f9fbdf84aca19
6
+ metadata.gz: 1b3055e3104e139834ac5e3538ac3d0e402c16b8f1456861ed0bca269c03870038c9aadc97ebb1c74e1486a273c6a44867f20a07f423075ab8e8b90f972c2acd
7
+ data.tar.gz: 9f1e7160de8c0d4ead180e10dfc40b36db5e155d08a1d7d2c62c6225636dc72751c7b1ee68d583ff8653409b8fdddb8e9ce0cd2ff920bd38995e5eab8408e04e
data/.gitlab-ci.yml CHANGED
@@ -69,12 +69,6 @@ secret_detection:
69
69
  variables:
70
70
  # Use a different scanning mode that doesn't rely on commit comparison
71
71
  SECURE_ANALYZERS_MODE: "full_repository_scan"
72
-
73
72
  rules:
74
73
  - if: '$CI_MERGE_REQUEST_IID'
75
74
  - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
76
-
77
- .gem-release-default-rules:
78
- # Limit gem publication to GitLab's ClickHouse Client project
79
- rules:
80
- - if: $CI_PROJECT_ID == '71171912'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- click_house-client (0.3.5)
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)
@@ -34,6 +34,7 @@ GEM
34
34
  base64 (0.3.0)
35
35
  benchmark (0.4.1)
36
36
  bigdecimal (3.2.2)
37
+ byebug (12.0.0)
37
38
  concurrent-ruby (1.3.5)
38
39
  connection_pool (2.5.3)
39
40
  diff-lcs (1.5.0)
@@ -117,6 +118,7 @@ PLATFORMS
117
118
  ruby
118
119
 
119
120
  DEPENDENCIES
121
+ byebug
120
122
  click_house-client!
121
123
  gitlab-styles (~> 12.0.1)
122
124
  rake (~> 13.0)
@@ -133,7 +135,8 @@ CHECKSUMS
133
135
  base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
134
136
  benchmark (0.4.1) sha256=d4ef40037bba27f03b28013e219b950b82bace296549ec15a78016552f8d2cce
135
137
  bigdecimal (3.2.2) sha256=39085f76b495eb39a79ce07af716f3a6829bc35eb44f2195e2753749f2fa5adc
136
- click_house-client (0.3.5)
138
+ byebug (12.0.0) sha256=d4a150d291cca40b66ec9ca31f754e93fed8aa266a17335f71bb0afa7fca1a1e
139
+ click_house-client (0.5.0)
137
140
  concurrent-ruby (1.3.5) sha256=813b3e37aca6df2a21a3b9f1d497f8cbab24a2b94cab325bffe65ee0f6cbebc6
138
141
  connection_pool (2.5.3) sha256=cfd74a82b9b094d1ce30c4f1a346da23ee19dc8a062a16a85f58eab1ced4305b
139
142
  diff-lcs (1.5.0) sha256=49b934001c8c6aedb37ba19daec5c634da27b318a7a3c654ae979d6ba1929b67
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.add_runtime_dependency "addressable", "~> 2.8"
26
26
  spec.add_runtime_dependency "json", "~> 2.7"
27
27
 
28
+ spec.add_development_dependency "byebug"
28
29
  spec.add_development_dependency "gitlab-styles", "~> 12.0.1"
29
30
  spec.add_development_dependency "rake", "~> 13.0"
30
31
  spec.add_development_dependency "rspec", "~> 3.0"
@@ -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
@@ -119,9 +120,45 @@ module ClickHouse
119
120
  self
120
121
  end
121
122
 
122
- def to_sql
123
- apply_conditions!
123
+ def from(subquery, alias_name)
124
+ clone.tap do |new_instance|
125
+ if subquery.is_a?(self.class)
126
+ new_instance.manager.from(subquery.to_arel.as(alias_name))
127
+ else
128
+ new_instance.manager.from(Arel::Nodes::TableAlias.new(subquery, alias_name))
129
+ end
130
+ end
131
+ end
124
132
 
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
160
+
161
+ def to_sql
125
162
  visitor = Arel::Visitors::ToSql.new(ClickHouse::Client::ArelEngine.new)
126
163
  visitor.accept(manager.ast, Arel::Collectors::SQLString.new).value
127
164
  end
@@ -130,34 +167,44 @@ module ClickHouse
130
167
  ClickHouse::Client::Redactor.redact(self, bind_index_manager)
131
168
  end
132
169
 
170
+ def to_arel
171
+ manager
172
+ end
173
+
133
174
  private
134
175
 
135
- def validate_condition_type!(condition)
136
- 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)
137
178
 
138
- raise ArgumentError, "Unsupported Arel node type for QueryBuilder: #{condition.class.name}"
179
+ raise ArgumentError, "Unsupported Arel node type for QueryBuilder: #{constraint.class.name}"
139
180
  end
140
181
 
141
- def add_conditions_to(instance, conditions)
142
- conditions.each do |key, value|
143
- instance.conditions << if value.is_a?(Array)
144
- instance.table[key].in(value)
145
- else
146
- instance.table[key].eq(value)
147
- 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
148
197
  end
149
198
  end
150
199
 
151
- def deep_clone
152
- self.class.new(table.name).tap do |new_instance|
153
- new_instance.manager = manager.clone
154
- new_instance.conditions = conditions.map(&:clone)
155
- end
156
- 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
157
206
 
158
- def apply_conditions!
159
- manager.constraints.clear
160
- conditions.each { |condition| manager.where(condition) }
207
+ instance.manager.where(constraint)
161
208
  end
162
209
 
163
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.3.6"
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.3.6
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-06 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
@@ -78,6 +78,20 @@ dependencies:
78
78
  - - "~>"
79
79
  - !ruby/object:Gem::Version
80
80
  version: '2.7'
81
+ - !ruby/object:Gem::Dependency
82
+ name: byebug
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
81
95
  - !ruby/object:Gem::Dependency
82
96
  name: gitlab-styles
83
97
  requirement: !ruby/object:Gem::Requirement