Class: ActiveRecord::ConnectionAdapters::DuckdbAdapter

Overview

This adapter provides a connection to a DuckDB database.

Constant Summary collapse

ADAPTER_NAME =
'DuckDB'
MEMORY_MODE_KEYS =

DB configuration if used in memory mode

[:memory, 'memory', ':memory:', ':memory'].freeze
TYPE_MAP =

Type map for DuckDB SQL types to ActiveRecord types

Type::TypeMap.new.tap { |m| initialize_type_map(m) }
NATIVE_DATABASE_TYPES =

https://duckdb.org/docs/stable/sql/data_types/overview.html Integer limits (in bytes): tinyint=1, smallint=2, integer=4, bigint=8

{
  primary_key: 'INTEGER PRIMARY KEY',
  string: { name: 'VARCHAR' },
  integer: { name: 'INTEGER', limit: 4 },
  float: { name: 'REAL' },
  decimal: { name: 'DECIMAL' },
  datetime: { name: 'TIMESTAMP' },
  time: { name: 'TIME' },
  date: { name: 'DATE' },
  bigint: { name: 'BIGINT', limit: 8 },
  binary: { name: 'BLOB' },
  boolean: { name: 'BOOLEAN' },
  uuid: { name: 'UUID' },
  # DuckDB-specific signed integer types
  tinyint: { name: 'TINYINT', limit: 1 },
  smallint: { name: 'SMALLINT', limit: 2 },
  hugeint: { name: 'HUGEINT' },
  # DuckDB-specific unsigned integer types
  utinyint: { name: 'UTINYINT', limit: 1 },
  usmallint: { name: 'USMALLINT', limit: 2 },
  uinteger: { name: 'UINTEGER', limit: 4 },
  ubigint: { name: 'UBIGINT', limit: 8 },
  uhugeint: { name: 'UHUGEINT' },
  # Other DuckDB types
  interval: { name: 'INTERVAL' }
}.freeze
EARLY_SETTINGS =

Settings that MUST be applied before loading extensions

%i[allow_persistent_secrets allow_community_extensions].freeze
DEFAULT_SETTINGS =

Default DuckDB settings for secure and predictable behavior Note: lock_configuration is handled separately at the end of configure_connection

{
  allow_persistent_secrets: false,
  allow_community_extensions: false,
  autoinstall_known_extensions: false,
  autoload_known_extensions: false,
  threads: 1,
  memory_limit: '1GiB',
  max_temp_directory_size: '4GiB'
}.freeze

Constants included from ActiveRecord::ConnectionAdapters::Duckdb::SchemaStatements

ActiveRecord::ConnectionAdapters::Duckdb::SchemaStatements::DUMPABLE_DUCKLAKE_OPTIONS

Constants included from ActiveRecord::ConnectionAdapters::Duckdb::DatabaseStatements

ActiveRecord::ConnectionAdapters::Duckdb::DatabaseStatements::READ_QUERY_PATTERN

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ActiveRecord::ConnectionAdapters::Duckdb::SchemaStatementsRails80

#new_column_from_field

Methods included from ActiveRecord::ConnectionAdapters::Duckdb::SchemaStatementsRails81

#new_column_from_field

Methods included from ActiveRecord::ConnectionAdapters::Duckdb::DatabaseStatementsRails72

#exec_delete, #internal_exec_query

Methods included from ActiveRecord::ConnectionAdapters::Duckdb::DatabaseStatementsRails8

#raw_execute

Methods included from ActiveRecord::ConnectionAdapters::Duckdb::SchemaStatements

#create_sequence, #create_table, #create_table_definition, #data_source_sql, #drop_sequence, #ducklake_metadata_schema, #ducklake_options, #ducklake_table_options, #execute, #internal_string_options_for_primary_key, #lookup_cast_type_from_column, #partition_expressions, #quoted_scope, #reset_sequence!, #schema_creation, #sequence_exists?, #sequences, #set_ducklake_option, #set_partitioned_by, #tables, #type_to_sql, #views

Methods included from ActiveRecord::ConnectionAdapters::Duckdb::Quoting

#quote, #quote_column_name, #quote_table_name

Methods included from ActiveRecord::ConnectionAdapters::Duckdb::DatabaseStatements

#affected_rows, #begin_db_transaction, #cast_result, #columns_for_insert, #commit_db_transaction, #exec_rollback_db_transaction, #execute, #last_inserted_id, #write_query?

Methods included from ActiveRecord::ConnectionAdapters::Duckdb::DatabaseLimits

#index_name_length, #max_identifier_length, #table_alias_length, #table_name_length

Class Method Details

.configure_primary_key_type(type) ⇒ Symbol

Configures the primary key type at the class level

Parameters:

  • type (Symbol)

    The primary key type to set

Returns:

  • (Symbol)

    The configured primary key type



508
509
510
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 508

def self.configure_primary_key_type(type)
  self.primary_key_type = type
end

.database_exists?(config) ⇒ Boolean

Checks if the DuckDB database file exists

Parameters:

  • config (Hash)

    Database configuration containing database path

Returns:

  • (Boolean)

    true if database exists or is in-memory, false otherwise



248
249
250
251
252
253
254
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 248

def database_exists?(config)
  # Logic to check if database exists
  database_path = config[:database]
  return true if MEMORY_MODE_KEYS.include?(database_path)

  File.exist?(database_path.to_s)
end

.dbconsole(config, options = {}) ⇒ void

This method returns an undefined value.

Opens the DuckDB command line console

Parameters:

  • config (ActiveRecord::DatabaseConfigurations::DatabaseConfig)

    Database configuration

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

    Additional options for the console



237
238
239
240
241
242
243
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 237

def dbconsole(config, options = {})
  db_config = config.configuration_hash
  args = []
  args << db_config[:database] if db_config[:database] && !MEMORY_MODE_KEYS.include?(db_config[:database])

  find_cmd_and_exec('duckdb', *args)
end

.new_client(config) ⇒ DuckDB::Connection

Creates a new DuckDB database connection

Parameters:

  • config (Hash)

    Database configuration

Returns:

  • (DuckDB::Connection)

    The raw database connection



100
101
102
103
104
105
106
107
108
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 100

def new_client(config)
  database = config[:database] || :memory
  db = if MEMORY_MODE_KEYS.include?(database)
         DuckDB::Database.open
       else
         DuckDB::Database.open(database.to_s)
       end
  db.connect
end

Instance Method Details

#active?Boolean

Checks if the database connection is active

Returns:

  • (Boolean)

    true if connection is active, false otherwise



213
214
215
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 213

def active?
  !!(@raw_connection || @connection)
end

#column_definitions(table_name) ⇒ Array<Array>

Returns column definitions for a table using PRAGMA table_info

Parameters:

  • table_name (String)

    The name of the table

Returns:

  • (Array<Array>)

    Array of column definition arrays



423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 423

def column_definitions(table_name)
  # Use PRAGMA table_info which gives us more accurate type information
  # including primary key detection that information_schema might miss
  pragma_results = execute("PRAGMA table_info(#{quote(table_name.to_s)})", 'SCHEMA')
  pragma_results.map do |row|
    _cid, column_name, data_type, not_null, column_default, pk = row

    # Format the type properly - DuckDB PRAGMA gives us the actual type
    # Preserve VARCHAR limits and other type information
    formatted_type = case data_type.to_s.upcase
                     when /^BIGINT$/i
                       'BIGINT'
                     when /^INTEGER$/i
                       'INTEGER'
                     when /^VARCHAR$/i, /^VARCHAR\(\d+\)$/i
                       data_type.to_s  # Preserve the full VARCHAR(n) format
                     when /^DECIMAL\(\d+,\d+\)$/i
                       data_type.to_s  # Preserve DECIMAL(p,s) format
                     when /^TIMESTAMP$/i
                       'TIMESTAMP'
                     when /^BOOLEAN$/i
                       'BOOLEAN'
                     when /^UUID$/i
                       'UUID'
                     when /^BLOB$/i
                       'BLOB'
                     when /^DATE$/i
                       'DATE'
                     when /^TIME$/i
                       'TIME'
                     when /^REAL$/i, /^DOUBLE$/i
                       data_type.to_s
                     else
                       data_type.to_s
                     end

    # Convert PRAGMA results to match information_schema format
    # Note: DuckLake returns booleans (true/false) while regular DuckDB may return integers (1/0)
    [
      column_name,                  # column_name
      formatted_type,               # formatted_type
      column_default,               # column_default
      not_null.in?([1, true]),      # not_null (true if NOT NULL constraint)
      nil,                          # type_id
      nil,                          # type_modifier
      nil,                          # collation_name
      nil,                          # comment
      nil,                          # identity
      nil,                          # generated
      pk.in?([1, true])             # primary_key flag (true if primary key)
    ]
  end
end

#configuration_locked?Boolean

Checks if DuckDB configuration is locked. Once lock_configuration is set to true, no configuration changes can be made.

Returns:

  • (Boolean)

    true if configuration is locked



411
412
413
414
415
416
417
418
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 411

def configuration_locked?
  return false unless raw_connection

  result = raw_connection.query("SELECT current_setting('lock_configuration')")
  result.first&.first == true
rescue DuckDB::Error
  false
end

#configure_connectionvoid

This method returns an undefined value.

Configures the DuckDB connection with extensions, settings, secrets, and attachments. DuckDB locks configuration permanently after initial setup, so we skip reconfiguration if the connection is already configured. This is necessary because Rails/test-prof may call reset! which triggers configure_connection again.



393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 393

def configure_connection
  # Skip reconfiguration if already configured - DuckDB locks configuration permanently
  return if configuration_locked?

  super
  DuckDB.default_timezone = ActiveRecord.default_timezone
  apply_early_settings
  install_extensions
  apply_settings
  create_secrets
  attach_databases
  use_database
  lock_configuration
end

#create_savepoint(_name = nil) ⇒ Object

DuckDB does not support savepoints.

Parameters:

  • _name (String) (defaults to: nil)

    The savepoint name (ignored)

Raises:



330
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 330

def create_savepoint(_name = nil) = raise SavepointsNotSupported

#create_schema_dumper(options) ⇒ Duckdb::SchemaDumper

Creates a schema dumper instance for DuckDB Uses the DuckDB-specific SchemaDumper class to handle DuckDB types and DuckLake features

Parameters:

  • options (Hash)

    Schema dumper options

Returns:



606
607
608
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 606

def create_schema_dumper(options) # :nodoc:
  Duckdb::SchemaDumper.create(self, options)
end

#default_sequence_name(table_name, column_name = 'id') ⇒ String

Generates default sequence name following PostgreSQL/Oracle conventions

Parameters:

  • table_name (String)

    The name of the table

  • column_name (String) (defaults to: 'id')

    The name of the column (defaults to 'id')

Returns:

  • (String)

    The generated sequence name



501
502
503
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 501

def default_sequence_name(table_name, column_name = 'id')
  "#{table_name}_#{column_name}_seq"
end

#default_value_for_table(table_name) ⇒ nil

Returns default value for table in schema dumping

Parameters:

  • table_name (String)

    The name of the table

Returns:

  • (nil)

    Always returns nil to prevent sequence defaults at table level



539
540
541
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 539

def default_value_for_table(table_name)
  nil
end

#disconnectvoid

This method returns an undefined value.

Disconnects from the DuckDB database and cleans up the connection



206
207
208
209
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 206

def disconnect
  @connection&.close
  @connection = nil
end

#ducklake?Boolean

Detects if the current database is a DuckLake database Uses a metadata query to check the database type

Returns:

  • (Boolean)

    true if current database is DuckLake, false otherwise



274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 274

