activerecord 7.1.3.3 → 7.2.0.beta1

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.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +507 -2128
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +9 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +18 -11
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +4 -2
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +3 -3
  17. data/lib/active_record/associations/has_one_association.rb +2 -2
  18. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  19. data/lib/active_record/associations/join_dependency.rb +5 -7
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +34 -11
  27. data/lib/active_record/attribute_assignment.rb +1 -11
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  30. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  31. data/lib/active_record/attribute_methods/read.rb +1 -13
  32. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  33. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  34. data/lib/active_record/attribute_methods.rb +87 -58
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +14 -30
  37. data/lib/active_record/base.rb +2 -3
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +248 -58
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +160 -75
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +22 -9
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -61
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  57. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  59. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  60. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
  63. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  64. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  67. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +109 -77
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  74. data/lib/active_record/connection_adapters.rb +121 -0
  75. data/lib/active_record/connection_handling.rb +56 -41
  76. data/lib/active_record/core.rb +59 -38
  77. data/lib/active_record/counter_cache.rb +23 -10
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  79. data/lib/active_record/database_configurations/database_config.rb +15 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  81. data/lib/active_record/database_configurations/url_config.rb +20 -1
  82. data/lib/active_record/database_configurations.rb +1 -1
  83. data/lib/active_record/delegated_type.rb +30 -6
  84. data/lib/active_record/destroy_association_async_job.rb +1 -1
  85. data/lib/active_record/dynamic_matchers.rb +2 -2
  86. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  87. data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
  88. data/lib/active_record/encryption/encryptor.rb +17 -2
  89. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  90. data/lib/active_record/encryption/message_serializer.rb +4 -0
  91. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  93. data/lib/active_record/encryption/scheme.rb +8 -4
  94. data/lib/active_record/enum.rb +11 -2
  95. data/lib/active_record/errors.rb +16 -11
  96. data/lib/active_record/explain.rb +13 -24
  97. data/lib/active_record/fixtures.rb +37 -31
  98. data/lib/active_record/future_result.rb +17 -4
  99. data/lib/active_record/gem_version.rb +3 -3
  100. data/lib/active_record/inheritance.rb +4 -2
  101. data/lib/active_record/insert_all.rb +18 -15
  102. data/lib/active_record/integration.rb +4 -1
  103. data/lib/active_record/internal_metadata.rb +48 -34
  104. data/lib/active_record/locking/optimistic.rb +8 -7
  105. data/lib/active_record/log_subscriber.rb +0 -21
  106. data/lib/active_record/marshalling.rb +1 -1
  107. data/lib/active_record/message_pack.rb +2 -2
  108. data/lib/active_record/migration/command_recorder.rb +2 -3
  109. data/lib/active_record/migration/compatibility.rb +11 -3
  110. data/lib/active_record/migration/default_strategy.rb +4 -5
  111. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  112. data/lib/active_record/migration.rb +85 -76
  113. data/lib/active_record/model_schema.rb +34 -69
  114. data/lib/active_record/nested_attributes.rb +11 -3
  115. data/lib/active_record/normalization.rb +3 -7
  116. data/lib/active_record/persistence.rb +32 -354
  117. data/lib/active_record/query_cache.rb +18 -6
  118. data/lib/active_record/query_logs.rb +15 -0
  119. data/lib/active_record/query_logs_formatter.rb +1 -1
  120. data/lib/active_record/querying.rb +21 -9
  121. data/lib/active_record/railtie.rb +52 -64
  122. data/lib/active_record/railties/controller_runtime.rb +13 -4
  123. data/lib/active_record/railties/databases.rake +41 -44
  124. data/lib/active_record/reflection.rb +98 -37
  125. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  126. data/lib/active_record/relation/batches.rb +3 -3
  127. data/lib/active_record/relation/calculations.rb +94 -61
  128. data/lib/active_record/relation/delegation.rb +8 -11
  129. data/lib/active_record/relation/finder_methods.rb +16 -2
  130. data/lib/active_record/relation/merger.rb +4 -6
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  132. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  133. data/lib/active_record/relation/predicate_builder.rb +3 -3
  134. data/lib/active_record/relation/query_methods.rb +196 -43
  135. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  136. data/lib/active_record/relation/spawn_methods.rb +2 -18
  137. data/lib/active_record/relation/where_clause.rb +7 -19
  138. data/lib/active_record/relation.rb +500 -66
  139. data/lib/active_record/result.rb +32 -45
  140. data/lib/active_record/runtime_registry.rb +39 -0
  141. data/lib/active_record/sanitization.rb +24 -19
  142. data/lib/active_record/schema.rb +8 -6
  143. data/lib/active_record/schema_dumper.rb +19 -9
  144. data/lib/active_record/schema_migration.rb +30 -14
  145. data/lib/active_record/signed_id.rb +11 -1
  146. data/lib/active_record/statement_cache.rb +7 -7
  147. data/lib/active_record/table_metadata.rb +1 -10
  148. data/lib/active_record/tasks/database_tasks.rb +70 -42
  149. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  152. data/lib/active_record/test_fixtures.rb +82 -91
  153. data/lib/active_record/testing/query_assertions.rb +121 -0
  154. data/lib/active_record/timestamp.rb +4 -2
  155. data/lib/active_record/token_for.rb +22 -12
  156. data/lib/active_record/touch_later.rb +1 -1
  157. data/lib/active_record/transaction.rb +68 -0
  158. data/lib/active_record/transactions.rb +43 -14
  159. data/lib/active_record/translation.rb +0 -2
  160. data/lib/active_record/type/serialized.rb +1 -3
  161. data/lib/active_record/type_caster/connection.rb +4 -4
  162. data/lib/active_record/validations/associated.rb +9 -3
  163. data/lib/active_record/validations/uniqueness.rb +14 -10
  164. data/lib/active_record/validations.rb +4 -1
  165. data/lib/active_record.rb +149 -40
  166. data/lib/arel/alias_predication.rb +1 -1
  167. data/lib/arel/collectors/bind.rb +2 -0
  168. data/lib/arel/collectors/composite.rb +7 -0
  169. data/lib/arel/collectors/sql_string.rb +1 -1
  170. data/lib/arel/collectors/substitute_binds.rb +1 -1
  171. data/lib/arel/nodes/binary.rb +0 -6
  172. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  173. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  174. data/lib/arel/nodes/node.rb +4 -3
  175. data/lib/arel/nodes/sql_literal.rb +7 -0
  176. data/lib/arel/nodes.rb +2 -2
  177. data/lib/arel/predications.rb +1 -1
  178. data/lib/arel/select_manager.rb +1 -1
  179. data/lib/arel/tree_manager.rb +8 -3
  180. data/lib/arel/update_manager.rb +2 -1
  181. data/lib/arel/visitors/dot.rb +1 -0
  182. data/lib/arel/visitors/mysql.rb +9 -4
  183. data/lib/arel/visitors/postgresql.rb +1 -12
  184. data/lib/arel/visitors/to_sql.rb +31 -17
  185. data/lib/arel.rb +7 -3
  186. 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
