activerecord-turso 0.3.0 → 0.3.3

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: cfdd23462f8313c9a19d64bab33314e3d5a4b7532249695f01811ebcf3fcccbd
4
- data.tar.gz: 74e33f479629655b50621e300dc7a1d38b2d94d95edd2da9b57b3d18c5799dbe
3
+ metadata.gz: a01719b3af125da831484f92746047dcc6b93870d38cc328f47dacd356b9bc53
4
+ data.tar.gz: 86fad027bd12ace8b43f359c2118c9c04ade3f0b5408d516a1e3ec165963a6e2
5
5
  SHA512:
6
- metadata.gz: 9dee234b1fa1abd37883de7c10b876cad339858a41643000eee969348ae0ad1da8458a8b546145aa1c7e38648e5e7da3734d35fa484b08c0d87260dc170f3fe5
7
- data.tar.gz: 314bda7c65ccc415af50204690e098859517419f8f7ba035852a6230c0654c40f94a82ba3bd22ba54a829920cfb7f9159fbf1acc0de8e04816641669b6789938
6
+ metadata.gz: 5bb60d2fd6f7068774f587d06614d4023b0a047ac02e97deb98d393c94686174a7c04b9a4122c80e7e1971fce0cf272cf6b6f1dd49d9db1936ed8f36d1dff181
7
+ data.tar.gz: bf562f2e7c986ed051de4e800800e62e19624d146ee88d76a18db4727bde00540f1825962da035ad8caf7dfeee3c4467f055282f940418e521fbbb77b9efb5c8
data/README.md CHANGED
@@ -14,7 +14,7 @@ Production ready for **embedded/local** ActiveRecord 8.1 applications using the
14
14
 
15
15
  ## Runtime dependencies
16
16
 
17
- This adapter uses the `turso` gem and does **not** require the `sqlite3` Ruby gem at runtime.
17
+ This adapter uses the `turso` gem and does **not** require the `sqlite3` Ruby gem at runtime. The `sqlite3` gem is used only in the Rails conformance test harness.
18
18
 
19
19
  ## Installation
20
20
 
