Class: ActiveRecord::ConnectionAdapters::LibsqlAdapter
- Inherits:
-
AbstractAdapter
- Object
- AbstractAdapter
- ActiveRecord::ConnectionAdapters::LibsqlAdapter
- Defined in:
- lib/active_record/connection_adapters/libsql_adapter.rb
Constant Summary collapse
- ADAPTER_NAME =
'Turso'- QUOTED_COLUMN_NAMES =
insert_all! / upsert_all は self.class.quote_column_name(クラスメソッド)を呼ぶ。AbstractAdapter の ClassMethods はこれを NotImplementedError にするため、クラスメソッドとして上書きする。
Concurrent::Map.new
- QUOTED_TABLE_NAMES =
:nodoc:
Concurrent::Map.new
- 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
Class Method Summary collapse
Instance Method Summary collapse
- #active? ⇒ Boolean
-
#adapter_name ⇒ Object
———————————————————————– Adapter 識別 ———————————————————————–.
- #affected_rows(_raw_result) ⇒ Object
-
#begin_db_transaction ⇒ Object
———————————————————————– トランザクション ———————————————————————–.
-
#build_insert_sql(insert) ⇒ Object
———————————————————————– insert_all! / upsert_all(ON CONFLICT 構文) SQLite3 adapter と同じ実装 ———————————————————————–.
-
#cast_result(raw_result) ⇒ Object
perform_query が返した結果をそのまま使う(すでに ActiveRecord::Result).
- #columns(table_name) ⇒ Object
- #commit_db_transaction ⇒ Object
-
#connect! ⇒ Object
———————————————————————– 接続管理(AR 8 スタイル) AR の ConnectionPool はスレッドごとに独立した Adapter インスタンスを払い出すため ———————————————————————–.
-
#discard! ⇒ Object
fork 後の子プロセスで呼ばれる。 sqlite3 gem の fork safety が接続を強制クローズするため、 参照を破棄して子プロセスで新しい接続を確立できるようにする。 AR の ConnectionPool が fork 後に各コネクションに対して呼ぶ。.
- #disconnect! ⇒ Object
- #exec_rollback_db_transaction ⇒ Object
-
#last_inserted_id(_result) ⇒ Object
———————————————————————– INSERT 後の id AR 8 は last_inserted_id(result) を呼ぶ ———————————————————————–.
-
#native_database_types ⇒ Object
———————————————————————– スキーマ情報 ———————————————————————–.
-
#perform_query(raw_connection, sql, _binds, type_casted_binds, prepare:, notification_payload:, batch: false) ⇒ Object
AR 8 が with_raw_connection { |conn| } で呼ぶ中核メソッド.
-
#quote_string(s) ⇒ Object
SQLite / libSQL はバックスラッシュをエスケープ文字として扱わない。 AbstractAdapter のデフォルト実装は \ → \\ の変換を行うため、 バックスラッシュを含む文字列の保存・取得で値が化ける。 SQLite3Adapter と同様に、シングルクォートのエスケープのみ行う。.
- #quoted_false ⇒ Object
- #quoted_true ⇒ Object
- #supports_ddl_transactions? ⇒ Boolean
- #supports_explain? ⇒ Boolean
- #supports_insert_conflict_target? ⇒ Boolean
-
#supports_insert_on_duplicate_skip? ⇒ Boolean
insert_all! / upsert_all のサポート libSQL / SQLite は ON CONFLICT 構文をサポートする.
- #supports_insert_on_duplicate_update? ⇒ Boolean
- #supports_lazy_transactions? ⇒ Boolean
- #supports_migrations? ⇒ Boolean
- #supports_primary_key? ⇒ Boolean
- #supports_savepoints? ⇒ Boolean
-
#sync ⇒ Object
Embedded Replica モードでリモートから最新フレームを手動同期する。 Remote モードでは何もしない(no-op)。.
- #table_exists?(table_name) ⇒ Boolean
- #tables(_name = nil) ⇒ Object
- #write_query?(sql) ⇒ Boolean
Class Method Details
.quote_column_name(name) ⇒ Object
28 29 30 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 28 def quote_column_name(name) QUOTED_COLUMN_NAMES[name] ||= %("#{name.to_s.gsub('"', '""')}").freeze end |
.quote_table_name(name) ⇒ Object
32 33 34 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 32 def quote_table_name(name) QUOTED_TABLE_NAMES[name] ||= %("#{name.to_s.gsub('"', '""').gsub('.', '"."')}").freeze end |
Instance Method Details
#active? ⇒ Boolean
142 143 144 145 146 147 148 149 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 142 def active? return false unless @raw_connection @raw_connection.query('SELECT 1') true rescue StandardError false end |
#adapter_name ⇒ Object
Adapter 識別
81 82 83 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 81 def adapter_name ADAPTER_NAME end |
#affected_rows(_raw_result) ⇒ Object
259 260 261 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 259 def affected_rows(_raw_result) @last_affected_rows || 0 end |
#begin_db_transaction ⇒ Object
トランザクション
267 268 269 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 267 def begin_db_transaction @raw_connection&.begin_transaction end |
#build_insert_sql(insert) ⇒ Object
insert_all! / upsert_all(ON CONFLICT 構文)SQLite3 adapter と同じ実装
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 196 def build_insert_sql(insert) # :nodoc: sql = +"INSERT #{insert.into} #{insert.values_list}" if insert.skip_duplicates? sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING" elsif insert.update_duplicates? sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET " if insert.raw_update_sql? sql << insert.raw_update_sql else sql << insert. { |column| "#{column} IS excluded.#{column}" } sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(',') end end # RETURNING 句は supports_insert_returning? が false(デフォルト)のため生成されない。 # perform_query は INSERT を write クエリとして扱い結果を捨てるため、 # RETURNING を有効にする場合は perform_query の read_query? 判定も合わせて修正が必要。 sql end |
#cast_result(raw_result) ⇒ Object
perform_query が返した結果をそのまま使う(すでに ActiveRecord::Result)
255 256 257 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 255 def cast_result(raw_result) raw_result end |
#columns(table_name) ⇒ Object
304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 304 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_transaction ⇒ Object
271 272 273 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 271 def commit_db_transaction @raw_connection&.commit_transaction end |
#connect! ⇒ Object
接続管理(AR 8 スタイル)AR の ConnectionPool はスレッドごとに独立した Adapter インスタンスを払い出すため
137 138 139 140 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 137 def connect! @raw_database, @raw_connection = build_libsql_connection super end |
#discard! ⇒ Object
fork 後の子プロセスで呼ばれる。sqlite3 gem の fork safety が接続を強制クローズするため、参照を破棄して子プロセスで新しい接続を確立できるようにする。AR の ConnectionPool が fork 後に各コネクションに対して呼ぶ。
161 162 163 164 165 166 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 161 def discard! @raw_connection = nil @raw_database = nil TursoLibsql.reinitialize_runtime! super end |
#disconnect! ⇒ Object
151 152 153 154 155 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 151 def disconnect! @raw_connection = nil @raw_database = nil super end |
#exec_rollback_db_transaction ⇒ Object
275 276 277 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 275 def exec_rollback_db_transaction @raw_connection&.rollback_transaction end |
#last_inserted_id(_result) ⇒ Object
INSERT 後の id AR 8 は last_inserted_id(result) を呼ぶ
284 285 286 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 284 def last_inserted_id(_result) @raw_connection&.last_insert_rowid end |
#native_database_types ⇒ Object
スキーマ情報
292 293 294 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 292 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| } で呼ぶ中核メソッド
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 223 def perform_query(raw_connection, sql, _binds, type_casted_binds, prepare:, notification_payload:, batch: false) # バインドパラメータを SQL に展開する(libsql の ? プレースホルダーに対応) = 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 を呼ぶため自前で除去する。 = sanitize_for_update() if read_query?() rows = raw_connection.query() notification_payload[:row_count] = rows.size if notification_payload build_result(rows) else affected = raw_connection.execute() @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., sql: , binds: []) end |
#quote_string(s) ⇒ Object
SQLite / libSQL はバックスラッシュをエスケープ文字として扱わない。AbstractAdapter のデフォルト実装は \ → \\ の変換を行うため、バックスラッシュを含む文字列の保存・取得で値が化ける。SQLite3Adapter と同様に、シングルクォートのエスケープのみ行う。
334 335 336 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 334 def quote_string(s) s.gsub("'", "''") end |
#quoted_false ⇒ Object
342 343 344 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 342 def quoted_false '0' end |
#quoted_true ⇒ Object
338 339 340 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 338 def quoted_true '1' end |
#supports_ddl_transactions? ⇒ Boolean
93 94 95 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 93 def supports_ddl_transactions? false end |
#supports_explain? ⇒ Boolean
101 102 103 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 101 def supports_explain? false end |
#supports_insert_conflict_target? ⇒ Boolean
119 120 121 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 119 def supports_insert_conflict_target? true end |
#supports_insert_on_duplicate_skip? ⇒ Boolean
insert_all! / upsert_all のサポートlibSQL / SQLite は ON CONFLICT 構文をサポートする
111 112 113 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 111 def supports_insert_on_duplicate_skip? true end |
#supports_insert_on_duplicate_update? ⇒ Boolean
115 116 117 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 115 def supports_insert_on_duplicate_update? true end |
#supports_lazy_transactions? ⇒ Boolean
105 106 107 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 105 def supports_lazy_transactions? false end |
#supports_migrations? ⇒ Boolean
85 86 87 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 85 def supports_migrations? true end |
#supports_primary_key? ⇒ Boolean
89 90 91 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 89 def supports_primary_key? true end |
#supports_savepoints? ⇒ Boolean
97 98 99 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 97 def supports_savepoints? false end |
#sync ⇒ Object
Embedded Replica モードでリモートから最新フレームを手動同期する。Remote モードでは何もしない(no-op)。
187 188 189 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 187 def sync @raw_database&.sync end |
#table_exists?(table_name) ⇒ Boolean
318 319 320 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 318 def table_exists?(table_name) tables.include?(table_name.to_s) end |
#tables(_name = nil) ⇒ Object
296 297 298 299 300 301 302 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 296 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
123 124 125 126 127 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 123 def write_query?(sql) !READ_QUERY.match?(sql) rescue ArgumentError !READ_QUERY.match?(sql.b) end |