activerecord 7.1.5.1 → 8.0.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 +4 -4
- data/CHANGELOG.md +369 -2484
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +21 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/builder/belongs_to.rb +1 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +4 -3
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +92 -295
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +25 -61
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -18
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +63 -49
- data/lib/active_record/autosave_association.rb +92 -57
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +48 -122
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +286 -77
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +119 -55
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +197 -76
- data/lib/active_record/connection_adapters/abstract/quoting.rb +66 -92
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -12
- data/lib/active_record/connection_adapters/abstract/transaction.rb +140 -67
- data/lib/active_record/connection_adapters/abstract_adapter.rb +85 -90
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +71 -52
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +56 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +92 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +13 -31
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -41
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +36 -20
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +75 -28
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -113
- data/lib/active_record/connection_adapters/schema_cache.rb +124 -131
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +81 -97
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +29 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +35 -3
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +183 -87
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +39 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +65 -0
- data/lib/active_record/connection_handling.rb +74 -37
- data/lib/active_record/core.rb +132 -51
- data/lib/active_record/counter_cache.rb +19 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +23 -4
- data/lib/active_record/database_configurations/hash_config.rb +46 -34
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +41 -17
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +7 -7
- data/lib/active_record/encryption/encrypted_attribute_type.rb +33 -4
- data/lib/active_record/encryption/encryptor.rb +28 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/enum.rb +20 -16
- data/lib/active_record/errors.rb +54 -20
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -33
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +19 -16
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/log_subscriber.rb +5 -32
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +33 -14
- data/lib/active_record/migration/compatibility.rb +8 -3
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +104 -98
- data/lib/active_record/model_schema.rb +32 -70
- data/lib/active_record/nested_attributes.rb +15 -9
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +127 -451
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +104 -37
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +24 -12
- data/lib/active_record/railtie.rb +26 -68
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +43 -61
- data/lib/active_record/reflection.rb +112 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +138 -72
- data/lib/active_record/relation/calculations.rb +122 -82
- data/lib/active_record/relation/delegation.rb +30 -22
- data/lib/active_record/relation/finder_methods.rb +32 -18
- data/lib/active_record/relation/merger.rb +12 -14
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +16 -3
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +317 -101
- data/lib/active_record/relation/spawn_methods.rb +3 -19
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +561 -119
- data/lib/active_record/result.rb +95 -46
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +31 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +53 -20
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/signed_id.rb +24 -4
- data/lib/active_record/statement_cache.rb +19 -19
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +2 -13
- data/lib/active_record/tasks/database_tasks.rb +87 -58
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -3
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +4 -3
- data/lib/active_record/test_fixtures.rb +98 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +72 -17
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +138 -57
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +5 -4
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +29 -16
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +18 -16
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -11,20 +11,13 @@ require "active_record/connection_adapters/sqlite3/schema_definitions"
|
|
11
11
|
require "active_record/connection_adapters/sqlite3/schema_dumper"
|
12
12
|
require "active_record/connection_adapters/sqlite3/schema_statements"
|
13
13
|
|
14
|
-
gem "sqlite3", ">= 1
|
14
|
+
gem "sqlite3", ">= 2.1"
|
15
15
|
require "sqlite3"
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
def sqlite3_adapter_class
|
20
|
-
ConnectionAdapters::SQLite3Adapter
|
21
|
-
end
|
22
|
-
|
23
|
-
def sqlite3_connection(config)
|
24
|
-
sqlite3_adapter_class.new(config)
|
25
|
-
end
|
26
|
-
end
|
17
|
+
# Suppress the warning that SQLite3 issues when open writable connections are carried across fork()
|
18
|
+
SQLite3::ForkSafety.suppress_warnings!
|
27
19
|
|
20
|
+
module ActiveRecord
|
28
21
|
module ConnectionAdapters # :nodoc:
|
29
22
|
# = Active Record SQLite3 Adapter
|
30
23
|
#
|
@@ -55,7 +48,7 @@ module ActiveRecord
|
|
55
48
|
args << "-header" if options[:header]
|
56
49
|
args << File.expand_path(config.database, Rails.respond_to?(:root) ? Rails.root : nil)
|
57
50
|
|
58
|
-
find_cmd_and_exec(
|
51
|
+
find_cmd_and_exec(ActiveRecord.database_cli[:sqlite], *args)
|
59
52
|
end
|
60
53
|
end
|
61
54
|
|
@@ -88,6 +81,15 @@ module ActiveRecord
|
|
88
81
|
json: { name: "json" },
|
89
82
|
}
|
90
83
|
|
84
|
+
DEFAULT_PRAGMAS = {
|
85
|
+
"foreign_keys" => true,
|
86
|
+
"journal_mode" => :wal,
|
87
|
+
"synchronous" => :normal,
|
88
|
+
"mmap_size" => 134217728, # 128 megabytes
|
89
|
+
"journal_size_limit" => 67108864, # 64 megabytes
|
90
|
+
"cache_size" => 2000
|
91
|
+
}
|
92
|
+
|
91
93
|
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
92
94
|
alias reset clear
|
93
95
|
|
@@ -113,20 +115,21 @@ module ActiveRecord
|
|
113
115
|
dirname = File.dirname(@config[:database])
|
114
116
|
unless File.directory?(dirname)
|
115
117
|
begin
|
116
|
-
|
117
|
-
rescue
|
118
|
-
|
119
|
-
raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool)
|
120
|
-
else
|
121
|
-
raise
|
122
|
-
end
|
118
|
+
FileUtils.mkdir_p(dirname)
|
119
|
+
rescue SystemCallError
|
120
|
+
raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool)
|
123
121
|
end
|
124
122
|
end
|
125
123
|
end
|
126
124
|
|
125
|
+
@last_affected_rows = nil
|
126
|
+
@previous_read_uncommitted = nil
|
127
127
|
@config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
|
128
|
-
@connection_parameters = @config.merge(
|
129
|
-
|
128
|
+
@connection_parameters = @config.merge(
|
129
|
+
database: @config[:database].to_s,
|
130
|
+
results_as_hash: true,
|
131
|
+
default_transaction_mode: :immediate,
|
132
|
+
)
|
130
133
|
end
|
131
134
|
|
132
135
|
def database_exists?
|
@@ -196,12 +199,19 @@ module ActiveRecord
|
|
196
199
|
!@memory_database
|
197
200
|
end
|
198
201
|
|
199
|
-
def
|
200
|
-
|
202
|
+
def supports_virtual_columns?
|
203
|
+
database_version >= "3.31.0"
|
204
|
+
end
|
205
|
+
|
206
|
+
def connected?
|
207
|
+
!(@raw_connection.nil? || @raw_connection.closed?)
|
201
208
|
end
|
202
209
|
|
203
|
-
def
|
204
|
-
|
210
|
+
def active?
|
211
|
+
if connected?
|
212
|
+
verified!
|
213
|
+
true
|
214
|
+
end
|
205
215
|
end
|
206
216
|
|
207
217
|
alias :reset! :reconnect!
|
@@ -236,6 +246,10 @@ module ActiveRecord
|
|
236
246
|
true
|
237
247
|
end
|
238
248
|
|
249
|
+
def supports_deferrable_constraints?
|
250
|
+
true
|
251
|
+
end
|
252
|
+
|
239
253
|
# REFERENTIAL INTEGRITY ====================================
|
240
254
|
|
241
255
|
def disable_referential_integrity # :nodoc:
|
@@ -258,7 +272,7 @@ module ActiveRecord
|
|
258
272
|
|
259
273
|
unless result.blank?
|
260
274
|
tables = result.map { |row| row["table"] }
|
261
|
-
raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql)
|
275
|
+
raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql, connection_pool: @pool)
|
262
276
|
end
|
263
277
|
end
|
264
278
|
|
@@ -277,6 +291,38 @@ module ActiveRecord
|
|
277
291
|
exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
278
292
|
end
|
279
293
|
|
294
|
+
VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((.+)\)/i
|
295
|
+
|
296
|
+
# Returns a list of defined virtual tables
|
297
|
+
def virtual_tables
|
298
|
+
query = <<~SQL
|
299
|
+
SELECT name, sql FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL %';
|
300
|
+
SQL
|
301
|
+
|
302
|
+
exec_query(query, "SCHEMA").cast_values.each_with_object({}) do |row, memo|
|
303
|
+
table_name, sql = row[0], row[1]
|
304
|
+
_, module_name, arguments = sql.match(VIRTUAL_TABLE_REGEX).to_a
|
305
|
+
memo[table_name] = [module_name, arguments]
|
306
|
+
end.to_a
|
307
|
+
end
|
308
|
+
|
309
|
+
# Creates a virtual table
|
310
|
+
#
|
311
|
+
# Example:
|
312
|
+
# create_virtual_table :emails, :fts5, ['sender', 'title',' body']
|
313
|
+
def create_virtual_table(table_name, module_name, values)
|
314
|
+
exec_query "CREATE VIRTUAL TABLE IF NOT EXISTS #{table_name} USING #{module_name} (#{values.join(", ")})"
|
315
|
+
end
|
316
|
+
|
317
|
+
# Drops a virtual table
|
318
|
+
#
|
319
|
+
# Although this command ignores +module_name+ and +values+,
|
320
|
+
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
|
321
|
+
# In that case, +module_name+, +values+ and +options+ will be used by #create_virtual_table.
|
322
|
+
def drop_virtual_table(table_name, module_name, values, **options)
|
323
|
+
drop_table(table_name)
|
324
|
+
end
|
325
|
+
|
280
326
|
# Renames a table.
|
281
327
|
#
|
282
328
|
# Example:
|
@@ -290,6 +336,7 @@ module ActiveRecord
|
|
290
336
|
end
|
291
337
|
|
292
338
|
def add_column(table_name, column_name, type, **options) # :nodoc:
|
339
|
+
type = type.to_sym
|
293
340
|
if invalid_alter_table_type?(type, options)
|
294
341
|
alter_table(table_name) do |definition|
|
295
342
|
definition.column(column_name, type, **options)
|
@@ -365,15 +412,31 @@ module ActiveRecord
|
|
365
412
|
end
|
366
413
|
alias :add_belongs_to :add_reference
|
367
414
|
|
415
|
+
FK_REGEX = /.*FOREIGN KEY\s+\("([^"]+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
|
416
|
+
DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
|
368
417
|
def foreign_keys(table_name)
|
369
418
|
# SQLite returns 1 row for each column of composite foreign keys.
|
370
419
|
fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
|
420
|
+
# Deferred or immediate foreign keys can only be seen in the CREATE TABLE sql
|
421
|
+
fk_defs = table_structure_sql(table_name)
|
422
|
+
.select do |column_string|
|
423
|
+
column_string.start_with?("CONSTRAINT") &&
|
424
|
+
column_string.include?("FOREIGN KEY")
|
425
|
+
end
|
426
|
+
.to_h do |fk_string|
|
427
|
+
_, from, table, to = fk_string.match(FK_REGEX).to_a
|
428
|
+
_, mode = fk_string.match(DEFERRABLE_REGEX).to_a
|
429
|
+
deferred = mode&.downcase&.to_sym || false
|
430
|
+
[[table, from, to], deferred]
|
431
|
+
end
|
432
|
+
|
371
433
|
grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
|
372
434
|
grouped_fk.map do |group|
|
373
435
|
row = group.first
|
374
436
|
options = {
|
375
437
|
on_delete: extract_foreign_key_action(row["on_delete"]),
|
376
|
-
on_update: extract_foreign_key_action(row["on_update"])
|
438
|
+
on_update: extract_foreign_key_action(row["on_update"]),
|
439
|
+
deferrable: fk_defs[[row["table"], row["from"], row["to"]]]
|
377
440
|
}
|
378
441
|
|
379
442
|
if group.one?
|
@@ -410,10 +473,6 @@ module ActiveRecord
|
|
410
473
|
@config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
|
411
474
|
end
|
412
475
|
|
413
|
-
def use_insert_returning?
|
414
|
-
@use_insert_returning
|
415
|
-
end
|
416
|
-
|
417
476
|
def get_database_version # :nodoc:
|
418
477
|
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
|
419
478
|
end
|
@@ -454,8 +513,8 @@ module ActiveRecord
|
|
454
513
|
end
|
455
514
|
|
456
515
|
def table_structure(table_name)
|
457
|
-
structure =
|
458
|
-
raise
|
516
|
+
structure = table_info(table_name)
|
517
|
+
raise ActiveRecord::StatementInvalid.new("Could not find table '#{table_name}'", connection_pool: @pool) if structure.empty?
|
459
518
|
table_structure_with_collation(table_name, structure)
|
460
519
|
end
|
461
520
|
alias column_definitions table_structure
|
@@ -494,8 +553,9 @@ module ActiveRecord
|
|
494
553
|
# See: https://www.sqlite.org/lang_altertable.html
|
495
554
|
# SQLite has an additional restriction on the ALTER TABLE statement
|
496
555
|
def invalid_alter_table_type?(type, options)
|
497
|
-
type
|
498
|
-
options[:null] == false && options[:default].nil?
|
556
|
+
type == :primary_key || options[:primary_key] ||
|
557
|
+
options[:null] == false && options[:default].nil? ||
|
558
|
+
(type == :virtual && options[:stored])
|
499
559
|
end
|
500
560
|
|
501
561
|
def alter_table(
|
@@ -551,12 +611,6 @@ module ActiveRecord
|
|
551
611
|
options[:rename][column.name.to_sym] ||
|
552
612
|
column.name) : column.name
|
553
613
|
|
554
|
-
if column.has_default?
|
555
|
-
type = lookup_cast_type_from_column(column)
|
556
|
-
default = type.deserialize(column.default)
|
557
|
-
default = -> { column.default_function } if default.nil?
|
558
|
-
end
|
559
|
-
|
560
614
|
column_options = {
|
561
615
|
limit: column.limit,
|
562
616
|
precision: column.precision,
|
@@ -566,19 +620,31 @@ module ActiveRecord
|
|
566
620
|
primary_key: column_name == from_primary_key
|
567
621
|
}
|
568
622
|
|
569
|
-
|
570
|
-
column_options[:
|
623
|
+
if column.virtual?
|
624
|
+
column_options[:as] = column.default_function
|
625
|
+
column_options[:stored] = column.virtual_stored?
|
626
|
+
column_options[:type] = column.type
|
627
|
+
elsif column.has_default?
|
628
|
+
type = lookup_cast_type_from_column(column)
|
629
|
+
default = type.deserialize(column.default)
|
630
|
+
default = -> { column.default_function } if default.nil?
|
631
|
+
|
632
|
+
unless column.auto_increment?
|
633
|
+
column_options[:default] = default
|
634
|
+
end
|
571
635
|
end
|
572
636
|
|
573
|
-
column_type = column.bigint? ? :bigint : column.type
|
637
|
+
column_type = column.virtual? ? :virtual : (column.bigint? ? :bigint : column.type)
|
574
638
|
@definition.column(column_name, column_type, **column_options)
|
575
639
|
end
|
576
640
|
|
577
641
|
yield @definition if block_given?
|
578
642
|
end
|
579
643
|
copy_table_indexes(from, to, options[:rename] || {})
|
644
|
+
|
645
|
+
columns_to_copy = @definition.columns.reject { |col| col.options.key?(:as) }.map(&:name)
|
580
646
|
copy_table_contents(from, to,
|
581
|
-
|
647
|
+
columns_to_copy,
|
582
648
|
options[:rename] || {})
|
583
649
|
end
|
584
650
|
|
@@ -636,6 +702,8 @@ module ActiveRecord
|
|
636
702
|
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
637
703
|
elsif exception.message.match?(/called on a closed database/i)
|
638
704
|
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
705
|
+
elsif exception.is_a?(::SQLite3::BusyException)
|
706
|
+
StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
639
707
|
else
|
640
708
|
super
|
641
709
|
end
|
@@ -643,32 +711,22 @@ module ActiveRecord
|
|
643
711
|
|
644
712
|
COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i
|
645
713
|
PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*"(\w+)".+PRIMARY KEY AUTOINCREMENT/i
|
714
|
+
GENERATED_ALWAYS_AS_REGEX = /.*"(\w+)".+GENERATED ALWAYS AS \((.+)\) (?:STORED|VIRTUAL)/i
|
646
715
|
|
647
716
|
def table_structure_with_collation(table_name, basic_structure)
|
648
717
|
collation_hash = {}
|
649
718
|
auto_increments = {}
|
650
|
-
|
651
|
-
SELECT sql FROM
|
652
|
-
(SELECT * FROM sqlite_master UNION ALL
|
653
|
-
SELECT * FROM sqlite_temp_master)
|
654
|
-
WHERE type = 'table' AND name = #{quote(table_name)}
|
655
|
-
SQL
|
656
|
-
|
657
|
-
# Result will have following sample string
|
658
|
-
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
659
|
-
# "password_digest" varchar COLLATE "NOCASE");
|
660
|
-
result = query_value(sql, "SCHEMA")
|
719
|
+
generated_columns = {}
|
661
720
|
|
662
|
-
|
663
|
-
# Splitting with left parentheses and discarding the first part will return all
|
664
|
-
# columns separated with comma(,).
|
665
|
-
columns_string = result.split("(", 2).last
|
721
|
+
column_strings = table_structure_sql(table_name, basic_structure.map { |column| column["name"] })
|
666
722
|
|
667
|
-
|
723
|
+
if column_strings.any?
|
724
|
+
column_strings.each do |column_string|
|
668
725
|
# This regex will match the column name and collation type and will save
|
669
726
|
# the value in $1 and $2 respectively.
|
670
727
|
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
|
671
728
|
auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
|
729
|
+
generated_columns[$1] = $2 if GENERATED_ALWAYS_AS_REGEX =~ column_string
|
672
730
|
end
|
673
731
|
|
674
732
|
basic_structure.map do |column|
|
@@ -682,6 +740,10 @@ module ActiveRecord
|
|
682
740
|
column["auto_increment"] = true
|
683
741
|
end
|
684
742
|
|
743
|
+
if generated_columns.has_key?(column_name)
|
744
|
+
column["dflt_value"] = generated_columns[column_name]
|
745
|
+
end
|
746
|
+
|
685
747
|
column
|
686
748
|
end
|
687
749
|
else
|
@@ -689,6 +751,50 @@ module ActiveRecord
|
|
689
751
|
end
|
690
752
|
end
|
691
753
|
|
754
|
+
UNQUOTED_OPEN_PARENS_REGEX = /\((?![^'"]*['"][^'"]*$)/
|
755
|
+
FINAL_CLOSE_PARENS_REGEX = /\);*\z/
|
756
|
+
|
757
|
+
def table_structure_sql(table_name, column_names = nil)
|
758
|
+
unless column_names
|
759
|
+
column_info = table_info(table_name)
|
760
|
+
column_names = column_info.map { |column| column["name"] }
|
761
|
+
end
|
762
|
+
|
763
|
+
sql = <<~SQL
|
764
|
+
SELECT sql FROM
|
765
|
+
(SELECT * FROM sqlite_master UNION ALL
|
766
|
+
SELECT * FROM sqlite_temp_master)
|
767
|
+
WHERE type = 'table' AND name = #{quote(table_name)}
|
768
|
+
SQL
|
769
|
+
|
770
|
+
# Result will have following sample string
|
771
|
+
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
772
|
+
# "password_digest" varchar COLLATE "NOCASE",
|
773
|
+
# "o_id" integer,
|
774
|
+
# CONSTRAINT "fk_rails_78146ddd2e" FOREIGN KEY ("o_id") REFERENCES "os" ("id"));
|
775
|
+
result = query_value(sql, "SCHEMA")
|
776
|
+
|
777
|
+
return [] unless result
|
778
|
+
|
779
|
+
# Splitting with left parentheses and discarding the first part will return all
|
780
|
+
# columns separated with comma(,).
|
781
|
+
result.partition(UNQUOTED_OPEN_PARENS_REGEX)
|
782
|
+
.last
|
783
|
+
.sub(FINAL_CLOSE_PARENS_REGEX, "")
|
784
|
+
# column definitions can have a comma in them, so split on commas followed
|
785
|
+
# by a space and a column name in quotes or followed by the keyword CONSTRAINT
|
786
|
+
.split(/,(?=\s(?:CONSTRAINT|"(?:#{Regexp.union(column_names).source})"))/i)
|
787
|
+
.map(&:strip)
|
788
|
+
end
|
789
|
+
|
790
|
+
def table_info(table_name)
|
791
|
+
if supports_virtual_columns?
|
792
|
+
internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
|
793
|
+
else
|
794
|
+
internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
795
|
+
end
|
796
|
+
end
|
797
|
+
|
692
798
|
def arel_visitor
|
693
799
|
Arel::Visitors::SQLite.new(self)
|
694
800
|
end
|
@@ -715,37 +821,27 @@ module ActiveRecord
|
|
715
821
|
if @config[:timeout] && @config[:retries]
|
716
822
|
raise ArgumentError, "Cannot specify both timeout and retries arguments"
|
717
823
|
elsif @config[:timeout]
|
718
|
-
|
824
|
+
timeout = self.class.type_cast_config_to_integer(@config[:timeout])
|
825
|
+
raise TypeError, "timeout must be integer, not #{timeout}" unless timeout.is_a?(Integer)
|
826
|
+
@raw_connection.busy_handler_timeout = timeout
|
719
827
|
elsif @config[:retries]
|
828
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
829
|
+
The retries option is deprecated and will be removed in Rails 8.1. Use timeout instead.
|
830
|
+
MSG
|
720
831
|
retries = self.class.type_cast_config_to_integer(@config[:retries])
|
721
|
-
raw_connection.busy_handler
|
722
|
-
count <= retries
|
723
|
-
end
|
832
|
+
raw_connection.busy_handler { |count| count <= retries }
|
724
833
|
end
|
725
834
|
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
# 2 = "FULL" (sync on every write), 1 = "NORMAL" (sync every 1000 written pages) and 0 = "NONE"
|
736
|
-
# https://www.sqlite.org/pragma.html#pragma_synchronous
|
737
|
-
raw_execute("PRAGMA synchronous = NORMAL", "SCHEMA")
|
738
|
-
# Set the global memory map so all processes can share some data
|
739
|
-
# https://www.sqlite.org/pragma.html#pragma_mmap_size
|
740
|
-
# https://www.sqlite.org/mmap.html
|
741
|
-
raw_execute("PRAGMA mmap_size = #{128.megabytes}", "SCHEMA")
|
835
|
+
super
|
836
|
+
|
837
|
+
pragmas = @config.fetch(:pragmas, {}).stringify_keys
|
838
|
+
DEFAULT_PRAGMAS.merge(pragmas).each do |pragma, value|
|
839
|
+
if ::SQLite3::Pragmas.method_defined?("#{pragma}=")
|
840
|
+
@raw_connection.public_send("#{pragma}=", value)
|
841
|
+
else
|
842
|
+
warn "Unknown SQLite pragma: #{pragma}"
|
843
|
+
end
|
742
844
|
end
|
743
|
-
# Impose a limit on the WAL file to prevent unlimited growth
|
744
|
-
# https://www.sqlite.org/pragma.html#pragma_journal_size_limit
|
745
|
-
raw_execute("PRAGMA journal_size_limit = #{64.megabytes}", "SCHEMA")
|
746
|
-
# Set the local connection cache to 2000 pages
|
747
|
-
# https://www.sqlite.org/pragma.html#pragma_cache_size
|
748
|
-
raw_execute("PRAGMA cache_size = 2000", "SCHEMA")
|
749
845
|
end
|
750
846
|
end
|
751
847
|
ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
|
@@ -4,93 +4,63 @@ module ActiveRecord
|
|
4
4
|
module ConnectionAdapters
|
5
5
|
module Trilogy
|
6
6
|
module DatabaseStatements
|
7
|
-
def select_all(*, **) # :nodoc:
|
8
|
-
result = super
|
9
|
-
with_raw_connection do |conn|
|
10
|
-
conn.next_result while conn.more_results_exist?
|
11
|
-
end
|
12
|
-
result
|
13
|
-
end
|
14
|
-
|
15
|
-
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
|
16
|
-
sql = transform_query(sql)
|
17
|
-
check_if_write_query(sql)
|
18
|
-
mark_transaction_written_if_write(sql)
|
19
|
-
|
20
|
-
result = raw_execute(sql, name, async: async)
|
21
|
-
ActiveRecord::Result.new(result.fields, result.to_a)
|
22
|
-
end
|
23
|
-
|
24
7
|
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil, returning: nil) # :nodoc:
|
25
|
-
sql =
|
26
|
-
|
27
|
-
mark_transaction_written_if_write(sql)
|
28
|
-
|
29
|
-
raw_execute(to_sql(sql, binds), name)
|
8
|
+
sql, _binds = sql_for_insert(sql, pk, binds, returning)
|
9
|
+
internal_execute(sql, name)
|
30
10
|
end
|
31
11
|
|
32
|
-
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
33
|
-
sql = transform_query(sql)
|
34
|
-
check_if_write_query(sql)
|
35
|
-
mark_transaction_written_if_write(sql)
|
36
|
-
|
37
|
-
result = raw_execute(to_sql(sql, binds), name)
|
38
|
-
result.affected_rows
|
39
|
-
end
|
40
|
-
|
41
|
-
alias :exec_update :exec_delete # :nodoc:
|
42
|
-
|
43
12
|
private
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
result = conn.query(sql)
|
49
|
-
verified!
|
50
|
-
handle_warnings(sql)
|
51
|
-
result
|
52
|
-
end
|
13
|
+
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
|
14
|
+
reset_multi_statement = if batch && !@config[:multi_statement]
|
15
|
+
raw_connection.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
|
16
|
+
true
|
53
17
|
end
|
54
|
-
end
|
55
18
|
|
56
|
-
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
def sync_timezone_changes(conn)
|
61
|
-
# Sync any changes since connection last established.
|
19
|
+
# Make sure we carry over any changes to ActiveRecord.default_timezone that have been
|
20
|
+
# made since we established the connection
|
62
21
|
if default_timezone == :local
|
63
|
-
|
22
|
+
raw_connection.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
64
23
|
else
|
65
|
-
|
24
|
+
raw_connection.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
25
|
+
end
|
26
|
+
|
27
|
+
result = raw_connection.query(sql)
|
28
|
+
while raw_connection.more_results_exist?
|
29
|
+
raw_connection.next_result
|
30
|
+
end
|
31
|
+
verified!
|
32
|
+
handle_warnings(sql)
|
33
|
+
notification_payload[:row_count] = result.count
|
34
|
+
result
|
35
|
+
ensure
|
36
|
+
if reset_multi_statement && active?
|
37
|
+
raw_connection.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
|
66
38
|
end
|
67
39
|
end
|
68
40
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
conn.next_result while conn.more_results_exist?
|
75
|
-
end
|
41
|
+
def cast_result(result)
|
42
|
+
if result.fields.empty?
|
43
|
+
ActiveRecord::Result.empty
|
44
|
+
else
|
45
|
+
ActiveRecord::Result.new(result.fields, result.rows)
|
76
46
|
end
|
77
47
|
end
|
78
48
|
|
79
|
-
def
|
80
|
-
|
49
|
+
def affected_rows(result)
|
50
|
+
result.affected_rows
|
81
51
|
end
|
82
52
|
|
83
|
-
def
|
84
|
-
if
|
85
|
-
|
53
|
+
def last_inserted_id(result)
|
54
|
+
if supports_insert_returning?
|
55
|
+
super
|
56
|
+
else
|
57
|
+
result.last_insert_id
|
86
58
|
end
|
59
|
+
end
|
87
60
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
yield
|
92
|
-
ensure
|
93
|
-
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
|
61
|
+
def execute_batch(statements, name = nil, **kwargs)
|
62
|
+
combine_multi_statements(statements).each do |statement|
|
63
|
+
raw_execute(statement, name, batch: true, **kwargs)
|
94
64
|
end
|
95
65
|
end
|
96
66
|
end
|