clickhouse-ruby 0.1.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 +7 -0
- data/CHANGELOG.md +80 -0
- data/LICENSE +21 -0
- data/README.md +251 -0
- data/lib/clickhouse_ruby/active_record/arel_visitor.rb +468 -0
- data/lib/clickhouse_ruby/active_record/connection_adapter.rb +723 -0
- data/lib/clickhouse_ruby/active_record/railtie.rb +192 -0
- data/lib/clickhouse_ruby/active_record/schema_statements.rb +693 -0
- data/lib/clickhouse_ruby/active_record.rb +121 -0
- data/lib/clickhouse_ruby/client.rb +471 -0
- data/lib/clickhouse_ruby/configuration.rb +145 -0
- data/lib/clickhouse_ruby/connection.rb +328 -0
- data/lib/clickhouse_ruby/connection_pool.rb +301 -0
- data/lib/clickhouse_ruby/errors.rb +144 -0
- data/lib/clickhouse_ruby/result.rb +189 -0
- data/lib/clickhouse_ruby/types/array.rb +183 -0
- data/lib/clickhouse_ruby/types/base.rb +77 -0
- data/lib/clickhouse_ruby/types/boolean.rb +68 -0
- data/lib/clickhouse_ruby/types/date_time.rb +163 -0
- data/lib/clickhouse_ruby/types/float.rb +115 -0
- data/lib/clickhouse_ruby/types/integer.rb +157 -0
- data/lib/clickhouse_ruby/types/low_cardinality.rb +58 -0
- data/lib/clickhouse_ruby/types/map.rb +249 -0
- data/lib/clickhouse_ruby/types/nullable.rb +73 -0
- data/lib/clickhouse_ruby/types/parser.rb +244 -0
- data/lib/clickhouse_ruby/types/registry.rb +148 -0
- data/lib/clickhouse_ruby/types/string.rb +83 -0
- data/lib/clickhouse_ruby/types/tuple.rb +206 -0
- data/lib/clickhouse_ruby/types/uuid.rb +84 -0
- data/lib/clickhouse_ruby/types.rb +69 -0
- data/lib/clickhouse_ruby/version.rb +5 -0
- data/lib/clickhouse_ruby.rb +101 -0
- metadata +150 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClickhouseRuby
|
|
4
|
+
# Configuration for ClickhouseRuby client connections
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# config = ClickhouseRuby::Configuration.new
|
|
8
|
+
# config.host = 'clickhouse.example.com'
|
|
9
|
+
# config.port = 8443
|
|
10
|
+
# config.ssl = true
|
|
11
|
+
#
|
|
12
|
+
class Configuration
|
|
13
|
+
# @return [String] the ClickHouse server hostname
|
|
14
|
+
attr_accessor :host
|
|
15
|
+
|
|
16
|
+
# @return [Integer] the ClickHouse HTTP port (default: 8123)
|
|
17
|
+
attr_accessor :port
|
|
18
|
+
|
|
19
|
+
# @return [String] the database name (default: 'default')
|
|
20
|
+
attr_accessor :database
|
|
21
|
+
|
|
22
|
+
# @return [String, nil] the username for authentication
|
|
23
|
+
attr_accessor :username
|
|
24
|
+
|
|
25
|
+
# @return [String, nil] the password for authentication
|
|
26
|
+
attr_accessor :password
|
|
27
|
+
|
|
28
|
+
# @return [Boolean] whether to use SSL/TLS
|
|
29
|
+
attr_accessor :ssl
|
|
30
|
+
|
|
31
|
+
# @return [Boolean] whether to verify SSL certificates (default: true)
|
|
32
|
+
# IMPORTANT: This defaults to true for security. Only disable in development.
|
|
33
|
+
attr_accessor :ssl_verify
|
|
34
|
+
|
|
35
|
+
# @return [String, nil] path to custom CA certificate file
|
|
36
|
+
attr_accessor :ssl_ca_path
|
|
37
|
+
|
|
38
|
+
# @return [Integer] connection timeout in seconds (default: 10)
|
|
39
|
+
attr_accessor :connect_timeout
|
|
40
|
+
|
|
41
|
+
# @return [Integer] read timeout in seconds (default: 60)
|
|
42
|
+
attr_accessor :read_timeout
|
|
43
|
+
|
|
44
|
+
# @return [Integer] write timeout in seconds (default: 60)
|
|
45
|
+
attr_accessor :write_timeout
|
|
46
|
+
|
|
47
|
+
# @return [Integer] connection pool size (default: 5)
|
|
48
|
+
attr_accessor :pool_size
|
|
49
|
+
|
|
50
|
+
# @return [Integer] time to wait for a pool connection in seconds (default: 5)
|
|
51
|
+
attr_accessor :pool_timeout
|
|
52
|
+
|
|
53
|
+
# @return [Logger, nil] logger instance for debugging
|
|
54
|
+
attr_accessor :logger
|
|
55
|
+
|
|
56
|
+
# @return [Symbol] log level (:debug, :info, :warn, :error)
|
|
57
|
+
attr_accessor :log_level
|
|
58
|
+
|
|
59
|
+
# @return [Hash] default ClickHouse settings for all queries
|
|
60
|
+
attr_accessor :default_settings
|
|
61
|
+
|
|
62
|
+
# Creates a new Configuration with sensible defaults
|
|
63
|
+
def initialize
|
|
64
|
+
@host = 'localhost'
|
|
65
|
+
@port = 8123
|
|
66
|
+
@database = 'default'
|
|
67
|
+
@username = nil
|
|
68
|
+
@password = nil
|
|
69
|
+
@ssl = false
|
|
70
|
+
@ssl_verify = true # SECURITY: Verify certificates by default
|
|
71
|
+
@ssl_ca_path = nil
|
|
72
|
+
@connect_timeout = 10
|
|
73
|
+
@read_timeout = 60
|
|
74
|
+
@write_timeout = 60
|
|
75
|
+
@pool_size = 5
|
|
76
|
+
@pool_timeout = 5
|
|
77
|
+
@logger = nil
|
|
78
|
+
@log_level = :info
|
|
79
|
+
@default_settings = {}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns the base URL for HTTP connections
|
|
83
|
+
#
|
|
84
|
+
# @return [String] the base URL
|
|
85
|
+
def base_url
|
|
86
|
+
scheme = ssl ? 'https' : 'http'
|
|
87
|
+
"#{scheme}://#{host}:#{port}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Returns whether SSL should be used based on configuration or port
|
|
91
|
+
# Automatically enables SSL for common secure ports (8443, 443)
|
|
92
|
+
#
|
|
93
|
+
# @return [Boolean] whether to use SSL
|
|
94
|
+
def use_ssl?
|
|
95
|
+
return ssl unless ssl.nil?
|
|
96
|
+
|
|
97
|
+
# Auto-enable SSL for secure ports
|
|
98
|
+
[8443, 443].include?(port)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Returns a hash suitable for creating HTTP connections
|
|
102
|
+
#
|
|
103
|
+
# @return [Hash] connection options
|
|
104
|
+
def to_connection_options
|
|
105
|
+
{
|
|
106
|
+
host: host,
|
|
107
|
+
port: port,
|
|
108
|
+
database: database,
|
|
109
|
+
username: username,
|
|
110
|
+
password: password,
|
|
111
|
+
use_ssl: use_ssl?,
|
|
112
|
+
ssl_verify: ssl_verify,
|
|
113
|
+
ssl_ca_path: ssl_ca_path,
|
|
114
|
+
connect_timeout: connect_timeout,
|
|
115
|
+
read_timeout: read_timeout,
|
|
116
|
+
write_timeout: write_timeout
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Creates a duplicate configuration
|
|
121
|
+
#
|
|
122
|
+
# @return [Configuration] a new configuration with the same settings
|
|
123
|
+
def dup
|
|
124
|
+
new_config = Configuration.new
|
|
125
|
+
instance_variables.each do |var|
|
|
126
|
+
value = instance_variable_get(var)
|
|
127
|
+
new_config.instance_variable_set(var, value.dup) rescue value
|
|
128
|
+
end
|
|
129
|
+
new_config
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Validates the configuration
|
|
133
|
+
#
|
|
134
|
+
# @raise [ConfigurationError] if the configuration is invalid
|
|
135
|
+
# @return [Boolean] true if valid
|
|
136
|
+
def validate!
|
|
137
|
+
raise ConfigurationError, 'host is required' if host.nil? || host.empty?
|
|
138
|
+
raise ConfigurationError, 'port must be a positive integer' unless port.is_a?(Integer) && port.positive?
|
|
139
|
+
raise ConfigurationError, 'database is required' if database.nil? || database.empty?
|
|
140
|
+
raise ConfigurationError, 'pool_size must be at least 1' unless pool_size >= 1
|
|
141
|
+
|
|
142
|
+
true
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'openssl'
|
|
6
|
+
|
|
7
|
+
module ClickhouseRuby
|
|
8
|
+
# Single HTTP connection wrapper for ClickHouse communication
|
|
9
|
+
#
|
|
10
|
+
# Provides a thin wrapper around Net::HTTP with:
|
|
11
|
+
# - SSL/TLS with verification ON by default (security best practice)
|
|
12
|
+
# - Configurable timeouts
|
|
13
|
+
# - Keep-alive support
|
|
14
|
+
# - Health check via ping
|
|
15
|
+
#
|
|
16
|
+
# @example Creating a connection
|
|
17
|
+
# connection = ClickhouseRuby::Connection.new(
|
|
18
|
+
# host: 'localhost',
|
|
19
|
+
# port: 8123,
|
|
20
|
+
# use_ssl: false
|
|
21
|
+
# )
|
|
22
|
+
# connection.ping # => true
|
|
23
|
+
#
|
|
24
|
+
# @example With SSL (verification enabled by default)
|
|
25
|
+
# connection = ClickhouseRuby::Connection.new(
|
|
26
|
+
# host: 'clickhouse.example.com',
|
|
27
|
+
# port: 8443,
|
|
28
|
+
# use_ssl: true,
|
|
29
|
+
# ssl_verify: true, # This is the default!
|
|
30
|
+
# ssl_ca_path: '/path/to/ca-bundle.crt'
|
|
31
|
+
# )
|
|
32
|
+
#
|
|
33
|
+
class Connection
|
|
34
|
+
# @return [String] the ClickHouse host
|
|
35
|
+
attr_reader :host
|
|
36
|
+
|
|
37
|
+
# @return [Integer] the ClickHouse port
|
|
38
|
+
attr_reader :port
|
|
39
|
+
|
|
40
|
+
# @return [String] the database name
|
|
41
|
+
attr_reader :database
|
|
42
|
+
|
|
43
|
+
# @return [String, nil] username for authentication
|
|
44
|
+
attr_reader :username
|
|
45
|
+
|
|
46
|
+
# @return [Boolean] whether SSL is enabled
|
|
47
|
+
attr_reader :use_ssl
|
|
48
|
+
|
|
49
|
+
# @return [Boolean] whether the connection is currently open
|
|
50
|
+
attr_reader :connected
|
|
51
|
+
alias connected? connected
|
|
52
|
+
|
|
53
|
+
# @return [Time, nil] when the connection was last used
|
|
54
|
+
attr_reader :last_used_at
|
|
55
|
+
|
|
56
|
+
# Creates a new connection
|
|
57
|
+
#
|
|
58
|
+
# @param host [String] ClickHouse server hostname
|
|
59
|
+
# @param port [Integer] ClickHouse HTTP port
|
|
60
|
+
# @param database [String] database name
|
|
61
|
+
# @param username [String, nil] username for authentication
|
|
62
|
+
# @param password [String, nil] password for authentication
|
|
63
|
+
# @param use_ssl [Boolean] whether to use SSL/TLS
|
|
64
|
+
# @param ssl_verify [Boolean] whether to verify SSL certificates (default: true)
|
|
65
|
+
# @param ssl_ca_path [String, nil] path to CA certificate file
|
|
66
|
+
# @param connect_timeout [Integer] connection timeout in seconds
|
|
67
|
+
# @param read_timeout [Integer] read timeout in seconds
|
|
68
|
+
# @param write_timeout [Integer] write timeout in seconds
|
|
69
|
+
def initialize(
|
|
70
|
+
host:,
|
|
71
|
+
port: 8123,
|
|
72
|
+
database: 'default',
|
|
73
|
+
username: nil,
|
|
74
|
+
password: nil,
|
|
75
|
+
use_ssl: false,
|
|
76
|
+
ssl_verify: true,
|
|
77
|
+
ssl_ca_path: nil,
|
|
78
|
+
connect_timeout: 10,
|
|
79
|
+
read_timeout: 60,
|
|
80
|
+
write_timeout: 60
|
|
81
|
+
)
|
|
82
|
+
@host = host
|
|
83
|
+
@port = port
|
|
84
|
+
@database = database
|
|
85
|
+
@username = username
|
|
86
|
+
@password = password
|
|
87
|
+
@use_ssl = use_ssl
|
|
88
|
+
@ssl_verify = ssl_verify
|
|
89
|
+
@ssl_ca_path = ssl_ca_path
|
|
90
|
+
@connect_timeout = connect_timeout
|
|
91
|
+
@read_timeout = read_timeout
|
|
92
|
+
@write_timeout = write_timeout
|
|
93
|
+
|
|
94
|
+
@http = nil
|
|
95
|
+
@connected = false
|
|
96
|
+
@last_used_at = nil
|
|
97
|
+
@mutex = Mutex.new
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Establishes the HTTP connection
|
|
101
|
+
#
|
|
102
|
+
# @return [self]
|
|
103
|
+
# @raise [ConnectionNotEstablished] if connection fails
|
|
104
|
+
# @raise [SSLError] if SSL handshake fails
|
|
105
|
+
def connect
|
|
106
|
+
@mutex.synchronize do
|
|
107
|
+
return self if @connected && @http&.started?
|
|
108
|
+
|
|
109
|
+
begin
|
|
110
|
+
@http = build_http
|
|
111
|
+
@http.start
|
|
112
|
+
@connected = true
|
|
113
|
+
@last_used_at = Time.now
|
|
114
|
+
rescue OpenSSL::SSL::SSLError => e
|
|
115
|
+
@connected = false
|
|
116
|
+
raise SSLError.new(
|
|
117
|
+
"SSL connection failed: #{e.message}",
|
|
118
|
+
original_error: e
|
|
119
|
+
)
|
|
120
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError => e
|
|
121
|
+
@connected = false
|
|
122
|
+
raise ConnectionNotEstablished.new(
|
|
123
|
+
"Failed to connect to #{@host}:#{@port}: #{e.message}",
|
|
124
|
+
original_error: e
|
|
125
|
+
)
|
|
126
|
+
rescue Net::OpenTimeout => e
|
|
127
|
+
@connected = false
|
|
128
|
+
raise ConnectionTimeout.new(
|
|
129
|
+
"Connection timeout to #{@host}:#{@port}",
|
|
130
|
+
original_error: e
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
self
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Closes the HTTP connection
|
|
139
|
+
#
|
|
140
|
+
# @return [self]
|
|
141
|
+
def disconnect
|
|
142
|
+
@mutex.synchronize do
|
|
143
|
+
if @http&.started?
|
|
144
|
+
@http.finish rescue nil
|
|
145
|
+
end
|
|
146
|
+
@http = nil
|
|
147
|
+
@connected = false
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
self
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Reconnects by closing and reopening the connection
|
|
154
|
+
#
|
|
155
|
+
# @return [self]
|
|
156
|
+
def reconnect
|
|
157
|
+
disconnect
|
|
158
|
+
connect
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Executes an HTTP POST request
|
|
162
|
+
#
|
|
163
|
+
# @param path [String] the request path
|
|
164
|
+
# @param body [String] the request body (SQL query)
|
|
165
|
+
# @param headers [Hash] additional headers
|
|
166
|
+
# @return [Net::HTTPResponse] the response
|
|
167
|
+
# @raise [ConnectionNotEstablished] if not connected
|
|
168
|
+
# @raise [ConnectionTimeout] if request times out
|
|
169
|
+
def post(path, body, headers = {})
|
|
170
|
+
ensure_connected
|
|
171
|
+
|
|
172
|
+
request = Net::HTTP::Post.new(path)
|
|
173
|
+
request.body = body
|
|
174
|
+
|
|
175
|
+
# Set default headers
|
|
176
|
+
request['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
177
|
+
request['Accept'] = 'application/json'
|
|
178
|
+
request['User-Agent'] = "ClickhouseRuby/#{ClickhouseRuby::VERSION} Ruby/#{RUBY_VERSION}"
|
|
179
|
+
|
|
180
|
+
# Add authentication
|
|
181
|
+
if @username
|
|
182
|
+
request.basic_auth(@username, @password || '')
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Merge custom headers
|
|
186
|
+
headers.each { |k, v| request[k] = v }
|
|
187
|
+
|
|
188
|
+
execute_request(request)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Executes an HTTP GET request
|
|
192
|
+
#
|
|
193
|
+
# @param path [String] the request path
|
|
194
|
+
# @param headers [Hash] additional headers
|
|
195
|
+
# @return [Net::HTTPResponse] the response
|
|
196
|
+
def get(path, headers = {})
|
|
197
|
+
ensure_connected
|
|
198
|
+
|
|
199
|
+
request = Net::HTTP::Get.new(path)
|
|
200
|
+
request['Accept'] = 'application/json'
|
|
201
|
+
request['User-Agent'] = "ClickhouseRuby/#{ClickhouseRuby::VERSION} Ruby/#{RUBY_VERSION}"
|
|
202
|
+
|
|
203
|
+
if @username
|
|
204
|
+
request.basic_auth(@username, @password || '')
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
headers.each { |k, v| request[k] = v }
|
|
208
|
+
|
|
209
|
+
execute_request(request)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Checks if ClickHouse is reachable and responsive
|
|
213
|
+
#
|
|
214
|
+
# @return [Boolean] true if ClickHouse responds to ping
|
|
215
|
+
def ping
|
|
216
|
+
connect unless connected?
|
|
217
|
+
|
|
218
|
+
response = get('/ping')
|
|
219
|
+
response.code == '200' && response.body&.strip == 'Ok.'
|
|
220
|
+
rescue StandardError
|
|
221
|
+
false
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Returns whether the connection is healthy
|
|
225
|
+
#
|
|
226
|
+
# @return [Boolean] true if connected and HTTP connection is active
|
|
227
|
+
def healthy?
|
|
228
|
+
@connected && @http&.started?
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Returns whether the connection has been idle too long
|
|
232
|
+
#
|
|
233
|
+
# @param max_idle_seconds [Integer] maximum idle time in seconds
|
|
234
|
+
# @return [Boolean] true if connection is stale
|
|
235
|
+
def stale?(max_idle_seconds = 300)
|
|
236
|
+
return true unless @last_used_at
|
|
237
|
+
|
|
238
|
+
Time.now - @last_used_at > max_idle_seconds
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Returns a string representation of the connection
|
|
242
|
+
#
|
|
243
|
+
# @return [String]
|
|
244
|
+
def inspect
|
|
245
|
+
scheme = @use_ssl ? 'https' : 'http'
|
|
246
|
+
status = @connected ? 'connected' : 'disconnected'
|
|
247
|
+
"#<#{self.class.name} #{scheme}://#{@host}:#{@port} #{status}>"
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
private
|
|
251
|
+
|
|
252
|
+
# Builds the Net::HTTP instance with proper configuration
|
|
253
|
+
#
|
|
254
|
+
# @return [Net::HTTP]
|
|
255
|
+
def build_http
|
|
256
|
+
http = Net::HTTP.new(@host, @port)
|
|
257
|
+
|
|
258
|
+
# Timeouts
|
|
259
|
+
http.open_timeout = @connect_timeout
|
|
260
|
+
http.read_timeout = @read_timeout
|
|
261
|
+
http.write_timeout = @write_timeout
|
|
262
|
+
|
|
263
|
+
# SSL configuration
|
|
264
|
+
if @use_ssl
|
|
265
|
+
http.use_ssl = true
|
|
266
|
+
|
|
267
|
+
# SECURITY: Enable SSL verification by default
|
|
268
|
+
# This is critical - existing gems disable this which is a vulnerability
|
|
269
|
+
if @ssl_verify
|
|
270
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
271
|
+
http.ca_file = @ssl_ca_path if @ssl_ca_path
|
|
272
|
+
else
|
|
273
|
+
# Only disable if explicitly requested (development only!)
|
|
274
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Use modern TLS versions
|
|
278
|
+
http.min_version = OpenSSL::SSL::TLS1_2_VERSION
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Enable keep-alive
|
|
282
|
+
http.keep_alive_timeout = 30
|
|
283
|
+
|
|
284
|
+
http
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Ensures the connection is established
|
|
288
|
+
#
|
|
289
|
+
# @raise [ConnectionNotEstablished] if not connected
|
|
290
|
+
def ensure_connected
|
|
291
|
+
unless @connected && @http&.started?
|
|
292
|
+
connect
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Executes an HTTP request with error handling
|
|
297
|
+
#
|
|
298
|
+
# @param request [Net::HTTPRequest] the request to execute
|
|
299
|
+
# @return [Net::HTTPResponse]
|
|
300
|
+
def execute_request(request)
|
|
301
|
+
@mutex.synchronize do
|
|
302
|
+
begin
|
|
303
|
+
response = @http.request(request)
|
|
304
|
+
@last_used_at = Time.now
|
|
305
|
+
response
|
|
306
|
+
rescue Net::ReadTimeout => e
|
|
307
|
+
@connected = false
|
|
308
|
+
raise ConnectionTimeout.new(
|
|
309
|
+
"Read timeout: #{e.message}",
|
|
310
|
+
original_error: e
|
|
311
|
+
)
|
|
312
|
+
rescue Net::WriteTimeout => e
|
|
313
|
+
@connected = false
|
|
314
|
+
raise ConnectionTimeout.new(
|
|
315
|
+
"Write timeout: #{e.message}",
|
|
316
|
+
original_error: e
|
|
317
|
+
)
|
|
318
|
+
rescue Errno::ECONNRESET, Errno::EPIPE, IOError => e
|
|
319
|
+
@connected = false
|
|
320
|
+
raise ConnectionError.new(
|
|
321
|
+
"Connection lost: #{e.message}",
|
|
322
|
+
original_error: e
|
|
323
|
+
)
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|