activerecord 7.1.3.3 → 7.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +507 -2128
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +9 -8
- data/lib/active_record/associations/belongs_to_association.rb +18 -11
- 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 +4 -2
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/has_many_association.rb +3 -3
- data/lib/active_record/associations/has_one_association.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +5 -7
- 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 +34 -11
- data/lib/active_record/attribute_assignment.rb +1 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +1 -1
- 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 +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods.rb +87 -58
- data/lib/active_record/attributes.rb +55 -42
- data/lib/active_record/autosave_association.rb +14 -30
- data/lib/active_record/base.rb +2 -3
- 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 +248 -58
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +160 -75
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +22 -9
- data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -61
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
- 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 +7 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
- 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/cidr.rb +6 -0
- 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 +20 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
- 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 +44 -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 +25 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +109 -77
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +59 -38
- data/lib/active_record/counter_cache.rb +23 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
- data/lib/active_record/database_configurations/database_config.rb +15 -4
- data/lib/active_record/database_configurations/hash_config.rb +44 -36
- 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 +30 -6
- data/lib/active_record/destroy_association_async_job.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +2 -2
- data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
- data/lib/active_record/encryption/encryptor.rb +17 -2
- 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 -4
- data/lib/active_record/enum.rb +11 -2
- data/lib/active_record/errors.rb +16 -11
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +17 -4
- data/lib/active_record/gem_version.rb +3 -3
- 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 +8 -7
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/marshalling.rb +1 -1
- data/lib/active_record/message_pack.rb +2 -2
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +11 -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 +85 -76
- data/lib/active_record/model_schema.rb +34 -69
- data/lib/active_record/nested_attributes.rb +11 -3
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +32 -354
- data/lib/active_record/query_cache.rb +18 -6
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/query_logs_formatter.rb +1 -1
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +52 -64
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +41 -44
- data/lib/active_record/reflection.rb +98 -37
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/calculations.rb +94 -61
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +16 -2
- 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/polymorphic_array_value.rb +6 -1
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +196 -43
- 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 +7 -19
- data/lib/active_record/relation.rb +500 -66
- data/lib/active_record/result.rb +32 -45
- 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 +19 -9
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/signed_id.rb +11 -1
- 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 +70 -42
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +82 -91
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +4 -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 +68 -0
- data/lib/active_record/transactions.rb +43 -14
- 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 +14 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +149 -40
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- 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/nodes/binary.rb +0 -6
- 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 +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- 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/tree_manager.rb +8 -3
- 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/to_sql.rb +31 -17
- data/lib/arel.rb +7 -3
- metadata +16 -11
@@ -6,10 +6,11 @@ module ActiveRecord
|
|
6
6
|
class Column < ConnectionAdapters::Column # :nodoc:
|
7
7
|
attr_reader :rowid
|
8
8
|
|
9
|
-
def initialize(*, auto_increment: nil, rowid: false, **)
|
9
|
+
def initialize(*, auto_increment: nil, rowid: false, generated_type: nil, **)
|
10
10
|
super
|
11
11
|
@auto_increment = auto_increment
|
12
12
|
@rowid = rowid
|
13
|
+
@generated_type = generated_type
|
13
14
|
end
|
14
15
|
|
15
16
|
def auto_increment?
|
@@ -20,6 +21,18 @@ module ActiveRecord
|
|
20
21
|
auto_increment? || rowid
|
21
22
|
end
|
22
23
|
|
24
|
+
def virtual?
|
25
|
+
!@generated_type.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
def virtual_stored?
|
29
|
+
virtual? && @generated_type == :stored
|
30
|
+
end
|
31
|
+
|
32
|
+
def has_default?
|
33
|
+
super && !virtual?
|
34
|
+
end
|
35
|
+
|
23
36
|
def init_with(coder)
|
24
37
|
@auto_increment = coder["auto_increment"]
|
25
38
|
super
|
@@ -21,7 +21,7 @@ module ActiveRecord
|
|
21
21
|
SQLite3::ExplainPrettyPrinter.new.pp(result)
|
22
22
|
end
|
23
23
|
|
24
|
-
def internal_exec_query(sql, name = nil, binds = [], prepare: false, async: false) # :nodoc:
|
24
|
+
def internal_exec_query(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
|
25
25
|
sql = transform_query(sql)
|
26
26
|
check_if_write_query(sql)
|
27
27
|
|
@@ -29,7 +29,7 @@ module ActiveRecord
|
|
29
29
|
|
30
30
|
type_casted_binds = type_casted_binds(binds)
|
31
31
|
|
32
|
-
log(sql, name, binds, type_casted_binds, async: async) do
|
32
|
+
log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
|
33
33
|
with_raw_connection do |conn|
|
34
34
|
# Don't cache statements if they are not prepared
|
35
35
|
unless prepare
|
@@ -52,7 +52,9 @@ module ActiveRecord
|
|
52
52
|
end
|
53
53
|
verified!
|
54
54
|
|
55
|
-
build_result(columns: cols, rows: records)
|
55
|
+
result = build_result(columns: cols, rows: records)
|
56
|
+
notification_payload[:row_count] = result.length
|
57
|
+
result
|
56
58
|
end
|
57
59
|
end
|
58
60
|
end
|
@@ -104,7 +106,7 @@ module ActiveRecord
|
|
104
106
|
|
105
107
|
# https://stackoverflow.com/questions/17574784
|
106
108
|
# https://www.sqlite.org/lang_datefunc.html
|
107
|
-
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')").freeze # :nodoc:
|
109
|
+
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')", retryable: true).freeze # :nodoc:
|
108
110
|
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
109
111
|
|
110
112
|
def high_precision_current_timestamp
|
@@ -113,10 +115,11 @@ module ActiveRecord
|
|
113
115
|
|
114
116
|
private
|
115
117
|
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: false)
|
116
|
-
log(sql, name, async: async) do
|
118
|
+
log(sql, name, async: async) do |notification_payload|
|
117
119
|
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
118
120
|
result = conn.execute(sql)
|
119
121
|
verified!
|
122
|
+
notification_payload[:row_count] = result.length
|
120
123
|
result
|
121
124
|
end
|
122
125
|
end
|
@@ -136,10 +139,11 @@ module ActiveRecord
|
|
136
139
|
check_if_write_query(sql)
|
137
140
|
mark_transaction_written_if_write(sql)
|
138
141
|
|
139
|
-
log(sql, name) do
|
142
|
+
log(sql, name) do |notification_payload|
|
140
143
|
with_raw_connection do |conn|
|
141
144
|
result = conn.execute_batch2(sql)
|
142
145
|
verified!
|
146
|
+
notification_payload[:row_count] = result.length
|
143
147
|
result
|
144
148
|
end
|
145
149
|
end
|
@@ -4,9 +4,52 @@ module ActiveRecord
|
|
4
4
|
module ConnectionAdapters
|
5
5
|
module SQLite3
|
6
6
|
module Quoting # :nodoc:
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
7
9
|
QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
|
8
10
|
QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
|
9
11
|
|
12
|
+
module ClassMethods # :nodoc:
|
13
|
+
def column_name_matcher
|
14
|
+
/
|
15
|
+
\A
|
16
|
+
(
|
17
|
+
(?:
|
18
|
+
# "table_name"."column_name" | function(one or no argument)
|
19
|
+
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
|
20
|
+
)
|
21
|
+
(?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
|
22
|
+
)
|
23
|
+
(?:\s*,\s*\g<1>)*
|
24
|
+
\z
|
25
|
+
/ix
|
26
|
+
end
|
27
|
+
|
28
|
+
def column_name_with_order_matcher
|
29
|
+
/
|
30
|
+
\A
|
31
|
+
(
|
32
|
+
(?:
|
33
|
+
# "table_name"."column_name" | function(one or no argument)
|
34
|
+
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
|
35
|
+
)
|
36
|
+
(?:\s+COLLATE\s+(?:\w+|"\w+"))?
|
37
|
+
(?:\s+ASC|\s+DESC)?
|
38
|
+
)
|
39
|
+
(?:\s*,\s*\g<1>)*
|
40
|
+
\z
|
41
|
+
/ix
|
42
|
+
end
|
43
|
+
|
44
|
+
def quote_column_name(name)
|
45
|
+
QUOTED_COLUMN_NAMES[name] ||= %Q("#{name.to_s.gsub('"', '""')}").freeze
|
46
|
+
end
|
47
|
+
|
48
|
+
def quote_table_name(name)
|
49
|
+
QUOTED_TABLE_NAMES[name] ||= %Q("#{name.to_s.gsub('"', '""').gsub(".", "\".\"")}").freeze
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
10
53
|
def quote_string(s)
|
11
54
|
::SQLite3::Database.quote(s)
|
12
55
|
end
|
@@ -15,14 +58,6 @@ module ActiveRecord
|
|
15
58
|
quote_column_name(attr)
|
16
59
|
end
|
17
60
|
|
18
|
-
def quote_table_name(name)
|
19
|
-
QUOTED_TABLE_NAMES[name] ||= super.gsub(".", "\".\"").freeze
|
20
|
-
end
|
21
|
-
|
22
|
-
def quote_column_name(name)
|
23
|
-
QUOTED_COLUMN_NAMES[name] ||= %Q("#{super.gsub('"', '""')}")
|
24
|
-
end
|
25
|
-
|
26
61
|
def quoted_time(value)
|
27
62
|
value = value.change(year: 2000, month: 1, day: 1)
|
28
63
|
quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "2000-01-01 ")
|
@@ -63,7 +98,7 @@ module ActiveRecord
|
|
63
98
|
|
64
99
|
def type_cast(value) # :nodoc:
|
65
100
|
case value
|
66
|
-
when BigDecimal
|
101
|
+
when BigDecimal, Rational
|
67
102
|
value.to_f
|
68
103
|
when String
|
69
104
|
if value.encoding == Encoding::ASCII_8BIT
|
@@ -75,43 +110,6 @@ module ActiveRecord
|
|
75
110
|
super
|
76
111
|
end
|
77
112
|
end
|
78
|
-
|
79
|
-
def column_name_matcher
|
80
|
-
COLUMN_NAME
|
81
|
-
end
|
82
|
-
|
83
|
-
def column_name_with_order_matcher
|
84
|
-
COLUMN_NAME_WITH_ORDER
|
85
|
-
end
|
86
|
-
|
87
|
-
COLUMN_NAME = /
|
88
|
-
\A
|
89
|
-
(
|
90
|
-
(?:
|
91
|
-
# "table_name"."column_name" | function(one or no argument)
|
92
|
-
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
|
93
|
-
)
|
94
|
-
(?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
|
95
|
-
)
|
96
|
-
(?:\s*,\s*\g<1>)*
|
97
|
-
\z
|
98
|
-
/ix
|
99
|
-
|
100
|
-
COLUMN_NAME_WITH_ORDER = /
|
101
|
-
\A
|
102
|
-
(
|
103
|
-
(?:
|
104
|
-
# "table_name"."column_name" | function(one or no argument)
|
105
|
-
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
|
106
|
-
)
|
107
|
-
(?:\s+COLLATE\s+(?:\w+|"\w+"))?
|
108
|
-
(?:\s+ASC|\s+DESC)?
|
109
|
-
)
|
110
|
-
(?:\s*,\s*\g<1>)*
|
111
|
-
\z
|
112
|
-
/ix
|
113
|
-
|
114
|
-
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
|
115
113
|
end
|
116
114
|
end
|
117
115
|
end
|
@@ -5,6 +5,18 @@ module ActiveRecord
|
|
5
5
|
module SQLite3
|
6
6
|
class SchemaCreation < SchemaCreation # :nodoc:
|
7
7
|
private
|
8
|
+
def visit_AddForeignKey(o)
|
9
|
+
super.dup.tap do |sql|
|
10
|
+
sql << " DEFERRABLE INITIALLY #{o.options[:deferrable].to_s.upcase}" if o.deferrable
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit_ForeignKeyDefinition(o)
|
15
|
+
super.dup.tap do |sql|
|
16
|
+
sql << " DEFERRABLE INITIALLY #{o.deferrable.to_s.upcase}" if o.deferrable
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
8
20
|
def supports_index_using?
|
9
21
|
false
|
10
22
|
end
|
@@ -13,6 +25,16 @@ module ActiveRecord
|
|
13
25
|
if options[:collation]
|
14
26
|
sql << " COLLATE \"#{options[:collation]}\""
|
15
27
|
end
|
28
|
+
|
29
|
+
if as = options[:as]
|
30
|
+
sql << " GENERATED ALWAYS AS (#{as})"
|
31
|
+
|
32
|
+
if options[:stored]
|
33
|
+
sql << " STORED"
|
34
|
+
else
|
35
|
+
sql << " VIRTUAL"
|
36
|
+
end
|
37
|
+
end
|
16
38
|
super
|
17
39
|
end
|
18
40
|
end
|
@@ -16,10 +16,23 @@ module ActiveRecord
|
|
16
16
|
end
|
17
17
|
alias :belongs_to :references
|
18
18
|
|
19
|
+
def new_column_definition(name, type, **options) # :nodoc:
|
20
|
+
case type
|
21
|
+
when :virtual
|
22
|
+
type = options[:type]
|
23
|
+
end
|
24
|
+
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
19
28
|
private
|
20
29
|
def integer_like_primary_key_type(type, options)
|
21
30
|
:primary_key
|
22
31
|
end
|
32
|
+
|
33
|
+
def valid_column_definition_options
|
34
|
+
super + [:as, :type, :stored]
|
35
|
+
end
|
23
36
|
end
|
24
37
|
end
|
25
38
|
end
|
@@ -12,6 +12,22 @@ module ActiveRecord
|
|
12
12
|
def explicit_primary_key_default?(column)
|
13
13
|
column.bigint?
|
14
14
|
end
|
15
|
+
|
16
|
+
def prepare_column_options(column)
|
17
|
+
spec = super
|
18
|
+
|
19
|
+
if @connection.supports_virtual_columns? && column.virtual?
|
20
|
+
spec[:as] = extract_expression_for_virtual_column(column)
|
21
|
+
spec[:stored] = column.virtual_stored?
|
22
|
+
spec = { type: schema_type(column).inspect }.merge!(spec)
|
23
|
+
end
|
24
|
+
|
25
|
+
spec
|
26
|
+
end
|
27
|
+
|
28
|
+
def extract_expression_for_virtual_column(column)
|
29
|
+
column.default_function.inspect
|
30
|
+
end
|
15
31
|
end
|
16
32
|
end
|
17
33
|
end
|
@@ -53,6 +53,8 @@ module ActiveRecord
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def add_foreign_key(from_table, to_table, **options)
|
56
|
+
assert_valid_deferrable(options[:deferrable])
|
57
|
+
|
56
58
|
alter_table(from_table) do |definition|
|
57
59
|
to_table = strip_table_name_prefix_and_suffix(to_table)
|
58
60
|
definition.foreign_key(to_table, **options)
|
@@ -137,7 +139,14 @@ module ActiveRecord
|
|
137
139
|
|
138
140
|
type_metadata = fetch_type_metadata(field["type"])
|
139
141
|
default_value = extract_value_from_default(default)
|
140
|
-
|
142
|
+
generated_type = extract_generated_type(field)
|
143
|
+
|
144
|
+
if generated_type.present?
|
145
|
+
default_function = default
|
146
|
+
else
|
147
|
+
default_function = extract_default_function(default_value, default)
|
148
|
+
end
|
149
|
+
|
141
150
|
rowid = is_column_the_rowid?(field, definitions)
|
142
151
|
|
143
152
|
Column.new(
|
@@ -148,7 +157,8 @@ module ActiveRecord
|
|
148
157
|
default_function,
|
149
158
|
collation: field["collation"],
|
150
159
|
auto_increment: field["auto_increment"],
|
151
|
-
rowid: rowid
|
160
|
+
rowid: rowid,
|
161
|
+
generated_type: generated_type
|
152
162
|
)
|
153
163
|
end
|
154
164
|
|
@@ -185,6 +195,19 @@ module ActiveRecord
|
|
185
195
|
scope[:type] = type if type
|
186
196
|
scope
|
187
197
|
end
|
198
|
+
|
199
|
+
def assert_valid_deferrable(deferrable)
|
200
|
+
return if !deferrable || %i(immediate deferred).include?(deferrable)
|
201
|
+
|
202
|
+
raise ArgumentError, "deferrable must be `:immediate` or `:deferred`, got: `#{deferrable.inspect}`"
|
203
|
+
end
|
204
|
+
|
205
|
+
def extract_generated_type(field)
|
206
|
+
case field["hidden"]
|
207
|
+
when 2 then :virtual
|
208
|
+
when 3 then :stored
|
209
|
+
end
|
210
|
+
end
|
188
211
|
end
|
189
212
|
end
|
190
213
|
end
|
@@ -11,20 +11,10 @@ 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", "
|
14
|
+
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
|
#
|
@@ -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,14 +191,16 @@ 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"
|
201
196
|
end
|
202
197
|
|
203
|
-
def
|
204
|
-
|
198
|
+
def connected?
|
199
|
+
!(@raw_connection.nil? || @raw_connection.closed?)
|
205
200
|
end
|
206
201
|
|
202
|
+
alias_method :active?, :connected?
|
203
|
+
|
207
204
|
alias :reset! :reconnect!
|
208
205
|
|
209
206
|
# Disconnects from the database if already connected. Otherwise, this
|
@@ -236,6 +233,10 @@ module ActiveRecord
|
|
236
233
|
true
|
237
234
|
end
|
238
235
|
|
236
|
+
def supports_deferrable_constraints?
|
237
|
+
true
|
238
|
+
end
|
239
|
+
|
239
240
|
# REFERENTIAL INTEGRITY ====================================
|
240
241
|
|
241
242
|
def disable_referential_integrity # :nodoc:
|
@@ -258,7 +259,7 @@ module ActiveRecord
|
|
258
259
|
|
259
260
|
unless result.blank?
|
260
261
|
tables = result.map { |row| row["table"] }
|
261
|
-
raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql)
|
262
|
+
raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql, connection_pool: @pool)
|
262
263
|
end
|
263
264
|
end
|
264
265
|
|
@@ -286,10 +287,11 @@ module ActiveRecord
|
|
286
287
|
schema_cache.clear_data_source_cache!(table_name.to_s)
|
287
288
|
schema_cache.clear_data_source_cache!(new_name.to_s)
|
288
289
|
exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
289
|
-
rename_table_indexes(table_name, new_name)
|
290
|
+
rename_table_indexes(table_name, new_name, **options)
|
290
291
|
end
|
291
292
|
|
292
293
|
def add_column(table_name, column_name, type, **options) # :nodoc:
|
294
|
+
type = type.to_sym
|
293
295
|
if invalid_alter_table_type?(type, options)
|
294
296
|
alter_table(table_name) do |definition|
|
295
297
|
definition.column(column_name, type, **options)
|
@@ -365,15 +367,31 @@ module ActiveRecord
|
|
365
367
|
end
|
366
368
|
alias :add_belongs_to :add_reference
|
367
369
|
|
370
|
+
FK_REGEX = /.*FOREIGN KEY\s+\("(\w+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
|
371
|
+
DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
|
368
372
|
def foreign_keys(table_name)
|
369
373
|
# SQLite returns 1 row for each column of composite foreign keys.
|
370
374
|
fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
|
375
|
+
# Deferred or immediate foreign keys can only be seen in the CREATE TABLE sql
|
376
|
+
fk_defs = table_structure_sql(table_name)
|
377
|
+
.select do |column_string|
|
378
|
+
column_string.start_with?("CONSTRAINT") &&
|
379
|
+
column_string.include?("FOREIGN KEY")
|
380
|
+
end
|
381
|
+
.to_h do |fk_string|
|
382
|
+
_, from, table, to = fk_string.match(FK_REGEX).to_a
|
383
|
+
_, mode = fk_string.match(DEFERRABLE_REGEX).to_a
|
384
|
+
deferred = mode&.downcase&.to_sym || false
|
385
|
+
[[table, from, to], deferred]
|
386
|
+
end
|
387
|
+
|
371
388
|
grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
|
372
389
|
grouped_fk.map do |group|
|
373
390
|
row = group.first
|
374
391
|
options = {
|
375
392
|
on_delete: extract_foreign_key_action(row["on_delete"]),
|
376
|
-
on_update: extract_foreign_key_action(row["on_update"])
|
393
|
+
on_update: extract_foreign_key_action(row["on_update"]),
|
394
|
+
deferrable: fk_defs[[row["table"], row["from"], row["to"]]]
|
377
395
|
}
|
378
396
|
|
379
397
|
if group.one?
|
@@ -454,8 +472,12 @@ module ActiveRecord
|
|
454
472
|
end
|
455
473
|
|
456
474
|
def table_structure(table_name)
|
457
|
-
structure =
|
458
|
-
|
475
|
+
structure = if supports_virtual_columns?
|
476
|
+
internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
|
477
|
+
else
|
478
|
+
internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
479
|
+
end
|
480
|
+
raise ActiveRecord::StatementInvalid.new("Could not find table '#{table_name}'", connection_pool: @pool) if structure.empty?
|
459
481
|
table_structure_with_collation(table_name, structure)
|
460
482
|
end
|
461
483
|
alias column_definitions table_structure
|
@@ -494,8 +516,9 @@ module ActiveRecord
|
|
494
516
|
# See: https://www.sqlite.org/lang_altertable.html
|
495
517
|
# SQLite has an additional restriction on the ALTER TABLE statement
|
496
518
|
def invalid_alter_table_type?(type, options)
|
497
|
-
type
|
498
|
-
options[:null] == false && options[:default].nil?
|
519
|
+
type == :primary_key || options[:primary_key] ||
|
520
|
+
options[:null] == false && options[:default].nil? ||
|
521
|
+
(type == :virtual && options[:stored])
|
499
522
|
end
|
500
523
|
|
501
524
|
def alter_table(
|
@@ -551,12 +574,6 @@ module ActiveRecord
|
|
551
574
|
options[:rename][column.name.to_sym] ||
|
552
575
|
column.name) : column.name
|
553
576
|
|
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
577
|
column_options = {
|
561
578
|
limit: column.limit,
|
562
579
|
precision: column.precision,
|
@@ -566,19 +583,31 @@ module ActiveRecord
|
|
566
583
|
primary_key: column_name == from_primary_key
|
567
584
|
}
|
568
585
|
|
569
|
-
|
570
|
-
column_options[:
|
586
|
+
if column.virtual?
|
587
|
+
column_options[:as] = column.default_function
|
588
|
+
column_options[:stored] = column.virtual_stored?
|
589
|
+
column_options[:type] = column.type
|
590
|
+
elsif column.has_default?
|
591
|
+
type = lookup_cast_type_from_column(column)
|
592
|
+
default = type.deserialize(column.default)
|
593
|
+
default = -> { column.default_function } if default.nil?
|
594
|
+
|
595
|
+
unless column.auto_increment?
|
596
|
+
column_options[:default] = default
|
597
|
+
end
|
571
598
|
end
|
572
599
|
|
573
|
-
column_type = column.bigint? ? :bigint : column.type
|
600
|
+
column_type = column.virtual? ? :virtual : (column.bigint? ? :bigint : column.type)
|
574
601
|
@definition.column(column_name, column_type, **column_options)
|
575
602
|
end
|
576
603
|
|
577
604
|
yield @definition if block_given?
|
578
605
|
end
|
579
606
|
copy_table_indexes(from, to, options[:rename] || {})
|
607
|
+
|
608
|
+
columns_to_copy = @definition.columns.reject { |col| col.options.key?(:as) }.map(&:name)
|
580
609
|
copy_table_contents(from, to,
|
581
|
-
|
610
|
+
columns_to_copy,
|
582
611
|
options[:rename] || {})
|
583
612
|
end
|
584
613
|
|
@@ -643,32 +672,22 @@ module ActiveRecord
|
|
643
672
|
|
644
673
|
COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i
|
645
674
|
PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*"(\w+)".+PRIMARY KEY AUTOINCREMENT/i
|
675
|
+
GENERATED_ALWAYS_AS_REGEX = /.*"(\w+)".+GENERATED ALWAYS AS \((.+)\) (?:STORED|VIRTUAL)/i
|
646
676
|
|
647
677
|
def table_structure_with_collation(table_name, basic_structure)
|
648
678
|
collation_hash = {}
|
649
679
|
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")
|
680
|
+
generated_columns = {}
|
661
681
|
|
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
|
682
|
+
column_strings = table_structure_sql(table_name)
|
666
683
|
|
667
|
-
|
684
|
+
if column_strings.any?
|
685
|
+
column_strings.each do |column_string|
|
668
686
|
# This regex will match the column name and collation type and will save
|
669
687
|
# the value in $1 and $2 respectively.
|
670
688
|
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
|
671
689
|
auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
|
690
|
+
generated_columns[$1] = $2 if GENERATED_ALWAYS_AS_REGEX =~ column_string
|
672
691
|
end
|
673
692
|
|
674
693
|
basic_structure.map do |column|
|
@@ -682,6 +701,10 @@ module ActiveRecord
|
|
682
701
|
column["auto_increment"] = true
|
683
702
|
end
|
684
703
|
|
704
|
+
if generated_columns.has_key?(column_name)
|
705
|
+
column["dflt_value"] = generated_columns[column_name]
|
706
|
+
end
|
707
|
+
|
685
708
|
column
|
686
709
|
end
|
687
710
|
else
|
@@ -689,6 +712,28 @@ module ActiveRecord
|
|
689
712
|
end
|
690
713
|
end
|
691
714
|
|
715
|
+
def table_structure_sql(table_name)
|
716
|
+
sql = <<~SQL
|
717
|
+
SELECT sql FROM
|
718
|
+
(SELECT * FROM sqlite_master UNION ALL
|
719
|
+
SELECT * FROM sqlite_temp_master)
|
720
|
+
WHERE type = 'table' AND name = #{quote(table_name)}
|
721
|
+
SQL
|
722
|
+
|
723
|
+
# Result will have following sample string
|
724
|
+
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
725
|
+
# "password_digest" varchar COLLATE "NOCASE");
|
726
|
+
result = query_value(sql, "SCHEMA")
|
727
|
+
|
728
|
+
return [] unless result
|
729
|
+
|
730
|
+
# Splitting with left parentheses and discarding the first part will return all
|
731
|
+
# columns separated with comma(,).
|
732
|
+
columns_string = result.split("(", 2).last
|
733
|
+
|
734
|
+
columns_string.split(",").map(&:strip)
|
735
|
+
end
|
736
|
+
|
692
737
|
def arel_visitor
|
693
738
|
Arel::Visitors::SQLite.new(self)
|
694
739
|
end
|
@@ -723,29 +768,16 @@ module ActiveRecord
|
|
723
768
|
end
|
724
769
|
end
|
725
770
|
|
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")
|
771
|
+
super
|
772
|
+
|
773
|
+
pragmas = @config.fetch(:pragmas, {}).stringify_keys
|
774
|
+
DEFAULT_PRAGMAS.merge(pragmas).each do |pragma, value|
|
775
|
+
if ::SQLite3::Pragmas.method_defined?("#{pragma}=")
|
776
|
+
@raw_connection.public_send("#{pragma}=", value)
|
777
|
+
else
|
778
|
+
warn "Unknown SQLite pragma: #{pragma}"
|
779
|
+
end
|
742
780
|
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
781
|
end
|
750
782
|
end
|
751
783
|
ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
|