Module: ActiveRecord::ConnectionAdapters::ClickHouse::SchemaStatements

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

Constant Summary collapse

NON_TABLE_ENGINES =
%w[View MaterializedView LiveView].freeze

Instance Method Summary collapse

Instance Method Details

#add_index(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) ⇒ Object

Data-skipping indexes on existing tables; new parts index immediately, existing parts only after MATERIALIZE INDEX (not issued here).



389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 389

def add_index(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options)
  # The full abstract option list is accepted so cross-database migrations
  # port verbatim; only using:/granularity: affect the DDL. unique: is
  # unenforceable — ClickHouse has no unique indexes, so
  # index_exists?(unique: true) stays false.
  options.assert_valid_keys(valid_index_options)
  index_name = (name || index_name(table_name, column_name)).to_s
  validate_index_length!(table_name, index_name, internal)

  # bloom_filter serves equality lookups on any scalar type, so vanilla Rails
  # add_index calls port without edits; specialized types stay a using: away.
  execute(<<~SQL.squish)
    ALTER TABLE #{quote_table_name(table_name)}#{on_cluster_clause}
    ADD INDEX #{"IF NOT EXISTS " if if_not_exists}#{quote_column_name(index_name)}
    #{index_expression(column_name)} TYPE #{options.fetch(:using, "bloom_filter")}
    GRANULARITY #{options.fetch(:granularity, 1)}
  SQL
end

#add_projection(table_name, projection_name, select: "*", order: nil, group: nil) ⇒ Object

Projections are per-part alternate physical layouts (sort orders or pre-aggregations) the optimizer picks automatically; materialize_projection backfills parts written before the projection existed (async mutation).



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

def add_projection(table_name, projection_name, select: "*", order: nil, group: nil)
  body = ["SELECT #{select}"]
  body << "GROUP BY #{group}" if group
  body << "ORDER BY #{order}" if order
  execute(<<~SQL.squish)
    ALTER TABLE #{quote_table_name(table_name)}
    ADD PROJECTION #{quote_column_name(projection_name)} (#{body.join(" ")})
  SQL
end

#attach_partition(table_name, partition_id) ⇒ Object



218
219
220
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 218

def attach_partition(table_name, partition_id)
  alter_partition(table_name, "ATTACH", partition_id)
end

#build_change_column_default_definition(table_name, column_name, default_or_changes) ⇒ Object



104
105
106
107
108
109
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 104

def build_change_column_default_definition(table_name, column_name, default_or_changes)
  column = column_for(table_name, column_name)
  return unless column

  ChangeColumnDefaultDefinition.new(column, extract_new_default_value(default_or_changes))
end

#build_change_column_definition(table_name, column_name, type) ⇒ Object

Dry-run seams (Rails 7.1+): describe the change without executing it.



99
100
101
102
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 99

def build_change_column_definition(table_name, column_name, type, **)
  definition = create_table_definition(table_name)
  ChangeColumnDefinition.new(definition.new_column_definition(column_name, type, **), column_name)
end

#change_column(table_name, column_name, type, **options) ⇒ Object

MODIFY COLUMN takes the full new definition; existing rows are cast in a mutation, so incompatible narrowing surfaces as a server error. Like Rails, the new definition fully replaces the old: an omitted default clears an existing one (the server keeps it through a bare type change, probed 2026-07-14).



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 82

def change_column(table_name, column_name, type, **options)
  sql_type = changed_column_sql_type(type, options)
  new_default = options.key?(:default) && !options[:default].nil?
  default_clause =
    if new_default
      " DEFAULT #{quote(options[:default])}"
    elsif !options[:null]
      narrowing_placeholder_default(table_name, column_name, sql_type)
    end
  execute(<<~SQL.squish)
    ALTER TABLE #{quote_table_name(table_name)}#{on_cluster_clause}
    MODIFY COLUMN #{quote_column_name(column_name)} #{sql_type}#{default_clause}
  SQL
  change_column_default(table_name, column_name, nil) unless new_default
end

#change_column_comment(table_name, column_name, comment_or_changes) ⇒ Object



363
364
365
366
367
368
369
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 363

def change_column_comment(table_name, column_name, comment_or_changes)
  comment = extract_new_comment_value(comment_or_changes)
  execute(<<~SQL.squish)
    ALTER TABLE #{quote_table_name(table_name)}#{on_cluster_clause}
    COMMENT COLUMN #{quote_column_name(column_name)} #{quote(comment.to_s)}
  SQL
