umbrellio-utils 1.12.0 → 1.13.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.
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module UmbrellioUtils
6
+ module ClickHouse
7
+ module Backends
8
+ # Abstract backend. Each concrete backend (Legacy for the `click_house`
9
+ # gem, Native for the `clickhouse-native` gem) implements the low-level
10
+ # ops (execute / query / insert / describe_table / server_version /
11
+ # tables / create_database / drop_database / config / logger) and a
12
+ # SERVER_ERROR constant used by `log_errors`.
13
+ class Base
14
+ include Singleton
15
+
16
+ # Concrete backends implement the low-level ops (execute / query /
17
+ # insert / describe_table / server_version / tables / admin_execute
18
+ # / config / logger) and define SERVER_ERROR.
19
+
20
+ def from(source, db_name: self.db_name)
21
+ ds =
22
+ case source
23
+ when Symbol
24
+ DB.from(db_name == self.db_name ? SQL[source] : SQL[db_name][source])
25
+ when nil
26
+ DB.dataset
27
+ else
28
+ DB.from(source)
29
+ end
30
+ ds.clone(ch: true)
31
+ end
32
+
33
+ def count(dataset)
34
+ query_value(dataset.select(SQL.ch_count))
35
+ end
36
+
37
+ def db_name
38
+ config.database.to_sym
39
+ end
40
+
41
+ def create_database(name, if_not_exists: false, cluster: nil, engine: nil)
42
+ admin_execute(
43
+ format(
44
+ "CREATE DATABASE %<exists>s %<name>s %<cluster>s %<engine>s",
45
+ exists: if_not_exists ? "IF NOT EXISTS" : "",
46
+ name:,
47
+ cluster: cluster ? "ON CLUSTER #{cluster}" : "",
48
+ engine: engine ? "ENGINE = #{engine}" : "",
49
+ ),
50
+ )
51
+ end
52
+
53
+ def drop_database(name, if_exists: false, cluster: nil)
54
+ admin_execute(
55
+ format(
56
+ "DROP DATABASE %<exists>s %<name>s %<cluster>s",
57
+ exists: if_exists ? "IF EXISTS" : "",
58
+ name:,
59
+ cluster: cluster ? "ON CLUSTER #{cluster}" : "",
60
+ ),
61
+ )
62
+ end
63
+
64
+ # Returns the `ON CLUSTER <name> [SYNC]` clause for DDL, or "" if
65
+ # `UmbrellioUtils.config.clickhouse_cluster` is blank or we're in
66
+ # a Rails test env. Test-env suppression saves hundreds of ms per
67
+ # DDL on a single-node CH (each ON CLUSTER op blocks waiting for
68
+ # replicas that don't exist). The cluster *name* is still used
69
+ # by callers like Distributed engine declarations, regardless of
70
+ # this clause.
71
+ def on_cluster(sync: false)
72
+ name = UmbrellioUtils.config.clickhouse_cluster
73
+ return "" if name.blank?
74
+ return "" if defined?(Rails) && Rails.env.test?
75
+ sync ? "ON CLUSTER #{name} SYNC" : "ON CLUSTER #{name}"
76
+ end
77
+
78
+ def truncate_table!(table_name, db_name: self.db_name)
79
+ execute("TRUNCATE TABLE #{db_name}.#{table_name} #{on_cluster(sync: true)}")
80
+ end
81
+
82
+ def drop_table!(table_name, db_name: self.db_name)
83
+ execute("DROP TABLE #{db_name}.#{table_name} #{on_cluster(sync: true)}")
84
+ end
85
+
86
+ def optimize_table!(table_name, db_name: self.db_name)
87
+ Timeout.timeout(UmbrellioUtils.config.ch_optimize_timeout) do
88
+ execute("OPTIMIZE TABLE #{db_name}.#{table_name} #{on_cluster} FINAL")
89
+ end
90
+ end
91
+
92
+ def parse_value(value, type:)
93
+ case type
94
+ when /Array/ then Array.wrap(value)
95
+ when /DateTime/
96
+ case value
97
+ when String then value.present? ? Time.zone.parse(value) : nil
98
+ else value
99
+ end
100
+ when /String/ then value&.to_s
101
+ else value
102
+ end
103
+ end
104
+
105
+ def pg_table_connection(table, schema: "public")
106
+ host = ENV["PGHOST"] || DB.opts[:host].presence || "localhost"
107
+ port = DB.opts[:port] || 5432
108
+ # Etc.getlogin returns "root" under non-TTY shells (e.g. rake from
109
+ # a CI runner), which is almost never a real PG role. Prefer $USER.
110
+ login = ENV["USER"].presence || Etc.getlogin
111
+ database = DB.opts[:database].presence || login
112
+ username = DB.opts[:user].presence || login
113
+ password = DB.opts[:password]
114
+ SQL.func(:postgresql, "#{host}:#{port}", database, table, username, password, schema)
115
+ end
116
+
117
+ def populate_temp_table!(temp_table_name, dataset, schema: "public")
118
+ execute(<<~SQL.squish)
119
+ INSERT INTO TABLE FUNCTION #{DB.literal(pg_table_connection(temp_table_name, schema:))}
120
+ #{dataset.sql}
121
+ SQL
122
+ end
123
+
124
+ def with_temp_table(
125
+ dataset, temp_table_name:, primary_key: [:id], primary_key_types: [:integer], **, &
126
+ )
127
+ unless DB.table_exists?(temp_table_name)
128
+ UmbrellioUtils::Database.create_temp_table(
129
+ nil, primary_key:, primary_key_types:, temp_table_name:, &
130
+ )
131
+ populate_temp_table!(temp_table_name, dataset)
132
+ end
133
+ UmbrellioUtils::Database.with_temp_table(nil, primary_key:, temp_table_name:, **, &)
134
+ end
135
+
136
+ protected
137
+
138
+ def log_errors(sql)
139
+ yield
140
+ rescue self.class::SERVER_ERROR => e
141
+ logger.error("ClickHouse error: #{e.inspect}\nSQL: #{sql}")
142
+ raise e
143
+ end
144
+
145
+ def sql_for(dataset)
146
+ return dataset if dataset.is_a?(String)
147
+ unless ch_dataset?(dataset)
148
+ raise "Non-ClickHouse dataset: #{dataset.inspect}. " \
149
+ "You should use `CH.from` instead of `DB`"
150
+ end
151
+ dataset.sql
152
+ end
153
+
154
+ def ch_dataset?(dataset)
155
+ case dataset
156
+ when Sequel::Dataset
157
+ dataset.opts[:ch] && Array(dataset.opts[:from]).all? { |x| ch_dataset?(x) }
158
+ when Sequel::SQL::AliasedExpression
159
+ ch_dataset?(dataset.expression)
160
+ when Sequel::SQL::Identifier, Sequel::SQL::QualifiedIdentifier
161
+ true
162
+ else
163
+ raise "Unknown dataset type: #{dataset.inspect}"
164
+ end
165
+ end
166
+
167
+ def normalize_identifier(name)
168
+ name = name.value if name.is_a?(Sequel::SQL::Identifier)
169
+ name.to_s
170
+ end
171
+
172
+ def full_table_name(table_name, db_name)
173
+ "#{db_name}.#{normalize_identifier(table_name)}"
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "click_house"
4
+
5
+ module UmbrellioUtils
6
+ module ClickHouse
7
+ module Backends
8
+ # Adapter for the umbrellio/click_house gem (HTTP driver).
9
+ class Legacy < Base
10
+ include Memery
11
+
12
+ SERVER_ERROR = ::ClickHouse::Error
13
+
14
+ def execute(sql, host: nil, **opts)
15
+ log_errors(sql) { client(host).execute(sql, params: opts) }
16
+ end
17
+
18
+ def query(dataset, host: nil, **opts)
19
+ sql = sql_for(dataset)
20
+ log_errors(sql) do
21
+ select_all(sql, host:, **opts).map { |x| Misc::StrictHash[x.symbolize_keys] }
22
+ end
23
+ end
24
+
25
+ def query_value(dataset, host: nil, **opts)
26
+ sql = sql_for(dataset)
27
+ log_errors(sql) { select_value(sql, host:, **opts) }
28
+ end
29
+
30
+ def query_each(dataset, host: nil, **, &)
31
+ query(dataset, host:, **).each(&)
32
+ end
33
+
34
+ def insert(table_name, db_name: self.db_name, rows: [])
35
+ client.insert(full_table_name(table_name, db_name), rows, format: "JSONEachRow")
36
+ end
37
+
38
+ def describe_table(table_name, db_name: self.db_name)
39
+ sql = "DESCRIBE TABLE #{full_table_name(table_name, db_name)} FORMAT JSON"
40
+ log_errors(sql) { select_all(sql).map { |x| Misc::StrictHash[x.symbolize_keys] } }
41
+ end
42
+
43
+ def server_version
44
+ select_value("SELECT version()").to_f
45
+ end
46
+
47
+ def tables
48
+ client.tables
49
+ end
50
+
51
+ # Legacy HTTP driver can issue DDL directly; no admin side-channel
52
+ # needed. Base#create_database / #drop_database call this.
53
+ def admin_execute(sql)
54
+ client.execute(sql)
55
+ end
56
+
57
+ def config
58
+ client.config
59
+ end
60
+
61
+ def logger
62
+ client.config.logger
63
+ end
64
+
65
+ private
66
+
67
+ def client(host = nil)
68
+ cfg = ::ClickHouse.config
69
+ cfg.host = resolve(host) if host
70
+ ::ClickHouse::Connection.new(cfg)
71
+ end
72
+ memoize :client, ttl: 1.minute
73
+
74
+ def resolve(host)
75
+ IPSocket.getaddress(host)
76
+ rescue => e
77
+ Exceptions.notify!(e, raise_errors: false)
78
+ config.host
79
+ end
80
+
81
+ def select_all(sql, host: nil, **opts)
82
+ response = client(host).get(body: sql, query: { default_format: "JSON", **opts })
83
+ ::ClickHouse::Response::Factory.response(response, client(host).config)
84
+ end
85
+
86
+ def select_value(...)
87
+ select_all(...).first.to_a.dig(0, -1)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "clickhouse-native"
4
+ require_relative "../config"
5
+
6
+ module UmbrellioUtils
7
+ module ClickHouse
8
+ module Backends
9
+ # Adapter for the clickhouse-native gem (TCP driver).
10
+ #
11
+ # Intentional differences from the HTTP-era module:
12
+ # - Values returned by query / query_value are real Ruby types
13
+ # (Time, Integer, etc.), not JSON-stringified.
14
+ # - The `host:` kwarg on execute / query / query_value is accepted
15
+ # for source compatibility but ignored — hostname is bound at
16
+ # Pool construction, not per query.
17
+ class Native < Base
18
+ SERVER_ERROR = ::ClickhouseNative::ServerError
19
+
20
+ # Server-side error codes that mean "object doesn't exist". Used by
21
+ # describe_table callers that want to tolerate eager-load against a
22
+ # database that hasn't been created yet (e.g. rake ch:create).
23
+ UNKNOWN_TABLE = 60
24
+ UNKNOWN_DATABASE = 81
25
+
26
+ def execute(sql, host: nil, **_opts) # rubocop:disable Lint/UnusedMethodArgument
27
+ sql_string = sql.is_a?(String) ? sql : sql.sql
28
+ log_errors(sql_string) { pool.execute(sql_string) }
29
+ end
30
+
31
+ def query(dataset, host: nil, **_opts) # rubocop:disable Lint/UnusedMethodArgument
32
+ sql = sql_for(dataset)
33
+ log_errors(sql) { pool.query(sql) }
34
+ end
35
+
36
+ def query_value(dataset, host: nil, **_opts) # rubocop:disable Lint/UnusedMethodArgument
37
+ sql = sql_for(dataset)
38
+ log_errors(sql) { pool.query_value(sql) }
39
+ end
40
+
41
+ def query_each(dataset, host: nil, **_opts, &) # rubocop:disable Lint/UnusedMethodArgument
42
+ sql = sql_for(dataset)
43
+ log_errors(sql) { pool.query_each(sql, &) }
44
+ end
45
+
46
+ def insert(table_name, db_name: self.db_name, rows: [])
47
+ return if rows.empty?
48
+ pool.insert(normalize_identifier(table_name), rows, db_name: db_name.to_s)
49
+ end
50
+
51
+ def describe_table(table_name, db_name: self.db_name)
52
+ pool.describe_table(normalize_identifier(table_name), db_name: db_name.to_s)
53
+ end
54
+
55
+ def server_version
56
+ pool.with(&:server_version).to_f
57
+ end
58
+
59
+ def tables
60
+ pool.query("SHOW TABLES").pluck(:name)
61
+ end
62
+
63
+ def config
64
+ ::ClickHouse.config
65
+ end
66
+
67
+ # Read through pool so test mocks of `pool` also redirect `db_name`.
68
+ def db_name
69
+ pool.database.to_sym
70
+ end
71
+
72
+ def logger
73
+ @logger ||= UmbrellioUtils.config.clickhouse_native_logger ||
74
+ (defined?(Rails) && Rails.logger) ||
75
+ Logger.new($stdout)
76
+ end
77
+
78
+ def pool
79
+ @pool ||= ::ClickhouseNative::Pool.new(
80
+ **client_options(database: (config[:database] || "default").to_s),
81
+ pool_size: Integer(config[:pool_size] || 5),
82
+ pool_timeout: Integer(config[:pool_timeout] || 10),
83
+ settings: UmbrellioUtils.config.clickhouse_native_settings || {},
84
+ )
85
+ end
86
+
87
+ # DDL that creates/drops the configured database can't run through
88
+ # the main pool (which is bound to that database). Open a one-shot
89
+ # client connected to the always-present "default" db instead.
90
+ def admin_execute(sql)
91
+ admin = ::ClickhouseNative::Client.new(**client_options(database: "default"))
92
+ admin.execute(sql)
93
+ ensure
94
+ admin&.close
95
+ end
96
+
97
+ private
98
+
99
+ def client_options(database:)
100
+ {
101
+ host: config[:host] || "localhost",
102
+ port: Integer(config[:port] || 9000),
103
+ database:,
104
+ user: (config[:username] || "default").to_s,
105
+ password: (config[:password] || "").to_s,
106
+ logger:,
107
+ }
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "backends/base"
4
+
5
+ module UmbrellioUtils
6
+ module ClickHouse
7
+ module Backends
8
+ autoload :Legacy, "umbrellio_utils/click_house/backends/legacy"
9
+ autoload :Native, "umbrellio_utils/click_house/backends/native"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Provides `::ClickHouse.config` when the legacy `click_house` gem is
4
+ # not loaded. The legacy gem defines `::ClickHouse::Connection` and its
5
+ # own `::ClickHouse.config`; we only step in when it's absent (typical
6
+ # for consumers that have migrated to the `clickhouse-native` gem).
7
+ unless defined?(ClickHouse::Connection)
8
+ module ClickHouse
9
+ def self.config
10
+ @config ||= Rails.application.config_for(:clickhouse)
11
+ end
12
+ end
13
+ end
@@ -1,188 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module UmbrellioUtils
4
+ # Polymorphic ClickHouse facade. The active backend is picked up from
5
+ # `UmbrellioUtils.config.clickhouse_backend` — `:legacy` routes through
6
+ # the `click_house` gem (HTTP), `:native` through the `clickhouse-native`
7
+ # gem (TCP). Both backends expose the same public surface so consumer
8
+ # code (including UmbrellioUtils::Migrations) is backend-agnostic.
4
9
  module ClickHouse
5
- include Memery
6
-
7
10
  extend self
8
11
 
9
- delegate :create_database, :drop_database, :tables, :config, to: :client
10
-
11
- def insert(table_name, db_name: self.db_name, rows: [])
12
- client.insert(full_table_name(table_name, db_name), rows, format: "JSONEachRow")
13
- end
14
-
15
- def from(source, db_name: self.db_name)
16
- ds =
17
- case source
18
- when Symbol
19
- DB.from(db_name == self.db_name ? SQL[source] : SQL[db_name][source])
20
- when nil
21
- DB.dataset
22
- else
23
- DB.from(source)
24
- end
25
-
26
- ds.clone(ch: true)
27
- end
28
-
29
- def execute(sql, host: nil, **opts)
30
- log_errors(sql) do
31
- client(host).execute(sql, params: opts)
32
- end
33
- end
34
-
35
- def query(dataset, host: nil, **opts)
36
- sql = sql_for(dataset)
37
-
38
- log_errors(sql) do
39
- select_all(sql, host:, **opts).map { |x| Misc::StrictHash[x.symbolize_keys] }
40
- end
41
- end
42
-
43
- def query_value(dataset, host: nil, **opts)
44
- sql = sql_for(dataset)
45
-
46
- log_errors(sql) do
47
- select_value(sql, host:, **opts)
48
- end
49
- end
50
-
51
- def count(dataset)
52
- query_value(dataset.select(SQL.ch_count))
53
- end
54
-
55
- def optimize_table!(table_name, db_name: self.db_name)
56
- Timeout.timeout(UmbrellioUtils.config.ch_optimize_timeout) do
57
- execute("OPTIMIZE TABLE #{db_name}.#{table_name} ON CLUSTER click_cluster FINAL")
58
- end
59
- end
60
-
61
- def truncate_table!(table_name, db_name: self.db_name)
62
- execute("TRUNCATE TABLE #{db_name}.#{table_name} ON CLUSTER click_cluster SYNC")
63
- end
64
-
65
- def drop_table!(table_name, db_name: self.db_name)
66
- execute("DROP TABLE #{db_name}.#{table_name} ON CLUSTER click_cluster SYNC")
67
- end
68
-
69
- def describe_table(table_name, db_name: self.db_name)
70
- sql = "DESCRIBE TABLE #{full_table_name(table_name, db_name)} FORMAT JSON"
12
+ autoload :Backends, "umbrellio_utils/click_house/backends"
71
13
 