def ducklake?
  return @ducklake if defined?(@ducklake)

  @ducklake = begin
    with_raw_connection do |conn|
      result = conn.query('SELECT type FROM duckdb_databases() WHERE database_name = current_database()')
      db_type = result.first&.first
      db_type.to_s.downcase == 'ducklake'
    end
  rescue DuckDB::Error
    false
  end
end

#ducklake_extension_available?Boolean

Checks if the DuckLake extension is available and can be loaded

Returns:

  • (Boolean)

    true if DuckLake extension is available, false otherwise



290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 290

def ducklake_extension_available?
  return @ducklake_extension_available if defined?(@ducklake_extension_available)

  @ducklake_extension_available = begin
    with_raw_connection do |conn|
      conn.execute('INSTALL ducklake')
      conn.execute('LOAD ducklake')
    end
    true
  rescue DuckDB::Error
    false
  end
end

#exec_rollback_to_savepoint(_name = nil) ⇒ Object

DuckDB does not support savepoints.

Parameters:

  • _name (String) (defaults to: nil)

    The savepoint name (ignored)

Raises:



335
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 335

def exec_rollback_to_savepoint(_name = nil) = raise SavepointsNotSupported

#indexes(table_name = nil) ⇒ Array

Returns indexes for a table or all tables

Parameters:

  • table_name (String, nil) (defaults to: nil)

    The table name, or nil for all tables

Returns:

  • (Array)

    Array of index definitions



569
570
571
572
573
574
575
576
577
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 569

def indexes(table_name = nil)
  if table_name
    # Delegate to the schema statements implementation
    super
  else
    # This shouldn't happen in normal schema dumping, but handle gracefully
    []
  end
end

#lookup_cast_type(sql_type) ⇒ ActiveRecord::Type::Value

Looks up the cast type for a given SQL type string Uses the DuckDB TYPE_MAP to return appropriate ActiveRecord types This ensures BIGINT columns use BigInteger type for full 8-byte range support

Parameters:

  • sql_type (String)

    The SQL type string (e.g., 'BIGINT', 'INTEGER')

Returns:

  • (ActiveRecord::Type::Value)

    The corresponding ActiveRecord type



228
229
230
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 228

def lookup_cast_type(sql_type)
  TYPE_MAP.lookup(sql_type)
end

#native_database_typesHash

Returns the native database types supported by DuckDB DuckLake doesn't support PRIMARY KEY constraints, so we return a modified version

Returns:

  • (Hash)

    Hash mapping ActiveRecord types to DuckDB native types



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

def native_database_types
  return NATIVE_DATABASE_TYPES unless ducklake?

  # DuckLake doesn't support PRIMARY KEY/UNIQUE constraints
  @native_database_types ||= NATIVE_DATABASE_TYPES.merge(
    primary_key: 'INTEGER'
  )
end

#next_sequence_value(sequence_name) ⇒ String

Generates SQL for getting the next sequence value

Parameters:

  • sequence_name (String)

    The name of the sequence

Returns:

  • (String)

    SQL expression for next sequence value



493
494
495
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 493

def next_sequence_value(sequence_name)
  "nextval(#{quote(sequence_name)})"
end

#prefetch_primary_key?(_table_name) ⇒ Boolean

Determines if primary key should be prefetched before insert

Parameters:

  • _table_name (String)

    The table name (unused)

Returns:

  • (Boolean)

    always returns false to exclude auto-increment columns from INSERT



345
346
347
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 345

def prefetch_primary_key?(_table_name)
  false
end

#primary_key(table_name) ⇒ String?

Returns the primary key column name for a table

Parameters:

  • table_name (String)

    The name of the table

Returns:

  • (String, nil)

    The primary key column name, or nil if no single primary key



515
516
517
518
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 515

def primary_key(table_name)
  pk_columns = primary_keys(table_name)
  pk_columns.size == 1 ? pk_columns.first : nil
end

#primary_key_definition(table_name) ⇒ nil

Override to prevent Rails schema dumper from detecting sequence defaults as table defaults

Parameters:

  • table_name (String, Symbol)

    The name of the table

Returns:

  • (nil)

    Always returns nil to prevent table-level defaults



523
524
525
526
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 523

def primary_key_definition(table_name)
  # Always return nil to prevent any table-level defaults from sequence columns
  nil
end

#primary_key_type_for_schema_dump(table_name) ⇒ Symbol?

Determines the primary key type for schema dumping

Parameters:

  • table_name (String)

    The name of the table

Returns:

  • (Symbol, nil)

    The primary key type symbol, or nil if no primary key



582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 582

def primary_key_type_for_schema_dump(table_name)
  pk_column = primary_key(table_name)
  return nil unless pk_column

  # Get the actual column definition
  col_def = columns(table_name).find { |c| c.name == pk_column }
  return nil unless col_def

  case col_def.sql_type.to_s.upcase
  when 'BIGINT'
    :bigint
  when 'INTEGER'
    :integer
  when 'UUID'
    :uuid
  when /^VARCHAR/
    :string
  end
end

#primary_keys(table_name) ⇒ Array<String>

Returns the primary key columns for a table

Parameters:

  • table_name (String)

    The name of the table

Returns:

  • (Array<String>)

    Array of primary key column names

Raises:

  • (ArgumentError)

    if table_name is blank



362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 362

def primary_keys(table_name) # :nodoc:
  raise ArgumentError, 'table_name cannot be blank' unless table_name.present?

  results = execute("PRAGMA table_info(#{quote(table_name.to_s)})", 'SCHEMA')
  results.filter_map do |result|
    _cid, name, _type, _notnull, _dflt_value, pk = result

    # pk can be true, false, 1, 0, or nil in DuckDB PRAGMA table_info
    # true or 1 means it's a primary key, false, 0, or nil means it's not
    pk_value = pk == true ? 1 : pk
    [pk_value, name] if [true, 1].include?(pk)
  end.sort_by(&:first).map(&:last)
end

#raw_connectionDuckDB::Connection?

Returns the raw DuckDB connection object

Returns:

  • (DuckDB::Connection, nil)

    The raw connection object or nil if not connected



219
220
221
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 219

def raw_connection
  @raw_connection || @connection
end

#release_savepoint(_name = nil) ⇒ Object

DuckDB does not support savepoints.

Parameters:

  • _name (String) (defaults to: nil)

    The savepoint name (ignored)

Raises:



340
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 340

def release_savepoint(_name = nil) = raise SavepointsNotSupported

#return_value_after_insert?(column) ⇒ Boolean

Determines if a column value should be returned after insert

Parameters:

  • column (ActiveRecord::ConnectionAdapters::Column)

    The column to check

Returns:

  • (Boolean)

    true if column has default function or parent method returns true



313
314
315
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 313

def return_value_after_insert?(column)
  column.default_function.present? || super
end

#schema_type(column) ⇒ Symbol

Maps DuckDB column types to ActiveRecord schema types Override schema dumping to fix type detection

Parameters:

  • column (ActiveRecord::ConnectionAdapters::Column)

    The column object

Returns:

  • (Symbol)

    The Rails schema type for the column



547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 547

def schema_type(column)
  case column.sql_type
  when /^BIGINT$/i
    :bigint
  when /^INTEGER$/i
    :integer
  when /^VARCHAR$/i, /^VARCHAR\(\d+\)$/i
    :string
  when /^TIMESTAMP$/i
    :datetime
  when /^BOOLEAN$/i
    :boolean
  when /^UUID$/i
    :uuid
  else
    super
  end
end

#serial_sequence(table, column) ⇒ nil

Returns the sequence name for a serial column

Parameters:

  • table (String)

    The table name

  • column (String)

    The column name

Returns:

  • (nil)

    always returns nil as DuckDB doesn't use named sequences



353
354
355
356
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 353

def serial_sequence(table, column)
  # Return sequence name if using sequences, nil otherwise
  nil
end

#supports_insert_on_duplicate_skip?Boolean

