Class: ClickhouseRuby::ActiveRecord::ConnectionAdapter
- Inherits:
-
ActiveRecord::ConnectionAdapters::AbstractAdapter
- Object
- ActiveRecord::ConnectionAdapters::AbstractAdapter
- ClickhouseRuby::ActiveRecord::ConnectionAdapter
- Includes:
- SchemaStatements
- Defined in:
- lib/clickhouse_ruby/active_record/connection_adapter.rb
Overview
ClickHouse has significant differences from traditional RDBMS:
-
No transaction support (commits are immediate)
-
DELETE uses ALTER TABLE … DELETE WHERE syntax
-
UPDATE uses ALTER TABLE … UPDATE … WHERE syntax
-
No foreign key constraints
-
No savepoints
ClickHouse database connection adapter for ActiveRecord
This adapter allows Rails applications to use ClickHouse as a database backend through ActiveRecord’s standard interface.
Constant Summary collapse
- ADAPTER_NAME =
"Clickhouse"- NATIVE_DATABASE_TYPES =
Native database types mapping for ClickHouse Used by migrations and schema definitions
{ primary_key: "UInt64", string: { name: "String" }, text: { name: "String" }, integer: { name: "Int32" }, bigint: { name: "Int64" }, float: { name: "Float32" }, decimal: { name: "Decimal", precision: 10, scale: 0 }, datetime: { name: "DateTime" }, timestamp: { name: "DateTime64", precision: 3 }, time: { name: "DateTime" }, date: { name: "Date" }, binary: { name: "String" }, boolean: { name: "UInt8" }, uuid: { name: "UUID" }, json: { name: "String" }, }.freeze
Class Method Summary collapse
-
.new_client(config) ⇒ ConnectionAdapter
Creates a new database connection Called by ActiveRecord’s connection handler.
Instance Method Summary collapse
-
#active? ⇒ Boolean
Check if the connection is active.
-
#adapter_name ⇒ String
Returns the adapter name.
-
#arel_visitor ⇒ ArelVisitor
Returns the Arel visitor for ClickHouse SQL generation.
-
#begin_db_transaction ⇒ void
Begin a transaction (no-op for ClickHouse) ClickHouse doesn’t support multi-statement transactions.
-
#commit_db_transaction ⇒ void
Commit a transaction (no-op for ClickHouse) All statements are auto-committed in ClickHouse.
-
#connect ⇒ void
Establish connection to ClickHouse.
-
#connected? ⇒ Boolean
Check if connected to the database.
-
#disconnect! ⇒ void
Disconnect from the database.
-
#exec_delete(sql, name = nil, _binds = []) ⇒ Integer
Execute a DELETE statement CRITICAL: This method MUST raise on errors (Issue #230).
-
#exec_insert(sql, name = nil, _binds = [], _pk = nil, _sequence_name = nil) ⇒ Object
Execute an INSERT statement For bulk inserts, use insert_all which is more efficient.
-
#exec_query(sql, name = "SQL", _binds = [], prepare: false) ⇒ ClickhouseRuby::Result
Execute a raw query, returning results.
-
#exec_rollback_db_transaction ⇒ void
Rollback a transaction (no-op for ClickHouse) ClickHouse doesn’t support rollback.
-
#exec_update(sql, name = nil, _binds = []) ⇒ Integer
Execute an UPDATE statement CRITICAL: This method MUST raise on errors.
-
#execute(sql, name = nil) ⇒ ClickhouseRuby::Result
Execute a SQL query CRITICAL: This method MUST raise on errors, never silently fail.
-
#initialize(connection, logger = nil, _connection_options = nil, config = {}) ⇒ ConnectionAdapter
constructor
Initialize a new ConnectionAdapter.
-
#initialize_type_map(m = type_map) ⇒ void
Initialize the type map with ClickHouse types.
-
#native_database_types ⇒ Hash
Returns native database types.
-
#quote_column_name(name) ⇒ String
Quote a column name for ClickHouse ClickHouse uses backticks or double quotes for identifiers.
-
#quote_string(string) ⇒ String
Quote a string value for ClickHouse.
-
#quote_table_name(name) ⇒ String
Quote a table name for ClickHouse.
-
#reconnect! ⇒ void
Reconnect to the database.
-
#reset! ⇒ void
Clear the connection (called when returning connection to pool).
-
#supports_bulk_alter? ⇒ Boolean
ClickHouse doesn’t support bulk alter.
-
#supports_check_constraints? ⇒ Boolean
ClickHouse doesn’t support check constraints in the traditional sense.
-
#supports_comments? ⇒ Boolean
ClickHouse doesn’t support standard comments on columns.
-
#supports_datetime_with_precision? ⇒ Boolean
ClickHouse supports datetime with precision (DateTime64).
-
#supports_ddl_transactions? ⇒ Boolean
ClickHouse doesn’t support DDL transactions.
-
#supports_explain? ⇒ Boolean
ClickHouse supports EXPLAIN.
-
#supports_expression_index? ⇒ Boolean
ClickHouse doesn’t support expression indexes.
-
#supports_foreign_keys? ⇒ Boolean
ClickHouse doesn’t support foreign keys.
-
#supports_insert_returning? ⇒ Boolean
ClickHouse doesn’t support INSERT RETURNING.
-
#supports_json? ⇒ Boolean
ClickHouse supports JSON type (as String with JSON functions).
-
#supports_partial_index? ⇒ Boolean
ClickHouse doesn’t support partial indexes.
-
#supports_savepoints? ⇒ Boolean
ClickHouse doesn’t support savepoints.
-
#supports_transaction_isolation? ⇒ Boolean
ClickHouse doesn’t support transaction isolation levels.
-
#supports_views? ⇒ Boolean
ClickHouse doesn’t support standard views (has MATERIALIZED VIEWS).
Methods included from SchemaStatements
#add_column, #add_index, #change_column, #column_exists?, #columns, #create_database, #create_table, #current_database, #databases, #drop_database, #drop_table, #index_exists?, #indexes, #primary_keys, #remove_column, #remove_index, #rename_column, #rename_table, #table_exists?, #tables, #truncate_table, #view_exists?, #views
Constructor Details
#initialize(connection, logger = nil, _connection_options = nil, config = {}) ⇒ ConnectionAdapter
Initialize a new ConnectionAdapter
114 115 116 117 118 119 120 121 122 123 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 114 def initialize(connection, logger = nil, = nil, config = {}) @config = config.symbolize_keys @clickhouse_client = nil @connection_parameters = nil super(connection, logger, config) # Extend ActiveRecord::Relation with our methods ::ActiveRecord::Relation.include(RelationExtensions) end |
Class Method Details
.new_client(config) ⇒ ConnectionAdapter
Creates a new database connection Called by ActiveRecord’s connection handler
76 77 78 79 80 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 76 def new_client(config) clickhouse_config = build_clickhouse_config(config) clickhouse_config.validate! ClickhouseRuby::Client.new(clickhouse_config) end |
Instance Method Details
#active? ⇒ Boolean
Check if the connection is active
146 147 148 149 150 151 152 153 154 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 146 def active? return false unless @clickhouse_client # Ping ClickHouse to verify connection execute_internal("SELECT 1") true rescue ClickhouseRuby::Error false end |
#adapter_name ⇒ String
Returns the adapter name
128 129 130 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 128 def adapter_name ADAPTER_NAME end |
#arel_visitor ⇒ ArelVisitor
Returns the Arel visitor for ClickHouse SQL generation
502 503 504 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 502 def arel_visitor @arel_visitor ||= ArelVisitor.new(self) end |
#begin_db_transaction ⇒ void
This method returns an undefined value.
Begin a transaction (no-op for ClickHouse) ClickHouse doesn’t support multi-statement transactions
444 445 446 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 444 def begin_db_transaction # No-op: ClickHouse doesn't support transactions end |
#commit_db_transaction ⇒ void
This method returns an undefined value.
Commit a transaction (no-op for ClickHouse) All statements are auto-committed in ClickHouse
452 453 454 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 452 def commit_db_transaction # No-op: ClickHouse doesn't support transactions end |
#connect ⇒ void
This method returns an undefined value.
Establish connection to ClickHouse
191 192 193 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 191 def connect @clickhouse_client = self.class.new_client(@config) end |
#connected? ⇒ Boolean
Check if connected to the database
159 160 161 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 159 def connected? !@clickhouse_client.nil? end |
#disconnect! ⇒ void
This method returns an undefined value.
Disconnect from the database
166 167 168 169 170 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 166 def disconnect! super @clickhouse_client&.close if @clickhouse_client.respond_to?(:close) @clickhouse_client = nil end |
#exec_delete(sql, name = nil, _binds = []) ⇒ Integer
Execute a DELETE statement CRITICAL: This method MUST raise on errors (Issue #230)
ClickHouse DELETE syntax: ALTER TABLE table DELETE WHERE condition This method handles the conversion automatically via Arel visitor
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 362 def exec_delete(sql, name = nil, _binds = []) ensure_connected! # The Arel visitor should have already converted this to # ALTER TABLE ... DELETE WHERE syntax # But if it's standard DELETE, convert it here clickhouse_sql = convert_delete_to_alter(sql) log(clickhouse_sql, name || "DELETE") do result = execute_internal(clickhouse_sql) # CRITICAL: Raise on any error raise_if_error!(result) # ClickHouse doesn't return affected row count for mutations # Return 0 as a safe default, but the operation succeeded 0 end rescue ClickhouseRuby::Error => e # CRITICAL: Always propagate errors, never silently fail raise_query_error(e, sql) rescue StandardError => e raise ClickhouseRuby::QueryError.new( "DELETE failed: #{e.message}", sql: sql, original_error: e, ) end |
#exec_insert(sql, name = nil, _binds = [], _pk = nil, _sequence_name = nil) ⇒ Object
Execute an INSERT statement For bulk inserts, use insert_all which is more efficient
344 345 346 347 348 349 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 344 def exec_insert(sql, name = nil, _binds = [], _pk = nil, _sequence_name = nil) execute(sql, name) # ClickHouse doesn't return inserted IDs # Return nil as we can't get the last insert ID nil end |
#exec_query(sql, name = "SQL", _binds = [], prepare: false) ⇒ ClickhouseRuby::Result
Execute a raw query, returning results
432 433 434 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 432 def exec_query(sql, name = "SQL", _binds = [], prepare: false) execute(sql, name) end |
#exec_rollback_db_transaction ⇒ void
This method returns an undefined value.
Rollback a transaction (no-op for ClickHouse) ClickHouse doesn’t support rollback
460 461 462 463 464 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 460 def exec_rollback_db_transaction # No-op: ClickHouse doesn't support transactions # Log a warning since rollback was requested but cannot be performed @logger&.warn("ClickHouse does not support transaction rollback") end |
#exec_update(sql, name = nil, _binds = []) ⇒ Integer
Execute an UPDATE statement CRITICAL: This method MUST raise on errors
ClickHouse UPDATE syntax: ALTER TABLE table UPDATE col = val WHERE condition This method handles the conversion automatically via Arel visitor
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 401 def exec_update(sql, name = nil, _binds = []) ensure_connected! # The Arel visitor should have already converted this to # ALTER TABLE ... UPDATE ... WHERE syntax clickhouse_sql = convert_update_to_alter(sql) log(clickhouse_sql, name || "UPDATE") do result = execute_internal(clickhouse_sql) raise_if_error!(result) # ClickHouse doesn't return affected row count for mutations 0 end rescue ClickhouseRuby::Error => e raise_query_error(e, sql) rescue StandardError => e raise ClickhouseRuby::QueryError.new( "UPDATE failed: #{e.message}", sql: sql, original_error: e, ) end |
#execute(sql, name = nil) ⇒ ClickhouseRuby::Result
Execute a SQL query CRITICAL: This method MUST raise on errors, never silently fail
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 311 def execute(sql, name = nil) ensure_connected! log(sql, name) do result = execute_internal(sql) # CRITICAL: Check for errors and raise them # ClickHouse may return 200 OK with error in body raise_if_error!(result) result end rescue ClickhouseRuby::Error => e # Re-raise ClickhouseRuby errors with the SQL context raise_query_error(e, sql) rescue StandardError => e # Wrap unexpected errors raise ClickhouseRuby::QueryError.new( "Query execution failed: #{e.message}", sql: sql, original_error: e, ) end |
#initialize_type_map(m = type_map) ⇒ void
This method returns an undefined value.
Initialize the type map with ClickHouse types
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 514 def initialize_type_map(m = type_map) # Register standard types register_class_with_limit m, /^String/i, ::ActiveRecord::Type::String register_class_with_limit m, /^FixedString/i, ::ActiveRecord::Type::String # Integer types m.register_type(/^Int8/i, ::ActiveRecord::Type::Integer.new(limit: 1)) m.register_type(/^Int16/i, ::ActiveRecord::Type::Integer.new(limit: 2)) m.register_type(/^Int32/i, ::ActiveRecord::Type::Integer.new(limit: 4)) m.register_type(/^Int64/i, ::ActiveRecord::Type::Integer.new(limit: 8)) m.register_type(/^UInt8/i, ::ActiveRecord::Type::Integer.new(limit: 1)) m.register_type(/^UInt16/i, ::ActiveRecord::Type::Integer.new(limit: 2)) m.register_type(/^UInt32/i, ::ActiveRecord::Type::Integer.new(limit: 4)) m.register_type(/^UInt64/i, ::ActiveRecord::Type::Integer.new(limit: 8)) # Float types m.register_type(/^Float32/i, ::ActiveRecord::Type::Float.new) m.register_type(/^Float64/i, ::ActiveRecord::Type::Float.new) # Decimal types m.register_type(/^Decimal/i, ::ActiveRecord::Type::Decimal.new) # Date/Time types m.register_type(/^Date$/i, ::ActiveRecord::Type::Date.new) m.register_type(/^DateTime/i, ::ActiveRecord::Type::DateTime.new) m.register_type(/^DateTime64/i, ::ActiveRecord::Type::DateTime.new) # Boolean (UInt8 with 0/1) m.register_type(/^Bool/i, ::ActiveRecord::Type::Boolean.new) # UUID m.register_type(/^UUID/i, ::ActiveRecord::Type::String.new) # Nullable wrapper - extract inner type m.register_type(/^Nullable\((.+)\)/i) do |sql_type| inner_type = sql_type.match(/^Nullable\((.+)\)/i)[1] lookup_cast_type(inner_type) end # Array types m.register_type(/^Array\(/i, ::ActiveRecord::Type::String.new) # Map types m.register_type(/^Map\(/i, ::ActiveRecord::Type::String.new) # Tuple types m.register_type(/^Tuple\(/i, ::ActiveRecord::Type::String.new) # Enum types (treated as strings) m.register_type(/^Enum/i, ::ActiveRecord::Type::String.new) # LowCardinality wrapper m.register_type(/^LowCardinality\((.+)\)/i) do |sql_type| inner_type = sql_type.match(/^LowCardinality\((.+)\)/i)[1] lookup_cast_type(inner_type) end end |
#native_database_types ⇒ Hash
Returns native database types
135 136 137 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 135 def native_database_types NATIVE_DATABASE_TYPES end |
#quote_column_name(name) ⇒ String
Quote a column name for ClickHouse ClickHouse uses backticks or double quotes for identifiers
475 476 477 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 475 def quote_column_name(name) "`#{name.to_s.gsub("`", "``")}`" end |
#quote_string(string) ⇒ String
Quote a string value for ClickHouse
491 492 493 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 491 def quote_string(string) string.gsub("\\", "\\\\\\\\").gsub("'", "\\\\'") end |
#quote_table_name(name) ⇒ String
Quote a table name for ClickHouse
483 484 485 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 483 def quote_table_name(name) "`#{name.to_s.gsub("`", "``")}`" end |
#reconnect! ⇒ void
This method returns an undefined value.
Reconnect to the database
175 176 177 178 179 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 175 def reconnect! super disconnect! connect end |
#reset! ⇒ void
This method returns an undefined value.
Clear the connection (called when returning connection to pool)
184 185 186 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 184 def reset! reconnect! end |
#supports_bulk_alter? ⇒ Boolean
ClickHouse doesn’t support bulk alter
287 288 289 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 287 def supports_bulk_alter? false end |
#supports_check_constraints? ⇒ Boolean
ClickHouse doesn’t support check constraints in the traditional sense
238 239 240 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 238 def supports_check_constraints? false end |
#supports_comments? ⇒ Boolean
ClickHouse doesn’t support standard comments on columns
280 281 282 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 280 def supports_comments? false end |
#supports_datetime_with_precision? ⇒ Boolean
ClickHouse supports datetime with precision (DateTime64)
266 267 268 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 266 def supports_datetime_with_precision? true end |
#supports_ddl_transactions? ⇒ Boolean
ClickHouse doesn’t support DDL transactions
203 204 205 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 203 def supports_ddl_transactions? false end |
#supports_explain? ⇒ Boolean
ClickHouse supports EXPLAIN
294 295 296 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 294 def supports_explain? true end |
#supports_expression_index? ⇒ Boolean
ClickHouse doesn’t support expression indexes
252 253 254 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 252 def supports_expression_index? false end |
#supports_foreign_keys? ⇒ Boolean
ClickHouse doesn’t support foreign keys
231 232 233 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 231 def supports_foreign_keys? false end |
#supports_insert_returning? ⇒ Boolean
ClickHouse doesn’t support INSERT RETURNING
224 225 226 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 224 def supports_insert_returning? false end |
#supports_json? ⇒ Boolean
ClickHouse supports JSON type (as String with JSON functions)
273 274 275 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 273 def supports_json? true end |
#supports_partial_index? ⇒ Boolean
ClickHouse doesn’t support partial indexes
245 246 247 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 245 def supports_partial_index? false end |
#supports_savepoints? ⇒ Boolean
ClickHouse doesn’t support savepoints
210 211 212 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 210 def supports_savepoints? false end |
#supports_transaction_isolation? ⇒ Boolean
ClickHouse doesn’t support transaction isolation levels
217 218 219 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 217 def supports_transaction_isolation? false end |
#supports_views? ⇒ Boolean
ClickHouse doesn’t support standard views (has MATERIALIZED VIEWS)
259 260 261 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 259 def supports_views? false end |