activerecord 7.0.8.7 → 7.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +781 -1777
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +30 -30
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +31 -23
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +40 -9
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +35 -21
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +4 -3
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +153 -33
  47. data/lib/active_record/attributes.rb +96 -71
  48. data/lib/active_record/autosave_association.rb +81 -39
  49. data/lib/active_record/base.rb +11 -7
  50. data/lib/active_record/callbacks.rb +11 -25
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +343 -91
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +229 -64
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +142 -12
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +539 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +289 -128
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +60 -55
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +108 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -1
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +371 -64
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +374 -203
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -45
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +51 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +298 -113
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +101 -105
  110. data/lib/active_record/core.rb +273 -178
  111. data/lib/active_record/counter_cache.rb +69 -35
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -3
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +56 -27
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +46 -22
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +48 -13
  130. data/lib/active_record/encryption/encryptor.rb +35 -19
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +130 -28
  143. data/lib/active_record/errors.rb +154 -34
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +48 -10
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +4 -4
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +236 -118
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +96 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +35 -10
  178. data/lib/active_record/railtie.rb +131 -87
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +147 -155
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +270 -108
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +97 -21
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +20 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +3 -2
  196. data/lib/active_record/relation/query_methods.rb +585 -109
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +15 -21
  200. data/lib/active_record/relation.rb +592 -92
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +90 -23
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +33 -11
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +23 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +108 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +3 -1
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/crud.rb +2 -0
  248. data/lib/arel/delete_manager.rb +5 -0
  249. data/lib/arel/errors.rb +10 -0
  250. data/lib/arel/factory_methods.rb +4 -0
  251. data/lib/arel/nodes/binary.rb +6 -7
  252. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  253. data/lib/arel/nodes/cte.rb +36 -0
  254. data/lib/arel/nodes/delete_statement.rb +4 -2
  255. data/lib/arel/nodes/fragments.rb +35 -0
  256. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  257. data/lib/arel/nodes/leading_join.rb +8 -0
  258. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  259. data/lib/arel/nodes/node.rb +115 -5
  260. data/lib/arel/nodes/sql_literal.rb +13 -0
  261. data/lib/arel/nodes/table_alias.rb +4 -0
  262. data/lib/arel/nodes/update_statement.rb +4 -2
  263. data/lib/arel/nodes.rb +6 -2
  264. data/lib/arel/predications.rb +3 -1
  265. data/lib/arel/select_manager.rb +7 -3
  266. data/lib/arel/table.rb +9 -5
  267. data/lib/arel/tree_manager.rb +8 -3
  268. data/lib/arel/update_manager.rb +7 -1
  269. data/lib/arel/visitors/dot.rb +3 -0
  270. data/lib/arel/visitors/mysql.rb +17 -5
  271. data/lib/arel/visitors/postgresql.rb +1 -12
  272. data/lib/arel/visitors/sqlite.rb +25 -0
  273. data/lib/arel/visitors/to_sql.rb +114 -34
  274. data/lib/arel/visitors/visitor.rb +2 -2
  275. data/lib/arel.rb +21 -3
  276. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  277. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  278. data/lib/rails/generators/active_record/migration.rb +3 -1
  279. data/lib/rails/generators/active_record/model/USAGE +113 -0
  280. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  281. metadata +56 -17
  282. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  283. data/lib/active_record/null_relation.rb +0 -63
@@ -15,39 +15,25 @@ module ActiveRecord
15
15
  !READ_QUERY.match?(sql.b)
16
16
  end
17
17
 
18
- def explain(arel, binds = [])
19
- sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
20
- SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
18
+ def explain(arel, binds = [], _options = [])
19
+ sql = "EXPLAIN QUERY PLAN " + to_sql(arel, binds)
20
+ result = internal_exec_query(sql, "EXPLAIN", [])
21
+ SQLite3::ExplainPrettyPrinter.new.pp(result)
21
22
  end
22
23
 