- default_function = extract_default_function(default_value, default)
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", "~> 1.4"
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
- Dir.mkdir(dirname)
117
- rescue Errno::ENOENT => error
118
- if error.message.include?("No such file or directory")
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 active?
200
- @raw_connection && !@raw_connection.closed?
194
+ def supports_virtual_columns?
195
+ database_version >= "3.31.0"
201
196
  end
202
197
 
203
- def return_value_after_insert?(column) # :nodoc:
204
- column.auto_populated?
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 = internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
458
- raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
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.to_sym == :primary_key || options[:primary_key] ||
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
- unless column.auto_increment?
570
- column_options[:default] = default
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
- @definition.columns.map(&:name),
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
- sql = <<~SQL
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
- if result
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
- columns_string.split(",").each do |column_string|
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
- # Enforce foreign key constraints
727
- # https://www.sqlite.org/pragma.html#pragma_foreign_keys
728
- # https://www.sqlite.org/foreignkeys.html
729
- raw_execute("PRAGMA foreign_keys = ON", "SCHEMA")
730
- unless @memory_database
731
- # Journal mode WAL allows for greater concurrency (many readers + one writer)
732
- # https://www.sqlite.org/pragma.html#pragma_journal_mode
733
- raw_execute("PRAGMA journal_mode = WAL", "SCHEMA")
734
- # Set more relaxed level of database durability
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)