72
- log_errors(sql) do
73
- select_all(sql).map { |x| Misc::StrictHash[x.symbolize_keys] }
74
- end
75
- end
14
+ VALID_BACKENDS = %i[legacy native].freeze
76
15
 
77
- def db_name
78
- client.config.database.to_sym
79
- end
16
+ DELEGATED = %i[
17
+ execute query query_value query_each count insert
18
+ from describe_table server_version tables
19
+ create_database drop_database db_name config
20
+ truncate_table! drop_table! optimize_table! on_cluster
21
+ parse_value pg_table_connection populate_temp_table! with_temp_table
22
+ ].freeze
80
23
 
81
- def parse_value(value, type:)
82
- case type
83
- when /String/
84
- value&.to_s
85
- when /DateTime/
86
- Time.zone.parse(value) if value
87
- else
88
- value
24
+ DELEGATED.each do |method_name|
25
+ define_method(method_name) do |*args, **kwargs, &block|
26
+ backend.public_send(method_name, *args, **kwargs, &block)
89
27
  end
90
28
  end
91
29
 
92
- def server_version
93
- select_value("SELECT version()").to_f
30
+ def backend
31
+ @backend ||= backend_for(UmbrellioUtils.config.clickhouse_backend)
94
32
  end
95
33
 
96
- def pg_table_connection(table)
97
- host = ENV["PGHOST"] || DB.opts[:host].presence || "localhost"
98
- port = DB.opts[:port] || 5432
99
- database = DB.opts[:database]
100
- username = DB.opts[:user]
101
- password = DB.opts[:password]
102
-
103
- Sequel.function(:postgresql, "#{host}:#{port}", database, table, username, password)
104
- end
105
-
106
- def with_temp_table(
107
- dataset, temp_table_name:, primary_key: [:id], primary_key_types: [:integer], **opts, &
108
- )
109
- unless DB.table_exists?(temp_table_name)
110
- UmbrellioUtils::Database.create_temp_table(
111
- nil, primary_key:, primary_key_types:, temp_table_name:, &
112
- )
113
- populate_temp_table!(temp_table_name, dataset)
114
- end
115
- UmbrellioUtils::Database.with_temp_table(nil, primary_key:, temp_table_name:, **opts, &)
34
+ # Testing hook — clears the memoized backend so specs can flip
35
+ # `clickhouse_backend` mid-run. Not part of the public API.
36
+ def reset_backend!
37
+ @backend = nil
116
38
  end
