Class: ClickhouseRuby::Connection

Inherits:
Object
  • Object
show all
Defined in:
lib/clickhouse_ruby/connection.rb

Overview

Single HTTP connection wrapper for ClickHouse communication

Provides a thin wrapper around Net::HTTP with:

  • SSL/TLS with verification ON by default (security best practice)

  • Configurable timeouts

  • Keep-alive support

  • Health check via ping

Examples:

Creating a connection

connection = ClickhouseRuby::Connection.new(
  host: 'localhost',
  port: 8123,
  use_ssl: false
)
connection.ping  # => true

With SSL (verification enabled by default)

connection = ClickhouseRuby::Connection.new(
  host: 'clickhouse.example.com',
  port: 8443,
  use_ssl: true,
  ssl_verify: true,  # This is the default!
  ssl_ca_path: '/path/to/ca-bundle.crt'
)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host:, port: 8123, database: 'default', username: nil, password: nil, use_ssl: false, ssl_verify: true, ssl_ca_path: nil, connect_timeout: 10, read_timeout: 60, write_timeout: 60) ⇒ Connection

Creates a new connection

Parameters:

  • host (String)

    ClickHouse server hostname

  • port (Integer) (defaults to: 8123)

    ClickHouse HTTP port

  • database (String) (defaults to: 'default')

    database name

  • username (String, nil) (defaults to: nil)

    username for authentication

  • password (String, nil) (defaults to: nil)

    password for authentication

  • use_ssl (Boolean) (defaults to: false)

    whether to use SSL/TLS

  • ssl_verify (Boolean) (defaults to: true)

    whether to verify SSL certificates (default: true)

  • ssl_ca_path (String, nil) (defaults to: nil)

    path to CA certificate file

  • connect_timeout (Integer) (defaults to: 10)

    connection timeout in seconds

  • read_timeout (Integer) (defaults to: 60)

    read timeout in seconds

  • write_timeout (Integer) (defaults to: 60)

    write timeout in seconds



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/clickhouse_ruby/connection.rb', line 69

def initialize(
  host:,
  port: 8123,
  database: 'default',
  username: nil,
  password: nil,
  use_ssl: false,
  ssl_verify: true,
  ssl_ca_path: nil,
  connect_timeout: 10,
  read_timeout: 60,
  write_timeout: 60
)
  @host = host
  @port = port
  @database = database
  @username = username
  @password = password
  @use_ssl = use_ssl
  @ssl_verify = ssl_verify
  @ssl_ca_path = ssl_ca_path
  @connect_timeout = connect_timeout
  @read_timeout = read_timeout
  @write_timeout = write_timeout

  @http = nil
  @connected = false
  @last_used_at = nil
  @mutex = Mutex.new
end

Instance Attribute Details

#connectedBoolean (readonly) Also known as: connected?

Returns whether the connection is currently open.

Returns:

  • (Boolean)

    whether the connection is currently open



50
51
52
# File 'lib/clickhouse_ruby/connection.rb', line 50

def connected
  @connected
end

#databaseString (readonly)

Returns the database name.

Returns:

  • (String)

    the database name



41
42
43
# File 'lib/clickhouse_ruby/connection.rb', line 41

def database
  @database
end

#hostString (readonly)

Returns the ClickHouse host.

Returns:

  • (String)

    the ClickHouse host



35
36
37
# File 'lib/clickhouse_ruby/connection.rb', line 35

def host
  @host
end

#last_used_atTime? (readonly)

Returns when the connection was last used.

Returns:

  • (Time, nil)

    when the connection was last used



54
55
56
# File 'lib/clickhouse_ruby/connection.rb', line 54

def last_used_at
  @last_used_at
end

#portInteger (readonly)

Returns the ClickHouse port.

Returns:

  • (Integer)

    the ClickHouse port



38
39
40
# File 'lib/clickhouse_ruby/connection.rb', line 38

def port
  @port
end

#use_sslBoolean (readonly)

Returns whether SSL is enabled.

Returns:

  • (Boolean)

    whether SSL is enabled



47
48
49
# File 'lib/clickhouse_ruby/connection.rb', line 47

def use_ssl
  @use_ssl
end

#usernameString? (readonly)

Returns username for authentication.

Returns:

  • (String, nil)

    username for authentication



44
45
46
# File 'lib/clickhouse_ruby/connection.rb', line 44

def username
  @username
end

Instance Method Details

#connectself

Establishes the HTTP connection

Returns:

  • (self)

Raises:



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/clickhouse_ruby/connection.rb', line 105

