77require "active_record/connection_adapters/sqlite3_adapter"
88require "enhanced_sqlite3/supports_virtual_columns"
99require "enhanced_sqlite3/supports_deferrable_constraints"
10+ require "enhanced_sqlite3/extralite/database_compatibility"
11+ require "enhanced_sqlite3/extralite/adapter_compatibility"
1012
1113module EnhancedSQLite3
1214 module Adapter
15+ module ClassMethods
16+ def new_client ( config )
17+ if config [ :client ] == "extralite"
18+ new_client_extralite ( config )
19+ else
20+ super
21+ end
22+ end
23+
24+ def new_client_extralite ( config )
25+ config . delete ( :results_as_hash )
26+
27+ if config [ :strict ] == true
28+ raise ArgumentError , "The :strict option is not supported by the SQLite3 adapter using Extralite"
29+ end
30+
31+ unsupported_configuration_keys = config . keys - %i[ database readonly client adapter strict ]
32+ if unsupported_configuration_keys . any?
33+ raise ArgumentError , "Unsupported configuration options for SQLite3 adapter using Extralite: #{ unsupported_configuration_keys } "
34+ end
35+
36+ ::Extralite ::Database . new ( config [ :database ] . to_s , read_only : config [ :readonly ] ) . tap do |database |
37+ database . singleton_class . prepend EnhancedSQLite3 ::Extralite ::DatabaseCompatibility
38+ end
39+ rescue Errno ::ENOENT => error
40+ if error . message . include? ( "No such file or directory" )
41+ raise ActiveRecord ::NoDatabaseError
42+ else
43+ raise
44+ end
45+ end
46+ end
47+
1348 # Setup the Rails SQLite3 adapter instance.
1449 #
1550 # extends https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L90
1651 def initialize ( ...)
1752 super
18- # Ensure that all connections default to immediate transaction mode.
19- # This is necessary to prevent SQLite from deadlocking when concurrent processes open write transactions.
20- # By default, SQLite opens transactions in deferred mode, which means that a transactions acquire
21- # a shared lock on the database, but will attempt to upgrade that lock to an exclusive lock if/when
22- # a write is attempted. Because SQLite is in the middle of a transaction, it cannot retry the transaction
23- # if a BUSY exception is raised, and so it will immediately raise a SQLITE_BUSY exception without calling
24- # the `busy_handler`. Because Rails only wraps writes in transactions, this means that all transactions
25- # will attempt to acquire an exclusive lock on the database. Thus, under any concurrent load, you are very
26- # likely to encounter a SQLITE_BUSY exception.
27- # By setting the default transaction mode to immediate, SQLite will instead attempt to acquire
28- # an exclusive lock as soon as the transaction is opened. If the lock cannot be acquired, it will
29- # immediately call the `busy_handler` to retry the transaction. This allows concurrent processes to
30- # coordinate and linearize their transactions, avoiding deadlocks.
31- @connection_parameters . merge! ( default_transaction_mode : :immediate )
53+
54+ if @config [ :client ] == "extralite"
55+ singleton_class . prepend EnhancedSQLite3 ::Extralite ::AdapterCompatibility
56+ else
57+ # Ensure that all connections default to immediate transaction mode.
58+ # This is necessary to prevent SQLite from deadlocking when concurrent processes open write transactions.
59+ # By default, SQLite opens transactions in deferred mode, which means that a transactions acquire
60+ # a shared lock on the database, but will attempt to upgrade that lock to an exclusive lock if/when
61+ # a write is attempted. Because SQLite is in the middle of a transaction, it cannot retry the transaction
62+ # if a BUSY exception is raised, and so it will immediately raise a SQLITE_BUSY exception without calling
63+ # the `busy_handler`. Because Rails only wraps writes in transactions, this means that all transactions
64+ # will attempt to acquire an exclusive lock on the database. Thus, under any concurrent load, you are very
65+ # likely to encounter a SQLITE_BUSY exception.
66+ # By setting the default transaction mode to immediate, SQLite will instead attempt to acquire
67+ # an exclusive lock as soon as the transaction is opened. If the lock cannot be acquired, it will
68+ # immediately call the `busy_handler` to retry the transaction. This allows concurrent processes to
69+ # coordinate and linearize their transactions, avoiding deadlocks.
70+ @connection_parameters . merge! ( default_transaction_mode : :immediate )
71+ end
3272 end
3373
3474 # Perform any necessary initialization upon the newly-established
@@ -111,7 +151,9 @@ def configure_pragmas
111151 end
112152
113153 def configure_extensions
114- @raw_connection . enable_load_extension ( true )
154+ # NOTE: Extralite enables extension loading by default and doesn't provide an API to toggle it.
155+ @raw_connection . enable_load_extension ( true ) if @raw_connection . is_a? ( ::SQLite3 ::Database )
156+
115157 @config . fetch ( :extensions , [ ] ) . each do |extension_name |
116158 require extension_name
117159 extension_classname = extension_name . camelize
@@ -122,7 +164,7 @@ def configure_extensions
122164 rescue NameError
123165 Rails . logger . error ( "Failed to find the SQLite extension class: #{ extension_classname } . Skipping..." )
124166 end
125- @raw_connection . enable_load_extension ( false )
167+ @raw_connection . enable_load_extension ( false ) if @raw_connection . is_a? ( :: SQLite3 :: Database )
126168 end
127169 end
128170end
0 commit comments