Class: ClickhouseRuby::Client

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

Overview

Main HTTP client for ClickHouse communication

The Client provides a high-level interface for executing queries and inserting data into ClickHouse. It handles:

  • Connection pooling for performance

  • Automatic format handling (JSONCompact for queries)

  • Proper error handling with rich context

  • Bulk inserts with JSONEachRow format

CRITICAL: This client ALWAYS checks HTTP status codes before parsing response bodies. This prevents the silent error bug found in clickhouse-activerecord (issue #230).

Examples:

Basic usage

config = ClickhouseRuby::Configuration.new
config.host = 'localhost'
client = ClickhouseRuby::Client.new(config)

result = client.execute('SELECT * FROM users LIMIT 10')
result.each { |row| puts row['name'] }

With settings

result = client.execute(
  'SELECT * FROM large_table',
  settings: { max_execution_time: 120 }
)

Bulk insert

client.insert('events', [
  { id: 1, event: 'click', timestamp: Time.now },
  { id: 2, event: 'view', timestamp: Time.now }
])

Constant Summary collapse

DEFAULT_FORMAT =

Default response format for queries

'JSONCompact'
INSERT_FORMAT =

Format for bulk inserts (5x faster than VALUES)

'JSONEachRow'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Client

Creates a new Client

Parameters:

Raises:



58
59
60
61
62
63
64
# File 'lib/clickhouse_ruby/client.rb', line 58

def initialize(config)
  @config = config
  @config.validate!
  @pool = ConnectionPool.new(config)
  @logger = config.logger
  @default_settings = config.default_settings || {}
end

Instance Attribute Details

#configConfiguration (readonly)

Returns the client configuration.

Returns:



49
50
51
# File 'lib/clickhouse_ruby/client.rb', line 49

def config
  @config
end

#poolConnectionPool (readonly)

Returns the connection pool.

Returns:



52
53
54
# File 'lib/clickhouse_ruby/client.rb', line 52

def pool
  @pool
end

Instance Method Details

#closevoid Also known as: disconnect

This method returns an undefined value.

Closes all connections in the pool

Call this when shutting down to clean up resources.



200
201
202
# File 'lib/clickhouse_ruby/client.rb', line 200

def close
  @pool.shutdown
end

#command(sql, settings: {}) ⇒ Boolean

Executes a command (INSERT, CREATE, DROP, etc.) that doesn’t return data

Examples:

client.command('CREATE TABLE test (id UInt64) ENGINE = Memory')
client.command('DROP TABLE test')

Parameters:

  • sql (String)

    the SQL command to execute

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

    ClickHouse settings

Returns:

  • (Boolean)

    true if successful

Raises:



108
109
110
111
112
# File 'lib/clickhouse_ruby/client.rb', line 108

def command(sql, settings: {})
  params = build_query_params(settings)
  execute_request(sql, params)
  true
end

#execute(sql, settings: {}, format: DEFAULT_FORMAT) ⇒ Result

Executes a SQL query and returns results

Examples:

result = client.execute('SELECT count() FROM users')
puts result.first['count()']

With settings

result = client.execute(
  'SELECT * FROM events',
  settings: { max_rows_to_read: 1_000_000 }
)

Parameters:

  • sql (String)

    the SQL query to execute

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

    ClickHouse settings for this query

  • format (String) (defaults to: DEFAULT_FORMAT)

    response format (default: JSONCompact)

Returns:

Raises:



84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/clickhouse_ruby/client.rb', line 84

def execute(sql, settings: {}, format: DEFAULT_FORMAT)
  # Build the query with format
  query_with_format = "#{sql.strip} FORMAT #{format}"

  # Build query parameters
  params = build_query_params(settings)

  # Execute via connection pool
  response = execute_request(query_with_format, params)

  # Parse response based on format
  parse_response(response, sql, format)
end

#insert(table, rows, columns: nil, settings: {}, format: :json_each_row) ⇒ Boolean

Inserts multiple rows using bulk insert (JSONEachRow format)

This is significantly faster than INSERT … VALUES for large datasets. The data is sent in JSONEachRow format which ClickHouse can parse efficiently.

Examples:

client.insert('events', [
  { id: 1, name: 'click' },
  { id: 2, name: 'view' }
])

With explicit columns

client.insert('events', [
  { id: 1, name: 'click', extra: 'ignored' },
], columns: ['id', 'name'])

Parameters:

  • table (String)

    the table name

  • rows (Array<Hash>)

    array of row hashes

  • columns (Array<String>, nil) (defaults to: nil)

    column names (inferred from first row if nil)

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

    ClickHouse settings

  • format (Symbol) (defaults to: :json_each_row)

    insert format (:json_each_row is default and recommended)

Returns:

  • (Boolean)

    true if successful

Raises:

  • (QueryError)

    if insert fails

  • (ArgumentError)

    if rows is empty



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/clickhouse_ruby/client.rb', line 139

def insert(table, rows, columns: nil, settings: {}, format: :json_each_row)
  raise ArgumentError, 'rows cannot be empty' if rows.nil? || rows.empty?

  # Determine columns from first row if not specified
  columns ||= rows.first.keys.map(&:to_s)

  # Build INSERT statement
  columns_str = columns.map { |c| quote_identifier(c) }.join(', ')
  sql = "INSERT INTO #{quote_identifier(table)} (#{columns_str}) FORMAT #{INSERT_FORMAT}"

  # Build JSON body
  body = rows.map do |row|
    row_data = {}
    columns.each do |col|
      key = col.to_s
      value = row[col] || row[col.to_sym]
      row_data[key] = serialize_value(value)
    end
    JSON.generate(row_data)
  end.join("\n")

  # Build params and execute
  params = build_query_params(settings)
  path = build_path(params)

  @pool.with_connection do |conn|
    log_query(sql) if @logger

    response = conn.post("#{path}&query=#{URI.encode_www_form_component(sql)}", body, {
      'Content-Type' => 'application/json'
    })

    handle_response(response, sql)
  end

  true
end

#pingBoolean

Checks if the ClickHouse server is reachable

Returns:

  • (Boolean)

    true if server responds to ping



180
181
182
183
184
# File 'lib/clickhouse_ruby/client.rb', line 180

def ping
  @pool.with_connection(&:ping)
rescue StandardError
  false
end

#pool_statsHash

Returns pool statistics

Returns:

  • (Hash)

    pool stats



208
209
210
# File 'lib/clickhouse_ruby/client.rb', line 208

def pool_stats
  @pool.stats
end

#server_versionString

Returns the ClickHouse server version

Returns:

  • (String)

    version string

Raises:



190
191
192
193
# File 'lib/clickhouse_ruby/client.rb', line 190

def server_version
  result = execute('SELECT version() AS version')
  result.first['version']
end