activerecord 7.1.5.2 → 8.1.2.1
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 +567 -2443
- data/README.rdoc +16 -16
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +3 -2
- 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 +31 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +18 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- 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/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +36 -5
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +268 -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/join_dependency.rb +4 -2
- 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/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +8 -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 +250 -315
- 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/query.rb +34 -0
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +20 -27
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +96 -68
- data/lib/active_record/autosave_association.rb +93 -58
- data/lib/active_record/base.rb +5 -7
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
- data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- 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/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
- data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
- 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 +43 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +66 -0
- data/lib/active_record/connection_handling.rb +88 -46
- data/lib/active_record/core.rb +142 -58
- data/lib/active_record/counter_cache.rb +52 -18
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +28 -5
- data/lib/active_record/database_configurations/hash_config.rb +103 -41
- data/lib/active_record/database_configurations/url_config.rb +30 -1
- data/lib/active_record/database_configurations.rb +8 -4
- data/lib/active_record/delegated_type.rb +43 -19
- data/lib/active_record/dynamic_matchers.rb +56 -71
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +12 -12
- data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
- data/lib/active_record/encryption/encryptor.rb +58 -22
- 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 +9 -2
- data/lib/active_record/enum.rb +57 -36
- data/lib/active_record/errors.rb +76 -26
- data/lib/active_record/explain.rb +14 -25
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +39 -35
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +5 -3
- data/lib/active_record/insert_all.rb +28 -20
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +15 -7
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -34
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +46 -14
- data/lib/active_record/migration/compatibility.rb +40 -25
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- 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 +119 -103
- data/lib/active_record/model_schema.rb +66 -78
- data/lib/active_record/nested_attributes.rb +17 -9
- data/lib/active_record/persistence.rb +161 -454
- data/lib/active_record/query_cache.rb +33 -15
- data/lib/active_record/query_logs.rb +106 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +28 -16
- data/lib/active_record/railtie.rb +61 -74
- data/lib/active_record/railties/controller_runtime.rb +19 -5
- data/lib/active_record/railties/databases.rake +64 -78
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +147 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +153 -73
- data/lib/active_record/relation/calculations.rb +146 -96
- data/lib/active_record/relation/delegation.rb +30 -23
- data/lib/active_record/relation/finder_methods.rb +64 -34
- 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 +19 -11
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +25 -10
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +351 -124
- data/lib/active_record/relation/spawn_methods.rb +9 -25
- data/lib/active_record/relation/where_clause.rb +17 -30
- data/lib/active_record/relation.rb +590 -124
- data/lib/active_record/result.rb +131 -59
- data/lib/active_record/runtime_registry.rb +44 -21
- data/lib/active_record/sanitization.rb +33 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +66 -31
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +68 -20
- data/lib/active_record/statement_cache.rb +29 -25
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +7 -33
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +118 -90
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +123 -89
- data/lib/active_record/testing/query_assertions.rb +127 -0
- data/lib/active_record/timestamp.rb +6 -4
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +129 -0
- data/lib/active_record/transactions.rb +97 -18
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +12 -7
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +5 -4
- data/lib/active_record/validations/associated.rb +10 -4
- data/lib/active_record/validations/uniqueness.rb +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +203 -59
- data/lib/arel/alias_predication.rb +3 -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/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +6 -5
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +2 -4
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +14 -5
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -3
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +56 -12
- data/lib/arel/visitors/sqlite.rb +80 -8
- data/lib/arel/visitors/to_sql.rb +34 -37
- data/lib/arel.rb +10 -4
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +23 -14
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -167
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
|
@@ -11,29 +11,38 @@ 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
|
-
# = Active Record SQLite3 Adapter
|
|
22
|
+
# = Active Record \SQLite3 Adapter
|
|
30
23
|
#
|
|
31
|
-
# The SQLite3 adapter works with the sqlite3-ruby
|
|
32
|
-
#
|
|
24
|
+
# The \SQLite3 adapter works with the sqlite3[https://sparklemotion.github.io/sqlite3-ruby/]
|
|
25
|
+
# driver.
|
|
33
26
|
#
|
|
34
|
-
# Options
|
|
27
|
+
# ==== Options
|
|
28
|
+
#
|
|
29
|
+
# * +:database+ (String): Filesystem path to the database file.
|
|
30
|
+
# * +:statement_limit+ (Integer): Maximum number of prepared statements to cache per database connection. (default: 1000)
|
|
31
|
+
# * +:timeout+ (Integer): Timeout in milliseconds to use when waiting for a lock. (default: no wait)
|
|
32
|
+
# * +:strict+ (Boolean): Enable or disable strict mode. When enabled, this will
|
|
33
|
+
# {disallow double-quoted string literals in SQL
|
|
34
|
+
# statements}[https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted].
|
|
35
|
+
# (default: see strict_strings_by_default)
|
|
36
|
+
# * +:extensions+ (Array): (<b>requires sqlite3 v2.4.0</b>) Each entry specifies a sqlite extension
|
|
37
|
+
# to load for this database. The entry may be a filesystem path, or the name of a class that
|
|
38
|
+
# responds to +.to_path+ to provide the filesystem path for the extension. See {sqlite3-ruby
|
|
39
|
+
# documentation}[https://sparklemotion.github.io/sqlite3-ruby/SQLite3/Database.html#class-SQLite3::Database-label-SQLite+Extensions]
|
|
40
|
+
# for more information.
|
|
41
|
+
#
|
|
42
|
+
# There may be other options available specific to the SQLite3 driver. Please read the
|
|
43
|
+
# documentation for
|
|
44
|
+
# {SQLite3::Database.new}[https://sparklemotion.github.io/sqlite3-ruby/SQLite3/Database.html#method-c-new]
|
|
35
45
|
#
|
|
36
|
-
# * <tt>:database</tt> - Path to the database file.
|
|
37
46
|
class SQLite3Adapter < AbstractAdapter
|
|
38
47
|
ADAPTER_NAME = "SQLite"
|
|
39
48
|
|
|
@@ -53,9 +62,13 @@ module ActiveRecord
|
|
|
53
62
|
|
|
54
63
|
args << "-#{options[:mode]}" if options[:mode]
|
|
55
64
|
args << "-header" if options[:header]
|
|
56
|
-
args << File.expand_path(config.database, Rails.
|
|
65
|
+
args << File.expand_path(config.database, defined?(Rails.root) ? Rails.root : nil)
|
|
66
|
+
|
|
67
|
+
find_cmd_and_exec(ActiveRecord.database_cli[:sqlite], *args)
|
|
68
|
+
end
|
|
57
69
|
|
|
58
|
-
|
|
70
|
+
def native_database_types # :nodoc:
|
|
71
|
+
NATIVE_DATABASE_TYPES
|
|
59
72
|
end
|
|
60
73
|
end
|
|
61
74
|
|
|
@@ -65,12 +78,19 @@ module ActiveRecord
|
|
|
65
78
|
|
|
66
79
|
##
|
|
67
80
|
# :singleton-method:
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
81
|
+
#
|
|
82
|
+
# Configure the SQLite3Adapter to be used in a "strict strings" mode. When enabled, this will
|
|
83
|
+
# {disallow double-quoted string literals in SQL
|
|
84
|
+
# statements}[https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted],
|
|
85
|
+
# which may prevent some typographical errors like creating an index for a non-existent
|
|
86
|
+
# column. The default is +false+.
|
|
87
|
+
#
|
|
71
88
|
# If you wish to enable this mode you can add the following line to your application.rb file:
|
|
72
89
|
#
|
|
73
90
|
# config.active_record.sqlite3_adapter_strict_strings_by_default = true
|
|
91
|
+
#
|
|
92
|
+
# This can also be configured on individual databases by setting the +strict:+ option.
|
|
93
|
+
#
|
|
74
94
|
class_attribute :strict_strings_by_default, default: false
|
|
75
95
|
|
|
76
96
|
NATIVE_DATABASE_TYPES = {
|
|
@@ -88,6 +108,15 @@ module ActiveRecord
|
|
|
88
108
|
json: { name: "json" },
|
|
89
109
|
}
|
|
90
110
|
|
|
111
|
+
DEFAULT_PRAGMAS = {
|
|
112
|
+
"foreign_keys" => true,
|
|
113
|
+
"journal_mode" => :wal,
|
|
114
|
+
"synchronous" => :normal,
|
|
115
|
+
"mmap_size" => 134217728, # 128 megabytes
|
|
116
|
+
"journal_size_limit" => 67108864, # 64 megabytes
|
|
117
|
+
"cache_size" => 2000
|
|
118
|
+
}
|
|
119
|
+
|
|
91
120
|
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
|
92
121
|
alias reset clear
|
|
93
122
|
|
|
@@ -113,20 +142,26 @@ module ActiveRecord
|
|
|
113
142
|
dirname = File.dirname(@config[:database])
|
|
114
143
|
unless File.directory?(dirname)
|
|
115
144
|
begin
|
|
116
|
-
|
|
117
|
-
rescue
|
|
118
|
-
|
|
119
|
-
raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool)
|
|
120
|
-
else
|
|
121
|
-
raise
|
|
122
|
-
end
|
|
145
|
+
FileUtils.mkdir_p(dirname)
|
|
146
|
+
rescue SystemCallError
|
|
147
|
+
raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool)
|
|
123
148
|
end
|
|
124
149
|
end
|
|
125
150
|
end
|
|
126
151
|
|
|
152
|
+
@previous_read_uncommitted = nil
|
|
127
153
|
@config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
|
|
128
|
-
|
|
129
|
-
|
|
154
|
+
|
|
155
|
+
extensions = @config.fetch(:extensions, []).map do |extension|
|
|
156
|
+
extension.safe_constantize || extension
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
@connection_parameters = @config.merge(
|
|
160
|
+
database: @config[:database].to_s,
|
|
161
|
+
results_as_hash: true,
|
|
162
|
+
default_transaction_mode: :immediate,
|
|
163
|
+
extensions: extensions
|
|
164
|
+
)
|
|
130
165
|
end
|
|
131
166
|
|
|
132
167
|
def database_exists?
|
|
@@ -150,7 +185,7 @@ module ActiveRecord
|
|
|
150
185
|
end
|
|
151
186
|
|
|
152
187
|
def supports_expression_index?
|
|
153
|
-
|
|
188
|
+
true
|
|
154
189
|
end
|
|
155
190
|
|
|
156
191
|
def requires_reloading?
|
|
@@ -178,7 +213,7 @@ module ActiveRecord
|
|
|
178
213
|
end
|
|
179
214
|
|
|
180
215
|
def supports_common_table_expressions?
|
|
181
|
-
|
|
216
|
+
true
|
|
182
217
|
end
|
|
183
218
|
|
|
184
219
|
def supports_insert_returning?
|
|
@@ -196,12 +231,19 @@ module ActiveRecord
|
|
|
196
231
|
!@memory_database
|
|
197
232
|
end
|
|
198
233
|
|
|
199
|
-
def
|
|
200
|
-
|
|
234
|
+
def supports_virtual_columns?
|
|
235
|
+
database_version >= "3.31.0"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def connected?
|
|
239
|
+
!(@raw_connection.nil? || @raw_connection.closed?)
|
|
201
240
|
end
|
|
202
241
|
|
|
203
|
-
def
|
|
204
|
-
|
|
242
|
+
def active?
|
|
243
|
+
if connected?
|
|
244
|
+
verified!
|
|
245
|
+
true
|
|
246
|
+
end
|
|
205
247
|
end
|
|
206
248
|
|
|
207
249
|
alias :reset! :reconnect!
|
|
@@ -219,10 +261,6 @@ module ActiveRecord
|
|
|
219
261
|
true
|
|
220
262
|
end
|
|
221
263
|
|
|
222
|
-
def native_database_types # :nodoc:
|
|
223
|
-
NATIVE_DATABASE_TYPES
|
|
224
|
-
end
|
|
225
|
-
|
|
226
264
|
# Returns the current database encoding format as a string, e.g. 'UTF-8'
|
|
227
265
|
def encoding
|
|
228
266
|
any_raw_connection.encoding.to_s
|
|
@@ -236,6 +274,10 @@ module ActiveRecord
|
|
|
236
274
|
true
|
|
237
275
|
end
|
|
238
276
|
|
|
277
|
+
def supports_deferrable_constraints?
|
|
278
|
+
true
|
|
279
|
+
end
|
|
280
|
+
|
|
239
281
|
# REFERENTIAL INTEGRITY ====================================
|
|
240
282
|
|
|
241
283
|
def disable_referential_integrity # :nodoc:
|
|
@@ -258,7 +300,7 @@ module ActiveRecord
|
|
|
258
300
|
|
|
259
301
|
unless result.blank?
|
|
260
302
|
tables = result.map { |row| row["table"] }
|
|
261
|
-
raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql)
|
|
303
|
+
raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql, connection_pool: @pool)
|
|
262
304
|
end
|
|
263
305
|
end
|
|
264
306
|
|
|
@@ -277,6 +319,38 @@ module ActiveRecord
|
|
|
277
319
|
exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
|
278
320
|
end
|
|
279
321
|
|
|
322
|
+
VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((.+)\)/i
|
|
323
|
+
|
|
324
|
+
# Returns a list of defined virtual tables
|
|
325
|
+
def virtual_tables
|
|
326
|
+
query = <<~SQL
|
|
327
|
+
SELECT name, sql FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL %';
|
|
328
|
+
SQL
|
|
329
|
+
|
|
330
|
+
exec_query(query, "SCHEMA").cast_values.each_with_object({}) do |row, memo|
|
|
331
|
+
table_name, sql = row[0], row[1]
|
|
332
|
+
_, module_name, arguments = sql.match(VIRTUAL_TABLE_REGEX).to_a
|
|
333
|
+
memo[table_name] = [module_name, arguments]
|
|
334
|
+
end.to_a
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Creates a virtual table
|
|
338
|
+
#
|
|
339
|
+
# Example:
|
|
340
|
+
# create_virtual_table :emails, :fts5, ['sender', 'title', 'body']
|
|
341
|
+
def create_virtual_table(table_name, module_name, values)
|
|
342
|
+
exec_query "CREATE VIRTUAL TABLE IF NOT EXISTS #{table_name} USING #{module_name} (#{values.join(", ")})"
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Drops a virtual table
|
|
346
|
+
#
|
|
347
|
+
# Although this command ignores +module_name+ and +values+,
|
|
348
|
+
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
|
|
349
|
+
# In that case, +module_name+, +values+ and +options+ will be used by #create_virtual_table.
|
|
350
|
+
def drop_virtual_table(table_name, module_name, values, **options)
|
|
351
|
+
drop_table(table_name)
|
|
352
|
+
end
|
|
353
|
+
|
|
280
354
|
# Renames a table.
|
|
281
355
|
#
|
|
282
356
|
# Example:
|
|
@@ -290,6 +364,7 @@ module ActiveRecord
|
|
|
290
364
|
end
|
|
291
365
|
|
|
292
366
|
def add_column(table_name, column_name, type, **options) # :nodoc:
|
|
367
|
+
type = type.to_sym
|
|
293
368
|
if invalid_alter_table_type?(type, options)
|
|
294
369
|
alter_table(table_name) do |definition|
|
|
295
370
|
definition.column(column_name, type, **options)
|
|
@@ -365,15 +440,31 @@ module ActiveRecord
|
|
|
365
440
|
end
|
|
366
441
|
alias :add_belongs_to :add_reference
|
|
367
442
|
|
|
443
|
+
FK_REGEX = /.*FOREIGN KEY\s+\("([^"]+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
|
|
444
|
+
DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
|
|
368
445
|
def foreign_keys(table_name)
|
|
369
446
|
# SQLite returns 1 row for each column of composite foreign keys.
|
|
370
447
|
fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
|
|
448
|
+
# Deferred or immediate foreign keys can only be seen in the CREATE TABLE sql
|
|
449
|
+
fk_defs = table_structure_sql(table_name)
|
|
450
|
+
.select do |column_string|
|
|
451
|
+
column_string.start_with?("CONSTRAINT") &&
|
|
452
|
+
column_string.include?("FOREIGN KEY")
|
|
453
|
+
end
|
|
454
|
+
.to_h do |fk_string|
|
|
455
|
+
_, from, table, to = fk_string.match(FK_REGEX).to_a
|
|
456
|
+
_, mode = fk_string.match(DEFERRABLE_REGEX).to_a
|
|
457
|
+
deferred = mode&.downcase&.to_sym || false
|
|
458
|
+
[[table, from, to], deferred]
|
|
459
|
+
end
|
|
460
|
+
|
|
371
461
|
grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
|
|
372
462
|
grouped_fk.map do |group|
|
|
373
463
|
row = group.first
|
|
374
464
|
options = {
|
|
375
465
|
on_delete: extract_foreign_key_action(row["on_delete"]),
|
|
376
|
-
on_update: extract_foreign_key_action(row["on_update"])
|
|
466
|
+
on_update: extract_foreign_key_action(row["on_update"]),
|
|
467
|
+
deferrable: fk_defs[[row["table"], row["from"], row["to"]]]
|
|
377
468
|
}
|
|
378
469
|
|
|
379
470
|
if group.one?
|
|
@@ -410,17 +501,13 @@ module ActiveRecord
|
|
|
410
501
|
@config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
|
|
411
502
|
end
|
|
412
503
|
|
|
413
|
-
def use_insert_returning?
|
|
414
|
-
@use_insert_returning
|
|
415
|
-
end
|
|
416
|
-
|
|
417
504
|
def get_database_version # :nodoc:
|
|
418
505
|
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
|
|
419
506
|
end
|
|
420
507
|
|
|
421
508
|
def check_version # :nodoc:
|
|
422
|
-
if database_version < "3.
|
|
423
|
-
raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.
|
|
509
|
+
if database_version < "3.23.0"
|
|
510
|
+
raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.23.0."
|
|
424
511
|
end
|
|
425
512
|
end
|
|
426
513
|
|
|
@@ -454,8 +541,8 @@ module ActiveRecord
|
|
|
454
541
|
end
|
|
455
542
|
|
|
456
543
|
def table_structure(table_name)
|
|
457
|
-
structure =
|
|
458
|
-
raise
|
|
544
|
+
structure = table_info(table_name)
|
|
545
|
+
raise ActiveRecord::StatementInvalid.new("Could not find table '#{table_name}'", connection_pool: @pool) if structure.empty?
|
|
459
546
|
table_structure_with_collation(table_name, structure)
|
|
460
547
|
end
|
|
461
548
|
alias column_definitions table_structure
|
|
@@ -476,6 +563,8 @@ module ActiveRecord
|
|
|
476
563
|
# Binary columns
|
|
477
564
|
when /x'(.*)'/
|
|
478
565
|
[ $1 ].pack("H*")
|
|
566
|
+
when "TRUE", "FALSE"
|
|
567
|
+
default
|
|
479
568
|
else
|
|
480
569
|
# Anything else is blank or some function
|
|
481
570
|
# and we can't know the value of that, so return nil.
|
|
@@ -494,8 +583,9 @@ module ActiveRecord
|
|
|
494
583
|
# See: https://www.sqlite.org/lang_altertable.html
|
|
495
584
|
# SQLite has an additional restriction on the ALTER TABLE statement
|
|
496
585
|
def invalid_alter_table_type?(type, options)
|
|
497
|
-
type
|
|
498
|
-
options[:null] == false && options[:default].nil?
|
|
586
|
+
type == :primary_key || options[:primary_key] ||
|
|
587
|
+
options[:null] == false && options[:default].nil? ||
|
|
588
|
+
(type == :virtual && options[:stored])
|
|
499
589
|
end
|
|
500
590
|
|
|
501
591
|
def alter_table(
|
|
@@ -523,8 +613,8 @@ module ActiveRecord
|
|
|
523
613
|
yield definition if block_given?
|
|
524
614
|
end
|
|
525
615
|
|
|
526
|
-
|
|
527
|
-
|
|
616
|
+
disable_referential_integrity do
|
|
617
|
+
transaction do
|
|
528
618
|
move_table(table_name, altered_table_name, options.merge(temporary: true))
|
|
529
619
|
move_table(altered_table_name, table_name, &caller)
|
|
530
620
|
end
|
|
@@ -551,12 +641,6 @@ module ActiveRecord
|
|
|
551
641
|
options[:rename][column.name.to_sym] ||
|
|
552
642
|
column.name) : column.name
|
|
553
643
|
|
|
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
644
|
column_options = {
|
|
561
645
|
limit: column.limit,
|
|
562
646
|
precision: column.precision,
|
|
@@ -566,19 +650,31 @@ module ActiveRecord
|
|
|
566
650
|
primary_key: column_name == from_primary_key
|
|
567
651
|
}
|
|
568
652
|
|
|
569
|
-
|
|
570
|
-
column_options[:
|
|
653
|
+
if column.virtual?
|
|
654
|
+
column_options[:as] = column.default_function
|
|
655
|
+
column_options[:stored] = column.virtual_stored?
|
|
656
|
+
column_options[:type] = column.type
|
|
657
|
+
elsif column.has_default?
|
|
658
|
+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
|
|
659
|
+
default = column.fetch_cast_type(self).deserialize(column.default)
|
|
660
|
+
default = -> { column.default_function } if default.nil?
|
|
661
|
+
|
|
662
|
+
unless column.auto_increment?
|
|
663
|
+
column_options[:default] = default
|
|
664
|
+
end
|
|
571
665
|
end
|
|
572
666
|
|
|
573
|
-
column_type = column.bigint? ? :bigint : column.type
|
|
667
|
+
column_type = column.virtual? ? :virtual : (column.bigint? ? :bigint : column.type)
|
|
574
668
|
@definition.column(column_name, column_type, **column_options)
|
|
575
669
|
end
|
|
576
670
|
|
|
577
671
|
yield @definition if block_given?
|
|
578
672
|
end
|
|
579
673
|
copy_table_indexes(from, to, options[:rename] || {})
|
|
674
|
+
|
|
675
|
+
columns_to_copy = @definition.columns.reject { |col| col.options.key?(:as) }.map(&:name)
|
|
580
676
|
copy_table_contents(from, to,
|
|
581
|
-
|
|
677
|
+
columns_to_copy,
|
|
582
678
|
options[:rename] || {})
|
|
583
679
|
end
|
|
584
680
|
|
|
@@ -634,8 +730,12 @@ module ActiveRecord
|
|
|
634
730
|
NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
635
731
|
elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
|
|
636
732
|
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
733
|
+
elsif exception.message.match?(/CHECK constraint failed: .*/i)
|
|
734
|
+
CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
637
735
|
elsif exception.message.match?(/called on a closed database/i)
|
|
638
736
|
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
|
737
|
+
elsif exception.is_a?(::SQLite3::BusyException)
|
|
738
|
+
StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
639
739
|
else
|
|
640
740
|
super
|
|
641
741
|
end
|
|
@@ -643,32 +743,22 @@ module ActiveRecord
|
|
|
643
743
|
|
|
644
744
|
COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i
|
|
645
745
|
PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*"(\w+)".+PRIMARY KEY AUTOINCREMENT/i
|
|
746
|
+
GENERATED_ALWAYS_AS_REGEX = /.*"(\w+)".+GENERATED ALWAYS AS \((.+)\) (?:STORED|VIRTUAL)/i
|
|
646
747
|
|
|
647
748
|
def table_structure_with_collation(table_name, basic_structure)
|
|
648
749
|
collation_hash = {}
|
|
649
750
|
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")
|
|
751
|
+
generated_columns = {}
|
|
661
752
|
|
|
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
|
|
753
|
+
column_strings = table_structure_sql(table_name, basic_structure.map { |column| column["name"] })
|
|
666
754
|
|
|
667
|
-
|
|
755
|
+
if column_strings.any?
|
|
756
|
+
column_strings.each do |column_string|
|
|
668
757
|
# This regex will match the column name and collation type and will save
|
|
669
758
|
# the value in $1 and $2 respectively.
|
|
670
759
|
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
|
|
671
760
|
auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
|
|
761
|
+
generated_columns[$1] = $2 if GENERATED_ALWAYS_AS_REGEX =~ column_string
|
|
672
762
|
end
|
|
673
763
|
|
|
674
764
|
basic_structure.map do |column|
|
|
@@ -682,6 +772,10 @@ module ActiveRecord
|
|
|
682
772
|
column["auto_increment"] = true
|
|
683
773
|
end
|
|
684
774
|
|
|
775
|
+
if generated_columns.has_key?(column_name)
|
|
776
|
+
column["dflt_value"] = generated_columns[column_name]
|
|
777
|
+
end
|
|
778
|
+
|
|
685
779
|
column
|
|
686
780
|
end
|
|
687
781
|
else
|
|
@@ -689,6 +783,50 @@ module ActiveRecord
|
|
|
689
783
|
end
|
|
690
784
|
end
|
|
691
785
|
|
|
786
|
+
UNQUOTED_OPEN_PARENS_REGEX = /\((?![^'"]*['"][^'"]*$)/
|
|
787
|
+
FINAL_CLOSE_PARENS_REGEX = /\);*\z/
|
|
788
|
+
|
|
789
|
+
def table_structure_sql(table_name, column_names = nil)
|
|
790
|
+
unless column_names
|
|
791
|
+
column_info = table_info(table_name)
|
|
792
|
+
column_names = column_info.map { |column| column["name"] }
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
sql = <<~SQL
|
|
796
|
+
SELECT sql FROM
|
|
797
|
+
(SELECT * FROM sqlite_master UNION ALL
|
|
798
|
+
SELECT * FROM sqlite_temp_master)
|
|
799
|
+
WHERE type = 'table' AND name = #{quote(table_name)}
|
|
800
|
+
SQL
|
|
801
|
+
|
|
802
|
+
# Result will have following sample string
|
|
803
|
+
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
804
|
+
# "password_digest" varchar COLLATE "NOCASE",
|
|
805
|
+
# "o_id" integer,
|
|
806
|
+
# CONSTRAINT "fk_rails_78146ddd2e" FOREIGN KEY ("o_id") REFERENCES "os" ("id"));
|
|
807
|
+
result = query_value(sql, "SCHEMA")
|
|
808
|
+
|
|
809
|
+
return [] unless result
|
|
810
|
+
|
|
811
|
+
# Splitting with left parentheses and discarding the first part will return all
|
|
812
|
+
# columns separated with comma(,).
|
|
813
|
+
result.partition(UNQUOTED_OPEN_PARENS_REGEX)
|
|
814
|
+
.last
|
|
815
|
+
.sub(FINAL_CLOSE_PARENS_REGEX, "")
|
|
816
|
+
# column definitions can have a comma in them, so split on commas followed
|
|
817
|
+
# by a space and a column name in quotes or followed by the keyword CONSTRAINT
|
|
818
|
+
.split(/,(?=\s(?:CONSTRAINT|"(?:#{Regexp.union(column_names).source})"))/i)
|
|
819
|
+
.map(&:strip)
|
|
820
|
+
end
|
|
821
|
+
|
|
822
|
+
def table_info(table_name)
|
|
823
|
+
if supports_virtual_columns?
|
|
824
|
+
internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA", allow_retry: true)
|
|
825
|
+
else
|
|
826
|
+
internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA", allow_retry: true)
|
|
827
|
+
end
|
|
828
|
+
end
|
|
829
|
+
|
|
692
830
|
def arel_visitor
|
|
693
831
|
Arel::Visitors::SQLite.new(self)
|
|
694
832
|
end
|
|
@@ -712,40 +850,22 @@ module ActiveRecord
|
|
|
712
850
|
end
|
|
713
851
|
|
|
714
852
|
def configure_connection
|
|
715
|
-
if @config[:timeout]
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
@raw_connection.
|
|
719
|
-
elsif @config[:retries]
|
|
720
|
-
retries = self.class.type_cast_config_to_integer(@config[:retries])
|
|
721
|
-
raw_connection.busy_handler do |count|
|
|
722
|
-
count <= retries
|
|
723
|
-
end
|
|
853
|
+
if @config[:timeout]
|
|
854
|
+
timeout = self.class.type_cast_config_to_integer(@config[:timeout])
|
|
855
|
+
raise TypeError, "timeout must be integer, not #{timeout}" unless timeout.is_a?(Integer)
|
|
856
|
+
@raw_connection.busy_handler_timeout = timeout
|
|
724
857
|
end
|
|
725
858
|
|
|
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")
|
|
859
|
+
super
|
|
860
|
+
|
|
861
|
+
pragmas = @config.fetch(:pragmas, {}).stringify_keys
|
|
862
|
+
DEFAULT_PRAGMAS.merge(pragmas).each do |pragma, value|
|
|
863
|
+
if ::SQLite3::Pragmas.method_defined?("#{pragma}=")
|
|
864
|
+
@raw_connection.public_send("#{pragma}=", value)
|
|
865
|
+
else
|
|
866
|
+
warn "Unknown SQLite pragma: #{pragma}"
|
|
867
|
+
end
|
|
742
868
|
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
869
|
end
|
|
750
870
|
end
|
|
751
871
|
ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
|