Skip to content

SQLite3 new_column_from_field doesn't pass cast_type to Column.new on ActiveRecord 8.1, leading to exceptions #1214

Description

@timriley

👋🏼 Hello! I might have found a bug here courtesy of dry-operation, where we test an optional ActiveRecord extension on JRuby against this adapter's master branch (since no released 80.x/81.x works with JRuby 10.1.x yet).

We don't commit Gemfile.lock, so we pull the very latest master on every run. After #1207 merged (which relaxed the activerecord dependency from ~> 8.0.0 to ~> 8.0), our CI started pulling in activerecord 8.1.3, and our ActiveRecord integration specs started failing on JRuby, after being green in the days earlier.

Our specs do nothing fancy: an in-memory SQLite DB, a one-column table, and a Model.create inside a transaction.

ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Schema.define do
  create_table(:x_ar_foo) { |t| t.string :bar }
end

model = Class.new(ActiveRecord::Base) { self.table_name = :x_ar_foo }
model.create(bar: "bar") # <- this now raises

The create is the first thing to touch the schema cache, which is where it blows up:

NoMethodError:
  undefined method 'mutable?' for nil
# ./vendor/bundle/jruby/4.0.0/gems/activerecord-8.1.3/lib/active_record/connection_adapters/column.rb:25:in 'initialize'
# ./vendor/bundle/jruby/4.0.0/bundler/gems/activerecord-jdbc-adapter-424d66984519/lib/arjdbc/sqlite3/column.rb:9:in 'initialize'
# ./vendor/bundle/jruby/4.0.0/gems/activerecord-8.1.3/lib/active_record/connection_adapters/deduplicable.rb:14:in 'new'
# ./vendor/bundle/jruby/4.0.0/bundler/gems/activerecord-jdbc-adapter-424d66984519/lib/arjdbc/sqlite3/adapter.rb:474:in 'new_column_from_field'

The versions at play here:

Likely root cause

ActiveRecord 8.1 added cast_type as a new required second positional argument for Column#initialize:

# activerecord 8.1.3, connection_adapters/column.rb
def initialize(name, cast_type, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **)
  ...
  @default = default.nil? || cast_type.mutable? ? default : cast_type.deserialize(default) # <- Line 25

(In 8.0, the signature was initialize(name, default, sql_type_metadata = nil, ...)).

SQLite3Column#initialize inside this adapter forwards its arguments via super, so the misaligned args come from the call to it in ArJdbc::SQLite3#new_column_from_field.

ActiveRecord::ConnectionAdapters::SQLite3Column.new(
  field["name"],
  default_value,        # <- lands in `cast_type`
  type_metadata,        # <- lands in `default`
  field["notnull"].to_i == 0,
  default_function,
  collation: field["collation"],
  ...
)

Here, cast_type receives default_value (nil for these columns) and AR's column.rb:25 calls nil.mutable?.

Fix

I'm not familiar enough with everything here to suggest an exact fix, but it seems like this cast_type will need to be built and passed through conditionally, only when using ActiveRecord 8.1 or later.

For our dry-operation CI in the meantime, we've worked around this by pinning our activerecord-jdbcsqlite3-adapter dependency to the 74aeab0 ref from this repo.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions