activerecord-libsql 0.1.5 → 0.1.6
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/CHANGELOG.md +128 -0
- data/README.md +88 -64
- data/lib/active_record/connection_adapters/libsql_adapter.rb +16 -6
- data/lib/activerecord/libsql/version.rb +1 -1
- data/lib/turso_libsql/connection.rb +7 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f696c91f955a35679cd2b4159bedc0a9b8429cd30397fe9631054de7f63eb510
|
|
4
|
+
data.tar.gz: 58df65dd6bfd7aad0a4f187c9935064b4d8d21d57079c44df5f51c765148aca0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0a1ec1b42c809eb6cc91516796bdd7ece669396f22415f4940e5a1051780638774948e558aaeefbb22f9837052c86575061ed408ae71b24f018b726d66424ce9
|
|
7
|
+
data.tar.gz: 8a99cd050bae2dbb898bb74bb798afd7432a943f231efc10685b041ccd98b5a47449ad536f7216be53ed50d9a897bec37fcafd2f7d5f12fe7fd71e77921644ee
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.1.6] - 2026-03-16
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **`datetime` columns not stored in UTC, breaking `WHERE scheduled_at <= ?` comparisons**
|
|
10
|
+
- `NATIVE_DATABASE_TYPES` mapped `datetime`/`timestamp` to `'TEXT'`, causing `PRAGMA table_info`
|
|
11
|
+
to return `TEXT` for those columns. ActiveRecord's type map then resolved them to `Type::Text`,
|
|
12
|
+
which serializes `Time` objects via `to_s` without UTC normalization.
|
|
13
|
+
- Changed `datetime` → `'datetime'`, `timestamp` → `'datetime'`, `time` → `'time'`,
|
|
14
|
+
`date` → `'date'` so that `PRAGMA table_info` returns the correct type name and AR maps
|
|
15
|
+
them to `Type::DateTime` / `Type::Time` / `Type::Date`.
|
|
16
|
+
- `Type::DateTime` serializes `Time` objects to UTC strings (e.g. `"2026-03-16 04:18:14"`),
|
|
17
|
+
making string-based `<=` / `>=` comparisons consistent.
|
|
18
|
+
- This fixes Solid Queue's Dispatcher, which queries `WHERE scheduled_at <= ?` to find
|
|
19
|
+
due jobs — the query was always returning empty results when the server timezone was non-UTC.
|
|
20
|
+
- Added `initialize_type_map` override to explicitly register `datetime`/`timestamp` →
|
|
21
|
+
`Type::DateTime` as a safety net for existing databases with `TEXT`-typed datetime columns.
|
|
22
|
+
|
|
23
|
+
- **`cannot commit - no transaction is active` in Hrana HTTP client after fork**
|
|
24
|
+
- `TursoLibsql::Connection#commit_transaction` now rescues `RuntimeError` containing
|
|
25
|
+
`"no transaction is active"` or `"cannot commit"`, matching the existing behavior of
|
|
26
|
+
`LocalConnection` (SQLite3 backend) added in v0.1.4.
|
|
27
|
+
- Occurs when `ActiveSupport::ForkTracker` triggers `PoolConfig.discard_pools!` after fork,
|
|
28
|
+
and AR attempts to commit a transaction on the discarded connection before reconnecting.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **Solid Queue fork simulation integration tests** (`spec/integration/solid_queue_fork_spec.rb`)
|
|
33
|
+
- 5 examples reproducing the actual Solid Queue supervisor → worker fork flow.
|
|
34
|
+
- Verifies that child processes can `INSERT`, call `SolidQueue::Process.create!`,
|
|
35
|
+
run `FOR UPDATE SKIP LOCKED` queries, and that multiple concurrent workers all succeed.
|
|
36
|
+
- Verifies that the parent process continues to work after child forks.
|
|
37
|
+
|
|
38
|
+
## [0.1.5] - 2026-03-13
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- **`FOR UPDATE SKIP LOCKED` syntax error with Solid Queue** (#12)
|
|
43
|
+
- Solid Queue uses `SELECT ... FOR UPDATE SKIP LOCKED` by default (`use_skip_locked: true`).
|
|
44
|
+
libSQL and SQLite do not support this clause, causing a parse error at the backend.
|
|
45
|
+
- `LibsqlAdapter#perform_query` now strips `FOR UPDATE` / `FOR UPDATE SKIP LOCKED` / `FOR SHARE`
|
|
46
|
+
before sending SQL to either the Hrana HTTP API or the local SQLite file.
|
|
47
|
+
- SQLite serializes all writes, so row-level locking is semantically unnecessary.
|
|
48
|
+
- Added a regression test that reproduces the exact query Solid Queue's Dispatcher sends.
|
|
49
|
+
|
|
50
|
+
## [0.1.4] - 2026-03-13
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- **Fork safety for Embedded Replica mode** (#9)
|
|
55
|
+
- `sqlite3` gem 2.x automatically closes SQLite connections after `fork()` (ForkSafety).
|
|
56
|
+
In Solid Queue, the supervisor forks worker processes. If a transaction was open at fork
|
|
57
|
+
time, the child process would receive `cannot commit/rollback - no transaction is active`.
|
|
58
|
+
- `LocalConnection#commit_transaction` and `#rollback_transaction` now rescue
|
|
59
|
+
`SQLite3::Exception` and silently ignore the "no transaction is active" case.
|
|
60
|
+
- ActiveRecord's `discard!` already nils `@raw_connection`, so child processes reconnect
|
|
61
|
+
cleanly on the next query.
|
|
62
|
+
|
|
63
|
+
### Changed
|
|
64
|
+
|
|
65
|
+
- Rakefile: removed `rb_sys` / `rake/extensiontask` dependencies (no longer needed after #7).
|
|
66
|
+
`task default` changed from `:compile` to `:spec`.
|
|
67
|
+
- `rake release` now uses `jj bookmark track` before `jj git push` to avoid the
|
|
68
|
+
"Refusing to create new remote bookmark" error for new version tags.
|
|
69
|
+
|
|
70
|
+
## [0.1.3] - 2026-03-13
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
|
|
74
|
+
- **Replaced Rust native extension with pure Ruby implementation** (#7)
|
|
75
|
+
- The original Rust extension used `tokio` + `rustls` (and later `curl` + OpenSSL) for HTTP
|
|
76
|
+
transport. All Rust TLS implementations are unsafe after `fork()` on macOS, causing SEGV
|
|
77
|
+
or deadlocks in Puma / Unicorn / Solid Queue multi-process environments.
|
|
78
|
+
- New implementation uses Ruby's built-in `Net::HTTP` (fork-safe, no native dependencies).
|
|
79
|
+
- Hrana v2 HTTP protocol is implemented directly in `TursoLibsql::Connection`.
|
|
80
|
+
- `TursoLibsql::Database` provides a factory for remote, Embedded Replica, and offline modes.
|
|
81
|
+
- `TursoLibsql.reinitialize_runtime!` is now a no-op (kept for API compatibility).
|
|
82
|
+
- Added `fork_spec.rb` with 7 integration examples verifying fork safety.
|
|
83
|
+
|
|
84
|
+
### Added
|
|
85
|
+
|
|
86
|
+
- `sqlite3 >= 1.4` runtime dependency for Embedded Replica / offline write mode.
|
|
87
|
+
|
|
88
|
+
### Removed
|
|
89
|
+
|
|
90
|
+
- Rust extension (`ext/turso_libsql/`), `rb_sys`, and `rake-compiler` dependencies.
|
|
91
|
+
No `bundle exec rake compile` step is needed anymore.
|
|
92
|
+
|
|
93
|
+
### Fixed
|
|
94
|
+
|
|
95
|
+
- `replica_sync`: SQL generation bug — `CREATE TABLE "foo" (...)` was being mangled.
|
|
96
|
+
Fixed by using `schema.sub(/\ACREATE TABLE\b/i, 'CREATE TABLE IF NOT EXISTS')`.
|
|
97
|
+
- Offline push: `results_as_hash = true` returns Hash rows; use `row.keys` / `row.values`.
|
|
98
|
+
|
|
99
|
+
## [0.1.2] - 2026-03-12
|
|
100
|
+
|
|
101
|
+
### Fixed
|
|
102
|
+
|
|
103
|
+
- `ActiveRecord::Result.empty` compatibility for `affected_rows` (#5).
|
|
104
|
+
`Result.empty` is a frozen singleton in AR 8; calling `.rows` on it is safe but
|
|
105
|
+
the adapter was incorrectly reading `affected_row_count` from it.
|
|
106
|
+
- Added Solid Queue integration tests (10 examples) covering `INSERT` into
|
|
107
|
+
`solid_queue_processes`, `solid_queue_jobs`, and related tables.
|
|
108
|
+
|
|
109
|
+
## [0.1.1] - 2026-03-12
|
|
110
|
+
|
|
111
|
+
### Fixed
|
|
112
|
+
|
|
113
|
+
- `Column.new` signature compatibility across ActiveRecord versions (#4):
|
|
114
|
+
- AR <= 8.0: `Column.new(name, default, sql_type_metadata, null)`
|
|
115
|
+
- AR >= 8.1: `Column.new(name, cast_type, default, sql_type_metadata, null)`
|
|
116
|
+
- Resolved at load time via `COLUMN_BUILDER` lambda to avoid per-call version checks.
|
|
117
|
+
|
|
118
|
+
## [0.1.0] - 2026-03-10
|
|
119
|
+
|
|
120
|
+
### Added
|
|
121
|
+
|
|
122
|
+
- Initial release: ActiveRecord adapter for Turso (libSQL) using a Rust native extension.
|
|
123
|
+
- Remote connection mode via libSQL remote protocol.
|
|
124
|
+
- Embedded Replica mode (`replica_path:`) with optional background sync (`sync_interval:`).
|
|
125
|
+
- Offline write mode (`offline: true`) — writes locally, syncs to remote on `#sync`.
|
|
126
|
+
- `rake turso:schema:apply` and `rake turso:schema:diff` via sqldef (#2).
|
|
127
|
+
- `rake build`, `rake install`, `rake release` tasks (#3).
|
|
128
|
+
- Unit and integration test suite — 57 examples (#1).
|
data/README.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
ActiveRecord adapter for [Turso](https://turso.tech) (libSQL) database.
|
|
4
4
|
|
|
5
|
-
Connects Rails/ActiveRecord models to Turso via
|
|
5
|
+
Connects Rails / ActiveRecord models to Turso Cloud via the **Hrana v2 HTTP protocol**,
|
|
6
|
+
implemented in pure Ruby using `Net::HTTP` — no native extension, no Rust toolchain required.
|
|
6
7
|
|
|
7
8
|
## Requirements
|
|
8
9
|
|
|
9
10
|
- Ruby >= 3.1
|
|
10
|
-
- Rust >= 1.70 (install via [rustup](https://rustup.rs))
|
|
11
11
|
- ActiveRecord >= 7.0
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
@@ -19,9 +19,10 @@ gem "activerecord-libsql"
|
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
21
|
bundle install
|
|
22
|
-
bundle exec rake compile
|
|
23
22
|
```
|
|
24
23
|
|
|
24
|
+
No compile step needed. The gem is pure Ruby.
|
|
25
|
+
|
|
25
26
|
## Configuration
|
|
26
27
|
|
|
27
28
|
### database.yml
|
|
@@ -39,7 +40,8 @@ production:
|
|
|
39
40
|
<<: *default
|
|
40
41
|
```
|
|
41
42
|
|
|
42
|
-
> **Note**: Use the `database:` key, not `url:`. ActiveRecord tries to resolve the adapter
|
|
43
|
+
> **Note**: Use the `database:` key, not `url:`. ActiveRecord tries to resolve the adapter
|
|
44
|
+
> from the URL scheme when `url:` is used, which causes a lookup failure.
|
|
43
45
|
|
|
44
46
|
### Direct connection
|
|
45
47
|
|
|
@@ -76,7 +78,8 @@ User.find(1).destroy
|
|
|
76
78
|
|
|
77
79
|
## Embedded Replicas
|
|
78
80
|
|
|
79
|
-
Embedded Replicas keep a local SQLite copy of your Turso database on disk, synced from the
|
|
81
|
+
Embedded Replicas keep a local SQLite copy of your Turso database on disk, synced from the
|
|
82
|
+
remote. Reads are served locally (sub-millisecond), writes go to the remote.
|
|
80
83
|
|
|
81
84
|
### Configuration
|
|
82
85
|
|
|
@@ -111,14 +114,46 @@ ActiveRecord::Base.connection.sync
|
|
|
111
114
|
|
|
112
115
|
### Notes
|
|
113
116
|
|
|
114
|
-
- `replica_path` must point to a
|
|
117
|
+
- `replica_path` must point to a writable path. The file is created automatically on first connect.
|
|
115
118
|
- `sync_interval` is in seconds. Set to `0` or omit to use manual sync only.
|
|
116
|
-
- **Multi-process
|
|
117
|
-
|
|
119
|
+
- **Multi-process (Puma / Solid Queue)**: Each worker process gets its own SQLite connection.
|
|
120
|
+
`sqlite3` gem 2.x handles fork safety automatically — connections are closed after `fork()`
|
|
121
|
+
and reopened in the child process. Do not share the same `replica_path` across multiple
|
|
122
|
+
Puma workers; use a unique path per worker (e.g. `/var/data/myapp-worker-#{worker_id}.db`).
|
|
123
|
+
|
|
124
|
+
## Solid Queue
|
|
125
|
+
|
|
126
|
+
This adapter is compatible with [Solid Queue](https://github.com/rails/solid_queue).
|
|
127
|
+
|
|
128
|
+
### Known behaviour
|
|
129
|
+
|
|
130
|
+
- **`FOR UPDATE SKIP LOCKED`**: Solid Queue uses this clause by default. libSQL and SQLite
|
|
131
|
+
do not support it, so the adapter strips it automatically before sending SQL to the backend.
|
|
132
|
+
SQLite serializes all writes, so row-level locking is not needed.
|
|
133
|
+
- **Fork safety**: Solid Queue forks worker processes. The adapter handles this correctly —
|
|
134
|
+
`sqlite3` gem 2.x closes connections after `fork()`, and ActiveRecord's `discard!` /
|
|
135
|
+
`reconnect` flow re-establishes them in the child process.
|
|
136
|
+
|
|
137
|
+
### Example config
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
# config/queue.yml
|
|
141
|
+
default: &default
|
|
142
|
+
dispatchers:
|
|
143
|
+
- polling_interval: 1
|
|
144
|
+
batch_size: 500
|
|
145
|
+
workers:
|
|
146
|
+
- queues: "*"
|
|
147
|
+
threads: 3
|
|
148
|
+
polling_interval: 0.1
|
|
149
|
+
```
|
|
118
150
|
|
|
119
151
|
## Schema Management
|
|
120
152
|
|
|
121
|
-
`turso:schema:apply` and `turso:schema:diff` use [sqldef](https://github.com/sqldef/sqldef)
|
|
153
|
+
`turso:schema:apply` and `turso:schema:diff` use [sqldef](https://github.com/sqldef/sqldef)
|
|
154
|
+
(`sqlite3def`) to manage your Turso schema declaratively — no migration files, no version
|
|
155
|
+
tracking. You define the desired schema in a `.sql` file and the task computes and applies
|
|
156
|
+
only the diff.
|
|
122
157
|
|
|
123
158
|
### Prerequisites
|
|
124
159
|
|
|
@@ -129,7 +164,8 @@ brew install sqldef/sqldef/sqlite3def
|
|
|
129
164
|
# Other platforms: https://github.com/sqldef/sqldef/releases
|
|
130
165
|
```
|
|
131
166
|
|
|
132
|
-
`replica_path` must be configured in `database.yml` (the tasks use the local replica to
|
|
167
|
+
`replica_path` must be configured in `database.yml` (the tasks use the local replica to
|
|
168
|
+
compute the diff without touching the remote directly).
|
|
133
169
|
|
|
134
170
|
### turso:schema:apply
|
|
135
171
|
|
|
@@ -139,31 +175,6 @@ Applies the diff between your desired schema and the current remote schema.
|
|
|
139
175
|
rake turso:schema:apply[db/schema.sql]
|
|
140
176
|
```
|
|
141
177
|
|
|
142
|
-
Example output:
|
|
143
|
-
|
|
144
|
-
```
|
|
145
|
-
==> [1/4] Pulling latest schema from remote...
|
|
146
|
-
Done.
|
|
147
|
-
==> [2/4] Computing schema diff...
|
|
148
|
-
2 statement(s) to apply:
|
|
149
|
-
ALTER TABLE users ADD COLUMN bio TEXT;
|
|
150
|
-
CREATE INDEX idx_users_email ON users (email);
|
|
151
|
-
==> [3/4] Applying schema to Turso Cloud...
|
|
152
|
-
Done.
|
|
153
|
-
==> [4/4] Pulling to confirm...
|
|
154
|
-
Done.
|
|
155
|
-
==> Schema applied successfully!
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
If the schema is already up to date:
|
|
159
|
-
|
|
160
|
-
```
|
|
161
|
-
==> [1/4] Pulling latest schema from remote...
|
|
162
|
-
Done.
|
|
163
|
-
==> [2/4] Computing schema diff...
|
|
164
|
-
Already up to date.
|
|
165
|
-
```
|
|
166
|
-
|
|
167
178
|
### turso:schema:diff
|
|
168
179
|
|
|
169
180
|
Shows what would be applied without making any changes (dry-run).
|
|
@@ -174,7 +185,8 @@ rake turso:schema:diff[db/schema.sql]
|
|
|
174
185
|
|
|
175
186
|
### schema.sql format
|
|
176
187
|
|
|
177
|
-
Plain SQL `CREATE TABLE` statements. sqldef handles `ALTER TABLE` / `CREATE INDEX` / `DROP`
|
|
188
|
+
Plain SQL `CREATE TABLE` statements. sqldef handles `ALTER TABLE` / `CREATE INDEX` / `DROP`
|
|
189
|
+
automatically based on the diff.
|
|
178
190
|
|
|
179
191
|
```sql
|
|
180
192
|
CREATE TABLE users (
|
|
@@ -182,14 +194,6 @@ CREATE TABLE users (
|
|
|
182
194
|
name TEXT NOT NULL,
|
|
183
195
|
email TEXT NOT NULL
|
|
184
196
|
);
|
|
185
|
-
|
|
186
|
-
CREATE TABLE posts (
|
|
187
|
-
id TEXT PRIMARY KEY,
|
|
188
|
-
user_id TEXT NOT NULL,
|
|
189
|
-
title TEXT NOT NULL,
|
|
190
|
-
body TEXT,
|
|
191
|
-
created_at TEXT NOT NULL
|
|
192
|
-
);
|
|
193
197
|
```
|
|
194
198
|
|
|
195
199
|
## Architecture
|
|
@@ -198,21 +202,36 @@ CREATE TABLE posts (
|
|
|
198
202
|
Rails Model (ActiveRecord)
|
|
199
203
|
↓ Arel → SQL string
|
|
200
204
|
LibsqlAdapter (lib/active_record/connection_adapters/libsql_adapter.rb)
|
|
201
|
-
↓ perform_query
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
↓ perform_query (strips FOR UPDATE, expands bind params)
|
|
206
|
+
↓
|
|
207
|
+
├─ Remote mode ──→ TursoLibsql::Connection (Hrana v2 HTTP via Net::HTTP)
|
|
208
|
+
│ ↓
|
|
209
|
+
│ Turso Cloud (HTTPS)
|
|
210
|
+
│
|
|
211
|
+
└─ Embedded Replica / Offline mode
|
|
212
|
+
↓
|
|
213
|
+
TursoLibsql::LocalConnection (sqlite3 gem)
|
|
214
|
+
↓
|
|
215
|
+
Local SQLite file ←─ sync ─→ Turso Cloud
|
|
207
216
|
```
|
|
208
217
|
|
|
218
|
+
### Why pure Ruby?
|
|
219
|
+
|
|
220
|
+
The original implementation used a Rust native extension (`tokio` + `rustls`). On macOS,
|
|
221
|
+
all Rust TLS libraries are unsafe after `fork()` — they cause SEGV or deadlocks in
|
|
222
|
+
multi-process servers (Puma, Unicorn, Solid Queue). Ruby's `Net::HTTP` has no such
|
|
223
|
+
restriction and is fully fork-safe.
|
|
224
|
+
|
|
209
225
|
## Thread Safety
|
|
210
226
|
|
|
211
|
-
|
|
227
|
+
ActiveRecord's `ConnectionPool` issues a separate `Adapter` instance per thread, so
|
|
228
|
+
`@raw_connection` is never shared across threads. `Net::HTTP` opens a new TCP connection
|
|
229
|
+
per request, which is safe for concurrent use.
|
|
212
230
|
|
|
213
231
|
## Performance
|
|
214
232
|
|
|
215
|
-
Benchmarked against a **Turso cloud database** (remote, over HTTPS) from a MacBook on a
|
|
233
|
+
Benchmarked against a **Turso cloud database** (remote, over HTTPS) from a MacBook on a
|
|
234
|
+
home network. All numbers include full round-trip network latency.
|
|
216
235
|
|
|
217
236
|
| Operation | ops/sec | avg latency |
|
|
218
237
|
|-----------|--------:|------------:|
|
|
@@ -224,26 +243,30 @@ Benchmarked against a **Turso cloud database** (remote, over HTTPS) from a MacBo
|
|
|
224
243
|
| DELETE single row | 6.9 | 145.2 ms |
|
|
225
244
|
| Transaction (10 inserts) | 1.9 | 539.0 ms |
|
|
226
245
|
|
|
227
|
-
> **Environment**: Ruby 3.4.8 · ActiveRecord 8.1.2 · Turso cloud (remote) · macOS arm64
|
|
246
|
+
> **Environment**: Ruby 3.4.8 · ActiveRecord 8.1.2 · Turso cloud (remote) · macOS arm64
|
|
228
247
|
> Run `bundle exec ruby bench/benchmark.rb` to reproduce.
|
|
229
248
|
|
|
230
|
-
Latency is dominated by network round-trips to the Turso cloud endpoint. For lower latency,
|
|
249
|
+
Latency is dominated by network round-trips to the Turso cloud endpoint. For lower latency,
|
|
250
|
+
use [Embedded Replicas](#embedded-replicas) — reads are served from a local SQLite file with
|
|
251
|
+
sub-millisecond latency.
|
|
231
252
|
|
|
232
253
|
## Feature Support
|
|
233
254
|
|
|
234
255
|
| Feature | Status |
|
|
235
256
|
|---------|--------|
|
|
236
|
-
| SELECT | ✅ |
|
|
237
|
-
| INSERT | ✅ |
|
|
238
|
-
| UPDATE | ✅ |
|
|
239
|
-
| DELETE | ✅ |
|
|
257
|
+
| SELECT / INSERT / UPDATE / DELETE | ✅ |
|
|
240
258
|
| Transactions | ✅ |
|
|
241
|
-
| Migrations (basic) | ✅ |
|
|
259
|
+
| Migrations (basic DDL) | ✅ |
|
|
242
260
|
| Schema management (sqldef) | ✅ |
|
|
243
|
-
|
|
|
244
|
-
|
|
|
245
|
-
|
|
|
246
|
-
|
|
|
261
|
+
| Bind parameters | ✅ |
|
|
262
|
+
| NOT NULL / UNIQUE / FK constraint → AR exceptions | ✅ |
|
|
263
|
+
| Embedded Replica (local reads) | ✅ |
|
|
264
|
+
| Offline write mode | ✅ |
|
|
265
|
+
| Solid Queue compatibility | ✅ |
|
|
266
|
+
| Fork safety (Puma / Solid Queue) | ✅ |
|
|
267
|
+
| Prepared statements (server-side) | ❌ libSQL HTTP does not support them |
|
|
268
|
+
| EXPLAIN | ❌ |
|
|
269
|
+
| Savepoints | ❌ |
|
|
247
270
|
|
|
248
271
|
## Testing
|
|
249
272
|
|
|
@@ -258,7 +281,8 @@ bundle exec rake spec:integration
|
|
|
258
281
|
bundle exec rake spec:all
|
|
259
282
|
```
|
|
260
283
|
|
|
261
|
-
Set `SKIP_INTEGRATION_TESTS=1` to skip integration tests in CI environments without Turso
|
|
284
|
+
Set `SKIP_INTEGRATION_TESTS=1` to skip integration tests in CI environments without Turso
|
|
285
|
+
credentials.
|
|
262
286
|
|
|
263
287
|
## License
|
|
264
288
|
|
|
@@ -19,6 +19,11 @@ module ActiveRecord
|
|
|
19
19
|
ADAPTER_NAME = 'Turso'
|
|
20
20
|
|
|
21
21
|
# SQLite 互換の型マッピング(libSQL は SQLite 方言)
|
|
22
|
+
# datetime / timestamp は 'datetime' を使う。
|
|
23
|
+
# 'TEXT' にすると PRAGMA table_info が 'TEXT' を返し、
|
|
24
|
+
# AR の type_map が Type::Text にマッピングしてしまう。
|
|
25
|
+
# Type::Text は Time を UTC に変換せず文字列化するため、
|
|
26
|
+
# WHERE scheduled_at <= ? の比較が文字列比較で壊れる。
|
|
22
27
|
NATIVE_DATABASE_TYPES = {
|
|
23
28
|
primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
24
29
|
string: { name: 'TEXT' },
|
|
@@ -26,10 +31,10 @@ module ActiveRecord
|
|
|
26
31
|
integer: { name: 'INTEGER' },
|
|
27
32
|
float: { name: 'REAL' },
|
|
28
33
|
decimal: { name: 'REAL' },
|
|
29
|
-
datetime: { name: '
|
|
30
|
-
timestamp: { name: '
|
|
31
|
-
time: { name: '
|
|
32
|
-
date: { name: '
|
|
34
|
+
datetime: { name: 'datetime' },
|
|
35
|
+
timestamp: { name: 'datetime' },
|
|
36
|
+
time: { name: 'time' },
|
|
37
|
+
date: { name: 'date' },
|
|
33
38
|
binary: { name: 'BLOB' },
|
|
34
39
|
boolean: { name: 'INTEGER' },
|
|
35
40
|
json: { name: 'TEXT' }
|
|
@@ -394,10 +399,15 @@ module ActiveRecord
|
|
|
394
399
|
def initialize_type_map(m = type_map)
|
|
395
400
|
m.register_type(/^integer/i, Type::Integer.new)
|
|
396
401
|
m.register_type(/^real/i, Type::Float.new)
|
|
397
|
-
m.register_type(/^text/i, Type::String.new)
|
|
398
402
|
m.register_type(/^blob/i, Type::Binary.new)
|
|
399
403
|
m.register_type(/^boolean/i, Type::Boolean.new)
|
|
400
|
-
|
|
404
|
+
# datetime / timestamp は Type::DateTime を使う。
|
|
405
|
+
# libSQL は datetime を TEXT として保存するが、AR の型キャストで
|
|
406
|
+
# UTC に正規化してから保存する必要がある(文字列比較の一貫性のため)。
|
|
407
|
+
register_class_with_precision m, /^datetime/i, Type::DateTime
|
|
408
|
+
m.alias_type(/^timestamp/i, 'datetime')
|
|
409
|
+
m.register_type(/^text/i, Type::String.new)
|
|
410
|
+
m.register_type(/./, Type::String.new)
|
|
401
411
|
end
|
|
402
412
|
|
|
403
413
|
def fetch_type_metadata(sql_type)
|
|
@@ -41,6 +41,10 @@ module TursoLibsql
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
# トランザクションをコミットする
|
|
44
|
+
# fork 後に sqlite3 gem の ForkSafety が接続を強制クローズした場合や、
|
|
45
|
+
# baton が無効になった場合は "cannot commit - no transaction is active" が返る。
|
|
46
|
+
# AR の discard! → reconnect フローで子プロセスは正常に動作するため、
|
|
47
|
+
# このエラーは無視して良い。
|
|
44
48
|
def commit_transaction
|
|
45
49
|
requests = [
|
|
46
50
|
{ 'type' => 'execute', 'stmt' => { 'sql' => 'COMMIT' } },
|
|
@@ -49,6 +53,9 @@ module TursoLibsql
|
|
|
49
53
|
resp = hrana_pipeline(@baton, requests)
|
|
50
54
|
@baton = nil
|
|
51
55
|
check_errors(resp)
|
|
56
|
+
rescue RuntimeError => e
|
|
57
|
+
raise unless e.message.include?('no transaction is active') ||
|
|
58
|
+
e.message.include?('cannot commit')
|
|
52
59
|
end
|
|
53
60
|
|
|
54
61
|
# トランザクションをロールバックする
|
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.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- aileron
|
|
@@ -89,6 +89,7 @@ executables: []
|
|
|
89
89
|
extensions: []
|
|
90
90
|
extra_rdoc_files: []
|
|
91
91
|
files:
|
|
92
|
+
- CHANGELOG.md
|
|
92
93
|
- README.md
|
|
93
94
|
- activerecord-libsql.gemspec
|
|
94
95
|
- lib/active_record/connection_adapters/libsql_adapter.rb
|