23
- def execute(sql, name = nil) # :nodoc:
24
+ def internal_exec_query(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
24
25
  sql = transform_query(sql)
25
26
  check_if_write_query(sql)
26
27
 
27
- materialize_transactions
28
- mark_transaction_written_if_write(sql)
29
-
30
- log(sql, name) do
31
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
32
- @connection.execute(sql)
33
- end
34
- end
35
- end
36
-
37
- def exec_query(sql, name = nil, binds = [], prepare: false, async: false) # :nodoc:
38
- sql = transform_query(sql)
39
- check_if_write_query(sql)
40
-
41
- materialize_transactions
42
28
  mark_transaction_written_if_write(sql)
43
29
 
44
30
  type_casted_binds = type_casted_binds(binds)
45
31
 
46
- log(sql, name, binds, type_casted_binds, async: async) do
47
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
32
+ log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
33
+ with_raw_connection do |conn|
48
34
  # Don't cache statements if they are not prepared
49
35
  unless prepare
50
- stmt = @connection.prepare(sql)
36
+ stmt = conn.prepare(sql)
51
37
  begin
52
38
  cols = stmt.columns
53
39
  unless without_prepared_statement?(binds)
@@ -58,21 +44,24 @@ module ActiveRecord
58
44
  stmt.close
59
45
  end
60
46
  else
61
- stmt = @statements[sql] ||= @connection.prepare(sql)
47
+ stmt = @statements[sql] ||= conn.prepare(sql)
62
48
  cols = stmt.columns
63
49
  stmt.reset!
64
50
  stmt.bind_params(type_casted_binds)
65
51
  records = stmt.to_a
66
52
  end
53
+ verified!
67
54
 
68
- build_result(columns: cols, rows: records)
55
+ result = build_result(columns: cols, rows: records)
56
+ notification_payload[:row_count] = result.length
57
+ result
69
58
  end
70
59
  end
71
60
  end
72
61
 
73
62
  def exec_delete(sql, name = "SQL", binds = []) # :nodoc:
74
- exec_query(sql, name, binds)
75
- @connection.changes
63
+ internal_exec_query(sql, name, binds)
64
+ @raw_connection.changes
76
65
  end
77
66
  alias :exec_update :exec_delete
78
67
 
@@ -80,28 +69,44 @@ module ActiveRecord
80
69
  raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
81
70
  raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
82
71
 
83
- ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted] = @connection.get_first_value("PRAGMA read_uncommitted")
84
- @connection.read_uncommitted = true
85
- begin_db_transaction
72
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
73
+ ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted] = conn.get_first_value("PRAGMA read_uncommitted")
74
+ conn.read_uncommitted = true
75
+ begin_db_transaction
76
+ end
86
77
  end
87
78
 
88
79
  def begin_db_transaction # :nodoc:
89
- log("begin transaction", "TRANSACTION") { @connection.transaction }
80
+ log("begin transaction", "TRANSACTION") do
81
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
82
+ result = conn.transaction
83
+ verified!
84
+ result
85
+ end
86
+ end
90
87
  end
91
88
 
92
89
  def commit_db_transaction # :nodoc:
93
- log("commit transaction", "TRANSACTION") { @connection.commit }
90
+ log("commit transaction", "TRANSACTION") do
91
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
92
+ conn.commit
93
+ end
94
+ end
94
95
  reset_read_uncommitted
95
96
  end
96
97
 
97
98
  def exec_rollback_db_transaction # :nodoc:
98
- log("rollback transaction", "TRANSACTION") { @connection.rollback }
99
+ log("rollback transaction", "TRANSACTION") do
100
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
101
+ conn.rollback
102
+ end
103
+ end
99
104
  reset_read_uncommitted
100
105
  end
101
106
 
102
107
  # https://stackoverflow.com/questions/17574784
103
108
  # https://www.sqlite.org/lang_datefunc.html
104
- 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:
105
110
  private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
106
111
 
107
112
  def high_precision_current_timestamp
@@ -109,11 +114,22 @@ module ActiveRecord
109
114
  end
110
115
 
111
116
  private
117
+ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: false)
118
+ log(sql, name, async: async) do |notification_payload|
119
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
120
+ result = conn.execute(sql)
121
+ verified!
122
+ notification_payload[:row_count] = result.length
123
+ result
124
+ end
125
+ end
126
+ end
127
+
112
128
  def reset_read_uncommitted
113
129
  read_uncommitted = ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted]
114
130
  return unless read_uncommitted
115
131
 
