7
7
require "active_record/connection_adapters/sqlite3_adapter"
8
8
require "enhanced_sqlite3/supports_virtual_columns"
9
9
require "enhanced_sqlite3/supports_deferrable_constraints"
10
+ require "enhanced_sqlite3/extralite/database_compatibility"
11
+ require "enhanced_sqlite3/extralite/adapter_compatibility"
10
12
11
13
module EnhancedSQLite3
12
14
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
+
13
48
# Setup the Rails SQLite3 adapter instance.
14
49
#
15
50
# extends https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L90
16
51
def initialize ( ...)
17
52
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
32
72
end
33
73
34
74
# Perform any necessary initialization upon the newly-established
@@ -111,7 +151,9 @@ def configure_pragmas
111
151
end
112
152
113
153
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
+
115
157
@config . fetch ( :extensions , [ ] ) . each do |extension_name |
116
158
require extension_name
117
159
extension_classname = extension_name . camelize
@@ -122,7 +164,7 @@ def configure_extensions
122
164
rescue NameError
123
165
Rails . logger . error ( "Failed to find the SQLite extension class: #{ extension_classname } . Skipping..." )
124
166
end
125
- @raw_connection . enable_load_extension ( false )
167
+ @raw_connection . enable_load_extension ( false ) if @raw_connection . is_a? ( :: SQLite3 :: Database )
126
168
end
127
169
end
128
170
end
0 commit comments