activerecord-turso 0.2.2 → 0.3.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: b8e00bcf26be0b39f02a05bd0f4850db41d277f7d72bdb15013c218f032d93eb
4
- data.tar.gz: 9e9ccad1e346648fef21eb2d0a3aceafa7afada0bc67348a0ed912b723e6cca5
3
+ metadata.gz: cfdd23462f8313c9a19d64bab33314e3d5a4b7532249695f01811ebcf3fcccbd
4
+ data.tar.gz: 74e33f479629655b50621e300dc7a1d38b2d94d95edd2da9b57b3d18c5799dbe
5
5
  SHA512:
6
- metadata.gz: c16dce07f7dbcf2bd60d07e58b10d6960c7d4a2945f88deda0082c532b0e1e42cc58213582acd2548c0c19d72a13ef30e5688503ba8ab9a00b7fa99f882048e8
7
- data.tar.gz: 22f295b0eda449f11b5f31c63b16718e5f609452904b0c9c5b534117fe4145324460d3ed56f7ac7365f38345ac165eb8969a3e01bb5c21d59c2e9f3bef2f4f4d
6
+ metadata.gz: 9dee234b1fa1abd37883de7c10b876cad339858a41643000eee969348ae0ad1da8458a8b546145aa1c7e38648e5e7da3734d35fa484b08c0d87260dc170f3fe5
7
+ data.tar.gz: 314bda7c65ccc415af50204690e098859517419f8f7ba035852a6230c0654c40f94a82ba3bd22ba54a829920cfb7f9159fbf1acc0de8e04816641669b6789938
data/README.md CHANGED
@@ -4,7 +4,7 @@ ActiveRecord adapter for [Turso](https://turso.tech) (SQLite compatible db).
4
4
 
5
5
  ## Status
6
6
 
7
- Production ready for ActiveRecord 8.1 applications. The underlying `turso` gem releases the GVL during blocking operations, supports fiber-aware connection ownership, and uses native batch execution.
7
+ Production ready for **embedded/local** ActiveRecord 8.1 applications using the Turso SQLite-compatible database engine. This adapter does **not** support remote `libsql://` URLs or Turso cloud authentication. The underlying `turso` gem releases the GVL during blocking operations, supports fiber-aware connection ownership, and uses native batch execution.
8
8
 
9
9
  ## Requirements
10
10
 
@@ -114,6 +114,7 @@ development:
114
114
  - `transaction(concurrent: true)` requires the same database connection to be held for the entire retry loop. Rails' connection pool may reap the connection between retries, which can silently break MVCC semantics. Use this only when you understand the pooling behavior of your app.
115
115
  - `lock!`, `with_lock`, and `lock_version` are not meaningful under MVCC. Do not use them inside concurrent transactions.
116
116
  - If the retry limit is exhausted, the conflict is raised as `ActiveRecord::StatementInvalid`. You must handle it in application code.
117
+ - Normal ActiveRecord model persistence (`create!`, `save!`, `update!`, `touch`) is **not supported** inside a concurrent transaction because ActiveRecord opens its own internal transaction for each model change. Use raw SQL (`execute`, `exec_query`) or bulk operations (`insert_all`, `update_all`, `update_columns`) instead.
117
118
 
118
119
  ### Full-text search (Tantivy)
119
120
 
@@ -65,7 +65,8 @@ module ActiveRecord
65
65
  timeout = @config[:busy_timeout] || @config[:timeout] || 5000
66
66
  @raw_connection.busy_timeout = timeout
67
67
 
68
-
68
+ query_timeout = @config[:query_timeout] || 30_000
69
+ @raw_connection.query_timeout = query_timeout
69
70
  end
70
71
  end
71
72
  end
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  class TursoAdapter < SQLite3Adapter
6
6
  module ErrorTranslation
7
7
  def translate_exception(exception, message:, sql:, binds:)
8
- cause = exception.cause
8
+ cause = exception.cause || exception
9
9
  case cause
10
10
  when ::Turso::ConstraintException
11
11
  translate_constraint_error(message, sql, binds)
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/tasks/database_tasks"
4
+
5
+ module ActiveRecord
6
+ module Tasks
7
+ class TursoDatabaseTasks < SQLiteDatabaseTasks
8
+ def initialize(db_config, root = nil)
9
+ @configuration = db_config
10
+ end
11
+
12
+ def create
13
+ path = configuration.database
14
+ return if path == ":memory:"
15
+
16
+ FileUtils.mkdir_p(File.dirname(path))
17
+ establish_connection(configuration)
18
+ connection.execute("SELECT 1")
19
+ end
20
+
21
+ def drop
22
+ path = configuration.database
23
+ return if path == ":memory:"
24
+
25
+ disconnect!
26
+ FileUtils.rm_f(path)
27
+ FileUtils.rm_f("#{path}-wal")
28
+ FileUtils.rm_f("#{path}-shm")
29
+ end
30
+
31
+ def purge
32
+ drop
33
+ create
34
+ end
35
+
36
+ def charset
37
+ "UTF-8"
38
+ end
39
+
40
+ def charset_collation
41
+ nil
42
+ end
43
+
44
+ def structure_dump(filename, extra_flags)
45
+ establish_connection(configuration)
46
+ io = File.open(filename, "w")
47
+ io.puts("PRAGMA foreign_keys = OFF;")
48
+
49
+ tables = connection.query_values(<<~SQL, "SCHEMA")
50
+ SELECT name FROM sqlite_master
51
+ WHERE type = 'table'
52
+ AND name NOT LIKE 'sqlite_%'
53
+ AND name NOT LIKE '__turso_internal_%'
54
+ ORDER BY name
55
+ SQL
56
+
57
+ tables.each do |table|
58
+ sql = connection.query_value(<<~SQL, "SCHEMA")
59
+ SELECT sql FROM sqlite_master WHERE name = #{connection.quote(table)}
60
+ SQL
61
+ io.puts("#{sql};") if sql
62
+ end
63
+
64
+ indexes = connection.query_values(<<~SQL, "SCHEMA")
65
+ SELECT name FROM sqlite_master
66
+ WHERE type = 'index'
67
+ AND name NOT LIKE 'sqlite_%'
68
+ AND sql IS NOT NULL
69
+ ORDER BY name
70
+ SQL
71
+
72
+ indexes.each do |index|
73
+ sql = connection.query_value(<<~SQL, "SCHEMA")
74
+ SELECT sql FROM sqlite_master WHERE name = #{connection.quote(index)}
75
+ SQL
76
+ io.puts("#{sql};") if sql
77
+ end
78
+
79
+ views = connection.query_values(<<~SQL, "SCHEMA")
80
+ SELECT name FROM sqlite_master
81
+ WHERE type = 'view'
82
+ AND name NOT LIKE 'sqlite_%'
83
+ ORDER BY name
84
+ SQL
85
+
86
+ views.each do |view|
87
+ sql = connection.query_value(<<~SQL, "SCHEMA")
88
+ SELECT sql FROM sqlite_master WHERE name = #{connection.quote(view)}
89
+ SQL
90
+ io.puts("#{sql};") if sql
91
+ end
92
+
93
+ io.puts("PRAGMA foreign_keys = ON;")
94
+ io.close
95
+ end
96
+
97
+ def structure_load(filename, extra_flags)
98
+ establish_connection(configuration)
99
+ sql = File.read(filename)
100
+ ActiveRecord::Base.connection.raw_connection.execute_batch(sql)
101
+ end
102
+
103
+ private
104
+
105
+ def configuration
106
+ @configuration
107
+ end
108
+
109
+ def connection
110
+ ActiveRecord::Base.connection
111
+ end
112
+
113
+ def establish_connection(config)
114
+ @configuration = config
115
+ ActiveRecord::Base.establish_connection(config)
116
+ ActiveRecord::Base.connection.connect!
117
+ end
118
+
119
+ def disconnect!
120
+ ActiveRecord::Base.connection_pool.disconnect!
121
+ end
122
+ end
123
+ end
124
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordTurso
4
- VERSION = "0.2.2"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -12,3 +12,8 @@ ActiveRecord::ConnectionAdapters.register(
12
12
  "ActiveRecord::ConnectionAdapters::TursoAdapter",
13
13
  "active_record/connection_adapters/turso_adapter"
14
14
  )
15
+
16
+ require "active_record/tasks/database_tasks"
17
+ require_relative "active_record/tasks/turso_database_tasks"
18
+
19
+ ActiveRecord::Tasks::DatabaseTasks.register_task(/turso/, "ActiveRecord::Tasks::TursoDatabaseTasks")
@@ -8,7 +8,8 @@ module Turso
8
8
  extend Forwardable
9
9
 
10
10
  def_delegators :@db, :close, :closed?, :changes, :total_changes,
11
- :last_insert_rowid, :prepare, :interrupt, :busy_timeout=
11
+ :last_insert_rowid, :prepare, :interrupt, :busy_timeout=,
12
+ :query_timeout, :query_timeout=
12
13
 
13
14
  DEFAULT_BUSY_TIMEOUT_MS = 5000
14
15
  DEFAULT_QUERY_TIMEOUT_MS = 30_000
@@ -19,7 +20,10 @@ module Turso
19
20
  busy_timeout: config[:busy_timeout] || config[:timeout] || DEFAULT_BUSY_TIMEOUT_MS,
20
21
  query_timeout: config[:query_timeout] || DEFAULT_QUERY_TIMEOUT_MS
21
22
  }