117
39
 
118
40
  private
119
41
 
120
- def client(host = nil)
121
- cfg = ::ClickHouse.config
122
- cfg.host = resolve(host) if host
123
- ::ClickHouse::Connection.new(cfg)
124
- end
125
- memoize :client, ttl: 1.minute
126
-
127
- def resolve(host)
128
- IPSocket.getaddress(host)
129
- rescue => e
130
- Exceptions.notify!(e, raise_errors: false)
131
- config.host
132
- end
133
-
134
- def logger
135
- client.config.logger
136
- end
137
-
138
- def log_errors(sql)
139
- yield
140
- rescue ::ClickHouse::Error => e
141
- logger.error("ClickHouse error: #{e.inspect}\nSQL: #{sql}")
142
- raise e
143
- end
144
-
145
- def sql_for(dataset)
146
- unless ch_dataset?(dataset)
147
- raise "Non-ClickHouse dataset: #{dataset.inspect}. " \
148
- "You should use `CH.from` instead of `DB`"
42
+ def backend_for(name)
43
+ case name
44
+ when :legacy then Backends::Legacy.instance
45
+ when :native then Backends::Native.instance
46
+ else raise "Unknown clickhouse_backend: #{name.inspect} (expected one of #{VALID_BACKENDS})"
149
47
  end
