activerecord-turso 0.2.0 → 0.2.2
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 +55 -30
- data/lib/active_record/connection_adapters/turso_adapter/connection_management.rb +4 -2
- data/lib/active_record/connection_adapters/turso_adapter/database_statements.rb +16 -27
- data/lib/active_record/connection_adapters/turso_adapter/error_translation.rb +13 -11
- data/lib/active_record/connection_adapters/turso_adapter/schema_statements.rb +1 -0
- data/lib/active_record/connection_adapters/turso_adapter/statement_pool.rb +1 -1
- data/lib/active_record/connection_adapters/turso_adapter/transaction_management.rb +5 -2
- data/lib/active_record/connection_adapters/turso_adapter.rb +11 -1
- data/lib/activerecord-turso/error.rb +6 -0
- data/lib/activerecord-turso/version.rb +1 -1
- data/lib/activerecord-turso.rb +1 -0
- data/lib/turso/ar/connection.rb +9 -105
- metadata +6 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b8e00bcf26be0b39f02a05bd0f4850db41d277f7d72bdb15013c218f032d93eb
|
|
4
|
+
data.tar.gz: 9e9ccad1e346648fef21eb2d0a3aceafa7afada0bc67348a0ed912b723e6cca5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c16dce07f7dbcf2bd60d07e58b10d6960c7d4a2945f88deda0082c532b0e1e42cc58213582acd2548c0c19d72a13ef30e5688503ba8ab9a00b7fa99f882048e8
|
|
7
|
+
data.tar.gz: 22f295b0eda449f11b5f31c63b16718e5f609452904b0c9c5b534117fe4145324460d3ed56f7ac7365f38345ac165eb8969a3e01bb5c21d59c2e9f3bef2f4f4d
|
data/README.md
CHANGED
|
@@ -4,14 +4,18 @@ ActiveRecord adapter for [Turso](https://turso.tech) (SQLite compatible db).
|
|
|
4
4
|
|
|
5
5
|
## Status
|
|
6
6
|
|
|
7
|
-
|
|
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.
|
|
8
8
|
|
|
9
9
|
## Requirements
|
|
10
10
|
|
|
11
|
-
- Ruby >= 3.
|
|
12
|
-
- ActiveRecord
|
|
11
|
+
- Ruby >= 3.2
|
|
12
|
+
- ActiveRecord ~> 8.1.0
|
|
13
13
|
- The `turso` Ruby gem
|
|
14
14
|
|
|
15
|
+
## Runtime dependencies
|
|
16
|
+
|
|
17
|
+
This adapter uses the `turso` gem and does **not** require the `sqlite3` Ruby gem at runtime.
|
|
18
|
+
|
|
15
19
|
## Installation
|
|
16
20
|
|
|
17
21
|
Add to your Gemfile:
|
|
@@ -47,7 +51,42 @@ end
|
|
|
47
51
|
Post.create!(title: "Hello", body: "World", published: true)
|
|
48
52
|
```
|
|
49
53
|
|
|
50
|
-
|
|
54
|
+
## Configuration
|
|
55
|
+
|
|
56
|
+
### Recommended production configuration
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
production:
|
|
60
|
+
adapter: turso
|
|
61
|
+
database: db/production.sqlite3
|
|
62
|
+
journal_mode: wal
|
|
63
|
+
pool: 5
|
|
64
|
+
timeout: 5000
|
|
65
|
+
busy_timeout: 5000
|
|
66
|
+
query_timeout: 30000
|
|
67
|
+
experimental_features: "index_method"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Connection options
|
|
71
|
+
|
|
72
|
+
| Option | Description | Default |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `database` | Path to the SQLite database file | Required |
|
|
75
|
+
| `journal_mode` | Journal mode (`wal`, `mvcc`, `delete`, etc.) | WAL |
|
|
76
|
+
| `pool` | Connection pool size | 5 |
|
|
77
|
+
| `timeout` | Busy timeout in milliseconds | 5000 |
|
|
78
|
+
| `busy_timeout` | Busy timeout in milliseconds (overrides `timeout`) | 5000 |
|
|
79
|
+
| `query_timeout` | Query timeout in milliseconds | 30000 |
|
|
80
|
+
| `experimental_features` | Comma-separated experimental features (`index_method`, `generated_columns`) | None |
|
|
81
|
+
| `statement_limit` | Maximum number of cached prepared statements | 1000 |
|
|
82
|
+
|
|
83
|
+
### `query_timeout` and `interrupt`
|
|
84
|
+
|
|
85
|
+
The adapter exposes `query_timeout=` to set a maximum execution time for queries. When a query exceeds this timeout, the Turso engine interrupts the operation and raises `ActiveRecord::QueryCanceled`. Use `interrupt` to cancel a running query from another thread.
|
|
86
|
+
|
|
87
|
+
### MVCC / BEGIN CONCURRENT (experimental)
|
|
88
|
+
|
|
89
|
+
⚠️ **Experimental.** Do not use in production unless you understand the caveats below. Normal ActiveRecord model persistence (`create!`, `save!`, `update!`, `touch`) is **not supported** inside `transaction(concurrent: true)`.
|
|
51
90
|
|
|
52
91
|
Turso supports `BEGIN CONCURRENT` for optimistic, multi-writer transactions. To opt in, pass `concurrent: true` to `transaction`:
|
|
53
92
|
|
|
@@ -124,8 +163,6 @@ development:
|
|
|
124
163
|
journal_mode: mvcc
|
|
125
164
|
busy_timeout: 5000
|
|
126
165
|
|
|
127
|
-
# Enable experimental Turso features when needed (e.g., custom index methods for FTS).
|
|
128
|
-
# Pass a comma-separated string or an array of feature names.
|
|
129
166
|
experimental_features: "index_method"
|
|
130
167
|
```
|
|
131
168
|
|
|
@@ -133,7 +170,6 @@ Use `concurrent: true` inside the same pinned connection:
|
|
|
133
170
|
|
|
134
171
|
```ruby
|
|
135
172
|
ActiveRecord::Base.connection.transaction(concurrent: true) do
|
|
136
|
-
# read-modify-write using raw SQL or bulk operations
|
|
137
173
|
end
|
|
138
174
|
```
|
|
139
175
|
|
|
@@ -143,20 +179,6 @@ Caveats:
|
|
|
143
179
|
- 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.
|
|
144
180
|
- FTS custom index modules are not supported in MVCC mode.
|
|
145
181
|
|
|
146
|
-
## Recommended production configuration
|
|
147
|
-
|
|
148
|
-
```yaml
|
|
149
|
-
production:
|
|
150
|
-
adapter: turso
|
|
151
|
-
database: db/production.sqlite3
|
|
152
|
-
journal_mode: wal # Use mvcc only if you need concurrent writers and understand the caveats above
|
|
153
|
-
pool: 5
|
|
154
|
-
timeout: 5000 # busy_timeout default in milliseconds
|
|
155
|
-
query_timeout: 30000 # maximum time a single query may run
|
|
156
|
-
busy_timeout: 5000 # explicit busy timeout (falls back to timeout)
|
|
157
|
-
experimental_features: "index_method"
|
|
158
|
-
```
|
|
159
|
-
|
|
160
182
|
## Limitations and Risks
|
|
161
183
|
|
|
162
184
|
The following limitations apply to the current implementation. Read this section carefully before deploying to production.
|
|
@@ -165,9 +187,9 @@ The following limitations apply to the current implementation. Read this section
|
|
|
165
187
|
|
|
166
188
|
The adapter builds column type maps from `column_decltype` metadata. This works for most column definitions but may not capture type information for computed expressions or subquery columns.
|
|
167
189
|
|
|
168
|
-
### 2. Batch SQL execution uses
|
|
190
|
+
### 2. Batch SQL execution uses the Turso native batch API
|
|
169
191
|
|
|
170
|
-
The adapter's batch execution path
|
|
192
|
+
The adapter's batch execution path uses the Turso native batch API (`execute_batch`), which correctly handles semicolons inside string literals, triggers, and stored expressions.
|
|
171
193
|
|
|
172
194
|
### 3. MVCC requires opt-in and has ActiveRecord compatibility caveats
|
|
173
195
|
|
|
@@ -176,7 +198,7 @@ The adapter's batch execution path splits multi-statement SQL on semicolons. Thi
|
|
|
176
198
|
- ActiveRecord expects transactions to commit unless the database returns an error. With `BEGIN CONCURRENT`, the commit can fail with a snapshot conflict and must be retried.
|
|
177
199
|
- The retry loop must run on the same connection. Rails' connection pool is not MVCC-aware and may return the connection to the pool between retries.
|
|
178
200
|
- Do not combine concurrent transactions with pessimistic locking (`lock!`, `with_lock`, `lock_version`).
|
|
179
|
-
- Normal model persistence (`create!`, `save!`, `update!`, `touch`) is unsupported inside a concurrent transaction because ActiveRecord opens an internal transaction for each model change.
|
|
201
|
+
- Normal model persistence (`create!`, `save!`, `update!`, `touch`) is unsupported inside a concurrent transaction because ActiveRecord opens an internal transaction for each model change.
|
|
180
202
|
|
|
181
203
|
Only use `transaction(concurrent: true)` after testing it under your app's concurrency patterns, and prefer raw SQL or bulk operations inside the block.
|
|
182
204
|
|
|
@@ -184,19 +206,22 @@ Only use `transaction(concurrent: true)` after testing it under your app's concu
|
|
|
184
206
|
|
|
185
207
|
The adapter uses a bounded statement pool with a default limit inherited from Rails (typically `1000`). Statements are evicted with an LRU policy and finalized against the underlying Turso connection. Set `statement_limit` in `database.yml` to tune the pool size.
|
|
186
208
|
|
|
187
|
-
### 5. ActiveRecord 8.
|
|
209
|
+
### 5. Only ActiveRecord 8.1 is supported
|
|
210
|
+
|
|
211
|
+
The adapter is pinned to ActiveRecord `~> 8.1.0`.
|
|
212
|
+
|
|
213
|
+
### 6. INSERT RETURNING is disabled (not supported by Turso)
|
|
188
214
|
|
|
189
|
-
|
|
215
|
+
The underlying Turso SQLite build does not support `INSERT ... RETURNING` syntax. The adapter reports `supports_insert_returning?` as `false`, so ActiveRecord falls back to `last_insert_rowid()` for retrieving inserted IDs.
|
|
190
216
|
|
|
191
|
-
###
|
|
217
|
+
### 7. Some SQLite-specific features are unsupported or conservatively flagged
|
|
192
218
|
|
|
193
219
|
- Transaction isolation levels other than the default are reported as unsupported (`supports_transaction_isolation?` returns `false`) because Turso remote connections do not provide shared-cache read-uncommitted semantics.
|
|
194
|
-
- `insert_returning` is enabled only when the reported SQLite version is `>= 3.35.0`.
|
|
195
220
|
- `insert_on_conflict` is enabled only when the reported SQLite version is `>= 3.24.0`.
|
|
196
221
|
|
|
197
|
-
###
|
|
222
|
+
### 8. `execute_batch` uses the Turso native batch API
|
|
198
223
|
|
|
199
|
-
The `turso` gem
|
|
224
|
+
The `turso` gem's `execute_batch` uses the Turso native batch API (`prepare_first`/`execute` loop), correctly handling edge cases like semicolons in string literals and triggers.
|
|
200
225
|
|
|
201
226
|
## Development
|
|
202
227
|
|
|
@@ -54,6 +54,9 @@ module ActiveRecord
|
|
|
54
54
|
execute("PRAGMA journal_mode = #{mode}")
|
|
55
55
|
if @config[:journal_mode].to_s.downcase == "mvcc"
|
|
56
56
|
@mvcc_enabled = true
|
|
57
|
+
ActiveRecord::Base.logger&.warn(
|
|
58
|
+
"Turso journal_mode: mvcc is experimental. Concurrent transactions require careful connection handling."
|
|
59
|
+
)
|
|
57
60
|
end
|
|
58
61
|
end
|
|
59
62
|
|
|
@@ -62,8 +65,7 @@ module ActiveRecord
|
|
|
62
65
|
timeout = @config[:busy_timeout] || @config[:timeout] || 5000
|
|
63
66
|
@raw_connection.busy_timeout = timeout
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
@raw_connection.query_timeout = query_timeout
|
|
68
|
+
|
|
67
69
|
end
|
|
68
70
|
end
|
|
69
71
|
end
|
|
@@ -6,7 +6,7 @@ module ActiveRecord
|
|
|
6
6
|
module DatabaseStatements
|
|
7
7
|
def execute(sql, name = nil)
|
|
8
8
|
materialize_transactions
|
|
9
|
-
raw_execute(sql, name)
|
|
9
|
+
raw_execute(sql, name)&.to_a
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def last_inserted_id(sql)
|
|
@@ -25,22 +25,13 @@ module ActiveRecord
|
|
|
25
25
|
binds
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
def select_rows(arel, name = nil)
|
|
28
|
+
def select_rows(arel, name = nil, binds = [], _options = {})
|
|
29
29
|
arel = arel_from_relation(arel)
|
|
30
|
-
sql, binds = to_sql_and_binds(arel)
|
|
30
|
+
sql, binds = to_sql_and_binds(arel, binds)
|
|
31
31
|
type_casted_binds = type_casted_binds(binds)
|
|
32
32
|
|
|
33
33
|
log(sql, name, binds, type_casted_binds) do
|
|
34
|
-
|
|
35
|
-
stmt = conn.prepare(sql)
|
|
36
|
-
stmt.bind_positional(type_casted_binds) unless type_casted_binds.empty?
|
|
37
|
-
rows = []
|
|
38
|
-
while stmt.step == 1
|
|
39
|
-
rows << stmt.row.to_a
|
|
40
|
-
end
|
|
41
|
-
stmt.finalize
|
|
42
|
-
rows
|
|
43
|
-
end
|
|
34
|
+
exec_query(sql, name, binds, prepare: false).rows
|
|
44
35
|
end
|
|
45
36
|
end
|
|
46
37
|
|
|
@@ -57,21 +48,20 @@ module ActiveRecord
|
|
|
57
48
|
end
|
|
58
49
|
|
|
59
50
|
stmt.reset if prepare
|
|
60
|
-
stmt.
|
|
51
|
+
stmt.bind(*type_casted_binds) unless type_casted_binds.empty?
|
|
61
52
|
|
|
62
53
|
begin
|
|
63
|
-
if write_query?(sql)
|
|
64
|
-
|
|
54
|
+
if write_query?(sql) && !sql.match?(/\bRETURNING\b/i)
|
|
55
|
+
result = stmt.run
|
|
56
|
+
affected_rows = result[:changes]
|
|
65
57
|
verified!
|
|
66
58
|
notification_payload[:affected_rows] = affected_rows
|
|
67
59
|
notification_payload[:row_count] = 0
|
|
68
60
|
ActiveRecord::Result.empty(affected_rows: affected_rows)
|
|
69
61
|
else
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
rows << stmt.row.to_a
|
|
74
|
-
end
|
|
62
|
+
columns_info = stmt.columns
|
|
63
|
+
columns = columns_info.map { |c| c[:name] }
|
|
64
|
+
rows = stmt.all.map(&:to_a)
|
|
75
65
|
affected_rows = raw_connection.changes
|
|
76
66
|
verified!
|
|
77
67
|
notification_payload[:affected_rows] = affected_rows
|
|
@@ -80,7 +70,7 @@ module ActiveRecord
|
|
|
80
70
|
ActiveRecord::Result.new(columns, rows, type_map, affected_rows: affected_rows)
|
|
81
71
|
end
|
|
82
72
|
ensure
|
|
83
|
-
stmt.
|
|
73
|
+
stmt.close unless prepare
|
|
84
74
|
end
|
|
85
75
|
end
|
|
86
76
|
|
|
@@ -89,11 +79,11 @@ module ActiveRecord
|
|
|
89
79
|
old_defer_foreign_keys = query_value("PRAGMA defer_foreign_keys")
|
|
90
80
|
|
|
91
81
|
begin
|
|
92
|
-
execute("PRAGMA defer_foreign_keys = ON")
|
|
82
|
+
execute("PRAGMA defer_foreign_keys = ON") unless old_defer_foreign_keys.nil?
|
|
93
83
|
execute("PRAGMA foreign_keys = OFF")
|
|
94
84
|
yield
|
|
95
85
|
ensure
|
|
96
|
-
|
|
86
|
+
unless old_defer_foreign_keys.nil?
|
|
97
87
|
execute("PRAGMA defer_foreign_keys = #{old_defer_foreign_keys}")
|
|
98
88
|
end
|
|
99
89
|
execute("PRAGMA foreign_keys = #{old_foreign_keys}")
|
|
@@ -104,9 +94,8 @@ module ActiveRecord
|
|
|
104
94
|
|
|
105
95
|
def build_type_map(stmt)
|
|
106
96
|
type_map = {}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
decltype = stmt.respond_to?(:column_decltype) ? stmt.column_decltype(i) : nil
|
|
97
|
+
stmt.columns.each_with_index do |col, i|
|
|
98
|
+
decltype = col[:type]
|
|
110
99
|
next unless decltype
|
|
111
100
|
type_map[i] = decltype_to_type(decltype)
|
|
112
101
|
end
|
|
@@ -7,23 +7,23 @@ module ActiveRecord
|
|
|
7
7
|
def translate_exception(exception, message:, sql:, binds:)
|
|
8
8
|
cause = exception.cause
|
|
9
9
|
case cause
|
|
10
|
-
when ::Turso::
|
|
10
|
+
when ::Turso::ConstraintException
|
|
11
11
|
translate_constraint_error(message, sql, binds)
|
|
12
|
-
when ::Turso::
|
|
12
|
+
when ::Turso::NotADatabaseException
|
|
13
13
|
ActiveRecord::NoDatabaseError.new(message, sql: sql, binds: binds)
|
|
14
|
-
when ::Turso::
|
|
14
|
+
when ::Turso::BusySnapshotException
|
|
15
15
|
ActiveRecord::SerializationFailure.new(message, sql: sql, binds: binds)
|
|
16
|
-
when ::Turso::
|
|
17
|
-
|
|
18
|
-
when ::Turso::
|
|
16
|
+
when ::Turso::BusyException
|
|
17
|
+
ActiveRecordTurso::BusyError.new(message, sql: sql, binds: binds)
|
|
18
|
+
when ::Turso::ReadonlyException
|
|
19
19
|
ActiveRecord::ReadOnlyRecord.new(message, sql: sql, binds: binds)
|
|
20
|
-
when ::Turso::
|
|
20
|
+
when ::Turso::IoException, ::Turso::CorruptException
|
|
21
21
|
ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds)
|
|
22
|
-
when ::Turso::
|
|
22
|
+
when ::Turso::DatabaseFullException
|
|
23
23
|
ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds)
|
|
24
|
-
when ::Turso::
|
|
24
|
+
when ::Turso::InterruptException
|
|
25
25
|
ActiveRecord::QueryCanceled.new(message, sql: sql, binds: binds)
|
|
26
|
-
when ::Turso::
|
|
26
|
+
when ::Turso::MisuseException
|
|
27
27
|
ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds)
|
|
28
28
|
else
|
|
29
29
|
super
|
|
@@ -34,10 +34,12 @@ module ActiveRecord
|
|
|
34
34
|
|
|
35
35
|
def translate_constraint_error(message, sql, binds)
|
|
36
36
|
case message
|
|
37
|
-
when /foreign key constraint/i
|
|
37
|
+
when /foreign key constraint|foreign key mismatch/i
|
|
38
38
|
ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds)
|
|
39
39
|
when /unique constraint|primary key/i
|
|
40
40
|
ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds)
|
|
41
|
+
when /NOT NULL|cannot be NULL/i
|
|
42
|
+
ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds)
|
|
41
43
|
else
|
|
42
44
|
ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds)
|
|
43
45
|
end
|
|
@@ -34,6 +34,10 @@ module ActiveRecord
|
|
|
34
34
|
|
|
35
35
|
def transaction(requires_new: nil, isolation: nil, joinable: true, **options, &block)
|
|
36
36
|
if options.delete(:concurrent)
|
|
37
|
+
ActiveRecord::Base.logger&.warn(
|
|
38
|
+
"transaction(concurrent: true) is experimental and may break ActiveRecord model persistence. " \
|
|
39
|
+
"See adapter documentation."
|
|
40
|
+
)
|
|
37
41
|
transaction_with_mvcc(options, &block)
|
|
38
42
|
else
|
|
39
43
|
super
|
|
@@ -85,8 +89,7 @@ module ActiveRecord
|
|
|
85
89
|
|
|
86
90
|
def concurrent_conflict?(exception)
|
|
87
91
|
cause = exception.cause
|
|
88
|
-
conflict_classes = [::Turso::
|
|
89
|
-
conflict_classes << ::Turso::WriteWriteConflict if defined?(::Turso::WriteWriteConflict)
|
|
92
|
+
conflict_classes = [::Turso::BusySnapshotException, ::Turso::BusyException]
|
|
90
93
|
|
|
91
94
|
conflict_classes.any? { |klass| cause.is_a?(klass) } ||
|
|
92
95
|
/snapshot conflict|busy snapshot|database is locked|cannot start a transaction within a transaction/i.match?(exception.message)
|
|
@@ -25,6 +25,10 @@ module ActiveRecord
|
|
|
25
25
|
false
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def sqlite_version
|
|
29
|
+
@sqlite_version ||= Gem::Version.new(query_value("SELECT sqlite_version()"))
|
|
30
|
+
end
|
|
31
|
+
|
|
28
32
|
def supports_transaction_isolation?
|
|
29
33
|
false
|
|
30
34
|
end
|
|
@@ -37,10 +41,16 @@ module ActiveRecord
|
|
|
37
41
|
true
|
|
38
42
|
end
|
|
39
43
|
|
|
44
|
+
def quote_string(s)
|
|
45
|
+
s.gsub("'", "''")
|
|
46
|
+
end
|
|
47
|
+
|
|
40
48
|
def explain(arel, binds = [], _options = [])
|
|
41
49
|
sql = "EXPLAIN QUERY PLAN " + to_sql(arel, binds)
|
|
42
50
|
result = exec_query(sql, "EXPLAIN", binds)
|
|
43
|
-
|
|
51
|
+
result.rows.map do |row|
|
|
52
|
+
row.join(" | ")
|
|
53
|
+
end.join("\n")
|
|
44
54
|
end
|
|
45
55
|
|
|
46
56
|
def build_statement_pool
|
data/lib/activerecord-turso.rb
CHANGED
data/lib/turso/ar/connection.rb
CHANGED
|
@@ -7,7 +7,8 @@ module Turso
|
|
|
7
7
|
class Connection
|
|
8
8
|
extend Forwardable
|
|
9
9
|
|
|
10
|
-
def_delegators :@db, :close, :closed?, :changes, :total_changes
|
|
10
|
+
def_delegators :@db, :close, :closed?, :changes, :total_changes,
|
|
11
|
+
:last_insert_rowid, :prepare, :interrupt, :busy_timeout=
|
|
11
12
|
|
|
12
13
|
DEFAULT_BUSY_TIMEOUT_MS = 5000
|
|
13
14
|
DEFAULT_QUERY_TIMEOUT_MS = 30_000
|
|
@@ -19,11 +20,10 @@ module Turso
|
|
|
19
20
|
query_timeout: config[:query_timeout] || DEFAULT_QUERY_TIMEOUT_MS
|
|
20
21
|
}
|
|
21
22
|
db_opts[:experimental_features] = config[:experimental_features] if config[:experimental_features]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
query("SELECT last_insert_rowid()").first&.values&.first.to_i
|
|
23
|
+
database = ::Turso::Database.new(config[:database].to_s, **db_opts)
|
|
24
|
+
@db = database.connection
|
|
25
|
+
@db.busy_timeout = db_opts[:busy_timeout]
|
|
26
|
+
@db.query_timeout = db_opts[:query_timeout]
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def raw_connection
|
|
@@ -39,116 +39,20 @@ module Turso
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def execute(sql, binds = [])
|
|
42
|
-
@db.execute(sql, normalize_binds(binds))
|
|
42
|
+
@db.execute(sql, *normalize_binds(binds))
|
|
43
43
|
nil
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def query(sql, params = [])
|
|
47
|
-
@db.query(sql, normalize_binds(params))
|
|
47
|
+
@db.query(sql, *normalize_binds(params))
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def execute_batch(sql)
|
|
51
|
-
|
|
52
|
-
@db.execute(stmt)
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def prepare(sql)
|
|
57
|
-
@db.prepare(sql)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def busy_timeout=(ms)
|
|
61
|
-
@db.busy_timeout = ms.to_i
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def query_timeout=(ms)
|
|
65
|
-
@db.query_timeout = ms.to_i
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def interrupt
|
|
69
|
-
@db.interrupt
|
|
51
|
+
@db.execute_batch(sql)
|
|
70
52
|
end
|
|
71
53
|
|
|
72
54
|
private
|
|
73
55
|
|
|
74
|
-
def split_batch(sql)
|
|
75
|
-
statements = []
|
|
76
|
-
current = +""
|
|
77
|
-
in_string = false
|
|
78
|
-
in_line_comment = false
|
|
79
|
-
in_block_comment = false
|
|
80
|
-
i = 0
|
|
81
|
-
while i < sql.length
|
|
82
|
-
char = sql[i]
|
|
83
|
-
next_char = sql[i + 1]
|
|
84
|
-
|
|
85
|
-
if in_line_comment
|
|
86
|
-
if char == "\n"
|
|
87
|
-
in_line_comment = false
|
|
88
|
-
end
|
|
89
|
-
i += 1
|
|
90
|
-
next
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
if in_block_comment
|
|
94
|
-
if char == "*" && next_char == "/"
|
|
95
|
-
in_block_comment = false
|
|
96
|
-
i += 2
|
|
97
|
-
else
|
|
98
|
-
i += 1
|
|
99
|
-
end
|
|
100
|
-
next
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
if in_string
|
|
104
|
-
if char == "'" && next_char == "'"
|
|
105
|
-
current << char << next_char
|
|
106
|
-
i += 2
|
|
107
|
-
next
|
|
108
|
-
elsif char == "'"
|
|
109
|
-
in_string = false
|
|
110
|
-
current << char
|
|
111
|
-
i += 1
|
|
112
|
-
next
|
|
113
|
-
end
|
|
114
|
-
current << char
|
|
115
|
-
i += 1
|
|
116
|
-
next
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
case char
|
|
120
|
-
when "'"
|
|
121
|
-
in_string = true
|
|
122
|
-
current << char
|
|
123
|
-
when "-"
|
|
124
|
-
if next_char == "-"
|
|
125
|
-
in_line_comment = true
|
|
126
|
-
i += 2
|
|
127
|
-
next
|
|
128
|
-
end
|
|
129
|
-
current << char
|
|
130
|
-
when "/"
|
|
131
|
-
if next_char == "*"
|
|
132
|
-
in_block_comment = true
|
|
133
|
-
i += 2
|
|
134
|
-
next
|
|
135
|
-
end
|
|
136
|
-
current << char
|
|
137
|
-
when ";"
|
|
138
|
-
stmt = current.strip
|
|
139
|
-
statements << stmt unless stmt.empty?
|
|
140
|
-
current = +""
|
|
141
|
-
else
|
|
142
|
-
current << char
|
|
143
|
-
end
|
|
144
|
-
i += 1
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
stmt = current.strip
|
|
148
|
-
statements << stmt unless stmt.empty?
|
|
149
|
-
statements
|
|
150
|
-
end
|
|
151
|
-
|
|
152
56
|
def normalize_binds(binds)
|
|
153
57
|
binds.map do |value|
|
|
154
58
|
case value
|
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.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ben D'Angelo
|
|
@@ -13,22 +13,16 @@ dependencies:
|
|
|
13
13
|
name: activerecord
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- - "
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '8.0'
|
|
19
|
-
- - "<"
|
|
16
|
+
- - "~>"
|
|
20
17
|
- !ruby/object:Gem::Version
|
|
21
|
-
version:
|
|
18
|
+
version: 8.1.0
|
|
22
19
|
type: :runtime
|
|
23
20
|
prerelease: false
|
|
24
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
25
22
|
requirements:
|
|
26
|
-
- - "
|
|
27
|
-
- !ruby/object:Gem::Version
|
|
28
|
-
version: '8.0'
|
|
29
|
-
- - "<"
|
|
23
|
+
- - "~>"
|
|
30
24
|
- !ruby/object:Gem::Version
|
|
31
|
-
version:
|
|
25
|
+
version: 8.1.0
|
|
32
26
|
- !ruby/object:Gem::Dependency
|
|
33
27
|
name: turso
|
|
34
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -70,6 +64,7 @@ files:
|
|
|
70
64
|
- lib/active_record/connection_adapters/turso_adapter/statement_pool.rb
|
|
71
65
|
- lib/active_record/connection_adapters/turso_adapter/transaction_management.rb
|
|
72
66
|
- lib/activerecord-turso.rb
|
|
67
|
+
- lib/activerecord-turso/error.rb
|
|
73
68
|
- lib/activerecord-turso/version.rb
|
|
74
69
|
- lib/turso/ar/connection.rb
|
|
75
70
|
licenses:
|