activerecord-libsql 0.1.0
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 +7 -0
- data/Cargo.lock +2406 -0
- data/Cargo.toml +3 -0
- data/README.md +265 -0
- data/activerecord-libsql.gemspec +41 -0
- data/ext/turso_libsql/Cargo.toml +20 -0
- data/ext/turso_libsql/extconf.rb +6 -0
- data/ext/turso_libsql/src/lib.rs +299 -0
- data/lib/active_record/connection_adapters/libsql_adapter.rb +371 -0
- data/lib/activerecord/libsql/railtie.rb +13 -0
- data/lib/activerecord/libsql/version.rb +7 -0
- data/lib/activerecord-libsql.rb +6 -0
- metadata +126 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_record'
|
|
4
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
|
5
|
+
require 'turso_libsql/turso_libsql'
|
|
6
|
+
|
|
7
|
+
# AR 7.2+ のアダプター登録 API
|
|
8
|
+
ActiveSupport.on_load(:active_record) do
|
|
9
|
+
ActiveRecord::ConnectionAdapters.register(
|
|
10
|
+
'turso',
|
|
11
|
+
'ActiveRecord::ConnectionAdapters::LibsqlAdapter',
|
|
12
|
+
'active_record/connection_adapters/libsql_adapter'
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module ActiveRecord
|
|
17
|
+
module ConnectionAdapters
|
|
18
|
+
class LibsqlAdapter < AbstractAdapter
|
|
19
|
+
ADAPTER_NAME = 'Turso'
|
|
20
|
+
|
|
21
|
+
# SQLite 互換の型マッピング(libSQL は SQLite 方言)
|
|
22
|
+
NATIVE_DATABASE_TYPES = {
|
|
23
|
+
primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
|
24
|
+
string: { name: 'TEXT' },
|
|
25
|
+
text: { name: 'TEXT' },
|
|
26
|
+
integer: { name: 'INTEGER' },
|
|
27
|
+
float: { name: 'REAL' },
|
|
28
|
+
decimal: { name: 'REAL' },
|
|
29
|
+
datetime: { name: 'TEXT' },
|
|
30
|
+
timestamp: { name: 'TEXT' },
|
|
31
|
+
time: { name: 'TEXT' },
|
|
32
|
+
date: { name: 'TEXT' },
|
|
33
|
+
binary: { name: 'BLOB' },
|
|
34
|
+
boolean: { name: 'INTEGER' },
|
|
35
|
+
json: { name: 'TEXT' }
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
# SQLite 互換: PRAGMA も読み取りクエリとして扱う
|
|
39
|
+
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
|
40
|
+
:pragma
|
|
41
|
+
)
|
|
42
|
+
private_constant :READ_QUERY
|
|
43
|
+
|
|
44
|
+
# -----------------------------------------------------------------------
|
|
45
|
+
# Adapter 識別
|
|
46
|
+
# -----------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
def adapter_name
|
|
49
|
+
ADAPTER_NAME
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def supports_migrations?
|
|
53
|
+
true
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def supports_primary_key?
|
|
57
|
+
true
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def supports_ddl_transactions?
|
|
61
|
+
false
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def supports_savepoints?
|
|
65
|
+
false
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def supports_explain?
|
|
69
|
+
false
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def supports_lazy_transactions?
|
|
73
|
+
false
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def write_query?(sql)
|
|
77
|
+
!READ_QUERY.match?(sql)
|
|
78
|
+
rescue ArgumentError
|
|
79
|
+
!READ_QUERY.match?(sql.b)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# -----------------------------------------------------------------------
|
|
83
|
+
# 接続管理(AR 8 スタイル)
|
|
84
|
+
# @raw_connection に TursoLibsql::Connection をセットする
|
|
85
|
+
# @raw_database に TursoLibsql::Database を保持する(sync / lifetime 管理)
|
|
86
|
+
# AR の ConnectionPool はスレッドごとに独立した Adapter インスタンスを払い出すため
|
|
87
|
+
# @raw_connection の競合は発生しない
|
|
88
|
+
# -----------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
def connect!
|
|
91
|
+
@raw_database, @raw_connection = build_libsql_connection
|
|
92
|
+
super
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def active?
|
|
96
|
+
return false unless @raw_connection
|
|
97
|
+
|
|
98
|
+
@raw_connection.query('SELECT 1')
|
|
99
|
+
true
|
|
100
|
+
rescue StandardError
|
|
101
|
+
false
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def reconnect!
|
|
105
|
+
@raw_database, @raw_connection = build_libsql_connection
|
|
106
|
+
super
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def disconnect!
|
|
110
|
+
@raw_connection = nil
|
|
111
|
+
@raw_database = nil
|
|
112
|
+
super
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Embedded Replica モードでリモートから最新フレームを手動同期する。
|
|
116
|
+
# Remote モードでは何もしない(no-op)。
|
|
117
|
+
def sync
|
|
118
|
+
@raw_database&.sync
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# -----------------------------------------------------------------------
|
|
122
|
+
# AR 8 クエリパイプライン
|
|
123
|
+
# raw_execute → perform_query → cast_result の流れ
|
|
124
|
+
# -----------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
# AR 8 が with_raw_connection { |conn| } で呼ぶ中核メソッド
|
|
127
|
+
def perform_query(raw_connection, sql, _binds, type_casted_binds, prepare:, notification_payload:, batch: false)
|
|
128
|
+
# バインドパラメータを SQL に展開する(libsql の ? プレースホルダーに対応)
|
|
129
|
+
expanded_sql = if type_casted_binds&.any?
|
|
130
|
+
i = -1
|
|
131
|
+
sql.gsub('?') do
|
|
132
|
+
i += 1
|
|
133
|
+
quote(type_casted_binds[i])
|
|
134
|
+
end
|
|
135
|
+
else
|
|
136
|
+
sql
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
if read_query?(expanded_sql)
|
|
140
|
+
rows = raw_connection.query(expanded_sql)
|
|
141
|
+
notification_payload[:row_count] = rows.size if notification_payload
|
|
142
|
+
build_result(rows)
|
|
143
|
+
else
|
|
144
|
+
affected = raw_connection.execute(expanded_sql)
|
|
145
|
+
notification_payload[:row_count] = affected if notification_payload
|
|
146
|
+
ActiveRecord::Result.empty(affected_rows: affected.to_i)
|
|
147
|
+
end
|
|
148
|
+
rescue RuntimeError => e
|
|
149
|
+
raise translate_exception(e, message: e.message, sql: expanded_sql, binds: [])
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# perform_query が返した結果をそのまま使う(すでに ActiveRecord::Result)
|
|
153
|
+
def cast_result(raw_result)
|
|
154
|
+
raw_result
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def affected_rows(raw_result)
|
|
158
|
+
raw_result.length
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# -----------------------------------------------------------------------
|
|
162
|
+
# トランザクション
|
|
163
|
+
# -----------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
def begin_db_transaction
|
|
166
|
+
@raw_connection&.begin_transaction
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def commit_db_transaction
|
|
170
|
+
@raw_connection&.commit_transaction
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def exec_rollback_db_transaction
|
|
174
|
+
@raw_connection&.rollback_transaction
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# -----------------------------------------------------------------------
|
|
178
|
+
# INSERT 後の id
|
|
179
|
+
# AR 8 は last_inserted_id(result) を呼ぶ
|
|
180
|
+
# -----------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
def last_inserted_id(_result)
|
|
183
|
+
@raw_connection&.last_insert_rowid
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# -----------------------------------------------------------------------
|
|
187
|
+
# スキーマ情報
|
|
188
|
+
# -----------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
def native_database_types
|
|
191
|
+
NATIVE_DATABASE_TYPES
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def tables(_name = nil)
|
|
195
|
+
result = internal_exec_query(
|
|
196
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'",
|
|
197
|
+
'SCHEMA'
|
|
198
|
+
)
|
|
199
|
+
result.rows.flatten
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def columns(table_name)
|
|
203
|
+
result = internal_exec_query(
|
|
204
|
+
"PRAGMA table_info(#{quote_table_name(table_name)})",
|
|
205
|
+
'SCHEMA'
|
|
206
|
+
)
|
|
207
|
+
result.map do |row|
|
|
208
|
+
sql_type = row['type'].to_s
|
|
209
|
+
cast_type = type_map.lookup(sql_type) || Type::Value.new
|
|
210
|
+
sql_type_md = fetch_type_metadata(sql_type)
|
|
211
|
+
# AR 8.1: Column.new(name, cast_type, default, sql_type_metadata, null)
|
|
212
|
+
Column.new(
|
|
213
|
+
row['name'],
|
|
214
|
+
cast_type,
|
|
215
|
+
row['dflt_value'],
|
|
216
|
+
sql_type_md,
|
|
217
|
+
row['notnull'].to_i.zero?
|
|
218
|
+
)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def table_exists?(table_name)
|
|
223
|
+
tables.include?(table_name.to_s)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# -----------------------------------------------------------------------
|
|
227
|
+
# クォート
|
|
228
|
+
# -----------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
def quote_column_name(name)
|
|
231
|
+
%("#{name.to_s.gsub('"', '""')}")
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def quote_table_name(name)
|
|
235
|
+
%("#{name.to_s.gsub('"', '""')}")
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def quoted_true
|
|
239
|
+
'1'
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def quoted_false
|
|
243
|
+
'0'
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
private
|
|
247
|
+
|
|
248
|
+
# libsql の RuntimeError を AR の標準例外に変換する
|
|
249
|
+
def translate_exception(exception, message:, sql:, binds:)
|
|
250
|
+
msg = exception.message
|
|
251
|
+
case msg
|
|
252
|
+
when /NOT NULL constraint failed/i
|
|
253
|
+
ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds)
|
|
254
|
+
when /UNIQUE constraint failed/i
|
|
255
|
+
ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds)
|
|
256
|
+
when /FOREIGN KEY constraint failed/i
|
|
257
|
+
ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds)
|
|
258
|
+
when /no such table/i
|
|
259
|
+
ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds)
|
|
260
|
+
else
|
|
261
|
+
super
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# [TursoLibsql::Database, TursoLibsql::Connection] を返す
|
|
266
|
+
def build_libsql_connection
|
|
267
|
+
database_url = @config[:database] || @config[:url]
|
|
268
|
+
raise ArgumentError, 'libsql adapter requires :database (libsql://...)' unless database_url
|
|
269
|
+
|
|
270
|
+
token = @config[:token] || ''
|
|
271
|
+
replica_path = @config[:replica_path]
|
|
272
|
+
sync_interval = (@config[:sync_interval] || 0).to_i
|
|
273
|
+
|
|
274
|
+
db = if replica_path && @config[:offline]
|
|
275
|
+
# Offline write モード:
|
|
276
|
+
# write はローカルに書いてすぐ返す。sync() でまとめてリモートへ反映。
|
|
277
|
+
# ULID + last-write-wins 設計に最適。
|
|
278
|
+
TursoLibsql::Database.new_synced(
|
|
279
|
+
replica_path.to_s,
|
|
280
|
+
database_url.to_s,
|
|
281
|
+
token.to_s,
|
|
282
|
+
sync_interval
|
|
283
|
+
)
|
|
284
|
+
elsif replica_path
|
|
285
|
+
# Embedded Replica モード:
|
|
286
|
+
# read はローカルから。write はリモートへ即送信。
|
|
287
|
+
TursoLibsql::Database.new_remote_replica(
|
|
288
|
+
replica_path.to_s,
|
|
289
|
+
database_url.to_s,
|
|
290
|
+
token.to_s,
|
|
291
|
+
sync_interval
|
|
292
|
+
)
|
|
293
|
+
else
|
|
294
|
+
raise ArgumentError, 'libsql adapter requires :token' if token.empty?
|
|
295
|
+
|
|
296
|
+
TursoLibsql::Database.new_remote(database_url.to_s, token.to_s)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
[db, db.connect]
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# PK 取得(PRAGMA table_info の pk カラムを使う)
|
|
303
|
+
def primary_keys(table_name)
|
|
304
|
+
result = internal_exec_query(
|
|
305
|
+
"PRAGMA table_info(#{quote_table_name(table_name)})",
|
|
306
|
+
'SCHEMA'
|
|
307
|
+
)
|
|
308
|
+
pks = result.select { |row| row['pk'].to_i > 0 }
|
|
309
|
+
pks.sort_by { |row| row['pk'].to_i }.map { |row| row['name'] }
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# AR が views / data_sources で使う(SQLite 互換実装)
|
|
313
|
+
def data_source_sql(name = nil, type: nil)
|
|
314
|
+
scope = quoted_scope(name, type: type)
|
|
315
|
+
scope[:type] ||= "'table','view'"
|
|
316
|
+
|
|
317
|
+
sql = +"SELECT name FROM pragma_table_list WHERE schema <> 'temp'"
|
|
318
|
+
sql << " AND name NOT IN ('sqlite_sequence', 'sqlite_schema')"
|
|
319
|
+
sql << " AND name = #{scope[:name]}" if scope[:name]
|
|
320
|
+
sql << " AND type IN (#{scope[:type]})"
|
|
321
|
+
sql
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def quoted_scope(name = nil, type: nil)
|
|
325
|
+
type = case type
|
|
326
|
+
when 'BASE TABLE' then "'table'"
|
|
327
|
+
when 'VIEW' then "'view'"
|
|
328
|
+
when 'VIRTUAL TABLE' then "'virtual'"
|
|
329
|
+
end
|
|
330
|
+
scope = {}
|
|
331
|
+
scope[:name] = quote(name) if name
|
|
332
|
+
scope[:type] = type if type
|
|
333
|
+
scope
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# SELECT 系クエリかどうかを判定
|
|
337
|
+
def read_query?(sql)
|
|
338
|
+
sql.lstrip.match?(/\A\s*(SELECT|PRAGMA|EXPLAIN|WITH)\b/i)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Array of Hash → ActiveRecord::Result
|
|
342
|
+
def build_result(rows)
|
|
343
|
+
return ActiveRecord::Result.new([], []) if rows.empty?
|
|
344
|
+
|
|
345
|
+
columns = rows.first.keys
|
|
346
|
+
values = rows.map(&:values)
|
|
347
|
+
ActiveRecord::Result.new(columns, values)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def initialize_type_map(m = type_map)
|
|
351
|
+
m.register_type(/^integer/i, Type::Integer.new)
|
|
352
|
+
m.register_type(/^real/i, Type::Float.new)
|
|
353
|
+
m.register_type(/^text/i, Type::String.new)
|
|
354
|
+
m.register_type(/^blob/i, Type::Binary.new)
|
|
355
|
+
m.register_type(/^boolean/i, Type::Boolean.new)
|
|
356
|
+
m.register_type(/./, Type::String.new)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def fetch_type_metadata(sql_type)
|
|
360
|
+
cast_type = type_map.lookup(sql_type) || Type::Value.new
|
|
361
|
+
SqlTypeMetadata.new(
|
|
362
|
+
sql_type: sql_type,
|
|
363
|
+
type: cast_type.type,
|
|
364
|
+
limit: cast_type.limit,
|
|
365
|
+
precision: cast_type.precision,
|
|
366
|
+
scale: cast_type.scale
|
|
367
|
+
)
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: activerecord-libsql
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- aileron
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rb_sys
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.9'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.9'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '13.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rake-compiler
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '1.2'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.2'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rspec
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '3.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '3.0'
|
|
82
|
+
description: |
|
|
83
|
+
An ActiveRecord adapter for Turso, the edge SQLite database powered by libSQL.
|
|
84
|
+
Uses a native Rust extension (via magnus) to connect directly to Turso via the
|
|
85
|
+
libSQL remote protocol, without requiring any external HTTP client.
|
|
86
|
+
email: []
|
|
87
|
+
executables: []
|
|
88
|
+
extensions:
|
|
89
|
+
- ext/turso_libsql/extconf.rb
|
|
90
|
+
extra_rdoc_files: []
|
|
91
|
+
files:
|
|
92
|
+
- Cargo.lock
|
|
93
|
+
- Cargo.toml
|
|
94
|
+
- README.md
|
|
95
|
+
- activerecord-libsql.gemspec
|
|
96
|
+
- ext/turso_libsql/Cargo.toml
|
|
97
|
+
- ext/turso_libsql/extconf.rb
|
|
98
|
+
- ext/turso_libsql/src/lib.rs
|
|
99
|
+
- lib/active_record/connection_adapters/libsql_adapter.rb
|
|
100
|
+
- lib/activerecord-libsql.rb
|
|
101
|
+
- lib/activerecord/libsql/railtie.rb
|
|
102
|
+
- lib/activerecord/libsql/version.rb
|
|
103
|
+
homepage: https://github.com/aileron-inc/activerecord-libsql
|
|
104
|
+
licenses:
|
|
105
|
+
- MIT
|
|
106
|
+
metadata:
|
|
107
|
+
homepage_uri: https://github.com/aileron-inc/activerecord-libsql
|
|
108
|
+
source_code_uri: https://github.com/aileron-inc/activerecord-libsql
|
|
109
|
+
rdoc_options: []
|
|
110
|
+
require_paths:
|
|
111
|
+
- lib
|
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: 3.1.0
|
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
118
|
+
requirements:
|
|
119
|
+
- - ">="
|
|
120
|
+
- !ruby/object:Gem::Version
|
|
121
|
+
version: '0'
|
|
122
|
+
requirements: []
|
|
123
|
+
rubygems_version: 4.0.7
|
|
124
|
+
specification_version: 4
|
|
125
|
+
summary: ActiveRecord adapter for Turso (libSQL) database
|
|
126
|
+
test_files: []
|