Class: ActiveRecord::ConnectionAdapters::LibsqlAdapter
- Inherits:
-
AbstractAdapter
- Object
- AbstractAdapter
- ActiveRecord::ConnectionAdapters::LibsqlAdapter
- Extended by:
- Quoting::ClassMethods
- Includes:
- Quoting
- Defined in:
- lib/active_record/connection_adapters/libsql_adapter.rb
Defined Under Namespace
Modules: Quoting
Constant Summary collapse
- ADAPTER_NAME =
"Libsql"- NATIVE_DATABASE_TYPES =
— Native database types —
{ 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: "BOOLEAN" }, json: { name: "TEXT" } }.freeze
- TYPE_MAP =
Type::TypeMap.new.tap { |m| initialize_type_map(m) }
- EXTENDED_TYPE_MAPS =
Concurrent::Map.new
Class Method Summary collapse
Instance Method Summary collapse
- #active? ⇒ Boolean
- #affected_rows(result) ⇒ Object
-
#begin_db_transaction ⇒ Object
— Transactions —.
- #cast_result(result) ⇒ Object
- #column_definitions(table_name) ⇒ Object
- #commit_db_transaction ⇒ Object
- #connect ⇒ Object
- #connected? ⇒ Boolean
- #create_savepoint(name = current_savepoint_name) ⇒ Object
-
#discard! ⇒ Object
Clean up Rust runtime after fork (Puma / Unicorn safety).
- #disconnect! ⇒ Object
- #exec_rollback_db_transaction ⇒ Object
- #exec_rollback_to_savepoint(name = current_savepoint_name) ⇒ Object
- #indexes(table_name) ⇒ Object
-
#initialize ⇒ LibsqlAdapter
constructor
— Connection lifecycle —.
- #last_inserted_id(_result) ⇒ Object
- #native_database_types ⇒ Object
- #new_column_from_field(_table_name, field, _definitions) ⇒ Object
- #perform_query(raw_connection, sql, _binds, type_casted_binds, prepare:, notification_payload:, batch: false) ⇒ Object
- #primary_keys(table_name) ⇒ Object
- #quote_table_name(name) ⇒ Object
- #quoted_false ⇒ Object
- #quoted_true ⇒ Object
- #release_savepoint(name = current_savepoint_name) ⇒ Object
- #rename_column(table_name, column_name, new_column_name) ⇒ Object
-
#rename_table(table_name, new_name) ⇒ Object
— Schema DDL —.
- #supports_ddl_transactions? ⇒ Boolean
- #supports_explain? ⇒ Boolean
- #supports_foreign_keys? ⇒ Boolean
- #supports_insert_returning? ⇒ Boolean
- #supports_json? ⇒ Boolean
- #supports_lazy_transactions? ⇒ Boolean
-
#supports_migrations? ⇒ Boolean
— Feature flags —.
- #supports_primary_key? ⇒ Boolean
- #supports_savepoints? ⇒ Boolean
-
#sync ⇒ Object
Sync embedded replica with remote.
- #table_exists?(table_name) ⇒ Boolean
-
#tables ⇒ Object
— Schema introspection —.
-
#translate_exception(exception, message:, sql:, binds:) ⇒ Object
— Exception translation —.
- #unquoted_false ⇒ Object
- #unquoted_true ⇒ Object
- #views ⇒ Object
-
#write_query?(sql) ⇒ Boolean
— Query execution pipeline —.
Methods included from Quoting::ClassMethods
Constructor Details
#initialize ⇒ LibsqlAdapter
— Connection lifecycle —
109 110 111 112 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 109 def initialize(...) super @raw_database = nil end |
Class Method Details
.quote_table_name(name) ⇒ Object
77 78 79 80 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 77 def self.quote_table_name(name) @quoted_table_names ||= {} @quoted_table_names[name] ||= %("#{name.to_s.gsub('"', '""').gsub('.', '"."')}").freeze end |
Instance Method Details
#active? ⇒ Boolean
118 119 120 121 122 123 124 125 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 118 def active? return false unless @raw_connection @raw_connection.query("SELECT 1") true rescue ::Libsql::Error, ::Libsql::ClosedError false end |
#affected_rows(result) ⇒ Object
187 188 189 190 191 192 193 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 187 def affected_rows(result) if result.respond_to?(:affected_rows) result.affected_rows else @last_affected_rows end end |
#begin_db_transaction ⇒ Object
— Transactions —
201 202 203 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 201 def begin_db_transaction @raw_connection.execute("BEGIN") end |
#cast_result(result) ⇒ Object
183 184 185 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 183 def cast_result(result) result end |
#column_definitions(table_name) ⇒ Object
243 244 245 246 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 243 def column_definitions(table_name) result = @raw_connection.query("PRAGMA table_info(#{quote_table_name(table_name)})") result.to_a end |
#commit_db_transaction ⇒ Object
205 206 207 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 205 def commit_db_transaction @raw_connection.execute("COMMIT") end |
#connect ⇒ Object
114 115 116 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 114 def connect @raw_database, @raw_connection = build_libsql_connection end |
#connected? ⇒ Boolean
127 128 129 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 127 def connected? !@raw_connection.nil? end |
#create_savepoint(name = current_savepoint_name) ⇒ Object
213 214 215 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 213 def create_savepoint(name = current_savepoint_name) @raw_connection.execute("SAVEPOINT #{quote_column_name(name)}") end |
#discard! ⇒ Object
Clean up Rust runtime after fork (Puma / Unicorn safety).
140 141 142 143 144 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 140 def discard! @raw_database&.discard! @raw_connection = nil @raw_database = nil end |
#disconnect! ⇒ Object
131 132 133 134 135 136 137 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 131 def disconnect! super @raw_connection&.close @raw_database&.close @raw_connection = nil @raw_database = nil end |
#exec_rollback_db_transaction ⇒ Object
209 210 211 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 209 def exec_rollback_db_transaction @raw_connection.execute("ROLLBACK") end |
#exec_rollback_to_savepoint(name = current_savepoint_name) ⇒ Object
221 222 223 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 221 def exec_rollback_to_savepoint(name = current_savepoint_name) @raw_connection.execute("ROLLBACK TO SAVEPOINT #{quote_column_name(name)}") end |
#indexes(table_name) ⇒ Object
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 273 def indexes(table_name) index_list = @raw_connection.query("PRAGMA index_list(#{quote_table_name(table_name)})").to_a index_list.filter_map do |idx| next if idx["name"].start_with?("sqlite_") columns = @raw_connection.query("PRAGMA index_info(#{quote_table_name(idx['name'])})").to_a column_names = columns.sort_by { |c| c["seqno"].to_i }.map { |c| c["name"] } IndexDefinition.new( table_name, idx["name"], idx["unique"].to_i != 0, column_names ) end end |
#last_inserted_id(_result) ⇒ Object
195 196 197 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 195 def last_inserted_id(_result) @last_inserted_id end |
#native_database_types ⇒ Object
103 104 105 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 103 def native_database_types NATIVE_DATABASE_TYPES end |
#new_column_from_field(_table_name, field, _definitions) ⇒ Object
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 248 def new_column_from_field(_table_name, field, _definitions) default_value = field["dflt_value"] # Unquote string defaults default_value = default_value[1..-2] if default_value&.start_with?("'") && default_value.end_with?("'") type = field["type"] = (type) cast_type = lookup_cast_type(type) null = field["notnull"].to_i.zero? default_function = nil COLUMN_BUILDER.call( field["name"], cast_type, default_value, , null, default_function ) end |
#perform_query(raw_connection, sql, _binds, type_casted_binds, prepare:, notification_payload:, batch: false) ⇒ Object
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 159 def perform_query(raw_connection, sql, _binds, type_casted_binds, prepare:, notification_payload:, batch: false) sanitized = sanitize_for_update(sql) params = type_casted_binds || [] result = if batch raw_connection.execute_batch(sanitized) build_empty_result(affected: 0) elsif write_query?(sanitized) affected = raw_connection.execute(sanitized, params) @last_inserted_id = raw_connection.last_insert_rowid build_empty_result(affected: affected) else rows_obj = raw_connection.query(sanitized, params) build_read_result(rows_obj, raw_connection) end notification_payload[:row_count] = result&.length || 0 notification_payload[:affected_rows] = affected_rows(result) if AR_8_1_OR_LATER result end |
#primary_keys(table_name) ⇒ Object
265 266 267 268 269 270 271 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 265 def primary_keys(table_name) result = @raw_connection.query("PRAGMA table_info(#{quote_table_name(table_name)})") result.to_a .select { |row| row["pk"].to_i.positive? } .sort_by { |row| row["pk"].to_i } .map { |row| row["name"] } end |
#quote_table_name(name) ⇒ Object
82 83 84 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 82 def quote_table_name(name) self.class.quote_table_name(name) end |
#quoted_false ⇒ Object
87 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 87 def quoted_false = "0" |
#quoted_true ⇒ Object
86 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 86 def quoted_true = "1" |
#release_savepoint(name = current_savepoint_name) ⇒ Object
217 218 219 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 217 def release_savepoint(name = current_savepoint_name) @raw_connection.execute("RELEASE SAVEPOINT #{quote_column_name(name)}") end |
#rename_column(table_name, column_name, new_column_name) ⇒ Object
297 298 299 300 301 302 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 297 def rename_column(table_name, column_name, new_column_name, **) execute( "ALTER TABLE #{quote_table_name(table_name)} " \ "RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}" ) end |
#rename_table(table_name, new_name) ⇒ Object
— Schema DDL —
293 294 295 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 293 def rename_table(table_name, new_name, **) execute("ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}") end |
#supports_ddl_transactions? ⇒ Boolean
98 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 98 def supports_ddl_transactions? = false |
#supports_explain? ⇒ Boolean
99 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 99 def supports_explain? = false |
#supports_foreign_keys? ⇒ Boolean
96 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 96 def supports_foreign_keys? = true |
#supports_insert_returning? ⇒ Boolean
101 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 101 def supports_insert_returning? = false |
#supports_json? ⇒ Boolean
97 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 97 def supports_json? = true |
#supports_lazy_transactions? ⇒ Boolean
100 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 100 def supports_lazy_transactions? = false |
#supports_migrations? ⇒ Boolean
— Feature flags —
93 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 93 def supports_migrations? = true |
#supports_primary_key? ⇒ Boolean
94 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 94 def supports_primary_key? = true |
#supports_savepoints? ⇒ Boolean
95 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 95 def supports_savepoints? = true |
#sync ⇒ Object
Sync embedded replica with remote.
147 148 149 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 147 def sync @raw_database&.sync end |
#table_exists?(table_name) ⇒ Boolean
233 234 235 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 233 def table_exists?(table_name) tables.include?(table_name.to_s) end |
#tables ⇒ Object
— Schema introspection —
227 228 229 230 231 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 227 def tables query = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name" result = @raw_connection.query(query) result.to_a.map { |row| row["name"] } end |
#translate_exception(exception, message:, sql:, binds:) ⇒ Object
— Exception translation —
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 306 def translate_exception(exception, message:, sql:, binds:) # Libsql::Error inherits from RuntimeError, so the default # translate_exception would return it as-is. We must handle # it explicitly to convert to AR exception classes. msg = exception. if /NOT NULL constraint failed/i.match?(msg) NotNullViolation.new(, sql: sql, binds: binds, connection_pool: @pool) elsif /UNIQUE constraint failed/i.match?(msg) RecordNotUnique.new(, sql: sql, binds: binds, connection_pool: @pool) elsif /FOREIGN KEY constraint failed/i.match?(msg) InvalidForeignKey.new(, sql: sql, binds: binds, connection_pool: @pool) elsif exception.is_a?(::Libsql::Error) StatementInvalid.new(, sql: sql, binds: binds, connection_pool: @pool) else super end end |
#unquoted_false ⇒ Object
89 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 89 def unquoted_false = 0 |
#unquoted_true ⇒ Object
88 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 88 def unquoted_true = 1 |
#views ⇒ Object
237 238 239 240 241 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 237 def views query = "SELECT name FROM sqlite_master WHERE type='view' AND name NOT LIKE 'sqlite_%' ORDER BY name" result = @raw_connection.query(query) result.to_a.map { |row| row["name"] } end |
#write_query?(sql) ⇒ Boolean
— Query execution pipeline —
153 154 155 156 157 |
# File 'lib/active_record/connection_adapters/libsql_adapter.rb', line 153 def write_query?(sql) !READ_QUERY.match?(sql) rescue ArgumentError !READ_QUERY.match?(sql.b) end |