activerecord-libsql 0.1.1 → 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.
data/Cargo.toml DELETED
@@ -1,3 +0,0 @@
1
- [workspace]
2
- members = ["ext/turso_libsql"]
3
- resolver = "2"
@@ -1,20 +0,0 @@
1
- [package]
2
- name = "turso_libsql"
3
- version = "0.1.0"
4
- edition = "2021"
5
-
6
- [lib]
7
- name = "turso_libsql"
8
- crate-type = ["cdylib"]
9
-
10
- [dependencies]
11
- magnus = { version = "0.8", features = [] }
12
- libsql = { version = "0.9.23", features = ["encryption", "sync"] }
13
- tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
14
- once_cell = "1"
15
-
16
- [profile.release]
17
- opt-level = 3
18
- lto = true
19
- codegen-units = 1
20
- panic = "abort"
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'mkmf'
4
- require 'rb_sys/mkmf'
5
-
6
- create_rust_makefile('turso_libsql/turso_libsql')
@@ -1,299 +0,0 @@
1
- use magnus::{function, method, prelude::*, Error, Ruby};
2
- use once_cell::sync::OnceCell;
3
- use std::sync::Arc;
4
- use std::time::Duration;
5
- use tokio::runtime::Runtime;
6
-
7
- // グローバル Tokio ランタイム(libsql は async API のため必要)
8
- static RUNTIME: OnceCell<Runtime> = OnceCell::new();
9
-
10
- fn runtime() -> &'static Runtime {
11
- RUNTIME.get_or_init(|| {
12
- tokio::runtime::Builder::new_multi_thread()
13
- .enable_all()
14
- .build()
15
- .expect("Failed to create Tokio runtime")
16
- })
17
- }
18
-
19
- /// async ブロック内から Ruby の RuntimeError を生成するヘルパー
20
- fn mk_err(msg: impl std::fmt::Display) -> Error {
21
- let ruby = Ruby::get().expect("called outside Ruby thread");
22
- Error::new(ruby.exception_runtime_error(), msg.to_string())
23
- }
24
-
25
- // -----------------------------------------------------------------------
26
- // TursoDatabase — Database を保持するラッパー(sync のために必要)
27
- // -----------------------------------------------------------------------
28
-
29
- #[magnus::wrap(class = "TursoLibsql::Database", free_immediately, size)]
30
- struct TursoDatabase {
31
- inner: Arc<libsql::Database>,
32
- }
33
-
34
- impl TursoDatabase {
35
- /// リモート接続用 Database を作成(既存の remote モード)
36
- fn new_remote(url: String, token: String) -> Result<Self, Error> {
37
- let db = runtime().block_on(async {
38
- libsql::Builder::new_remote(url, token)
39
- .build()
40
- .await
41
- .map_err(mk_err)
42
- })?;
43
- Ok(Self { inner: Arc::new(db) })
44
- }
45
-
46
- /// Embedded Replica 用 Database を作成
47
- /// path: ローカル DB ファイルパス
48
- /// url: Turso リモート URL (libsql://...)
49
- /// token: 認証トークン
50
- /// sync_interval_secs: バックグラウンド自動同期間隔(秒)。0 なら手動のみ
51
- fn new_remote_replica(
52
- path: String,
53
- url: String,
54
- token: String,
55
- sync_interval_secs: u64,
56
- ) -> Result<Self, Error> {
57
- let db = runtime().block_on(async {
58
- let mut builder = libsql::Builder::new_remote_replica(path, url, token);
59
- if sync_interval_secs > 0 {
60
- builder = builder.sync_interval(Duration::from_secs(sync_interval_secs));
61
- }
62
- builder.build().await.map_err(mk_err)
63
- })?;
64
- Ok(Self { inner: Arc::new(db) })
65
- }
66
-
67
- /// Offline write 用 Database を作成
68
- /// write はローカルに書いてすぐ返す。sync() でまとめてリモートへ反映する。
69
- /// path: ローカル DB ファイルパス
70
- /// url: Turso リモート URL (libsql://...)
71
- /// token: 認証トークン
72
- /// sync_interval_secs: バックグラウンド自動同期間隔(秒)。0 なら手動のみ
73
- fn new_synced(
74
- path: String,
75
- url: String,
76
- token: String,
77
- sync_interval_secs: u64,
78
- ) -> Result<Self, Error> {
79
- let db = runtime().block_on(async {
80
- let mut builder = libsql::Builder::new_synced_database(path, url, token);
81
- if sync_interval_secs > 0 {
82
- builder = builder.sync_interval(Duration::from_secs(sync_interval_secs));
83
- }
84
- builder.build().await.map_err(mk_err)
85
- })?;
86
- Ok(Self { inner: Arc::new(db) })
87
- }
88
-
89
- /// リモートから最新フレームを手動で同期する(pull)
90
- /// offline モードでは write もまとめてリモートへ push される
91
- fn sync(&self) -> Result<(), Error> {
92
- let db = Arc::clone(&self.inner);
93
- runtime().block_on(async move {
94
- db.sync().await.map_err(mk_err)
95
- })?;
96
- Ok(())
97
- }
98
-
99
- /// この Database から Connection を取得して TursoConnection を返す
100
- fn connect(&self) -> Result<TursoConnection, Error> {
101
- let conn = self.inner.connect().map_err(mk_err)?;
102
- Ok(TursoConnection {
103
- inner: Arc::new(conn),
104
- })
105
- }
106
- }
107
-
108
- // -----------------------------------------------------------------------
109
- // TursoConnection — Ruby に公開する接続オブジェクト
110
- // -----------------------------------------------------------------------
111
-
112
- #[magnus::wrap(class = "TursoLibsql::Connection", free_immediately, size)]
113
- struct TursoConnection {
114
- inner: Arc<libsql::Connection>,
115
- }
116
-
117
- impl TursoConnection {
118
- /// 新しいリモート接続を作成する(Ruby: TursoLibsql::Connection.new(url, token))
119
- /// 後方互換のために残す。内部では TursoDatabase を経由する
120
- fn new(url: String, token: String) -> Result<Self, Error> {
121
- let db = runtime().block_on(async {
122
- libsql::Builder::new_remote(url, token)
123
- .build()
124
- .await
125
- .map_err(mk_err)
126
- })?;
127
- let conn = db.connect().map_err(mk_err)?;
128
- Ok(Self {
129
- inner: Arc::new(conn),
130
- })
131
- }
132
-
133
- /// SQL を実行し、影響を受けた行数を返す(INSERT/UPDATE/DELETE 用)
134
- fn execute(&self, sql: String) -> Result<u64, Error> {
135
- let conn = Arc::clone(&self.inner);
136
- runtime().block_on(async move {
137
- conn.execute(&sql, ()).await.map_err(mk_err)
138
- })
139
- }
140
-
141
- /// SQL を実行し、結果を Array of Hash で返す(SELECT 用)
142
- fn query(&self, sql: String) -> Result<magnus::RArray, Error> {
143
- let conn = Arc::clone(&self.inner);
144
-
145
- let rows_data: Vec<Vec<(String, libsql::Value)>> =
146
- runtime().block_on(async move {
147
- let mut rows = conn.query(&sql, ()).await.map_err(mk_err)?;
148
- let mut result: Vec<Vec<(String, libsql::Value)>> = Vec::new();
149
-
150
- while let Some(row) = rows.next().await.map_err(mk_err)? {
151
- let col_count = rows.column_count();
152
- let mut record: Vec<(String, libsql::Value)> =
153
- Vec::with_capacity(col_count as usize);
154
-
155
- for i in 0..col_count {
156
- let name = rows.column_name(i).unwrap_or("?").to_string();
157
- let val = row.get_value(i).map_err(mk_err)?;
158
- record.push((name, val));
159
- }
160
- result.push(record);
161
- }
162
- Ok::<_, Error>(result)
163
- })?;
164
-
165
- let ruby = Ruby::get().expect("called outside Ruby thread");
166
- let outer = ruby.ary_new_capa(rows_data.len());
167
- for record in rows_data {
168
- let hash = ruby.hash_new();
169
- for (col, val) in record {
170
- let ruby_key = ruby.str_new(&col);
171
- let ruby_val = libsql_value_to_ruby(&ruby, val)?;
172
- hash.aset(ruby_key, ruby_val)?;
173
- }
174
- outer.push(hash)?;
175
- }
176
- Ok(outer)
177
- }
178
-
179
- /// プリペアドステートメントで SQL を実行(パラメータ付き)
180
- fn execute_with_params(&self, sql: String, params: Vec<String>) -> Result<u64, Error> {
181
- let conn = Arc::clone(&self.inner);
182
- runtime().block_on(async move {
183
- let params: Vec<libsql::Value> =
184
- params.into_iter().map(libsql::Value::Text).collect();
185
- conn.execute(&sql, libsql::params_from_iter(params))
186
- .await
187
- .map_err(mk_err)
188
- })
189
- }
190
-
191
- /// トランザクションを開始する
192
- fn begin_transaction(&self) -> Result<(), Error> {
193
- let conn = Arc::clone(&self.inner);
194
- runtime().block_on(async move {
195
- conn.execute("BEGIN", ()).await.map_err(mk_err)
196
- })?;
197
- Ok(())
198
- }
199
-
200
- /// トランザクションをコミットする
201
- fn commit_transaction(&self) -> Result<(), Error> {
202
- let conn = Arc::clone(&self.inner);
203
- runtime().block_on(async move {
204
- conn.execute("COMMIT", ()).await.map_err(mk_err)
205
- })?;
206
- Ok(())
207
- }
208
-
209
- /// トランザクションをロールバックする
210
- fn rollback_transaction(&self) -> Result<(), Error> {
211
- let conn = Arc::clone(&self.inner);
212
- runtime().block_on(async move {
213
- conn.execute("ROLLBACK", ()).await.map_err(mk_err)
214
- })?;
215
- Ok(())
216
- }
217
-
218
- /// 最後に挿入した行の rowid を返す
219
- fn last_insert_rowid(&self) -> Result<i64, Error> {
220
- let conn = Arc::clone(&self.inner);
221
- runtime().block_on(async move {
222
- let mut rows = conn
223
- .query("SELECT last_insert_rowid()", ())
224
- .await
225
- .map_err(mk_err)?;
226
-
227
- if let Some(row) = rows.next().await.map_err(mk_err)? {
228
- row.get::<i64>(0).map_err(mk_err)
229
- } else {
230
- Ok(0)
231
- }
232
- })
233
- }
234
- }
235
-
236
- // -----------------------------------------------------------------------
237
- // libsql::Value → Ruby Value 変換
238
- // -----------------------------------------------------------------------
239
-
240
- fn libsql_value_to_ruby(ruby: &Ruby, val: libsql::Value) -> Result<magnus::Value, Error> {
241
- match val {
242
- libsql::Value::Null => Ok(ruby.qnil().as_value()),
243
- libsql::Value::Integer(i) => Ok(ruby.integer_from_i64(i).as_value()),
244
- libsql::Value::Real(f) => Ok(ruby.float_from_f64(f).as_value()),
245
- libsql::Value::Text(s) => Ok(ruby.str_new(&s).as_value()),
246
- libsql::Value::Blob(b) => Ok(ruby.str_from_slice(&b).as_value()),
247
- }
248
- }
249
-
250
- // -----------------------------------------------------------------------
251
- // Magnus init — Ruby 拡張のエントリポイント
252
- // -----------------------------------------------------------------------
253
-
254
- #[magnus::init]
255
- fn init(ruby: &Ruby) -> Result<(), Error> {
256
- let module = ruby.define_module("TursoLibsql")?;
257
-
258
- // TursoLibsql::Database
259
- let db_class = module.define_class("Database", ruby.class_object())?;
260
- db_class.define_singleton_method("new_remote", function!(TursoDatabase::new_remote, 2))?;
261
- db_class.define_singleton_method(
262
- "new_remote_replica",
263
- function!(TursoDatabase::new_remote_replica, 4),
264
- )?;
265
- db_class.define_singleton_method(
266
- "new_synced",
267
- function!(TursoDatabase::new_synced, 4),
268
- )?;
269
- db_class.define_method("sync", method!(TursoDatabase::sync, 0))?;
270
- db_class.define_method("connect", method!(TursoDatabase::connect, 0))?;
271
-
272
- // TursoLibsql::Connection
273
- let conn_class = module.define_class("Connection", ruby.class_object())?;
274
- conn_class.define_singleton_method("new", function!(TursoConnection::new, 2))?;
275
- conn_class.define_method("execute", method!(TursoConnection::execute, 1))?;
276
- conn_class.define_method("query", method!(TursoConnection::query, 1))?;
277
- conn_class.define_method(
278
- "execute_with_params",
279
- method!(TursoConnection::execute_with_params, 2),
280
- )?;
281
- conn_class.define_method(
282
- "begin_transaction",
283
- method!(TursoConnection::begin_transaction, 0),
284
- )?;
285
- conn_class.define_method(
286
- "commit_transaction",
287
- method!(TursoConnection::commit_transaction, 0),
288
- )?;
289
- conn_class.define_method(
290
- "rollback_transaction",
291
- method!(TursoConnection::rollback_transaction, 0),
292
- )?;
293
- conn_class.define_method(
294
- "last_insert_rowid",
295
- method!(TursoConnection::last_insert_rowid, 0),
296
- )?;
297
-
298
- Ok(())
299
- }