Class: ActiveRecord::ConnectionAdapters::LibsqlAdapter

Inherits:
AbstractAdapter
  • Object
show all
Defined in:
lib/active_record/connection_adapters/libsql_adapter.rb

Constant Summary collapse

ADAPTER_NAME =
'Turso'
NATIVE_DATABASE_TYPES =

SQLite 互換の型マッピング(libSQL は SQLite 方言)datetime / timestamp は ‘datetime’ を使う。‘TEXT’ にすると PRAGMA table_info が ‘TEXT’ を返し、AR の type_map が Type::Text にマッピングしてしまう。Type::Text は Time を UTC に変換せず文字列化するため、WHERE scheduled_at <= ? の比較が文字列比較で壊れる。

{
  primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT',
  string: { name: 'TEXT' },
  text: { name: 'TEXT' },
  integer: { name: 'INTEGER' },
  float: { name: 'REAL' },
  decimal: { name: 'REAL' },
  datetime: { name: 'datetime' },
  timestamp: { name: 'datetime' },
  time: { name: 'time' },
  date: { name: 'date' },
  binary: { name: 'BLOB' },
  boolean: { name: 'INTEGER' },
  json: { name: 'TEXT' }
}.freeze

Instance Method Summary collapse

Instance Method Details

#active?Boolean

Returns:

  • (Boolean)


112
113
114
115
116
117
118
119
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 112

def active?
  return false unless @raw_connection

  @raw_connection.query('SELECT 1')
  true
rescue StandardError
  false
end

#adapter_nameObject


Adapter 識別




65
66
67
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 65

def adapter_name
  ADAPTER_NAME
end

#affected_rows(_raw_result) ⇒ Object



203
204
205
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 203

def affected_rows(_raw_result)
  @last_affected_rows || 0
end

#begin_db_transactionObject


トランザクション




211
212
213
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 211

def begin_db_transaction
  @raw_connection&.begin_transaction
end

#cast_result(raw_result) ⇒ Object

perform_query が返した結果をそのまま使う(すでに ActiveRecord::Result)



199
200
201
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 199

def cast_result(raw_result)
  raw_result
end

#columns(table_name) ⇒ Object



248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 248

def columns(table_name)
  result = internal_exec_query(
    "PRAGMA table_info(#{quote_table_name(table_name)})",
    'SCHEMA'
  )
  result.map do |row|
    sql_type    = row['type'].to_s
    cast_type   = type_map.lookup(sql_type) || Type::Value.new
    sql_type_md = (sql_type)
    null        = row['notnull'].to_i.zero?
    COLUMN_BUILDER.call(row['name'], cast_type, row['dflt_value'], sql_type_md, null)
  end
end

#commit_db_transactionObject



215
216
217
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 215

def commit_db_transaction
  @raw_connection&.commit_transaction
end

#connect!Object


接続管理(AR 8 スタイル)AR の ConnectionPool はスレッドごとに独立した Adapter インスタンスを払い出すため




107
108
109
110
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 107

def connect!
  @raw_database, @raw_connection = build_libsql_connection
  super
end

#discard!Object

fork 後の子プロセスで呼ばれる。sqlite3 gem の fork safety が接続を強制クローズするため、参照を破棄して子プロセスで新しい接続を確立できるようにする。AR の ConnectionPool が fork 後に各コネクションに対して呼ぶ。



131
132
133
134
135
136
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 131

def discard!
  @raw_connection = nil
  @raw_database = nil
  TursoLibsql.reinitialize_runtime!
  super
end

#disconnect!Object



121
122
123
124
125
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 121

def disconnect!
  @raw_connection = nil
  @raw_database = nil
  super
end

#exec_rollback_db_transactionObject



219
220
221
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 219

def exec_rollback_db_transaction
  @raw_connection&.rollback_transaction
end

#last_inserted_id(_result) ⇒ Object


INSERT 後の id AR 8 は last_inserted_id(result) を呼ぶ




228
229
230
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 228

def last_inserted_id(_result)
  @raw_connection&.last_insert_rowid
end

#native_database_typesObject


スキーマ情報




236
237
238
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 236

def native_database_types
  NATIVE_DATABASE_TYPES
end

#perform_query(raw_connection, sql, _binds, type_casted_binds, prepare:, notification_payload:, batch: false) ⇒ Object

AR 8 が with_raw_connection { |conn| } で呼ぶ中核メソッド



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 167

def perform_query(raw_connection, sql, _binds, type_casted_binds, prepare:, notification_payload:, batch: false)
  # バインドパラメータを SQL に展開する(libsql の ? プレースホルダーに対応)
  expanded_sql = if type_casted_binds&.any?
                   i = -1
                   sql.gsub('?') do
                     i += 1
                     quote(type_casted_binds[i])
                   end
                 else
                   sql
                 end

  # libSQL / SQLite は FOR UPDATE / FOR UPDATE SKIP LOCKED / FOR SHARE を
  # サポートしない。AR の SQLite3 adapter はこれらを除去するが、
  # libsql_adapter は直接 Hrana HTTP / SQLite3::Database を呼ぶため自前で除去する。
  expanded_sql = sanitize_for_update(expanded_sql)

  if read_query?(expanded_sql)
    rows = raw_connection.query(expanded_sql)
    notification_payload[:row_count] = rows.size if notification_payload
    build_result(rows)
  else
    affected = raw_connection.execute(expanded_sql)
    @last_affected_rows = affected.to_i
    notification_payload[:row_count] = @last_affected_rows if notification_payload
    ActiveRecord::Result.empty
  end
rescue RuntimeError => e
  raise translate_exception(e, message: e.message, sql: expanded_sql, binds: [])
end

#quote_column_name(name) ⇒ Object


クォート




270
271
272
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 270

def quote_column_name(name)
  %("#{name.to_s.gsub('"', '""')}")
end

#quote_table_name(name) ⇒ Object



274
275
276
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 274

def quote_table_name(name)
  %("#{name.to_s.gsub('"', '""')}")
end

#quoted_falseObject



282
283
284
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 282

def quoted_false
  '0'
end

#quoted_trueObject



278
279
280
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 278

def quoted_true
  '1'
end

#supports_ddl_transactions?Boolean

Returns:

  • (Boolean)


77
78
79
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 77

def supports_ddl_transactions?
  false
end

#supports_explain?Boolean

Returns:

  • (Boolean)


85
86
87
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 85

def supports_explain?
  false
end

#supports_lazy_transactions?Boolean

Returns:

  • (Boolean)


89
90
91
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 89

def supports_lazy_transactions?
  false
end

#supports_migrations?Boolean

Returns:

  • (Boolean)


69
70
71
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 69

def supports_migrations?
  true
end

#supports_primary_key?Boolean

Returns:

  • (Boolean)


73
74
75
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 73

def supports_primary_key?
  true
end

#supports_savepoints?Boolean

Returns:

  • (Boolean)


81
82
83
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 81

def supports_savepoints?
  false
end

#syncObject

Embedded Replica モードでリモートから最新フレームを手動同期する。Remote モードでは何もしない(no-op)。



157
158
159
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 157

def sync
  @raw_database&.sync
end

#table_exists?(table_name) ⇒ Boolean

Returns:

  • (Boolean)


262
263
264
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 262

def table_exists?(table_name)
  tables.include?(table_name.to_s)
end

#tables(_name = nil) ⇒ Object



240
241
242
243
244
245
246
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 240

def tables(_name = nil)
  result = internal_exec_query(
    "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'",
    'SCHEMA'
  )
  result.rows.flatten
end

#write_query?(sql) ⇒ Boolean

Returns:

  • (Boolean)


93
94
95
96
97
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 93

def write_query?(sql)
  !READ_QUERY.match?(sql)
rescue ArgumentError
  !READ_QUERY.match?(sql.b)
end