Module: ActiveRecord::ConnectionAdapters::ClickHouse::DatabaseStatements

Included in:
ActiveRecord::ConnectionAdapters::ClickHouseAdapter
Defined in:
lib/active_record/connection_adapters/clickhouse/database_statements.rb

Instance Method Summary collapse

Instance Method Details

#build_explain_clause(options = []) ⇒ Object



60
61
62
63
64
65
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 60

def build_explain_clause(options = [])
  variant = options.first || :plan
  EXPLAIN_VARIANTS.fetch(variant.to_sym) do
    raise ArgumentError, "unknown EXPLAIN variant #{variant.inspect}; use #{EXPLAIN_VARIANTS.keys.inspect}"
  end
end

#create_savepoint(_name = nil) ⇒ Object

Rails nests a SavepointTransaction inside any dirty transaction (e.g. the retry in create_or_find_by) even though supports_savepoints? is false. Like begin/commit/rollback (PLAN.md §5 decision 4), the savepoint verbs are honest no-ops — nothing transactional exists to restore.



146
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 146

def create_savepoint(_name = nil); end

#default_sequence_name(table_name, column_name) ⇒ Object

The "sequence" is the pk column itself; encode table.column so next_sequence_value can check it against the sorting key. Composite keys have no single column to generate.



78
79
80
81
82
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 78

def default_sequence_name(table_name, column_name)
  return nil if column_name.is_a?(Array)

  "#{table_name}.#{column_name}"
end

#delete(arel, name = nil, binds = []) ⇒ Object



124
125
126
127
128
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 124

def delete(arel, name = nil, binds = [])
  matched_rows = mutation_target_count(arel, name)
  result = super
  matched_rows || result
end

#exec_rollback_to_savepoint(_name = nil) ⇒ Object



147
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 147

def exec_rollback_to_savepoint(_name = nil); end

#explain(arel, binds = [], options = []) ⇒ Object

:nodoc:



54
55
56
57
58
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 54

def explain(arel, binds = [], options = []) # :nodoc:
  sql = "#{build_explain_clause(options)} #{to_sql(arel, binds)}"
  result = select_all(sql, "EXPLAIN", binds)
  ([result.columns.join("\t")] + result.rows.map { |row| row.join("\t") }).join("\n")
end

#high_precision_current_timestampObject



138
139
140
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 138

def high_precision_current_timestamp
  HIGH_PRECISION_CURRENT_TIMESTAMP
end

#insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [], returning: nil) ⇒ Object

No INSERT ... RETURNING in ClickHouse: the prefetched client-side id is the only post-insert-knowable value, so surface it aligned to the requested returning columns for _create_record's write-back. The signature is Rails' DatabaseStatements#insert contract.



108
109
110
111
112
113
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 108

def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [], returning: nil) # rubocop:disable Metrics/ParameterLists
  inserted_id = super(arel, name, pk, id_value, sequence_name, binds, returning: nil)
  return inserted_id if returning.nil?

  returning.map { |column| column.to_s == pk.to_s ? inserted_id : nil }
end

#insert_fixtures_set(fixture_set, tables_to_delete = []) ⇒ Object

The abstract version wraps bare DELETEs in a transaction; ClickHouse has neither, so fixtures load as TRUNCATE + batched INSERTs.



152
153
154
155
156
157
158
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 152

def insert_fixtures_set(fixture_set, tables_to_delete = [])
  statements = tables_to_delete.map { |table| build_truncate_statement(table) }
  statements += fixture_set.filter_map do |table_name, fixtures|
    build_fixture_sql(fixtures, table_name) unless fixtures.empty?
  end
  statements.each { |statement| execute(statement, "Fixtures Load") }
end

#insert_stream(table_name, rows, columns: nil) ⇒ Object

Bulk ingestion without materializing the batch: rows (any Enumerable of Hashes, lazy included) stream to the server as one chunked JSONCompactEachRow INSERT. Returns the server-reported written row count.



44
45
46
47
48
49
50
51
52
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 44

def insert_stream(table_name, rows, columns: nil)
  column_names = columns || stream_column_names(rows)
  sql = insert_stream_sql(table_name, column_names)
  lines = Enumerator.new do |yielder|
    rows.each { |row| yielder << encoded_stream_row(row, column_names) }
  end

  stream_lines(sql, "#{table_name} Stream Insert", lines)
end

#next_sequence_value(sequence_name) ⇒ Object

Sequence names are usually the "table.column" this adapter encodes, but models may override sequence_name with a free-form label (Oracle legacy); those get integer ids on trust — the prefetch gate already vetted the table. nil means a composite primary key, which Rails' prefetch path cannot populate.



88
89
90
91
92
93
94
95
96
97
98
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 88

def next_sequence_value(sequence_name)
  raise_ungeneratable_primary_key(sequence_name) if sequence_name.nil?

  table_name, column_name = sequence_name.to_s.split(".", 2)
  return generate_time_ordered_id unless column_name

  generatable_column, sql_type = generatable_primary_key(table_name)
  raise_ungeneratable_primary_key(sequence_name) unless generatable_column == column_name

  sql_type == "UUID" ? generate_uuid_v7 : generate_time_ordered_id
end

#prefetch_primary_key?(table_name = nil) ⇒ Boolean

No autoincrement and no INSERT ... RETURNING: primary keys are generated client-side before the INSERT (the Oracle-adapter prefetch seam), but only for tables whose sorting key is a single generatable column — Rails' prefetch path cannot handle composite primary keys.

Returns:

  • (Boolean)


71
72
73
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 71

def prefetch_primary_key?(table_name = nil)
  !table_name.nil? && !generatable_primary_key(table_name).nil?
end

#release_savepoint(_name = nil) ⇒ Object



148
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 148

def release_savepoint(_name = nil); end

#return_value_after_insert?(_column) ⇒ Boolean

Without RETURNING the server hands nothing back after an insert, whatever a column's default expression says; only the prefetched pk is knowable.

Returns:

  • (Boolean)


102
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 102

def return_value_after_insert?(_column) = false

#update(arel, name = nil, binds = []) ⇒ Object

Mutations report no affected-row count (X-ClickHouse-Summary stays all zeros — probed 2026-07-13), so matching rows are counted just before mutating. Rows touched by concurrent writes between the two statements are not reflected.



118
119
120
121
122
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 118

def update(arel, name = nil, binds = [])
  matched_rows = mutation_target_count(arel, name)
  result = super
  matched_rows || result
end

#with_request_settings(settings, &block) ⇒ Object

Scopes extra ClickHouse settings to every request made inside the block — the write-path counterpart of a relation's in-SQL SETTINGS clause. Validated here, before with_raw_connection wraps errors into StatementInvalid.



32
33
34
35
36
37
38
39
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 32

def with_request_settings(settings, &block)
  settings.each_key do |name|
    unless /\A[a-zA-Z_][a-zA-Z0-9_]*\z/.match?(name.to_s)
      raise ArgumentError, "invalid ClickHouse setting name: #{name.inspect}"
    end
  end
  with_raw_connection { |raw_connection| raw_connection.with_request_settings(settings, &block) }
end

#write_query?(sql) ⇒ Boolean

:nodoc:

Returns:

  • (Boolean)


17
18
19
20
21
# File 'lib/active_record/connection_adapters/clickhouse/database_statements.rb', line 17

def write_query?(sql) # :nodoc:
  !READ_QUERY.match?(sql)
rescue ArgumentError # non-UTF8 SQL, mirror the built-in adapters
  !READ_QUERY.match?(sql.b)
end