activerecord 7.1.6 → 7.2.3
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 +839 -2248
- data/README.rdoc +16 -16
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +15 -8
- data/lib/active_record/associations/belongs_to_association.rb +31 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- 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 +16 -8
- data/lib/active_record/associations/collection_proxy.rb +14 -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 +7 -1
- 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 +2 -1
- 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 +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +59 -292
- 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 +23 -55
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +5 -25
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods.rb +51 -60
- data/lib/active_record/attributes.rb +93 -68
- data/lib/active_record/autosave_association.rb +25 -32
- data/lib/active_record/base.rb +4 -5
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +294 -72
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -75
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -2
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +18 -6
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
- data/lib/active_record/connection_adapters/abstract_adapter.rb +46 -44
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +53 -15
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +19 -18
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -23
- data/lib/active_record/connection_adapters/pool_config.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- 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/schema_definitions.rb +30 -8
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +36 -26
- data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +133 -78
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +68 -49
- data/lib/active_record/core.rb +112 -44
- 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 +19 -4
- data/lib/active_record/database_configurations/hash_config.rb +38 -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 +42 -18
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +25 -5
- data/lib/active_record/encryption/encryptor.rb +35 -19
- 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/enum.rb +31 -13
- data/lib/active_record/errors.rb +49 -23
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +8 -4
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +7 -6
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +5 -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 +87 -77
- data/lib/active_record/model_schema.rb +31 -68
- data/lib/active_record/nested_attributes.rb +11 -3
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +19 -0
- data/lib/active_record/querying.rb +25 -13
- data/lib/active_record/railtie.rb +39 -57
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +42 -44
- data/lib/active_record/reflection.rb +98 -36
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +14 -8
- data/lib/active_record/relation/calculations.rb +127 -89
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +26 -12
- data/lib/active_record/relation/merger.rb +4 -6
- 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.rb +3 -3
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +238 -65
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +2 -18
- data/lib/active_record/relation/where_clause.rb +15 -21
- data/lib/active_record/relation.rb +508 -74
- data/lib/active_record/result.rb +31 -44
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +24 -19
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +48 -20
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/scoping/named.rb +1 -0
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +27 -7
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/table_metadata.rb +1 -10
- data/lib/active_record/tasks/database_tasks.rb +69 -41
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +8 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +86 -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 +73 -15
- 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 +15 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +148 -39
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +3 -1
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/crud.rb +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +0 -6
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +7 -3
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -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 +31 -16
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +16 -10
|
@@ -15,23 +15,13 @@ gem "sqlite3", ">= 1.4"
|
|
|
15
15
|
require "sqlite3"
|
|
16
16
|
|
|
17
17
|
module ActiveRecord
|
|
18
|
-
module ConnectionHandling # :nodoc:
|
|
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
|
|
27
|
-
|
|
28
18
|
module ConnectionAdapters # :nodoc:
|
|
29
19
|
# = Active Record SQLite3 Adapter
|
|
30
20
|
#
|
|
31
21
|
# The SQLite3 adapter works with the sqlite3-ruby drivers
|
|
32
22
|
# (available as gem from https://rubygems.org/gems/sqlite3).
|
|
33
23
|
#
|
|
34
|
-
# Options
|
|
24
|
+
# ==== Options
|
|
35
25
|
#
|
|
36
26
|
# * <tt>:database</tt> - Path to the database file.
|
|
37
27
|
class SQLite3Adapter < AbstractAdapter
|
|
@@ -88,6 +78,15 @@ module ActiveRecord
|
|
|
88
78
|
json: { name: "json" },
|
|
89
79
|
}
|
|
90
80
|
|
|
81
|
+
DEFAULT_PRAGMAS = {
|
|
82
|
+
"foreign_keys" => true,
|
|
83
|
+
"journal_mode" => :wal,
|
|
84
|
+
"synchronous" => :normal,
|
|
85
|
+
"mmap_size" => 134217728, # 128 megabytes
|
|
86
|
+
"journal_size_limit" => 67108864, # 64 megabytes
|
|
87
|
+
"cache_size" => 2000
|
|
88
|
+
}
|
|
89
|
+
|
|
91
90
|
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
|
92
91
|
alias reset clear
|
|
93
92
|
|
|
@@ -113,13 +112,9 @@ module ActiveRecord
|
|
|
113
112
|
dirname = File.dirname(@config[:database])
|
|
114
113
|
unless File.directory?(dirname)
|
|
115
114
|
begin
|
|
116
|
-
|
|
117
|
-
rescue
|
|
118
|
-
|
|
119
|
-
raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool)
|
|
120
|
-
else
|
|
121
|
-
raise
|
|
122
|
-
end
|
|
115
|
+
FileUtils.mkdir_p(dirname)
|
|
116
|
+
rescue SystemCallError
|
|
117
|
+
raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool)
|
|
123
118
|
end
|
|
124
119
|
end
|
|
125
120
|
end
|
|
@@ -196,12 +191,19 @@ module ActiveRecord
|
|
|
196
191
|
!@memory_database
|
|
197
192
|
end
|
|
198
193
|
|
|
199
|
-
def
|
|
200
|
-
|
|
194
|
+
def supports_virtual_columns?
|
|
195
|
+
database_version >= "3.31.0"
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def connected?
|
|
199
|
+
!(@raw_connection.nil? || @raw_connection.closed?)
|
|
201
200
|
end
|
|
202
201
|
|
|
203
|
-
def
|
|
204
|
-
|
|
202
|
+
def active?
|
|
203
|
+
if connected?
|
|
204
|
+
verified!
|
|
205
|
+
true
|
|
206
|
+
end
|
|
205
207
|
end
|
|
206
208
|
|
|
207
209
|
alias :reset! :reconnect!
|
|
@@ -236,6 +238,10 @@ module ActiveRecord
|
|
|
236
238
|
true
|
|
237
239
|
end
|
|
238
240
|
|
|
241
|
+
def supports_deferrable_constraints?
|
|
242
|
+
true
|
|
243
|
+
end
|
|
244
|
+
|
|
239
245
|
# REFERENTIAL INTEGRITY ====================================
|
|
240
246
|
|
|
241
247
|
def disable_referential_integrity # :nodoc:
|
|
@@ -258,7 +264,7 @@ module ActiveRecord
|
|
|
258
264
|
|
|
259
265
|
unless result.blank?
|
|
260
266
|
tables = result.map { |row| row["table"] }
|
|
261
|
-
raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql)
|
|
267
|
+
raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql, connection_pool: @pool)
|
|
262
268
|
end
|
|
263
269
|
end
|
|
264
270
|
|
|
@@ -290,6 +296,7 @@ module ActiveRecord
|
|
|
290
296
|
end
|
|
291
297
|
|
|
292
298
|
def add_column(table_name, column_name, type, **options) # :nodoc:
|
|
299
|
+
type = type.to_sym
|
|
293
300
|
if invalid_alter_table_type?(type, options)
|
|
294
301
|
alter_table(table_name) do |definition|
|
|
295
302
|
definition.column(column_name, type, **options)
|
|
@@ -365,15 +372,31 @@ module ActiveRecord
|
|
|
365
372
|
end
|
|
366
373
|
alias :add_belongs_to :add_reference
|
|
367
374
|
|
|
375
|
+
FK_REGEX = /.*FOREIGN KEY\s+\("([^"]+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
|
|
376
|
+
DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
|
|
368
377
|
def foreign_keys(table_name)
|
|
369
378
|
# SQLite returns 1 row for each column of composite foreign keys.
|
|
370
379
|
fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
|
|
380
|
+
# Deferred or immediate foreign keys can only be seen in the CREATE TABLE sql
|
|
381
|
+
fk_defs = table_structure_sql(table_name)
|
|
382
|
+
.select do |column_string|
|
|
383
|
+
column_string.start_with?("CONSTRAINT") &&
|
|
384
|
+
column_string.include?("FOREIGN KEY")
|
|
385
|
+
end
|
|
386
|
+
.to_h do |fk_string|
|
|
387
|
+
_, from, table, to = fk_string.match(FK_REGEX).to_a
|
|
388
|
+
_, mode = fk_string.match(DEFERRABLE_REGEX).to_a
|
|
389
|
+
deferred = mode&.downcase&.to_sym || false
|
|
390
|
+
[[table, from, to], deferred]
|
|
391
|
+
end
|
|
392
|
+
|
|
371
393
|
grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
|
|
372
394
|
grouped_fk.map do |group|
|
|
373
395
|
row = group.first
|
|
374
396
|
options = {
|
|
375
397
|
on_delete: extract_foreign_key_action(row["on_delete"]),
|
|
376
|
-
on_update: extract_foreign_key_action(row["on_update"])
|
|
398
|
+
on_update: extract_foreign_key_action(row["on_update"]),
|
|
399
|
+
deferrable: fk_defs[[row["table"], row["from"], row["to"]]]
|
|
377
400
|
}
|
|
378
401
|
|
|
379
402
|
if group.one?
|
|
@@ -454,8 +477,8 @@ module ActiveRecord
|
|
|
454
477
|
end
|
|
455
478
|
|
|
456
479
|
def table_structure(table_name)
|
|
457
|
-
structure =
|
|
458
|
-
raise
|
|
480
|
+
structure = table_info(table_name)
|
|
481
|
+
raise ActiveRecord::StatementInvalid.new("Could not find table '#{table_name}'", connection_pool: @pool) if structure.empty?
|
|
459
482
|
table_structure_with_collation(table_name, structure)
|
|
460
483
|
end
|
|
461
484
|
alias column_definitions table_structure
|
|
@@ -494,8 +517,9 @@ module ActiveRecord
|
|
|
494
517
|
# See: https://www.sqlite.org/lang_altertable.html
|
|
495
518
|
# SQLite has an additional restriction on the ALTER TABLE statement
|
|
496
519
|
def invalid_alter_table_type?(type, options)
|
|
497
|
-
type
|
|
498
|
-
options[:null] == false && options[:default].nil?
|
|
520
|
+
type == :primary_key || options[:primary_key] ||
|
|
521
|
+
options[:null] == false && options[:default].nil? ||
|
|
522
|
+
(type == :virtual && options[:stored])
|
|
499
523
|
end
|
|
500
524
|
|
|
501
525
|
def alter_table(
|
|
@@ -523,8 +547,8 @@ module ActiveRecord
|
|
|
523
547
|
yield definition if block_given?
|
|
524
548
|
end
|
|
525
549
|
|
|
526
|
-
|
|
527
|
-
|
|
550
|
+
disable_referential_integrity do
|
|
551
|
+
transaction do
|
|
528
552
|
move_table(table_name, altered_table_name, options.merge(temporary: true))
|
|
529
553
|
move_table(altered_table_name, table_name, &caller)
|
|
530
554
|
end
|
|
@@ -551,12 +575,6 @@ module ActiveRecord
|
|
|
551
575
|
options[:rename][column.name.to_sym] ||
|
|
552
576
|
column.name) : column.name
|
|
553
577
|
|
|
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
578
|
column_options = {
|
|
561
579
|
limit: column.limit,
|
|
562
580
|
precision: column.precision,
|
|
@@ -566,19 +584,31 @@ module ActiveRecord
|
|
|
566
584
|
primary_key: column_name == from_primary_key
|
|
567
585
|
}
|
|
568
586
|
|
|
569
|
-
|
|
570
|
-
column_options[:
|
|
587
|
+
if column.virtual?
|
|
588
|
+
column_options[:as] = column.default_function
|
|
589
|
+
column_options[:stored] = column.virtual_stored?
|
|
590
|
+
column_options[:type] = column.type
|
|
591
|
+
elsif column.has_default?
|
|
592
|
+
type = lookup_cast_type_from_column(column)
|
|
593
|
+
default = type.deserialize(column.default)
|
|
594
|
+
default = -> { column.default_function } if default.nil?
|
|
595
|
+
|
|
596
|
+
unless column.auto_increment?
|
|
597
|
+
column_options[:default] = default
|
|
598
|
+
end
|
|
571
599
|
end
|
|
572
600
|
|
|
573
|
-
column_type = column.bigint? ? :bigint : column.type
|
|
601
|
+
column_type = column.virtual? ? :virtual : (column.bigint? ? :bigint : column.type)
|
|
574
602
|
@definition.column(column_name, column_type, **column_options)
|
|
575
603
|
end
|
|
576
604
|
|
|
577
605
|
yield @definition if block_given?
|
|
578
606
|
end
|
|
579
607
|
copy_table_indexes(from, to, options[:rename] || {})
|
|
608
|
+
|
|
609
|
+
columns_to_copy = @definition.columns.reject { |col| col.options.key?(:as) }.map(&:name)
|
|
580
610
|
copy_table_contents(from, to,
|
|
581
|
-
|
|
611
|
+
columns_to_copy,
|
|
582
612
|
options[:rename] || {})
|
|
583
613
|
end
|
|
584
614
|
|
|
@@ -643,32 +673,22 @@ module ActiveRecord
|
|
|
643
673
|
|
|
644
674
|
COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i
|
|
645
675
|
PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*"(\w+)".+PRIMARY KEY AUTOINCREMENT/i
|
|
676
|
+
GENERATED_ALWAYS_AS_REGEX = /.*"(\w+)".+GENERATED ALWAYS AS \((.+)\) (?:STORED|VIRTUAL)/i
|
|
646
677
|
|
|
647
678
|
def table_structure_with_collation(table_name, basic_structure)
|
|
648
679
|
collation_hash = {}
|
|
649
680
|
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")
|
|
681
|
+
generated_columns = {}
|
|
661
682
|
|
|
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
|
|
683
|
+
column_strings = table_structure_sql(table_name, basic_structure.map { |column| column["name"] })
|
|
666
684
|
|
|
667
|
-
|
|
685
|
+
if column_strings.any?
|
|
686
|
+
column_strings.each do |column_string|
|
|
668
687
|
# This regex will match the column name and collation type and will save
|
|
669
688
|
# the value in $1 and $2 respectively.
|
|
670
689
|
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
|
|
671
690
|
auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
|
|
691
|
+
generated_columns[$1] = $2 if GENERATED_ALWAYS_AS_REGEX =~ column_string
|
|
672
692
|
end
|
|
673
693
|
|
|
674
694
|
basic_structure.map do |column|
|
|
@@ -682,6 +702,10 @@ module ActiveRecord
|
|
|
682
702
|
column["auto_increment"] = true
|
|
683
703
|
end
|
|
684
704
|
|
|
705
|
+
if generated_columns.has_key?(column_name)
|
|
706
|
+
column["dflt_value"] = generated_columns[column_name]
|
|
707
|
+
end
|
|
708
|
+
|
|
685
709
|
column
|
|
686
710
|
end
|
|
687
711
|
else
|
|
@@ -689,6 +713,50 @@ module ActiveRecord
|
|
|
689
713
|
end
|
|
690
714
|
end
|
|
691
715
|
|
|
716
|
+
UNQUOTED_OPEN_PARENS_REGEX = /\((?![^'"]*['"][^'"]*$)/
|
|
717
|
+
FINAL_CLOSE_PARENS_REGEX = /\);*\z/
|
|
718
|
+
|
|
719
|
+
def table_structure_sql(table_name, column_names = nil)
|
|
720
|
+
unless column_names
|
|
721
|
+
column_info = table_info(table_name)
|
|
722
|
+
column_names = column_info.map { |column| column["name"] }
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
sql = <<~SQL
|
|
726
|
+
SELECT sql FROM
|
|
727
|
+
(SELECT * FROM sqlite_master UNION ALL
|
|
728
|
+
SELECT * FROM sqlite_temp_master)
|
|
729
|
+
WHERE type = 'table' AND name = #{quote(table_name)}
|
|
730
|
+
SQL
|
|
731
|
+
|
|
732
|
+
# Result will have following sample string
|
|
733
|
+
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
734
|
+
# "password_digest" varchar COLLATE "NOCASE",
|
|
735
|
+
# "o_id" integer,
|
|
736
|
+
# CONSTRAINT "fk_rails_78146ddd2e" FOREIGN KEY ("o_id") REFERENCES "os" ("id"));
|
|
737
|
+
result = query_value(sql, "SCHEMA")
|
|
738
|
+
|
|
739
|
+
return [] unless result
|
|
740
|
+
|
|
741
|
+
# Splitting with left parentheses and discarding the first part will return all
|
|
742
|
+
# columns separated with comma(,).
|
|
743
|
+
result.partition(UNQUOTED_OPEN_PARENS_REGEX)
|
|
744
|
+
.last
|
|
745
|
+
.sub(FINAL_CLOSE_PARENS_REGEX, "")
|
|
746
|
+
# column definitions can have a comma in them, so split on commas followed
|
|
747
|
+
# by a space and a column name in quotes or followed by the keyword CONSTRAINT
|
|
748
|
+
.split(/,(?=\s(?:CONSTRAINT|"(?:#{Regexp.union(column_names).source})"))/i)
|
|
749
|
+
.map(&:strip)
|
|
750
|
+
end
|
|
751
|
+
|
|
752
|
+
def table_info(table_name)
|
|
753
|
+
if supports_virtual_columns?
|
|
754
|
+
internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
|
|
755
|
+
else
|
|
756
|
+
internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
|
757
|
+
end
|
|
758
|
+
end
|
|
759
|
+
|
|
692
760
|
def arel_visitor
|
|
693
761
|
Arel::Visitors::SQLite.new(self)
|
|
694
762
|
end
|
|
@@ -723,29 +791,16 @@ module ActiveRecord
|
|
|
723
791
|
end
|
|
724
792
|
end
|
|
725
793
|
|
|
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")
|
|
794
|
+
super
|
|
795
|
+
|
|
796
|
+
pragmas = @config.fetch(:pragmas, {}).stringify_keys
|
|
797
|
+
DEFAULT_PRAGMAS.merge(pragmas).each do |pragma, value|
|
|
798
|
+
if ::SQLite3::Pragmas.method_defined?("#{pragma}=")
|
|
799
|
+
@raw_connection.public_send("#{pragma}=", value)
|
|
800
|
+
else
|
|
801
|
+
warn "Unknown SQLite pragma: #{pragma}"
|
|
802
|
+
end
|
|
742
803
|
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
804
|
end
|
|
750
805
|
end
|
|
751
806
|
ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
|
|
@@ -4,20 +4,12 @@ module ActiveRecord
|
|
|
4
4
|
module ConnectionAdapters
|
|
5
5
|
module Trilogy
|
|
6
6
|
module DatabaseStatements
|
|
7
|
-
def
|
|
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:
|
|
7
|
+
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
|
|
16
8
|
sql = transform_query(sql)
|
|
17
9
|
check_if_write_query(sql)
|
|
18
10
|
mark_transaction_written_if_write(sql)
|
|
19
11
|
|
|
20
|
-
result = raw_execute(sql, name, async: async)
|
|
12
|
+
result = raw_execute(sql, name, async: async, allow_retry: allow_retry)
|
|
21
13
|
ActiveRecord::Result.new(result.fields, result.to_a)
|
|
22
14
|
end
|
|
23
15
|
|
|
@@ -26,7 +18,8 @@ module ActiveRecord
|
|
|
26
18
|
check_if_write_query(sql)
|
|
27
19
|
mark_transaction_written_if_write(sql)
|
|
28
20
|
|
|
29
|
-
|
|
21
|
+
sql, _binds = sql_for_insert(sql, pk, binds, returning)
|
|
22
|
+
raw_execute(sql, name)
|
|
30
23
|
end
|
|
31
24
|
|
|
32
25
|
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
|
@@ -42,19 +35,27 @@ module ActiveRecord
|
|
|
42
35
|
|
|
43
36
|
private
|
|
44
37
|
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
|
45
|
-
log(sql, name, async: async) do
|
|
38
|
+
log(sql, name, async: async) do |notification_payload|
|
|
46
39
|
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
|
47
40
|
sync_timezone_changes(conn)
|
|
48
41
|
result = conn.query(sql)
|
|
42
|
+
while conn.more_results_exist?
|
|
43
|
+
conn.next_result
|
|
44
|
+
end
|
|
49
45
|
verified!
|
|
50
46
|
handle_warnings(sql)
|
|
47
|
+
notification_payload[:row_count] = result.count
|
|
51
48
|
result
|
|
52
49
|
end
|
|
53
50
|
end
|
|
54
51
|
end
|
|
55
52
|
|
|
56
53
|
def last_inserted_id(result)
|
|
57
|
-
|
|
54
|
+
if supports_insert_returning?
|
|
55
|
+
super
|
|
56
|
+
else
|
|
57
|
+
result.last_insert_id
|
|
58
|
+
end
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
def sync_timezone_changes(conn)
|
|
@@ -71,7 +72,6 @@ module ActiveRecord
|
|
|
71
72
|
combine_multi_statements(statements).each do |statement|
|
|
72
73
|
with_raw_connection do |conn|
|
|
73
74
|
raw_execute(statement, name)
|
|
74
|
-
conn.next_result while conn.more_results_exist?
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
77
|
end
|
|
@@ -90,7 +90,7 @@ module ActiveRecord
|
|
|
90
90
|
|
|
91
91
|
yield
|
|
92
92
|
ensure
|
|
93
|
-
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
|
|
93
|
+
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF) if active?
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
96
|
end
|
|
@@ -2,44 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
require "active_record/connection_adapters/abstract_mysql_adapter"
|
|
4
4
|
|
|
5
|
-
gem "trilogy", "~> 2.
|
|
5
|
+
gem "trilogy", "~> 2.7"
|
|
6
6
|
require "trilogy"
|
|
7
7
|
|
|
8
8
|
require "active_record/connection_adapters/trilogy/database_statements"
|
|
9
9
|
|
|
10
10
|
module ActiveRecord
|
|
11
|
-
module ConnectionHandling # :nodoc:
|
|
12
|
-
def trilogy_adapter_class
|
|
13
|
-
ConnectionAdapters::TrilogyAdapter
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Establishes a connection to the database that's used by all Active Record objects.
|
|
17
|
-
def trilogy_connection(config)
|
|
18
|
-
configuration = config.dup
|
|
19
|
-
|
|
20
|
-
# Set FOUND_ROWS capability on the connection so UPDATE queries returns number of rows
|
|
21
|
-
# matched rather than number of rows updated.
|
|
22
|
-
configuration[:found_rows] = true
|
|
23
|
-
|
|
24
|
-
options = [
|
|
25
|
-
configuration[:host],
|
|
26
|
-
configuration[:port],
|
|
27
|
-
configuration[:database],
|
|
28
|
-
configuration[:username],
|
|
29
|
-
configuration[:password],
|
|
30
|
-
configuration[:socket],
|
|
31
|
-
0
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
trilogy_adapter_class.new nil, logger, options, configuration
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
11
|
module ConnectionAdapters
|
|
38
12
|
class TrilogyAdapter < AbstractMysqlAdapter
|
|
39
13
|
ER_BAD_DB_ERROR = 1049
|
|
40
14
|
ER_DBACCESS_DENIED_ERROR = 1044
|
|
41
15
|
ER_ACCESS_DENIED_ERROR = 1045
|
|
42
|
-
ER_SERVER_SHUTDOWN = 1053
|
|
43
16
|
|
|
44
17
|
ADAPTER_NAME = "Trilogy"
|
|
45
18
|
|
|
@@ -99,16 +72,22 @@ module ActiveRecord
|
|
|
99
72
|
end
|
|
100
73
|
end
|
|
101
74
|
|
|
102
|
-
def initialize(
|
|
103
|
-
|
|
75
|
+
def initialize(config, *)
|
|
76
|
+
config = config.dup
|
|
77
|
+
|
|
78
|
+
# Trilogy ignores `socket` if `host is set. We want the opposite to allow
|
|
79
|
+
# configuring UNIX domain sockets via `DATABASE_URL`.
|
|
80
|
+
config.delete(:host) if config[:socket]
|
|
104
81
|
|
|
105
|
-
|
|
82
|
+
# Set FOUND_ROWS capability on the connection so UPDATE queries returns number of rows
|
|
83
|
+
# matched rather than number of rows updated.
|
|
84
|
+
config[:found_rows] = true
|
|
85
|
+
|
|
86
|
+
if config[:prepared_statements]
|
|
106
87
|
raise ArgumentError, "Trilogy currently doesn't support prepared statements. Remove `prepared_statements: true` from your database configuration."
|
|
107
88
|
end
|
|
108
89
|
|
|
109
|
-
|
|
110
|
-
# configuring UNIX domain sockets via `DATABASE_URL`.
|
|
111
|
-
@config.delete(:host) if @config[:socket]
|
|
90
|
+
super
|
|
112
91
|
end
|
|
113
92
|
|
|
114
93
|
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
|
|
@@ -137,14 +116,12 @@ module ActiveRecord
|
|
|
137
116
|
true
|
|
138
117
|
end
|
|
139
118
|
|
|
140
|
-
def
|
|
141
|
-
|
|
142
|
-
conn.escape(string)
|
|
143
|
-
end
|
|
119
|
+
def connected?
|
|
120
|
+
!(@raw_connection.nil? || @raw_connection.closed?)
|
|
144
121
|
end
|
|
145
122
|
|
|
146
123
|
def active?
|
|
147
|
-
|
|
124
|
+
connected? && @lock.synchronize { @raw_connection&.ping; verified! } || false
|
|
148
125
|
rescue ::Trilogy::Error
|
|
149
126
|
false
|
|
150
127
|
end
|
|
@@ -206,7 +183,7 @@ module ActiveRecord
|
|
|
206
183
|
end
|
|
207
184
|
|
|
208
185
|
def full_version
|
|
209
|
-
|
|
186
|
+
database_version.full_version_string
|
|
210
187
|
end
|
|
211
188
|
|
|
212
189
|
def get_full_version
|
|
@@ -219,18 +196,12 @@ module ActiveRecord
|
|
|
219
196
|
if exception.is_a?(::Trilogy::TimeoutError) && !exception.error_code
|
|
220
197
|
return ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
221
198
|
end
|
|
222
|
-
error_code = exception.error_code if exception.respond_to?(:error_code)
|
|
223
|
-
|
|
224
|
-
case error_code
|
|
225
|
-
when ER_SERVER_SHUTDOWN
|
|
226
|
-
return ConnectionFailed.new(message, connection_pool: @pool)
|
|
227
|
-
end
|
|
228
199
|
|
|
229
200
|
case exception
|
|
230
|
-
when
|
|
201
|
+
when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError
|
|
231
202
|
return ConnectionFailed.new(message, connection_pool: @pool)
|
|
232
203
|
when ::Trilogy::Error
|
|
233
|
-
if
|
|
204
|
+
if exception.is_a?(SystemCallError) || exception.message.include?("TRILOGY_INVALID_SEQUENCE_ID")
|
|
234
205
|
return ConnectionFailed.new(message, connection_pool: @pool)
|
|
235
206
|
end
|
|
236
207
|
end
|