22
- db_opts[:experimental_features] = config[:experimental_features] if config[:experimental_features]
23
+ if config[:experimental_features]
24
+ features = Array(config[:experimental_features]).map(&:to_s).reject(&:empty?)
25
+ db_opts[:experimental_features] = features.join(",") unless features.empty?
26
+ end
23
27
  database = ::Turso::Database.new(config[:database].to_s, **db_opts)
24
28
  @db = database.connection
25
29
  @db.busy_timeout = db_opts[:busy_timeout]
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.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben D'Angelo
@@ -63,6 +63,7 @@ files:
63
63
  - lib/active_record/connection_adapters/turso_adapter/schema_statements.rb
64
64
  - lib/active_record/connection_adapters/turso_adapter/statement_pool.rb
65
65
  - lib/active_record/connection_adapters/turso_adapter/transaction_management.rb
66
+ - lib/active_record/tasks/turso_database_tasks.rb
66
67
  - lib/activerecord-turso.rb
67
68
  - lib/activerecord-turso/error.rb
68
69
  - lib/activerecord-turso/version.rb
@@ -86,5 +87,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
87
  requirements: []
87
88
  rubygems_version: 4.0.15
88
89
  specification_version: 4
89
- summary: ActiveRecord adapter for Turso
90
+ summary: Embedded ActiveRecord adapter for Turso (SQLite-compatible)
90
91
  test_files: []