@@ -141,7 +141,7 @@ match = ActiveRecord::Base.connection.fts_match(:posts, [:title, :body], "databa
141
141
  Post.where(match)
142
142
 
143
143
  score = ActiveRecord::Base.connection.fts_score(:posts, [:title, :body], "database")
144
- Post.select(:id, :title, score.as("rank")).where(match).order("rank ASC")
144
+ Post.select(:id, :title, score.as("rank")).where(match).order("rank DESC")
145
145
  ```
146
146
 
147
147
  Note: `fts5` virtual tables are not supported. Use `USING fts` indexes instead. This requires the `index_method` experimental feature:
@@ -247,6 +247,12 @@ TURSO_TEST_EXPERIMENTAL_FEATURES=generated_columns bundle exec rake test
247
247
 
248
248
  The CI workflow in `.github/workflows/ci.yml` runs all three configurations automatically.
249
249
 
250
+ Run the Rails sqlite3 adapter conformance suite:
251
+
252
+ ```bash
253
+ bundle exec rake test:conformance
254
+ ```
255
+
250
256
  ## License
251
257
 
252
258
  MIT
@@ -6,6 +6,7 @@ module ActiveRecord
6
6
  module ConnectionManagement
7
7
  def self.included(base)
8
8
  base.extend(ClassMethods)
9
+ base.set_callback :checkout, :before, :reconnect_for_execution_context
9
10
  end
10
11
 
11
12
  module ClassMethods
@@ -17,24 +18,37 @@ module ActiveRecord
17
18
  end
18
19
 
19
20
  def active?
20
- return false unless @raw_connection
21
- !@raw_connection.closed?
21
+ connected?
22
+ end
23
+
24
+ def connected?
25
+ !(@raw_connection.nil? || @raw_connection.closed?)
22
26
  rescue ::Turso::Error
23
27
  false
24
28
  end
25
29
 
26
- def reconnect!(**)
27
- @lock.synchronize do
28
- disconnect!
29
- @raw_connection = self.class.new_client(@config)
30
- configure_connection
30
+ def disconnect!
31
+ super
32
+
33
+ if @raw_connection && !@raw_connection.closed?
34
+ begin
35
+ @raw_connection.execute("PRAGMA wal_checkpoint(TRUNCATE)")
36
+ rescue ::Turso::Error
37
+ end
38
+ @raw_connection.close
31
39
  end
40
+ @raw_connection = nil
32
41
  end
33
42
 
34
- def disconnect!
43
+ def reconnect!(**)
35
44
  @lock.synchronize do
36
- @raw_connection&.close
37
- @raw_connection = nil
45
+ disconnect!
46
+ begin
47
+ @raw_connection = self.class.new_client(@config)
48
+ configure_connection
49
+ rescue => original_exception
50
+ raise translate_exception_class(original_exception, nil, nil)
51
+ end
38
52
  end
39
53
  end
40
54
 
@@ -62,12 +76,24 @@ module ActiveRecord
62
76
 
63
77
  execute("PRAGMA foreign_keys = ON")
64
78
 
79
+ autocheckpoint = @config.fetch(:wal_autocheckpoint, 1000)
80
+ execute("PRAGMA wal_autocheckpoint = #{autocheckpoint.to_i}")
81
+
65
82
  timeout = @config[:busy_timeout] || @config[:timeout] || 5000
66
83
  @raw_connection.busy_timeout = timeout
67
84
 
68
85
  query_timeout = @config[:query_timeout] || 30_000
69
86
  @raw_connection.query_timeout = query_timeout
70
87
  end
88
+
89
+ private
90
+
91
+ def reconnect_for_execution_context
92
+ return unless @raw_connection
93
+ return if @raw_connection.owned_by_current_execution_context?
94
+
95
+ reconnect!
96
+ end
71
97
  end
72
98
  end
73
99
  end
@@ -69,6 +69,10 @@ module ActiveRecord
69
69
  type_map = build_type_map(stmt)
70
70
  ActiveRecord::Result.new(columns, rows, type_map, affected_rows: affected_rows)
71
71
  end
72
+ rescue
73
+ @statements.delete(sql) if prepare
74
+ stmt.close
75
+ raise
72
76
  ensure
73
77
  stmt.close unless prepare
74
78
  end
@@ -5,18 +5,17 @@ module ActiveRecord
5
5
  class TursoAdapter < SQLite3Adapter
6
6
  module ErrorTranslation
7
7
  def translate_exception(exception, message:, sql:, binds:)
8
- cause = exception.cause || exception
9
- case cause
8
+ case exception
10
9
  when ::Turso::ConstraintException
11
10
  translate_constraint_error(message, sql, binds)
12
11
  when ::Turso::NotADatabaseException
13
- ActiveRecord::NoDatabaseError.new(message, sql: sql, binds: binds)
12
+ ActiveRecord::NoDatabaseError.new(message, connection_pool: pool)
14
13
  when ::Turso::BusySnapshotException
15
14
  ActiveRecord::SerializationFailure.new(message, sql: sql, binds: binds)
16
15
  when ::Turso::BusyException
17
16
  ActiveRecordTurso::BusyError.new(message, sql: sql, binds: binds)
18
17
  when ::Turso::ReadonlyException
19
- ActiveRecord::ReadOnlyRecord.new(message, sql: sql, binds: binds)
18
+ ActiveRecord::ReadOnlyRecord.new(message)
20
19
  when ::Turso::IoException, ::Turso::CorruptException
21
20
  ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds)
22
21
  when ::Turso::DatabaseFullException
@@ -25,11 +24,21 @@ module ActiveRecord
25
24
  ActiveRecord::QueryCanceled.new(message, sql: sql, binds: binds)
26
25
  when ::Turso::MisuseException
27
26
  ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds)
27
+ when ::Turso::Exception
28
+ ActiveRecord::ConnectionFailed.new(message, connection_pool: pool)
28
29
  else
29
30
  super
30
31
  end
31
32
  end
32
33
 
34
+ def retryable_connection_error?(exception)
35
+ super || exception.is_a?(::Turso::Exception)
36
+ end
37
+
38
+ def retryable_query_error?(exception)
39
+ super || exception.is_a?(::Turso::Exception)
40
+ end
41
+
33
42
  private
34
43
 
35
44
  def translate_constraint_error(message, sql, binds)
@@ -97,7 +97,9 @@ module ActiveRecord
97
97
 
98
98
  def rollback_if_active
99
99
  return unless @raw_connection && !@raw_connection.closed?
100
- @raw_connection.execute("ROLLBACK") rescue nil
100
+ @raw_connection.execute("ROLLBACK")
101
+ rescue ::Turso::Error
102
+ disconnect! rescue nil
101
103
  end
102
104
 
103
105
  def backoff(base_delay_ms, retries)
@@ -26,6 +26,7 @@ module ActiveRecord
26
26
  FileUtils.rm_f(path)
27
27
  FileUtils.rm_f("#{path}-wal")
28
28
  FileUtils.rm_f("#{path}-shm")
29
+ FileUtils.rm_f(mvcc_log_path(path))
29
30
  end
30
31
 
31
32
  def purge
@@ -65,6 +66,7 @@ module ActiveRecord
65
66
  SELECT name FROM sqlite_master
66
67
  WHERE type = 'index'
67
68
  AND name NOT LIKE 'sqlite_%'
69
+ AND name NOT LIKE '__turso_internal_%'
68
70
  AND sql IS NOT NULL
69
71
  ORDER BY name
70
72
  SQL
@@ -106,6 +108,12 @@ module ActiveRecord
106
108
  @configuration
107
109
  end
108
110
 
111
+ # Turso stores the MVCC logical log beside the database as
112
+ # `<database>.db-log` (Rust's `Path::with_extension`).
113
+ def mvcc_log_path(path)
114
+ path.sub(/\.[^.\/]+\z/, "") + ".db-log"
115
+ end
116
+
109
117
  def connection
110
118
  ActiveRecord::Base.connection
111
119
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordTurso
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.3"
5
5
  end
@@ -17,3 +17,7 @@ require "active_record/tasks/database_tasks"
17
17
  require_relative "active_record/tasks/turso_database_tasks"
18
18
 
19
19
  ActiveRecord::Tasks::DatabaseTasks.register_task(/turso/, "ActiveRecord::Tasks::TursoDatabaseTasks")
20
+
21
+ at_exit do
22
+ ActiveRecord::Base.connection_pool&.disconnect! rescue nil
23
+ end
@@ -16,6 +16,7 @@ module Turso
16
16
 
17
17
  def initialize(config)
18
18
  @config = config
19
+ @owner = owner_token
19
20
  db_opts = {
20
21
  busy_timeout: config[:busy_timeout] || config[:timeout] || DEFAULT_BUSY_TIMEOUT_MS,
21
22
  query_timeout: config[:query_timeout] || DEFAULT_QUERY_TIMEOUT_MS
@@ -24,8 +25,8 @@ module Turso
24
25
  features = Array(config[:experimental_features]).map(&:to_s).reject(&:empty?)
25
26
  db_opts[:experimental_features] = features.join(",") unless features.empty?
26
27
  end
27
- database = ::Turso::Database.new(config[:database].to_s, **db_opts)
28
- @db = database.connection
28
+ @database = ::Turso::Database.new(config[:database].to_s, **db_opts)
29
+ @db = @database.connection
29
30
  @db.busy_timeout = db_opts[:busy_timeout]
30
31
  @db.query_timeout = db_opts[:query_timeout]
31
32
  end
@@ -38,6 +39,10 @@ module Turso
38
39
  !@db.closed?
39
40
  end
40
41
 
42
+ def owned_by_current_execution_context?
43
+ @owner == owner_token
44
+ end
45
+
41
46
  def disconnect!
42
47
  @db.close unless @db.closed?
43
48
  end
@@ -57,6 +62,10 @@ module Turso
57
62
 
58
63
  private
59
64
 
65
+ def owner_token
66
+ [Thread.current.object_id, Fiber.current.object_id]
67
+ end
68
+
60
69
  def normalize_binds(binds)
61
70
  binds.map do |value|
62
71
  case value
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-turso
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben D'Angelo
@@ -85,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
85
  - !ruby/object:Gem::Version
86
86
  version: '0'
87
87
  requirements: []
88
- rubygems_version: 4.0.15
88
+ rubygems_version: 4.0.18
89
89
  specification_version: 4
90
90
  summary: Embedded ActiveRecord adapter for Turso (SQLite-compatible)
91
91
  test_files: []