150
-
151
- dataset.sql
152
- end
153
-
154
- def ch_dataset?(dataset)
155
- case dataset
156
- when Sequel::Dataset
157
- dataset.opts[:ch] && Array(dataset.opts[:from]).all? { |x| ch_dataset?(x) }
158
- when Sequel::SQL::AliasedExpression
159
- ch_dataset?(dataset.expression)
160
- when Sequel::SQL::Identifier, Sequel::SQL::QualifiedIdentifier
161
- true
162
- else
163
- raise "Unknown dataset type: #{dataset.inspect}"
164
- end
165
- end
166
-
167
- def full_table_name(table_name, db_name)
168
- table_name = table_name.value if table_name.is_a?(Sequel::SQL::Identifier)
169
- "#{db_name}.#{table_name}"
170
- end
171
-
172
- def select_all(sql, host: nil, **opts)
173
- response = client(host).get(body: sql, query: { default_format: "JSON", **opts })
174
- ::ClickHouse::Response::Factory.response(response, client(host).config)
175
- end
176
-
177
- def select_value(...)
178
- select_all(...).first.to_a.dig(0, -1)
179
- end
180
-
181
- def populate_temp_table!(temp_table_name, dataset)
182
- execute(<<~SQL.squish)
183
- INSERT INTO TABLE FUNCTION #{DB.literal(pg_table_connection(temp_table_name))}
184
- #{dataset.sql}
185
- SQL
186
48
  end
