activerecord-enhancedsqlite3-adapter 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/lib/enhanced_sqlite3/adapter.rb +67 -12
- data/lib/enhanced_sqlite3/version.rb +1 -3
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6bd667df1779690780802a3f91133b9a6b8155f9f3c040393b5a722a299ce370
|
4
|
+
data.tar.gz: ed4008074efd597d2bc79ab2a4b3aea8e7ccccb37fd8158892843d20918b0015
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3252cf16cc3619a857488b450542ef6b9402b666bdb1a5458d3615d880c073226cbf8d7472032b74bcfa205196f4febf0aaa8bc1fa2e6e00f00dc0d66e975d97
|
7
|
+
data.tar.gz: ddbafb131266c8edcf5e66303083ab964e8ac9248eaa12eeea4c377501f00e820b54edf406280eb832f0996120e666434e256e308a108d6df945bb21168f8416
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.5.0] - 2023-12-24
|
4
|
+
|
5
|
+
- Load extensions installed via project-scoped `sqlpkg`
|
6
|
+
|
7
|
+
## [0.4.0] - 2023-12-10
|
8
|
+
|
9
|
+
- Ensure transactions are IMMEDIATE and not DEFERRED
|
10
|
+
- Ensure that our `busy_handler` is the very first configuration to be set on a connection
|
11
|
+
- Simplify and speed up our `busy_handler` implementation
|
12
|
+
|
3
13
|
## [0.3.0] - 2023-12-06
|
4
14
|
|
5
15
|
- Added a more performant implementation of the the `timeout` mechanism
|
@@ -10,6 +10,27 @@ require "enhanced_sqlite3/supports_deferrable_constraints"
|
|
10
10
|
|
11
11
|
module EnhancedSQLite3
|
12
12
|
module Adapter
|
13
|
+
# Setup the Rails SQLite3 adapter instance.
|
14
|
+
#
|
15
|
+
# extends https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L90
|
16
|
+
def initialize(...)
|
17
|
+
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)
|
32
|
+
end
|
33
|
+
|
13
34
|
# Perform any necessary initialization upon the newly-established
|
14
35
|
# @raw_connection -- this is the place to modify the adapter's
|
15
36
|
# connection settings, run queries to configure any application-global
|
@@ -18,13 +39,12 @@ module EnhancedSQLite3
|
|
18
39
|
# Implementations may assume this method will only be called while
|
19
40
|
# holding @lock (or from #initialize).
|
20
41
|
#
|
21
|
-
#
|
42
|
+
# overrides https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L691
|
22
43
|
def configure_connection
|
23
|
-
super
|
24
|
-
|
25
44
|
configure_busy_handler_timeout
|
45
|
+
check_version
|
26
46
|
configure_pragmas
|
27
|
-
|
47
|
+
load_extensions
|
28
48
|
|
29
49
|
EnhancedSQLite3::SupportsVirtualColumns.apply!
|
30
50
|
EnhancedSQLite3::SupportsDeferrableConstraints.apply!
|
@@ -36,32 +56,67 @@ module EnhancedSQLite3
|
|
36
56
|
return unless @config.key?(:timeout)
|
37
57
|
|
38
58
|
timeout = self.class.type_cast_config_to_integer(@config[:timeout])
|
59
|
+
timeout_seconds = timeout.fdiv(1000)
|
60
|
+
retry_interval = 6e-5 # 60 microseconds
|
61
|
+
|
39
62
|
@raw_connection.busy_handler do |count|
|
40
63
|
timed_out = false
|
41
|
-
# capture the start time of this blocked write
|
42
|
-
@start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) if count == 0
|
43
64
|
# keep track of elapsed time every 100 iterations (to lower load)
|
44
|
-
if count % 100
|
45
|
-
@elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start_time
|
65
|
+
if (count % 100).zero?
|
46
66
|
# fail if we exceed the timeout value (captured from the timeout config option, converted to seconds)
|
47
|
-
timed_out =
|
67
|
+
timed_out = (count * retry_interval) > timeout_seconds
|
48
68
|
end
|
49
69
|
if timed_out
|
50
70
|
false # this will cause the BusyException to be raised
|
51
71
|
else
|
52
|
-
sleep
|
72
|
+
sleep(retry_interval)
|
73
|
+
true
|
53
74
|
end
|
54
75
|
end
|
55
76
|
end
|
56
77
|
|
57
78
|
def configure_pragmas
|
58
|
-
|
79
|
+
defaults = {
|
80
|
+
# Enforce foreign key constraints
|
81
|
+
# https://www.sqlite.org/pragma.html#pragma_foreign_keys
|
82
|
+
# https://www.sqlite.org/foreignkeys.html
|
83
|
+
"foreign_keys" => "ON",
|
84
|
+
# Impose a limit on the WAL file to prevent unlimited growth
|
85
|
+
# https://www.sqlite.org/pragma.html#pragma_journal_size_limit
|
86
|
+
"journal_size_limit" => 64.megabytes,
|
87
|
+
# Set the local connection cache to 2000 pages
|
88
|
+
# https://www.sqlite.org/pragma.html#pragma_cache_size
|
89
|
+
"cache_size" => 2000
|
90
|
+
}
|
91
|
+
unless @memory_database
|
92
|
+
defaults.merge!(
|
93
|
+
# Journal mode WAL allows for greater concurrency (many readers + one writer)
|
94
|
+
# https://www.sqlite.org/pragma.html#pragma_journal_mode
|
95
|
+
"journal_mode" => "WAL",
|
96
|
+
# Set more relaxed level of database durability
|
97
|
+
# 2 = "FULL" (sync on every write), 1 = "NORMAL" (sync every 1000 written pages) and 0 = "NONE"
|
98
|
+
# https://www.sqlite.org/pragma.html#pragma_synchronous
|
99
|
+
"synchronous" => "NORMAL",
|
100
|
+
# Set the global memory map so all processes can share some data
|
101
|
+
# https://www.sqlite.org/pragma.html#pragma_mmap_size
|
102
|
+
# https://www.sqlite.org/mmap.html
|
103
|
+
"mmap_size" => 128.megabytes
|
104
|
+
)
|
105
|
+
end
|
106
|
+
pragmas = defaults.merge(@config.fetch(:pragmas, {}))
|
107
|
+
|
108
|
+
pragmas.each do |key, value|
|
59
109
|
execute("PRAGMA #{key} = #{value}", "SCHEMA")
|
60
110
|
end
|
61
111
|
end
|
62
112
|
|
63
|
-
def
|
113
|
+
def load_extensions
|
64
114
|
@raw_connection.enable_load_extension(true)
|
115
|
+
# first, load any extensions installed via `sqlpkg`
|
116
|
+
Dir.glob(".sqlpkg/**/*.{dll,so,dylib}") do |extension_path|
|
117
|
+
@raw_connection.load_extension(extension_path)
|
118
|
+
end
|
119
|
+
# then, load any extensions specified in the `database.yml`
|
65
120
|
@config.fetch(:extensions, []).each do |extension_name|
|
66
121
|
require extension_name
|
67
122
|
extension_classname = extension_name.camelize
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-enhancedsqlite3-adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen Margheim
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-12-
|
11
|
+
date: 2023-12-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -137,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '0'
|
139
139
|
requirements: []
|
140
|
-
rubygems_version: 3.
|
140
|
+
rubygems_version: 3.5.1
|
141
141
|
signing_key:
|
142
142
|
specification_version: 4
|
143
143
|
summary: ActiveRecord adapter for SQLite that enhances the default.
|