Module: EnhancedSQLite3::Adapter

Defined in:
lib/enhanced_sqlite3/adapter.rb

Instance Method Summary collapse

Instance Method Details

#configure_connectionObject

Perform any necessary initialization upon the newly-established connection settings, run queries to configure any application-global “session” variables, etc.

Implementations may assume this method will only be called while holding @lock (or from #initialize).

overrides github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L691



43
44
45
46
47
48
49
50
51
52
# File 'lib/enhanced_sqlite3/adapter.rb', line 43

def configure_connection
  configure_busy_handler_timeout
  check_version
  configure_pragmas
  configure_extensions

  EnhancedSQLite3::SupportsVirtualColumns.apply! unless try(:supports_virtual_columns?)
  EnhancedSQLite3::SupportsDeferrableConstraints.apply! unless try(:supports_deferrable_constraints?)
  EnhancedSQLite3::SupportsInsertReturning.apply! unless try(:supports_insert_returning?)
end

#initializeObject



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/enhanced_sqlite3/adapter.rb', line 16

def initialize(...)
  super
  # Ensure that all connections default to immediate transaction mode.
  # This is necessary to prevent SQLite from deadlocking when concurrent processes open write transactions.
  # By default, SQLite opens transactions in deferred mode, which means that a transactions acquire
  # a shared lock on the database, but will attempt to upgrade that lock to an exclusive lock if/when
  # a write is attempted. Because SQLite is in the middle of a transaction, it cannot retry the transaction
  # if a BUSY exception is raised, and so it will immediately raise a SQLITE_BUSY exception without calling
  # the `busy_handler`. Because Rails only wraps writes in transactions, this means that all transactions
  # will attempt to acquire an exclusive lock on the database. Thus, under any concurrent load, you are very
  # likely to encounter a SQLITE_BUSY exception.
  # By setting the default transaction mode to immediate, SQLite will instead attempt to acquire
  # an exclusive lock as soon as the transaction is opened. If the lock cannot be acquired, it will
  # immediately call the `busy_handler` to retry the transaction. This allows concurrent processes to
  # coordinate and linearize their transactions, avoiding deadlocks.
  @connection_parameters.merge!(default_transaction_mode: :immediate)
end

#logObject

Patch the #log method to ensure that all log messages are tagged with the database connection name.



62
63
64
65
66
67
68
69
# File 'lib/enhanced_sqlite3/adapter.rb', line 62

def log(...)
  db_connection_name = ActiveRecord::Base.connection_db_config.name
  if Rails.logger.formatter.current_tags.include? db_connection_name
    super
  else
    Rails.logger.tagged(db_connection_name) { super }
  end
end

#transactionObject

Patch the #transaction method to ensure that all transactions are sent to the writing role database connection pool.



55
56
57
58
59
# File 'lib/enhanced_sqlite3/adapter.rb', line 55

def transaction(...)
  ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role, prevent_writes: false) do
    super(...)
  end
end