end

#change_column_default(table_name, column_name, default_or_changes) ⇒ Object

MODIFY COLUMN accepts DEFAULT without restating the type; REMOVE DEFAULT drops it (probed 2026-07-13) but errors when none exists (code 36, probed 2026-07-14) — Rails treats clearing an absent default as a no-op.



241
242
243
244
245
246
247
248
249
250
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 241

def change_column_default(table_name, column_name, default_or_changes)
  default = extract_new_default_value(default_or_changes)
  alteration = default_alteration_clause(table_name, column_name, default)
  return unless alteration

  execute(<<~SQL.squish)
    ALTER TABLE #{quote_table_name(table_name)}#{on_cluster_clause}
    MODIFY COLUMN #{quote_column_name(column_name)} #{alteration}
  SQL
end

#change_column_null(table_name, column_name, null, default = nil) ⇒ Object

Narrowing to non-Nullable would silently rewrite stored NULLs to the type default (26.6+) or fail mid-mutation (25.8, code 349), so the Rails backfill default runs first as a synchronous mutation and is required when NULLs exist.

Raises:

  • (ArgumentError)


114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 114

def change_column_null(table_name, column_name, null, default = nil)
  validate_change_column_null_argument!(null)

  column = columns(table_name).find { |candidate| candidate.name == column_name.to_s }
  raise ArgumentError, "no such column #{column_name} in #{table_name}" unless column

  inner_type = column.sql_type.sub(/\ANullable\((.*)\)\z/m, '\1')
  return widen_column_to_nullable(table_name, column_name, inner_type) if null

  if default.nil?
    assert_no_stored_nulls(table_name, column_name)
  else
    backfill_nulls(table_name, column_name, default)
  end
  narrow_column(table_name, column_name, inner_type, column)
end

#change_table_comment(table_name, comment_or_changes) ⇒ Object



371
372
373
374
375
376
377
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 371

def change_table_comment(table_name, comment_or_changes)
  comment = extract_new_comment_value(comment_or_changes)
  execute(<<~SQL.squish)
    ALTER TABLE #{quote_table_name(table_name)}#{on_cluster_clause}
    MODIFY COMMENT #{quote(comment.to_s)}
  SQL
end

#create_dictionary(name, source:, primary_key:, layout: :flat, lifetime: 300, database: nil) ⇒ Object

Dictionaries replace star-schema dimension JOINs with in-memory lookups. Columns are inferred from the source table, and the SOURCE clause carries the adapter's credentials — the dictionary's own loader authenticates separately and would otherwise connect as default (probed 2026-07-14).



179
180
181
182
183
184
185
186
187
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 179

def create_dictionary(name, source:, primary_key:, layout: :flat, lifetime: 300, database: nil)
  execute(<<~SQL.squish)
    CREATE DICTIONARY #{quote_table_name(name)} (#{dictionary_columns(source, database)})
    PRIMARY KEY #{quote_column_name(primary_key)}
    SOURCE(CLICKHOUSE(#{dictionary_source(source, database)}))
    LAYOUT(#{dictionary_layout(layout)})
    LIFETIME(#{dictionary_lifetime(lifetime)})
  SQL
end

#create_join_table(first_table, second_table, **options) ⇒ Object

HABTM join tables have an obvious sorting key: the two reference columns — unless they are nullable (sorting keys reject Nullable columns, PLAN.md §2).



30
31
32
33
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 30

def create_join_table(first_table, second_table, **options)
  options[:order] ||= join_table_sorting_key(first_table, second_table, options)
  super
end

#create_materialized_view(view_name, to: nil, as: nil) ⇒ Object

The insert-trigger half of the OLAP ingest-raw/read-aggregated idiom. A TO target is required: inner-storage views hide data in an implicit table and POPULATE misses concurrent inserts, so neither is supported.

Raises:

  • (ArgumentError)


134
135
136
137
138
139
140
141
142
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 134

def create_materialized_view(view_name, to: nil, as: nil)
  raise ArgumentError, "create_materialized_view requires to: (a target table)" if to.nil?
  raise ArgumentError, "create_materialized_view requires as: (a SELECT)" if as.nil?

  execute(<<~SQL.squish)
    CREATE MATERIALIZED VIEW #{quote_table_name(view_name)}
    TO #{quote_table_name(to)} AS #{as}
  SQL
end

#create_table(table_name, id: false, **options, &block) ⇒ Object

ClickHouse has no autoincrement; tables default to no id column and the sorting key acts as the primary key.



11
12
13
14
15
16
17
18
19
20
21
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 11

def create_table(table_name, id: false, **options, &block)
  clear_generatable_primary_key_cache
  options = internal_table_options(table_name, options)
  # With id: false Rails' own primary_key: kwarg is inert, so the DSL reuses the
  # ClickHouse clause name; renamed here because super would swallow it. With an
  # explicit id column the Rails meaning (pk column name) wins untouched.
  if id == false && options.key?(:primary_key)
    options[:primary_key_clause] = primary_key_clause(options.delete(:primary_key))
  end
  super
end

#data_source_exists?(name) ⇒ Boolean

Returns:

  • (Boolean)


272
273
274
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 272

def data_source_exists?(name)
  data_sources.include?(name.to_s)
end

#data_sourcesObject



268
269
270
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 268

def data_sources
  select_values(data_source_sql, "SCHEMA")
end

#detach_partition(table_name, partition_id) ⇒ Object



214
215
216
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 214

def detach_partition(table_name, partition_id)
  alter_partition(table_name, "DETACH", partition_id)
end

#dictionariesObject



193
194
195
196
197
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 193

def dictionaries
  select_values(<<~SQL.squish, "SCHEMA")
    SELECT name FROM system.dictionaries WHERE database = currentDatabase() ORDER BY name
  SQL
end

#drop_dictionary(name, if_exists: false) ⇒ Object



189
190
191
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 189

def drop_dictionary(name, if_exists: false)
  execute("DROP DICTIONARY #{"IF EXISTS " if if_exists}#{quote_table_name(name)}")
end

#drop_materialized_view(view_name, if_exists: false) ⇒ Object



144
145
146
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 144

def drop_materialized_view(view_name, if_exists: false)
  execute("DROP VIEW #{"IF EXISTS " if if_exists}#{quote_table_name(view_name)}")
end

#drop_partition(table_name, partition_id) ⇒ Object



222
223
224
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 222

def drop_partition(table_name, partition_id)
  alter_partition(table_name, "DROP", partition_id)
end

#drop_projection(table_name, projection_name, if_exists: false) ⇒ Object



161
162
163
164
165
166
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 161

def drop_projection(table_name, projection_name, if_exists: false)
  execute(<<~SQL.squish)
    ALTER TABLE #{quote_table_name(table_name)}
    DROP PROJECTION #{"IF EXISTS " if if_exists}#{quote_column_name(projection_name)}
  SQL
end

#drop_tableObject



23
24
25
26
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 23

def drop_table(*, **)
  clear_generatable_primary_key_cache
  super
end

#freeze_partition(table_name, partition_id, name: nil) ⇒ Object

Hard-links the partition into shadow/ as an instant local backup.



227
228
229
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 227

def freeze_partition(table_name, partition_id, name: nil)
  alter_partition(table_name, "FREEZE", partition_id, suffix: name && " WITH NAME #{quote(name)}")
end

#indexes(table_name) ⇒ Object



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 299

def indexes(table_name)
  skipping_indices_sql = <<~SQL.squish
    SELECT name, expr, type_full, granularity FROM system.data_skipping_indices
    WHERE database = currentDatabase() AND table = #{quote(table_name.to_s)}
  SQL

  select_all(skipping_indices_sql, "SCHEMA").map do |row|
    ClickHouse::IndexDefinition.new(
      table_name.to_s, row["name"],
      # The server stores the expression bare ("a, b", probed 2026-07-14);
      # Rails' index helpers expect a column-name array.
      columns: row["expr"].split(", "), using: row["type_full"], granularity: row["granularity"]
    )
  end
end

#internal_string_options_for_primary_keyObject

Rails' schema_migrations/ar_internal_metadata bookkeeping arrives via fixed create_table calls; give those tables an append-safe ReplacingMergeTree shape.



325
326
327
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 325

def internal_string_options_for_primary_key
  {}
end

#materialize_projection(table_name, projection_name) ⇒ Object



168
169
170
171
172
173
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 168

def materialize_projection(table_name, projection_name)
  execute(<<~SQL.squish)
    ALTER TABLE #{quote_table_name(table_name)}
    MATERIALIZE PROJECTION #{quote_column_name(projection_name)}
  SQL
end

#optimize_table(table_name, final: true) ⇒ Object

Forces an unscheduled merge; FINAL merges down to one part per partition — the maintenance verb that makes ReplacingMergeTree deduplication actually happen instead of eventually.



234
235
236
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 234

def optimize_table(table_name, final: true)
  execute("OPTIMIZE TABLE #{quote_table_name(table_name)}#{" FINAL" if final}")
end

#partitions(table_name) ⇒ Object

Partition lifecycle: the OLAP replacement for bulk deletes and archival. All verbs take the partition_id string (see #partitions) — the ID form is a plain quoted literal, so arbitrary expressions never reach the ALTER.



206
207
208
209
210
211
212
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 206

def partitions(table_name)
  select_values(<<~SQL.squish, "SCHEMA")
    SELECT DISTINCT partition_id FROM system.parts
    WHERE database = currentDatabase() AND table = #{quote(table_name.to_s)} AND active
    ORDER BY partition_id
  SQL
end

#primary_keys(table_name) ⇒ Object

ClickHouse PRIMARY KEY is an index prefix, not a uniqueness guarantee. The one shape safe to report as Active Record identity is a sorting key that is exactly one id-typed column — the same class of keys the adapter generates client-side (decision #64), so Rails auto-detects the pk on id-keyed tables. Composite, expression, and non-id sorting keys report none; models may still declare self.primary_key explicitly (decision #13).



282
283
284
285
286
287
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 282

def primary_keys(table_name)
  return [] if @suppress_primary_key_reporting

  column, = generatable_primary_key(table_name)
  column ? [column] : []
end

#reload_dictionary(name) ⇒ Object



199
200
201
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 199

def reload_dictionary(name)
  execute("SYSTEM RELOAD DICTIONARY #{quote_table_name(name)}")
end

#remove_column(table_name, column_name, type = nil, **options) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 42

def remove_column(table_name, column_name, type = nil, **options)
  return if options[:if_exists] == true && !column_exists?(table_name, column_name)

  # The DROP mutation refuses to break a skip index (UNKNOWN_IDENTIFIER,
  # probed 2026-07-14); Rails semantics drop dependent indexes with the column.
  indexes(table_name).each do |index|
    remove_index(table_name, name: index.name) if index.columns.include?(column_name.to_s)
  end
  execute(<<~SQL.squish)
    ALTER TABLE #{quote_table_name(table_name)}#{on_cluster_clause}
    #{remove_column_for_alter(table_name, column_name, type, **options)}
  SQL
end

#remove_index(table_name, column_name = nil, **options) ⇒ Object



408
409
410
411
412
413
414
415
416
417
418
419
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 408

def remove_index(table_name, column_name = nil, **options)
  return if options[:if_exists] && !index_exists?(table_name, column_name, **options)

  # Rails' resolver matches by columns, not derived name, so a custom-named
  # index is found by its columns and a name-shaped string is refused.
  name = index_name_for_remove(table_name, column_name, options.except(:if_exists))

  execute(<<~SQL.squish)
    ALTER TABLE #{quote_table_name(table_name)}#{on_cluster_clause}
    DROP INDEX #{quote_column_name(name)}
  SQL
end

#rename_column(table_name, column_name, new_column_name) ⇒ Object

The server rewrites skip-index expressions to the new column name itself (probed 2026-07-14); Rails' shared helper then renames auto-named indexes.



58
59
60
61
62
63
64
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 58

def rename_column(table_name, column_name, new_column_name)
  execute(<<~SQL.squish)
    ALTER TABLE #{quote_table_name(table_name)}#{on_cluster_clause}
    RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}
  SQL
  rename_column_indexes(table_name, column_name, new_column_name)
end

#rename_index(table_name, old_name, new_name) ⇒ Object

No RENAME INDEX in ClickHouse (probed 2026-07-14) — drop and re-add. New parts index immediately; existing parts after MATERIALIZE INDEX, same contract as add_index.

Raises:

  • (ArgumentError)


69
70
71
72
73
74
75
76
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 69

def rename_index(table_name, old_name, new_name)
  validate_index_length!(table_name, new_name.to_s)
  index = indexes(table_name).find { |candidate| candidate.name == old_name.to_s }
  raise ArgumentError, "no such index #{old_name} in #{table_name}" unless index

  remove_index(table_name, name: old_name)
  add_index(table_name, index.columns, name: new_name, using: index.using, granularity: index.granularity)
end

#rename_table(table_name, new_name) ⇒ Object



35
36
37
38
39
40
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 35

def rename_table(table_name, new_name, **)
  clear_generatable_primary_key_cache
  validate_table_length!(new_name.to_s)
  execute("RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}#{on_cluster_clause}")
  rename_table_indexes(table_name, new_name)
end

#table_comment(table_name) ⇒ Object



379
380
381
382
383
384
385
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 379

def table_comment(table_name)
  comment = select_value(<<~SQL.squish, "SCHEMA")
    SELECT comment FROM system.tables
    WHERE database = currentDatabase() AND name = #{quote(table_name.to_s)}
  SQL
  comment.presence
end

#table_exists?(table_name) ⇒ Boolean

Returns:

  • (Boolean)


260
261
262
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 260

def table_exists?(table_name)
  tables.include?(table_name.to_s)
end

#table_options(table_name) ⇒ Object

Everything system.tables knows about the table beyond columns, in the shape our create_table DSL accepts — this is what the schema dumper emits.



354
355
356
357
358
359
360
361
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 354

def table_options(table_name)
  row = select_one(<<~SQL.squish, "SCHEMA")
    SELECT engine_full, sorting_key, partition_key, primary_key, sampling_key
    FROM system.tables
    WHERE database = currentDatabase() AND name = #{quote(table_name.to_s)}
  SQL
  row ? dumpable_table_options(row) : {}
end

#tablesObject



252
253
254
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 252

def tables
  select_values(data_source_sql(type: "BASE TABLE"), "SCHEMA")
end

#type_to_sql(type, limit: nil, precision: nil, scale: nil) ⇒ Object



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 329

def type_to_sql(type, limit: nil, precision: nil, scale: nil, **)
  case type.to_s
  when "integer" then integer_to_sql(limit)
  # No autoincrement; a Rails-style pk is a plain Int64 filled client-side.
  when "bigint", "primary_key" then "Int64"
  # binary/blob included: ClickHouse String is an arbitrary byte sequence.
  when "string", "text", "binary", "blob" then "String"
  when "float" then "Float64"
  when "decimal", "numeric" then decimal_to_sql(precision, scale)
  # Rails' shared tests pass mysql-style parenthesized precision ("datetime(6)").
  # A nil precision here was explicit — Rails injects 6 for a bare t.datetime —
  # so it means the second-precision base type, like MySQL's plain datetime.
  when /\A(?:datetime|timestamp)(?:\((\d+)\))?\z/
    datetime_to_sql(Regexp.last_match(1) || precision)
  when "date" then "Date32"
  when "boolean" then "Bool"
  when "uuid" then "UUID"
  when "json" then "JSON"
  else
    clickhouse_type_verbatim(type)
  end
end

#valid_column_definition_optionsObject



319
320
321
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 319

def valid_column_definition_options
  super + %i[low_cardinality codec materialized alias]
end

#valid_table_definition_optionsObject



315
316
317
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 315

def valid_table_definition_options
  super + %i[engine order partition ttl settings primary_key_clause sample]
end

#view_exists?(view_name) ⇒ Boolean

Returns:

  • (Boolean)


264
265
266
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 264

def view_exists?(view_name)
  views.include?(view_name.to_s)
end

#viewsObject



256
257
258
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 256

def views
  select_values(data_source_sql(type: "VIEW"), "SCHEMA")
end

#with_suppressed_primary_key_reportingObject

The schema dumper opts out: with a reported pk Rails' dumper would imply the id column (degrading UInt64 to Int64 on reload) instead of dumping the id: false + explicit-columns + order: shape this adapter round-trips.



292
293
294
295
296
297
# File 'lib/active_record/connection_adapters/clickhouse/schema_statements.rb', line 292

def with_suppressed_primary_key_reporting
  @suppress_primary_key_reporting = true
  yield
ensure
  @suppress_primary_key_reporting = false
end