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
113 114 115 116 117 118 119 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 113 def initialize(connection, logger = nil, = nil, config = {}) @config = config.symbolize_keys @chruby_client = nil @connection_parameters = nil super(connection, logger, config) end |
Class Method Details
.new_client(config) ⇒ ConnectionAdapter
Creates a new database connection Called by ActiveRecord’s connection handler
75 76 77 78 79 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 75 def new_client(config) chruby_config = build_chruby_config(config) chruby_config.validate! ClickhouseRuby::Client.new(chruby_config) end |
Instance Method Details
#active? ⇒ Boolean
Check if the connection is active
142 143 144 145 146 147 148 149 150 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 142 def active? return false unless @chruby_client # Ping ClickHouse to verify connection execute_internal('SELECT 1') true rescue ClickhouseRuby::Error false end |
#adapter_name ⇒ String
Returns the adapter name
124 125 126 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 124 def adapter_name ADAPTER_NAME end |
#arel_visitor ⇒ ArelVisitor
Returns the Arel visitor for ClickHouse SQL generation
498 499 500 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 498 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
440 441 442 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 440 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
448 449 450 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 448 def commit_db_transaction # No-op: ClickHouse doesn't support transactions end |
#connect ⇒ void
This method returns an undefined value.
Establish connection to ClickHouse
187 188 189 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 187 def connect @chruby_client = self.class.new_client(@config) end |
#connected? ⇒ Boolean
Check if connected to the database
155 156 157 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 155 def connected? !@chruby_client.nil? end |
#disconnect! ⇒ void
This method returns an undefined value.
Disconnect from the database
162 163 164 165 166 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 162 def disconnect! super @chruby_client&.close if @chruby_client.respond_to?(:close) @chruby_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
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 358 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
340 341 342 343 344 345 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 340 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
428 429 430 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 428 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
456 457 458 459 460 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 456 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
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 397 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
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 307 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
510 511 512 513 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 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 510 def initialize_type_map(m = type_map) # Register standard types register_class_with_limit m, %r{^String}i, ::ActiveRecord::Type::String register_class_with_limit m, %r{^FixedString}i, ::ActiveRecord::Type::String # Integer types m.register_type %r{^Int8}i, ::ActiveRecord::Type::Integer.new(limit: 1) m.register_type %r{^Int16}i, ::ActiveRecord::Type::Integer.new(limit: 2) m.register_type %r{^Int32}i, ::ActiveRecord::Type::Integer.new(limit: 4) m.register_type %r{^Int64}i, ::ActiveRecord::Type::Integer.new(limit: 8) m.register_type %r{^UInt8}i, ::ActiveRecord::Type::Integer.new(limit: 1) m.register_type %r{^UInt16}i, ::ActiveRecord::Type::Integer.new(limit: 2) m.register_type %r{^UInt32}i, ::ActiveRecord::Type::Integer.new(limit: 4) m.register_type %r{^UInt64}i, ::ActiveRecord::Type::Integer.new(limit: 8) # Float types m.register_type %r{^Float32}i, ::ActiveRecord::Type::Float.new m.register_type %r{^Float64}i, ::ActiveRecord::Type::Float.new # Decimal types m.register_type %r{^Decimal}i, ::ActiveRecord::Type::Decimal.new # Date/Time types m.register_type %r{^Date$}i, ::ActiveRecord::Type::Date.new m.register_type %r{^DateTime}i, ::ActiveRecord::Type::DateTime.new m.register_type %r{^DateTime64}i, ::ActiveRecord::Type::DateTime.new # Boolean (UInt8 with 0/1) m.register_type %r{^Bool}i, ::ActiveRecord::Type::Boolean.new # UUID m.register_type %r{^UUID}i, ::ActiveRecord::Type::String.new # Nullable wrapper - extract inner type m.register_type %r{^Nullable\((.+)\)}i do |sql_type| inner_type = sql_type.match(%r{^Nullable\((.+)\)}i)[1] lookup_cast_type(inner_type) end # Array types m.register_type %r{^Array\(}i, ::ActiveRecord::Type::String.new # Map types m.register_type %r{^Map\(}i, ::ActiveRecord::Type::String.new # Tuple types m.register_type %r{^Tuple\(}i, ::ActiveRecord::Type::String.new # Enum types (treated as strings) m.register_type %r{^Enum}i, ::ActiveRecord::Type::String.new # LowCardinality wrapper m.register_type %r{^LowCardinality\((.+)\)}i do |sql_type| inner_type = sql_type.match(%r{^LowCardinality\((.+)\)}i)[1] lookup_cast_type(inner_type) end end |
#native_database_types ⇒ Hash
Returns native database types
131 132 133 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 131 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
471 472 473 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 471 def quote_column_name(name) "`#{name.to_s.gsub('`', '``')}`" end |
#quote_string(string) ⇒ String
Quote a string value for ClickHouse
487 488 489 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 487 def quote_string(string) string.gsub("\\", "\\\\\\\\").gsub("'", "\\\\'") end |
#quote_table_name(name) ⇒ String
Quote a table name for ClickHouse
479 480 481 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 479 def quote_table_name(name) "`#{name.to_s.gsub('`', '``')}`" end |
#reconnect! ⇒ void
This method returns an undefined value.
Reconnect to the database
171 172 173 174 175 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 171 def reconnect! super disconnect! connect end |
#reset! ⇒ void
This method returns an undefined value.
Clear the connection (called when returning connection to pool)
180 181 182 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 180 def reset! reconnect! end |
#supports_bulk_alter? ⇒ Boolean
ClickHouse doesn’t support bulk alter
283 284 285 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 283 def supports_bulk_alter? false end |
#supports_check_constraints? ⇒ Boolean
ClickHouse doesn’t support check constraints in the traditional sense
234 235 236 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 234 def supports_check_constraints? false end |
#supports_comments? ⇒ Boolean
ClickHouse doesn’t support standard comments on columns
276 277 278 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 276 def supports_comments? false end |
#supports_datetime_with_precision? ⇒ Boolean
ClickHouse supports datetime with precision (DateTime64)
262 263 264 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 262 def supports_datetime_with_precision? true end |
#supports_ddl_transactions? ⇒ Boolean
ClickHouse doesn’t support DDL transactions
199 200 201 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 199 def supports_ddl_transactions? false end |
#supports_explain? ⇒ Boolean
ClickHouse supports EXPLAIN
290 291 292 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 290 def supports_explain? true end |
#supports_expression_index? ⇒ Boolean
ClickHouse doesn’t support expression indexes
248 249 250 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 248 def supports_expression_index? false end |
#supports_foreign_keys? ⇒ Boolean
ClickHouse doesn’t support foreign keys
227 228 229 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 227 def supports_foreign_keys? false end |
#supports_insert_returning? ⇒ Boolean
ClickHouse doesn’t support INSERT RETURNING
220 221 222 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 220 def supports_insert_returning? false end |
#supports_json? ⇒ Boolean
ClickHouse supports JSON type (as String with JSON functions)
269 270 271 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 269 def supports_json? true end |
#supports_partial_index? ⇒ Boolean
ClickHouse doesn’t support partial indexes
241 242 243 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 241 def supports_partial_index? false end |
#supports_savepoints? ⇒ Boolean
ClickHouse doesn’t support savepoints
206 207 208 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 206 def supports_savepoints? false end |
#supports_transaction_isolation? ⇒ Boolean
ClickHouse doesn’t support transaction isolation levels
213 214 215 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 213 def supports_transaction_isolation? false end |
#supports_views? ⇒ Boolean
ClickHouse doesn’t support standard views (has MATERIALIZED VIEWS)
255 256 257 |
# File 'lib/clickhouse_ruby/active_record/connection_adapter.rb', line 255 def supports_views? false end |