187
49
  end
188
50
  end
@@ -45,8 +45,8 @@ module UmbrellioUtils
45
45
  end
46
46
  end
47
47
 
48
- def run_non_critical(rescue_all: false, in_transaction: false, &block)
49
- in_transaction ? DB.transaction(savepoint: true, &block) : yield
48
+ def run_non_critical(rescue_all: false, in_transaction: false, &)
49
+ in_transaction ? DB.transaction(savepoint: true, &) : yield
50
50
  rescue (rescue_all ? Exception : StandardError) => e
51
51
  Exceptions.notify!(e)
52
52
  nil
@@ -4,8 +4,11 @@ module UmbrellioUtils
4
4
  module Database
5
5
  extend self
6
6
 
7
- HandledConstaintError = Class.new(StandardError)
8
- InvalidPkError = Class.new(StandardError)
7
+ class HandledConstaintError < StandardError
8
+ end
9
+
10
+ class InvalidPkError < StandardError
11
+ end
9
12
 
10
13
  def handle_constraint_error(constraint_name, &)
11
14
  DB.transaction(savepoint: true, &)
@@ -6,16 +6,16 @@ module UmbrellioUtils
6
6
  class HTTPClient
7
7
  include Singleton
8
8
 
9
- def perform(*args, **kwargs)
10
- client.perform(*args, **kwargs)
9
+ def perform(*, **)
10
+ client.perform(*, **)
11
11
  end
12
12
 
13
- def perform!(*args, **kwargs)
14
- client.perform!(*args, **kwargs)
13
+ def perform!(*, **)
14
+ client.perform!(*, **)
15
15
  end
16
16
 
17
- def request(*args, **kwargs)
18
- client.request(*args, **kwargs)
17
+ def request(*, **)
18
+ client.request(*, **)
19
19
  end
20
20
 
21
21
  private