def connect
  @mutex.synchronize do
    return self if @connected && @http&.started?

    begin
      @http = build_http
      @http.start
      @connected = true
      @last_used_at = Time.now
    rescue OpenSSL::SSL::SSLError => e
      @connected = false
      raise SSLError.new(
        "SSL connection failed: #{e.message}",
        original_error: e
      )
    rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError => e
      @connected = false
      raise ConnectionNotEstablished.new(
        "Failed to connect to #{@host}:#{@port}: #{e.message}",
        original_error: e
      )
    rescue Net::OpenTimeout => e
      @connected = false
      raise ConnectionTimeout.new(
        "Connection timeout to #{@host}:#{@port}",
        original_error: e
      )
    end
  end

  self
end

#disconnectself

Closes the HTTP connection

Returns:

  • (self)


141
142
143
144
145
146
147
148
149
150
151
# File 'lib/clickhouse_ruby/connection.rb', line 141

def disconnect
  @mutex.synchronize do
    if @http&.started?
      @http.finish rescue nil
    end
    @http = nil
    @connected = false
  end

  self
end

#get(path, headers = {}) ⇒ Net::HTTPResponse

Executes an HTTP GET request

Parameters:

  • path (String)

    the request path

  • headers (Hash) (defaults to: {})

    additional headers

Returns:

  • (Net::HTTPResponse)

    the response



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/clickhouse_ruby/connection.rb', line 196

def get(path, headers = {})
  ensure_connected

  request = Net::HTTP::Get.new(path)
  request['Accept'] = 'application/json'
  request['User-Agent'] = "ClickhouseRuby/#{ClickhouseRuby::VERSION} Ruby/#{RUBY_VERSION}"

  if @username
    request.basic_auth(@username, @password || '')
  end

  headers.each { |k, v| request[k] = v }

  execute_request(request)
end

#healthy?Boolean

Returns whether the connection is healthy

Returns:

  • (Boolean)

    true if connected and HTTP connection is active



227
228
229
# File 'lib/clickhouse_ruby/connection.rb', line 227

def healthy?
  @connected && @http&.started?
end

#inspectString

Returns a string representation of the connection

Returns:

  • (String)


244
245
246
247
248
# File 'lib/clickhouse_ruby/connection.rb', line 244

def inspect
  scheme = @use_ssl ? 'https' : 'http'
  status = @connected ? 'connected' : 'disconnected'
  "#<#{self.class.name} #{scheme}://#{@host}:#{@port} #{status}>"
end

#pingBoolean

Checks if ClickHouse is reachable and responsive

Returns:

  • (Boolean)

    true if ClickHouse responds to ping



215
216
217
218
219
220
221
222
# File 'lib/clickhouse_ruby/connection.rb', line 215

def ping
  connect unless connected?

  response = get('/ping')
  response.code == '200' && response.body&.strip == 'Ok.'
rescue StandardError
  false
end

#post(path, body, headers = {}) ⇒ Net::HTTPResponse

Executes an HTTP POST request

Parameters:

  • path (String)

    the request path

  • body (String)

    the request body (SQL query)

  • headers (Hash) (defaults to: {})

    additional headers

Returns:

  • (Net::HTTPResponse)

    the response

Raises:



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/clickhouse_ruby/connection.rb', line 169

def post(path, body, headers = {})
  ensure_connected

  request = Net::HTTP::Post.new(path)
  request.body = body

  # Set default headers
  request['Content-Type'] = 'application/x-www-form-urlencoded'
  request['Accept'] = 'application/json'
  request['User-Agent'] = "ClickhouseRuby/#{ClickhouseRuby::VERSION} Ruby/#{RUBY_VERSION}"

  # Add authentication
  if @username
    request.basic_auth(@username, @password || '')
  end

  # Merge custom headers
  headers.each { |k, v| request[k] = v }

  execute_request(request)
end

#reconnectself

Reconnects by closing and reopening the connection

Returns:

  • (self)


156
157
158
159
# File 'lib/clickhouse_ruby/connection.rb', line 156

def reconnect
  disconnect
  connect
end

#stale?(max_idle_seconds = 300) ⇒ Boolean

Returns whether the connection has been idle too long

Parameters:

  • max_idle_seconds (Integer) (defaults to: 300)

    maximum idle time in seconds

Returns:

  • (Boolean)

    true if connection is stale



235
236
237
238
239
# File 'lib/clickhouse_ruby/connection.rb', line 235

def stale?(max_idle_seconds = 300)
  return true unless @last_used_at

  Time.now - @last_used_at > max_idle_seconds
end