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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69e6be67a21fd809792bf867f81f6205bb06a6cc457aa05c2b80080cf272fd9b
4
- data.tar.gz: d1a18f2e763cb6b5781521f1e8bd12f8a7159d3350c2fe5e91fe02ee00a50f1f
3
+ metadata.gz: b8e00bcf26be0b39f02a05bd0f4850db41d277f7d72bdb15013c218f032d93eb
4
+ data.tar.gz: 9e9ccad1e346648fef21eb2d0a3aceafa7afada0bc67348a0ed912b723e6cca5
5
5
  SHA512:
6
- metadata.gz: defa80378418aabb5b0b619392e180102f633ce7f8f6a13d6778f34a47c47f14c2739dc5248aaf0011e8f40788002ae42fea373789e3fc8b5e6e1ea5ebac8b35
7
- data.tar.gz: 573a7f4f59c98621b9c4f95af1daa2a8fe133b3e487ef83c7d73af51c8c65655cbff080ef2d3813d7e64868273b186f121d69653499bee67f1180463fc946f9a
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
- This adapter is under **active** development. It can run basic ActiveRecord operations against Turso today, but several production features are still being hardened. See [Limitations and Risks](#limitations-and-risks) before using it in production.
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.0
12
- - ActiveRecord >= 8.0, < 8.2
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
- ### MVCC / BEGIN CONCURRENT
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 a simple string splitter
190
+ ### 2. Batch SQL execution uses the Turso native batch API
169
191
 
170
- The adapter's batch execution path splits multi-statement SQL on semicolons. This means SQL containing semicolons inside string literals, triggers, or stored expressions may be split incorrectly. Avoid relying on multi-statement strings other than simple schema dumps.
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. The error is retried a few times, then raised as `ActiveRecord::StatementInvalid`.
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.0 support is CI-tested, not locally tested
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
- Only ActiveRecord 8.1 is installed in the primary development environment. ActiveRecord 8.0 compatibility is validated through CI. If you run into 8.0-specific issues, please report them.
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
- ### 6. Some SQLite-specific features are unsupported or conservatively flagged
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
- ### 7. `execute_batch` in the underlying bindings is a Ruby-side fallback
222
+ ### 8. `execute_batch` uses the Turso native batch API
198
223
 
199
- The `turso` gem provides `DB#execute_batch` as a convenience that splits and executes statements one by one. It does not use a native batch API, so it carries the same semicolon-splitting risk as item 2 above.
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
- query_timeout = @config[:query_timeout] || 30_000
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
- with_raw_connection do |conn|
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.bind_positional(type_casted_binds) unless type_casted_binds.empty?
51
+ stmt.bind(*type_casted_binds) unless type_casted_binds.empty?
61
52
 
62
53
  begin
63
- if write_query?(sql)
64
- affected_rows = stmt.execute
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
- columns = (0...stmt.column_count).map { |i| stmt.column_name(i) }
71
- rows = []
72
- while stmt.step == 1
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.finalize unless prepare
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
- if old_defer_foreign_keys
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
- count = stmt.column_count
108
- count.times do |i|
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::ConstraintError
10
+ when ::Turso::ConstraintException
11
11
  translate_constraint_error(message, sql, binds)
12
- when ::Turso::NotADatabaseError
12
+ when ::Turso::NotADatabaseException
13
13
  ActiveRecord::NoDatabaseError.new(message, sql: sql, binds: binds)
14
- when ::Turso::BusySnapshotError
14
+ when ::Turso::BusySnapshotException
15
15
  ActiveRecord::SerializationFailure.new(message, sql: sql, binds: binds)
16
- when ::Turso::BusyError
17
- ActiveRecord::Deadlocked.new(message, sql: sql, binds: binds)
18
- when ::Turso::ReadonlyError
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::IoError, ::Turso::CorruptError
20
+ when ::Turso::IoException, ::Turso::CorruptException
21
21
  ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds)
22
- when ::Turso::DatabaseFullError
22
+ when ::Turso::DatabaseFullException
23
23
  ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds)
24
- when ::Turso::InterruptError
24
+ when ::Turso::InterruptException
25
25
  ActiveRecord::QueryCanceled.new(message, sql: sql, binds: binds)
26
- when ::Turso::MisuseError
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
@@ -11,6 +11,7 @@ module ActiveRecord
11
11
  AND name NOT LIKE 'sqlite_%'
12
12
  AND name NOT LIKE 'fts_dir_%'
13
13
  AND name NOT LIKE 'sqlite_fts_%'
14
+ AND name NOT LIKE '__turso_internal_%'
14
15
  SQL
15
16
  end
16
17
 
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  private
10
10
 
11
11
  def dealloc(stmt)
12
- stmt.finalize
12
+ stmt.close
13
13
  rescue ::Turso::Error
14
14
  end
15
15
  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::BusySnapshotError, ::Turso::BusyError]
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
- SQLite3::ExplainPrettyPrinter.new.pp(result)
51
+ result.rows.map do |row|
52
+ row.join(" | ")
53
+ end.join("\n")
44
54
  end
45
55
 
46
56
  def build_statement_pool
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordTurso
4
+ class Error < ActiveRecord::StatementInvalid; end
5
+ class BusyError < Error; end
6
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordTurso
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.2"
5
5
  end
@@ -3,6 +3,7 @@
3
3
  require "active_record"
4
4
  require "turso"
5
5
 
6
+ require_relative "activerecord-turso/error"
6
7
  require_relative "turso/ar/connection"
7
8
  require_relative "active_record/connection_adapters/turso_adapter"
8
9
 
@@ -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
- @db = ::Turso::DB.new(config[:database].to_s, **db_opts)
23
- end
24
-
25
- def last_insert_rowid
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
- split_batch(sql).each do |stmt|
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.0
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: '8.2'
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: '8.2'
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: