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 +4 -4
- data/.gitlab-ci.yml +0 -6
- data/Gemfile.lock +5 -2
- data/click_house-client.gemspec +1 -0
- data/lib/click_house/client/query_builder.rb +83 -36
- data/lib/click_house/client/redactor.rb +56 -51
- data/lib/click_house/client/version.rb +1 -1
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4fb73c384094387fbb53365e18a5378aca87148e07e501b25e03f9371cead01e
|
4
|
+
data.tar.gz: f5a179a229a5c9fe115ce03d84d22d14b01c56dfa99694cea5f75d9f9c034f37
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
-
|
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
|
data/click_house-client.gemspec
CHANGED
@@ -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 :
|
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
|
-
|
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
|
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
|
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(
|
49
|
-
|
53
|
+
def where(constraints)
|
54
|
+
validate_constraint_type!(constraints)
|
50
55
|
|
51
|
-
|
52
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
123
|
-
|
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
|
136
|
-
return unless
|
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: #{
|
179
|
+
raise ArgumentError, "Unsupported Arel node type for QueryBuilder: #{constraint.class.name}"
|
139
180
|
end
|
140
181
|
|
141
|
-
def
|
142
|
-
|
143
|
-
instance.
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
-
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
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
|
-
|
30
|
-
visitor.accept(cloned_query_builder.manager.ast, Arel::Collectors::SQLString.new).value
|
31
|
-
end
|
33
|
+
private
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
56
|
+
def redact_named_function(constraint, bind_manager)
|
57
|
+
redacted_constraint =
|
58
|
+
Arel::Nodes::NamedFunction.new(constraint.name, constraint.expressions.dup)
|
57
59
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
69
|
+
redacted_constraint
|
70
|
+
end
|
66
71
|
end
|
67
72
|
end
|
68
73
|
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
|
+
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-
|
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
|