116
- @connection.read_uncommitted = read_uncommitted
132
+ @raw_connection&.read_uncommitted = read_uncommitted
117
133
  end
118
134
 
119
135
  def execute_batch(statements, name = nil)
@@ -121,21 +137,18 @@ module ActiveRecord
121
137
  sql = combine_multi_statements(statements)
122
138
 
123
139
  check_if_write_query(sql)
124
-
125
- materialize_transactions
126
140
  mark_transaction_written_if_write(sql)
127
141
 
128
- log(sql, name) do
129
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
130
- @connection.execute_batch2(sql)
142
+ log(sql, name) do |notification_payload|
143
+ with_raw_connection do |conn|
144
+ result = conn.execute_batch2(sql)
145
+ verified!
146
+ notification_payload[:row_count] = result.length
147
+ result
131
148
  end
132
149
  end
133
150
  end
134
151
 
135
- def last_inserted_id(result)
136
- @connection.last_insert_row_id
137
- end
138
-
139
152
  def build_fixture_statements(fixture_set)
140
153
  fixture_set.flat_map do |table_name, fixtures|
141
154
  next if fixtures.empty?
@@ -146,6 +159,10 @@ module ActiveRecord
146
159
  def build_truncate_statement(table_name)
147
160
  "DELETE FROM #{quote_table_name(table_name)}"
148
161
  end
162
+
163
+ def returning_column_values(result)
164
+ result.rows.first
165
+ end
149
166
  end
150
167
  end
151
168
  end
@@ -4,23 +4,71 @@ 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
 
10
- def quote_string(s)
11
- @connection.class.quote(s)
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
12
51
  end
13
52
 
14
- def quote_table_name_for_assignment(table, attr)
15
- quote_column_name(attr)
53
+ def quote(value) # :nodoc:
54
+ case value
55
+ when Numeric
56
+ if value.finite?
57
+ super
58
+ else
59
+ "'#{value}'"
60
+ end
61
+ else
62
+ super
63
+ end
16
64
  end
17
65
 
18
- def quote_table_name(name)
19
- QUOTED_TABLE_NAMES[name] ||= super.gsub(".", "\".\"").freeze
66
+ def quote_string(s)
67
+ ::SQLite3::Database.quote(s)
20
68
  end
21
69
 
22
- def quote_column_name(name)
23
- QUOTED_COLUMN_NAMES[name] ||= %Q("#{super.gsub('"', '""')}")
70
+ def quote_table_name_for_assignment(table, attr)
71
+ quote_column_name(attr)
24
72
  end
25
73
 
26
74
  def quoted_time(value)
@@ -63,7 +111,7 @@ module ActiveRecord
63
111
 
64
112
  def type_cast(value) # :nodoc:
65
113
  case value
66
- when BigDecimal
114
+ when BigDecimal, Rational
67
115
  value.to_f
68
116
  when String
69
117
  if value.encoding == Encoding::ASCII_8BIT
@@ -75,42 +123,6 @@ module ActiveRecord
75
123
  super
76
124
  end
77
125
  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+ASC|\s+DESC)?
108
- )
109
- (?:\s*,\s*\g<1>)*
110
- \z
111
- /ix
112
-
113
- private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
114
126
  end
115
127
  end
116
128
  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
@@ -3,6 +3,7 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLite3
6
+ # = Active Record SQLite3 Adapter \Table Definition
6
7
  class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
7
8
  def change_column(column_name, type, **options)
8
9
  name = column_name.to_s
@@ -15,10 +16,23 @@ module ActiveRecord
15
16
  end
16
17
  alias :belongs_to :references
17
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
+
18
28
  private
19
29
  def integer_like_primary_key_type(type, options)
20
30
  :primary_key
21
31
  end
32
+
33
+ def valid_column_definition_options
34
+ super + [:as, :type, :stored]
35
+ end
22
36
  end
23
37
  end
24
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
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  module SchemaStatements # :nodoc:
7
7
  # Returns an array of indexes for the given table.
8
8
  def indexes(table_name)
9
- exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").filter_map do |row|
9
+ internal_exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").filter_map do |row|
10
10
  # Indexes SQLite creates implicitly for internal use start with "sqlite_".
11
11
  # See https://www.sqlite.org/fileformat2.html#intschema
