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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24efb62453474faa0b37357d98f5b0f8ba13fa98d7e633b5d3a7de5e16d875f6
4
- data.tar.gz: 0de2b8cae842c4262d81932c577df7731e570f347f0a22a2151ef9671bd8648d
3
+ metadata.gz: 6bd667df1779690780802a3f91133b9a6b8155f9f3c040393b5a722a299ce370
4
+ data.tar.gz: ed4008074efd597d2bc79ab2a4b3aea8e7ccccb37fd8158892843d20918b0015
5
5
  SHA512:
6
- metadata.gz: a55c35f42fef85b0373df52f06d1b50b66c4191382844ee6a9836049af03386085bfe9616de8bfc259fcfd6325d5a84f4cc8ca396d721ff87fdcfc49c80dae17
7
- data.tar.gz: b50f2484872aa18a0681c31abfd4d6a56e044ff9610cb288c73611cb0f56ed8b8242a771b9c4e95a07119ec094aef5bcf6c85f1bc363fd08e59d45a57b162902
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
- # extends https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L691
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
- configure_extensions
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 == 0
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 = @elapsed_time > timeout
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 0.001 # sleep 1 millisecond (or whatever)
72
+ sleep(retry_interval)
73
+ true
53
74
  end
54
75
  end
55
76
  end
56
77
 
57
78
  def configure_pragmas
58
- @config.fetch(:pragmas, []).each do |key, value|
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 configure_extensions
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
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module EnhancedSQLite3
4
- VERSION = '0.3.0'
2
+ VERSION = "0.5.0"
5
3
  end
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.3.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-06 00:00:00.000000000 Z
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.4.19
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.