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 +4 -4
- data/README.md +2 -1
- data/lib/active_record/connection_adapters/turso_adapter/connection_management.rb +2 -1
- data/lib/active_record/connection_adapters/turso_adapter/error_translation.rb +1 -1
- data/lib/active_record/tasks/turso_database_tasks.rb +124 -0
- data/lib/activerecord-turso/version.rb +1 -1
- data/lib/activerecord-turso.rb +5 -0
- data/lib/turso/ar/connection.rb +6 -2
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cfdd23462f8313c9a19d64bab33314e3d5a4b7532249695f01811ebcf3fcccbd
|
|
4
|
+
data.tar.gz: 74e33f479629655b50621e300dc7a1d38b2d94d95edd2da9b57b3d18c5799dbe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
|
@@ -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
|
data/lib/activerecord-turso.rb
CHANGED
|
@@ -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")
|
data/lib/turso/ar/connection.rb
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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: []
|