activerecord-libsql 0.1.2 → 0.1.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 +4 -4
- data/activerecord-libsql.gemspec +5 -7
- data/lib/active_record/connection_adapters/libsql_adapter.rb +27 -4
- data/lib/activerecord/libsql/version.rb +1 -1
- data/lib/turso_libsql/connection.rb +192 -0
- data/lib/turso_libsql/database.rb +153 -0
- data/lib/turso_libsql.rb +12 -0
- metadata +13 -15
- data/Cargo.lock +0 -2406
- data/Cargo.toml +0 -3
- data/ext/turso_libsql/Cargo.toml +0 -20
- data/ext/turso_libsql/extconf.rb +0 -6
- data/ext/turso_libsql/src/lib.rs +0 -299
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bc66869ba4dd6395bcac2e61abbef9e58dc7bb87f7fc23029aa19f2e5a2419b4
|
|
4
|
+
data.tar.gz: 5a4feeb7234a157cec8b3f8f2be015d24f5163581e42dc5c501b844830e4b704
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8aea75dac63b1dabf1775d84a99a7a7c80dde393436b72e9e76ae071889fc91f232d11cbcd272eac60c7ae734df2a6a805a3dc35ec976a58b191734b913c1a8e
|
|
7
|
+
data.tar.gz: f18071f7a38d32d9ca84ae82ae8e529c5c7e2237b4db87bae0f12eb591f30fbe97b5df599470c17a2dc59676335610cafe78f86ffc263d112a5bf38fd706459d
|
data/activerecord-libsql.gemspec
CHANGED
|
@@ -11,8 +11,9 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.summary = 'ActiveRecord adapter for Turso (libSQL) database'
|
|
12
12
|
spec.description = <<~DESC
|
|
13
13
|
An ActiveRecord adapter for Turso, the edge SQLite database powered by libSQL.
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
Connects to Turso Cloud via the Hrana v2 HTTP protocol using Ruby's built-in
|
|
15
|
+
Net::HTTP, making it fork-safe and dependency-free. Supports Embedded Replica
|
|
16
|
+
mode via the sqlite3 gem for local read performance.
|
|
16
17
|
DESC
|
|
17
18
|
spec.homepage = 'https://github.com/aileron-inc/activerecord-libsql'
|
|
18
19
|
spec.license = 'MIT'
|
|
@@ -23,17 +24,14 @@ Gem::Specification.new do |spec|
|
|
|
23
24
|
|
|
24
25
|
spec.files = Dir[
|
|
25
26
|
'lib/**/*.rb',
|
|
26
|
-
'ext/**/*.{rs,toml,rb}',
|
|
27
27
|
'*.md',
|
|
28
|
-
'*.gemspec'
|
|
29
|
-
'Cargo.{toml,lock}'
|
|
28
|
+
'*.gemspec'
|
|
30
29
|
]
|
|
31
30
|
|
|
32
31
|
spec.require_paths = ['lib']
|
|
33
|
-
spec.extensions = ['ext/turso_libsql/extconf.rb']
|
|
34
32
|
|
|
35
33
|
spec.add_dependency 'activerecord', '>= 7.0'
|
|
36
|
-
spec.add_dependency '
|
|
34
|
+
spec.add_dependency 'sqlite3', '>= 1.4'
|
|
37
35
|
|
|
38
36
|
spec.add_development_dependency 'rake', '~> 13.0'
|
|
39
37
|
spec.add_development_dependency 'rake-compiler', '~> 1.2'
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'active_record'
|
|
4
4
|
require 'active_record/connection_adapters/abstract_adapter'
|
|
5
|
-
require 'turso_libsql
|
|
5
|
+
require 'turso_libsql'
|
|
6
6
|
|
|
7
7
|
# AR 7.2+ のアダプター登録 API
|
|
8
8
|
ActiveSupport.on_load(:active_record) do
|
|
@@ -113,17 +113,40 @@ module ActiveRecord
|
|
|
113
113
|
false
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
-
def
|
|
117
|
-
@
|
|
116
|
+
def disconnect!
|
|
117
|
+
@raw_connection = nil
|
|
118
|
+
@raw_database = nil
|
|
118
119
|
super
|
|
119
120
|
end
|
|
120
121
|
|
|
121
|
-
|
|
122
|
+
# fork 後の子プロセスで呼ばれる。親プロセスの Rust オブジェクトを
|
|
123
|
+
# 子プロセスから触ると SEGV するため、参照を即座に破棄する。
|
|
124
|
+
# さらに tokio ランタイムも fork で壊れるため reinitialize_runtime! で作り直す。
|
|
125
|
+
# AR の ConnectionPool が fork 後に各コネクションに対して呼ぶ。
|
|
126
|
+
def discard!
|
|
122
127
|
@raw_connection = nil
|
|
123
128
|
@raw_database = nil
|
|
129
|
+
TursoLibsql.reinitialize_runtime!
|
|
124
130
|
super
|
|
125
131
|
end
|
|
126
132
|
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
# AR 8 の reconnect! が内部で呼ぶ private メソッド。
|
|
136
|
+
# 既存接続を破棄して新しい接続を確立する。
|
|
137
|
+
def reconnect
|
|
138
|
+
@raw_connection = nil
|
|
139
|
+
@raw_database = nil
|
|
140
|
+
@raw_database, @raw_connection = build_libsql_connection
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# AR 8 の connect! が内部で呼ぶ private メソッド(一部のパスで使われる)。
|
|
144
|
+
def connect
|
|
145
|
+
@raw_database, @raw_connection = build_libsql_connection
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
public
|
|
149
|
+
|
|
127
150
|
# Embedded Replica モードでリモートから最新フレームを手動同期する。
|
|
128
151
|
# Remote モードでは何もしない(no-op)。
|
|
129
152
|
def sync
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
module TursoLibsql
|
|
8
|
+
# Hrana v2 HTTP プロトコルを使ったリモート接続
|
|
9
|
+
# Net::HTTP を使用するため fork 後も安全
|
|
10
|
+
class Connection
|
|
11
|
+
def initialize(url, token)
|
|
12
|
+
@hrana_url = hrana_url(url)
|
|
13
|
+
@token = token
|
|
14
|
+
@baton = nil
|
|
15
|
+
@last_insert_rowid = 0
|
|
16
|
+
@last_affected_rows = 0
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# SQL を実行し、影響を受けた行数を返す(INSERT/UPDATE/DELETE 用)
|
|
20
|
+
def execute(sql)
|
|
21
|
+
execute_sql(sql, [])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# SQL を実行し、結果を Array of Hash で返す(SELECT 用)
|
|
25
|
+
def query(sql)
|
|
26
|
+
query_sql(sql, [])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# プリペアドステートメントで SQL を実行(パラメータ付き)
|
|
30
|
+
def execute_with_params(sql, params)
|
|
31
|
+
json_params = params.map { |p| { 'type' => 'text', 'value' => p.to_s } }
|
|
32
|
+
execute_sql(sql, json_params)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# トランザクションを開始する
|
|
36
|
+
def begin_transaction
|
|
37
|
+
requests = [{ 'type' => 'execute', 'stmt' => { 'sql' => 'BEGIN' } }]
|
|
38
|
+
resp = hrana_pipeline(nil, requests)
|
|
39
|
+
@baton = resp['baton']
|
|
40
|
+
check_errors(resp)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# トランザクションをコミットする
|
|
44
|
+
def commit_transaction
|
|
45
|
+
requests = [
|
|
46
|
+
{ 'type' => 'execute', 'stmt' => { 'sql' => 'COMMIT' } },
|
|
47
|
+
{ 'type' => 'close' }
|
|
48
|
+
]
|
|
49
|
+
resp = hrana_pipeline(@baton, requests)
|
|
50
|
+
@baton = nil
|
|
51
|
+
check_errors(resp)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# トランザクションをロールバックする
|
|
55
|
+
def rollback_transaction
|
|
56
|
+
requests = [
|
|
57
|
+
{ 'type' => 'execute', 'stmt' => { 'sql' => 'ROLLBACK' } },
|
|
58
|
+
{ 'type' => 'close' }
|
|
59
|
+
]
|
|
60
|
+
# baton が無効になっている場合(サーバー側でエラー後に破棄された場合)は
|
|
61
|
+
# baton なしで ROLLBACK を試みる。失敗しても無視する(接続は既に破棄済み)
|
|
62
|
+
baton = @baton
|
|
63
|
+
@baton = nil
|
|
64
|
+
begin
|
|
65
|
+
resp = hrana_pipeline(baton, requests)
|
|
66
|
+
check_errors(resp)
|
|
67
|
+
rescue StandardError
|
|
68
|
+
# ROLLBACK 失敗は無視(接続が既に破棄されている場合)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# 最後に挿入した行の rowid を返す
|
|
73
|
+
attr_reader :last_insert_rowid
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def execute_sql(sql, params)
|
|
78
|
+
stmt = build_stmt(sql, params)
|
|
79
|
+
requests = if @baton
|
|
80
|
+
[stmt]
|
|
81
|
+
else
|
|
82
|
+
[stmt, { 'type' => 'close' }]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
resp = hrana_pipeline(@baton, requests)
|
|
86
|
+
check_errors(resp)
|
|
87
|
+
|
|
88
|
+
@baton = resp['baton'] if @baton
|
|
89
|
+
if (results = resp['results'])&.first
|
|
90
|
+
@last_affected_rows = results.first.dig('response', 'result', 'affected_row_count').to_i
|
|
91
|
+
rowid_str = results.first.dig('response', 'result', 'last_insert_rowid')
|
|
92
|
+
@last_insert_rowid = rowid_str.to_i
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@last_affected_rows
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def query_sql(sql, params)
|
|
99
|
+
stmt = build_stmt(sql, params)
|
|
100
|
+
requests = if @baton
|
|
101
|
+
[stmt]
|
|
102
|
+
else
|
|
103
|
+
[stmt, { 'type' => 'close' }]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
resp = hrana_pipeline(@baton, requests)
|
|
107
|
+
check_errors(resp)
|
|
108
|
+
|
|
109
|
+
@baton = resp['baton'] if @baton
|
|
110
|
+
|
|
111
|
+
result = resp.dig('results', 0, 'response', 'result')
|
|
112
|
+
return [] unless result
|
|
113
|
+
|
|
114
|
+
cols = result['cols']&.map { |c| c['name'] } || []
|
|
115
|
+
rows = result['rows'] || []
|
|
116
|
+
|
|
117
|
+
rows.map do |row|
|
|
118
|
+
record = {}
|
|
119
|
+
cols.each_with_index do |col, i|
|
|
120
|
+
record[col] = hrana_value_to_ruby(row[i])
|
|
121
|
+
end
|
|
122
|
+
record
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def build_stmt(sql, params)
|
|
127
|
+
stmt = { 'sql' => sql }
|
|
128
|
+
stmt['args'] = params if params.any?
|
|
129
|
+
{ 'type' => 'execute', 'stmt' => stmt }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def hrana_pipeline(baton, requests)
|
|
133
|
+
body = { 'requests' => requests }
|
|
134
|
+
body['baton'] = baton if baton
|
|
135
|
+
|
|
136
|
+
uri = URI.parse(@hrana_url)
|
|
137
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
138
|
+
http.use_ssl = (uri.scheme == 'https')
|
|
139
|
+
http.open_timeout = 10
|
|
140
|
+
http.read_timeout = 30
|
|
141
|
+
|
|
142
|
+
request = Net::HTTP::Post.new(uri.path.empty? ? '/' : uri.path)
|
|
143
|
+
request['Authorization'] = "Bearer #{@token}"
|
|
144
|
+
request['Content-Type'] = 'application/json'
|
|
145
|
+
request.body = JSON.generate(body)
|
|
146
|
+
|
|
147
|
+
response = http.request(request)
|
|
148
|
+
|
|
149
|
+
raise "HTTP error #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
|
|
150
|
+
|
|
151
|
+
JSON.parse(response.body)
|
|
152
|
+
rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED,
|
|
153
|
+
SocketError, Socket::ResolutionError => e
|
|
154
|
+
raise "HTTP request failed: #{@hrana_url}: #{e.message}"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def check_errors(resp)
|
|
158
|
+
return unless (results = resp['results'])
|
|
159
|
+
|
|
160
|
+
results.each do |r|
|
|
161
|
+
next unless r['type'] == 'error'
|
|
162
|
+
|
|
163
|
+
msg = r.dig('error', 'message') || 'Unknown error'
|
|
164
|
+
raise msg
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def hrana_value_to_ruby(cell)
|
|
169
|
+
return nil if cell.nil?
|
|
170
|
+
|
|
171
|
+
type = cell['type']
|
|
172
|
+
val = cell['value']
|
|
173
|
+
|
|
174
|
+
case type
|
|
175
|
+
when 'null' then nil
|
|
176
|
+
when 'integer' then val.to_i
|
|
177
|
+
when 'float' then val.to_f
|
|
178
|
+
when 'text' then val.to_s
|
|
179
|
+
when 'blob' then val.to_s
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def hrana_url(url)
|
|
184
|
+
# libsql:// → https:// に変換
|
|
185
|
+
if url.start_with?('libsql://')
|
|
186
|
+
"https://#{url.sub('libsql://', '')}/v2/pipeline"
|
|
187
|
+
else
|
|
188
|
+
"#{url.chomp('/')}/v2/pipeline"
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'connection'
|
|
4
|
+
|
|
5
|
+
module TursoLibsql
|
|
6
|
+
# Database ラッパー
|
|
7
|
+
# リモート接続と Embedded Replica の両方をサポート
|
|
8
|
+
class Database
|
|
9
|
+
# リモート接続用 Database を作成
|
|
10
|
+
def self.new_remote(url, token)
|
|
11
|
+
new(mode: :remote, url: url, token: token)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Embedded Replica 用 Database を作成
|
|
15
|
+
def self.new_remote_replica(path, url, token, sync_interval_secs = 0)
|
|
16
|
+
new(mode: :replica, path: path, url: url, token: token, sync_interval: sync_interval_secs)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Offline write 用 Database を作成
|
|
20
|
+
def self.new_synced(path, url, token, sync_interval_secs = 0)
|
|
21
|
+
new(mode: :offline, path: path, url: url, token: token, sync_interval: sync_interval_secs)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize(mode:, url: nil, token: nil, path: nil, sync_interval: 0)
|
|
25
|
+
@mode = mode
|
|
26
|
+
@url = url
|
|
27
|
+
@token = token || ''
|
|
28
|
+
@path = path
|
|
29
|
+
@sync_interval = sync_interval
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# この Database から Connection を取得して返す
|
|
33
|
+
def connect
|
|
34
|
+
case @mode
|
|
35
|
+
when :remote
|
|
36
|
+
Connection.new(@url, @token)
|
|
37
|
+
when :replica, :offline
|
|
38
|
+
# ローカルファイルを開く(なければ作成される)
|
|
39
|
+
LocalConnection.new(@path, @url, @token, @mode)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# リモートから最新フレームを手動で同期する
|
|
44
|
+
def sync
|
|
45
|
+
case @mode
|
|
46
|
+
when :remote
|
|
47
|
+
# remote モードでは no-op
|
|
48
|
+
nil
|
|
49
|
+
when :replica, :offline
|
|
50
|
+
replica_sync(@path, @url, @token, @mode == :offline)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def replica_sync(path, url, token, offline)
|
|
57
|
+
remote_conn = Connection.new(url, token)
|
|
58
|
+
|
|
59
|
+
# ローカル DB を開く
|
|
60
|
+
require 'sqlite3'
|
|
61
|
+
local = SQLite3::Database.new(path)
|
|
62
|
+
local.results_as_hash = true
|
|
63
|
+
|
|
64
|
+
# remote からテーブル一覧を取得
|
|
65
|
+
tables = remote_conn.query(
|
|
66
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
|
67
|
+
).map { |r| r['name'] }
|
|
68
|
+
|
|
69
|
+
tables.each do |table|
|
|
70
|
+
# remote からスキーマを取得
|
|
71
|
+
schema_rows = remote_conn.query(
|
|
72
|
+
"SELECT sql FROM sqlite_master WHERE type='table' AND name='#{table.gsub("'", "''")}'"
|
|
73
|
+
)
|
|
74
|
+
next if schema_rows.empty?
|
|
75
|
+
|
|
76
|
+
schema = schema_rows.first['sql']
|
|
77
|
+
# CREATE TABLE "foo" (...) → CREATE TABLE IF NOT EXISTS "foo" (...)
|
|
78
|
+
create_sql = schema.sub(/\ACREATE TABLE\b/i, 'CREATE TABLE IF NOT EXISTS')
|
|
79
|
+
local.execute(create_sql)
|
|
80
|
+
|
|
81
|
+
# pull: remote → local(offline モードも pull してからローカル write を優先)
|
|
82
|
+
remote_rows = remote_conn.query("SELECT * FROM \"#{table.gsub('"', '""')}\"")
|
|
83
|
+
remote_rows.each do |row|
|
|
84
|
+
cols = row.keys.map { |c| "\"#{c.gsub('"', '""')}\"" }.join(', ')
|
|
85
|
+
vals = row.values.map { |v| v.nil? ? 'NULL' : "'#{v.to_s.gsub("'", "''")}'" }.join(', ')
|
|
86
|
+
local.execute("INSERT OR REPLACE INTO \"#{table.gsub('"', '""')}\" (#{cols}) VALUES (#{vals})")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
next unless offline
|
|
90
|
+
|
|
91
|
+
# push: local → remote(offline モードのみ)
|
|
92
|
+
# results_as_hash = true なので Hash の配列が返る
|
|
93
|
+
local_rows = local.execute("SELECT * FROM \"#{table.gsub('"', '""')}\"")
|
|
94
|
+
next if local_rows.empty?
|
|
95
|
+
|
|
96
|
+
local_rows.each do |row|
|
|
97
|
+
cols = row.keys.map { |c| "\"#{c.gsub('"', '""')}\"" }.join(', ')
|
|
98
|
+
vals = row.values.map { |v| v.nil? ? 'NULL' : "'#{v.to_s.gsub("'", "''")}'" }.join(', ')
|
|
99
|
+
remote_conn.execute("INSERT OR REPLACE INTO \"#{table.gsub('"', '""')}\" (#{cols}) VALUES (#{vals})")
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
local.close
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# ローカル SQLite 接続(Embedded Replica 用)
|
|
108
|
+
# sqlite3 gem を使用
|
|
109
|
+
class LocalConnection
|
|
110
|
+
def initialize(path, remote_url, token, mode)
|
|
111
|
+
require 'sqlite3'
|
|
112
|
+
@db = SQLite3::Database.new(path)
|
|
113
|
+
@db.results_as_hash = true
|
|
114
|
+
@remote_url = remote_url
|
|
115
|
+
@token = token
|
|
116
|
+
@mode = mode
|
|
117
|
+
@last_insert_rowid = 0
|
|
118
|
+
@last_affected_rows = 0
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def execute(sql)
|
|
122
|
+
@db.execute(sql)
|
|
123
|
+
@last_affected_rows = @db.changes
|
|
124
|
+
@last_insert_rowid = @db.last_insert_row_id
|
|
125
|
+
@last_affected_rows
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def query(sql)
|
|
129
|
+
@db.execute(sql)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def execute_with_params(sql, params)
|
|
133
|
+
@db.execute(sql, params)
|
|
134
|
+
@last_affected_rows = @db.changes
|
|
135
|
+
@last_insert_rowid = @db.last_insert_row_id
|
|
136
|
+
@last_affected_rows
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def begin_transaction
|
|
140
|
+
@db.execute('BEGIN')
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def commit_transaction
|
|
144
|
+
@db.execute('COMMIT')
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def rollback_transaction
|
|
148
|
+
@db.execute('ROLLBACK')
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
attr_reader :last_insert_rowid
|
|
152
|
+
end
|
|
153
|
+
end
|
data/lib/turso_libsql.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'turso_libsql/connection'
|
|
4
|
+
require_relative 'turso_libsql/database'
|
|
5
|
+
|
|
6
|
+
module TursoLibsql
|
|
7
|
+
# fork 後の子プロセスで呼ぶ(Ruby 実装では no-op)
|
|
8
|
+
# Net::HTTP は fork 後も安全なため何もしなくてよい
|
|
9
|
+
def self.reinitialize_runtime!
|
|
10
|
+
# no-op: Ruby の Net::HTTP は fork 後も安全
|
|
11
|
+
end
|
|
12
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-libsql
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- aileron
|
|
@@ -24,19 +24,19 @@ dependencies:
|
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '7.0'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
|
-
name:
|
|
27
|
+
name: sqlite3
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
|
-
- - "
|
|
30
|
+
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
32
|
+
version: '1.4'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
|
-
- - "
|
|
37
|
+
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '
|
|
39
|
+
version: '1.4'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: rake
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -81,25 +81,23 @@ dependencies:
|
|
|
81
81
|
version: '3.0'
|
|
82
82
|
description: |
|
|
83
83
|
An ActiveRecord adapter for Turso, the edge SQLite database powered by libSQL.
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
Connects to Turso Cloud via the Hrana v2 HTTP protocol using Ruby's built-in
|
|
85
|
+
Net::HTTP, making it fork-safe and dependency-free. Supports Embedded Replica
|
|
86
|
+
mode via the sqlite3 gem for local read performance.
|
|
86
87
|
email: []
|
|
87
88
|
executables: []
|
|
88
|
-
extensions:
|
|
89
|
-
- ext/turso_libsql/extconf.rb
|
|
89
|
+
extensions: []
|
|
90
90
|
extra_rdoc_files: []
|
|
91
91
|
files:
|
|
92
|
-
- Cargo.lock
|
|
93
|
-
- Cargo.toml
|
|
94
92
|
- README.md
|
|
95
93
|
- activerecord-libsql.gemspec
|
|
96
|
-
- ext/turso_libsql/Cargo.toml
|
|
97
|
-
- ext/turso_libsql/extconf.rb
|
|
98
|
-
- ext/turso_libsql/src/lib.rs
|
|
99
94
|
- lib/active_record/connection_adapters/libsql_adapter.rb
|
|
100
95
|
- lib/activerecord-libsql.rb
|
|
101
96
|
- lib/activerecord/libsql/railtie.rb
|
|
102
97
|
- lib/activerecord/libsql/version.rb
|
|
98
|
+
- lib/turso_libsql.rb
|
|
99
|
+
- lib/turso_libsql/connection.rb
|
|
100
|
+
- lib/turso_libsql/database.rb
|
|
103
101
|
homepage: https://github.com/aileron-inc/activerecord-libsql
|
|
104
102
|
licenses:
|
|
105
103
|
- MIT
|