12
12
  next if row["name"].start_with?("sqlite_")
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
 
24
24
  /\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+))?(?:\s*\/\*.*\*\/)?\z/i =~ index_sql
25
25
 
26
- columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
26
+ columns = internal_exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
27
27
  col["name"]
28
28
  end
29
29
 
@@ -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)
@@ -72,6 +74,7 @@ module ActiveRecord
72
74
  Base.pluralize_table_names ? table.pluralize : table
73
75
  end
74
76
  table = strip_table_name_prefix_and_suffix(table)
77
+ options = options.slice(*fk.options.keys)
75
78
  fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
76
79
  fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
77
80
  end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
@@ -102,7 +105,9 @@ module ActiveRecord
102
105
  end
103
106
  end
104
107
 
105
- def remove_check_constraint(table_name, expression = nil, **options)
108
+ def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
109
+ return if if_exists && !check_constraint_exists?(table_name, **options)
110
+
106
111
  check_constraints = check_constraints(table_name)
107
112
  chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
108
113
  check_constraints.delete_if { |chk| chk.name == chk_name_to_delete }
@@ -113,9 +118,13 @@ module ActiveRecord
113
118
  SQLite3::SchemaDumper.create(self, options)
114
119
  end
115
120
 
121
+ def schema_creation # :nodoc
122
+ SQLite3::SchemaCreation.new(self)
123
+ end
124
+
116
125
  private
117
- def schema_creation
118
- SQLite3::SchemaCreation.new(self)
126
+ def valid_table_definition_options
127
+ super + [:rename]
119
128
  end
120
129
 
121
130
  def create_table_definition(name, **options)
@@ -126,12 +135,20 @@ module ActiveRecord
126
135
  super unless internal
127
136
  end
128
137
 
129
- def new_column_from_field(table_name, field)
138
+ def new_column_from_field(table_name, field, definitions)
130
139
  default = field["dflt_value"]
131
140
 
132
141
  type_metadata = fetch_type_metadata(field["type"])
133
142
  default_value = extract_value_from_default(default)
134
- default_function = extract_default_function(default_value, default)
143
+ generated_type = extract_generated_type(field)
144
+
145
+ if generated_type.present?
146
+ default_function = default
147
+ else
148
+ default_function = extract_default_function(default_value, default)
149
+ end
150
+
151
+ rowid = is_column_the_rowid?(field, definitions)
135
152
 
136
153
  Column.new(
137
154
  field["name"],
@@ -139,10 +156,23 @@ module ActiveRecord
139
156
  type_metadata,
140
157
  field["notnull"].to_i == 0,
141
158
  default_function,
142
- collation: field["collation"]
159
+ collation: field["collation"],
160
+ auto_increment: field["auto_increment"],
161
+ rowid: rowid,
162
+ generated_type: generated_type
143
163
  )
144
164
  end
145
165
 
166
+ INTEGER_REGEX = /integer/i
167
+ # if a rowid table has a primary key that consists of a single column
168
+ # and the declared type of that column is "INTEGER" in any mixture of upper and lower case,
169
+ # then the column becomes an alias for the rowid.
170
+ def is_column_the_rowid?(field, column_definitions)
171
+ return false unless INTEGER_REGEX.match?(field["type"]) && field["pk"] == 1
172
+ # is the primary key a single column?
173
+ column_definitions.one? { |c| c["pk"] > 0 }
174
+ end
175
+
146
176
  def data_source_sql(name = nil, type: nil)
147
177
  scope = quoted_scope(name, type: type)
148
178
  scope[:type] ||= "'table','view'"
@@ -166,6 +196,19 @@ module ActiveRecord
166
196
  scope[:type] = type if type
167
197
  scope
168
198
  end
199
+
200
+ def assert_valid_deferrable(deferrable)
201
+ return if !deferrable || %i(immediate deferred).include?(deferrable)
202
+
203
+ raise ArgumentError, "deferrable must be `:immediate` or `:deferred`, got: `#{deferrable.inspect}`"
204
+ end
205
+
206
+ def extract_generated_type(field)
207
+ case field["hidden"]
208
+ when 2 then :virtual
209
+ when 3 then :stored
210
+ end
211
+ end
169
212
  end
170
213
  end
171
214
  end