Indicates whether the adapter supports INSERT ON DUPLICATE SKIP syntax

Returns:

  • (Boolean)

    always returns false for DuckDB



306
307
308
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 306

def supports_insert_on_duplicate_skip?
  false
end

#supports_insert_on_duplicate_update?Boolean

Indicates whether the adapter supports INSERT ON DUPLICATE UPDATE syntax

Returns:

  • (Boolean)

    always returns false for DuckDB



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

def supports_insert_on_duplicate_update?
  false
end

#supports_insert_returning?Boolean

Indicates whether the adapter supports INSERT RETURNING for Rails 8 DuckLake does NOT support INSERT...RETURNING, so we check for DuckLake mode

Returns:

  • (Boolean)

    true for regular DuckDB, false for DuckLake



267
268
269
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 267

def supports_insert_returning?
  !ducklake?
end

#supports_primary_key_type?(type) ⇒ Boolean

Indicates whether the adapter supports a specific primary key type Support Rails id: :uuid convention

Parameters:

  • type (Symbol)

    The primary key type to check

Returns:

  • (Boolean)

    true if the type is supported, false otherwise



481
482
483
484
485
486
487
488
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 481

def supports_primary_key_type?(type)
  case type
  when :uuid, :string, :integer, :bigint, :primary_key
    true
  else
    false
  end
end

#supports_savepoints?Boolean

DuckDB does not support savepoints at the SQL level.

Returns:

  • (Boolean)

    always returns false



325
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 325

def supports_savepoints? = false

#table_default_value(table_name) ⇒ nil

Override to ensure sequence defaults never appear at table level

Parameters:

  • table_name (String, Symbol)

    The name of the table

Returns:

  • (nil)

    Always returns nil as defaults belong to columns



531
532
533
534
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 531

def table_default_value(table_name)
  # Never return defaults at table level - they belong to columns
  nil
end

#table_options(table_name) ⇒ Hash

Returns table options for schema dumping

Parameters:

  • table_name (String)

    The name of the table

Returns:

  • (Hash)

    Hash of table options for schema dumping



613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 613

def table_options(table_name)
  options = {}

  # Check if primary key has sequence default
  pk_name = primary_key(table_name)
  pk_column = pk_name ? columns(table_name).find { |c| c.name == pk_name } : nil
  has_sequence_default = pk_column&.default_function&.include?('nextval(')

  pk_type = primary_key_type_for_schema_dump(table_name)
  if has_sequence_default
    # Force explicit primary key inclusion when sequence is involved
    # This prevents Rails from putting sequence default at table level
    options[:id] = pk_type || :bigint
  elsif pk_type && pk_type != :bigint
    # Set the correct primary key type only when different from default
    options[:id] = pk_type # Rails 5+ default is bigint
  end

  # NEVER include defaults at table level - sequence defaults belong to columns
  options
end

#use_insert_returning?Boolean

Indicates whether the adapter supports INSERT...RETURNING syntax DuckLake does NOT support INSERT...RETURNING, so we check for DuckLake mode

Returns:

  • (Boolean)

    true for regular DuckDB, false for DuckLake



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

def use_insert_returning?
  !ducklake?
end

#valid_type?(type) ⇒ Boolean

Check if a type is valid for schema dumping

Parameters:

  • type (Symbol, String)

    The type to validate

Returns:

  • (Boolean)

    true if type is valid for DuckDB schema dumping



638
639
640
641
642
643
644
645
646
647
648
# File 'lib/active_record/connection_adapters/duckdb_adapter.rb', line 638

def valid_type?(type)
  case type.to_s.to_sym
  when :string, :text, :integer, :bigint, :float, :decimal, :datetime, :timestamp,
       :time, :date, :binary, :boolean, :uuid, :interval, :bit,
       :hugeint, :tinyint, :smallint, :utinyint, :usmallint, :uinteger, :ubigint, :uhugeint,
       :varint, :blob, :list, :struct, :map, :enum, :union, :real, :double, :numeric
    true
  else
    false
  end
end