activerecord 6.1.7 → 7.2.0

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 (332) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +520 -1385
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +17 -14
  7. data/lib/active_record/association_relation.rb +2 -12
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +60 -21
  10. data/lib/active_record/associations/association_scope.rb +17 -12
  11. data/lib/active_record/associations/belongs_to_association.rb +37 -11
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
  13. data/lib/active_record/associations/builder/association.rb +11 -5
  14. data/lib/active_record/associations/builder/belongs_to.rb +41 -14
  15. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +4 -4
  19. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  20. data/lib/active_record/associations/collection_association.rb +46 -36
  21. data/lib/active_record/associations/collection_proxy.rb +44 -16
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/errors.rb +265 -0
  24. data/lib/active_record/associations/foreign_association.rb +10 -3
  25. data/lib/active_record/associations/has_many_association.rb +29 -19
  26. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  27. data/lib/active_record/associations/has_one_association.rb +20 -10
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  30. data/lib/active_record/associations/join_dependency.rb +23 -15
  31. data/lib/active_record/associations/nested_error.rb +47 -0
  32. data/lib/active_record/associations/preloader/association.rb +212 -53
  33. data/lib/active_record/associations/preloader/batch.rb +48 -0
  34. data/lib/active_record/associations/preloader/branch.rb +153 -0
  35. data/lib/active_record/associations/preloader/through_association.rb +50 -16
  36. data/lib/active_record/associations/preloader.rb +50 -121
  37. data/lib/active_record/associations/singular_association.rb +15 -3
  38. data/lib/active_record/associations/through_association.rb +25 -14
  39. data/lib/active_record/associations.rb +404 -509
  40. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  41. data/lib/active_record/attribute_assignment.rb +2 -14
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  43. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  44. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  45. data/lib/active_record/attribute_methods/primary_key.rb +47 -27
  46. data/lib/active_record/attribute_methods/query.rb +31 -19
  47. data/lib/active_record/attribute_methods/read.rb +14 -11
  48. data/lib/active_record/attribute_methods/serialization.rb +174 -37
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -9
  50. data/lib/active_record/attribute_methods/write.rb +12 -15
  51. data/lib/active_record/attribute_methods.rb +164 -52
  52. data/lib/active_record/attributes.rb +51 -49
  53. data/lib/active_record/autosave_association.rb +74 -57
  54. data/lib/active_record/base.rb +27 -5
  55. data/lib/active_record/callbacks.rb +18 -34
  56. data/lib/active_record/coders/column_serializer.rb +61 -0
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +70 -46
  59. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +327 -612
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -64
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +377 -142
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +345 -166
  75. data/lib/active_record/connection_adapters/column.rb +13 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -130
  78. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
  79. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  81. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  82. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  83. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +107 -68
  85. data/lib/active_record/connection_adapters/pool_config.rb +26 -16
  86. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  87. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  88. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
  89. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  94. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  97. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  100. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  102. data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
  103. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  104. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  105. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +173 -3
  106. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  107. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +401 -77
  108. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  109. data/lib/active_record/connection_adapters/postgresql_adapter.rb +518 -251
  110. data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
  111. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  112. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
  113. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
  114. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  115. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +66 -22
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
  119. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  120. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  121. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  122. data/lib/active_record/connection_adapters.rb +130 -6
  123. data/lib/active_record/connection_handling.rb +132 -146
  124. data/lib/active_record/core.rb +276 -251
  125. data/lib/active_record/counter_cache.rb +68 -34
  126. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -3
  127. data/lib/active_record/database_configurations/database_config.rb +34 -10
  128. data/lib/active_record/database_configurations/hash_config.rb +107 -31
  129. data/lib/active_record/database_configurations/url_config.rb +38 -13
  130. data/lib/active_record/database_configurations.rb +96 -60
  131. data/lib/active_record/delegated_type.rb +90 -20
  132. data/lib/active_record/deprecator.rb +7 -0
  133. data/lib/active_record/destroy_association_async_job.rb +4 -2
  134. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  135. data/lib/active_record/dynamic_matchers.rb +3 -3
  136. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +68 -0
  140. data/lib/active_record/encryption/configurable.rb +60 -0
  141. data/lib/active_record/encryption/context.rb +42 -0
  142. data/lib/active_record/encryption/contexts.rb +76 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  147. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  148. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  149. data/lib/active_record/encryption/encryptor.rb +170 -0
  150. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  151. data/lib/active_record/encryption/errors.rb +15 -0
  152. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  153. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  154. data/lib/active_record/encryption/key.rb +28 -0
  155. data/lib/active_record/encryption/key_generator.rb +53 -0
  156. data/lib/active_record/encryption/key_provider.rb +46 -0
  157. data/lib/active_record/encryption/message.rb +33 -0
  158. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  159. data/lib/active_record/encryption/message_serializer.rb +96 -0
  160. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  161. data/lib/active_record/encryption/properties.rb +76 -0
  162. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  163. data/lib/active_record/encryption/scheme.rb +100 -0
  164. data/lib/active_record/encryption.rb +56 -0
  165. data/lib/active_record/enum.rb +163 -63
  166. data/lib/active_record/errors.rb +210 -27
  167. data/lib/active_record/explain.rb +21 -12
  168. data/lib/active_record/explain_registry.rb +11 -6
  169. data/lib/active_record/explain_subscriber.rb +1 -1
  170. data/lib/active_record/fixture_set/file.rb +15 -1
  171. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  172. data/lib/active_record/fixture_set/render_context.rb +2 -0
  173. data/lib/active_record/fixture_set/table_row.rb +70 -14
  174. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  175. data/lib/active_record/fixtures.rb +179 -112
  176. data/lib/active_record/future_result.rb +178 -0
  177. data/lib/active_record/gem_version.rb +4 -4
  178. data/lib/active_record/inheritance.rb +85 -31
  179. data/lib/active_record/insert_all.rb +148 -32
  180. data/lib/active_record/integration.rb +14 -10
  181. data/lib/active_record/internal_metadata.rb +123 -23
  182. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  183. data/lib/active_record/locking/optimistic.rb +43 -27
  184. data/lib/active_record/locking/pessimistic.rb +15 -6
  185. data/lib/active_record/log_subscriber.rb +41 -29
  186. data/lib/active_record/marshalling.rb +56 -0
  187. data/lib/active_record/message_pack.rb +124 -0
  188. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  189. data/lib/active_record/middleware/database_selector.rb +23 -13
  190. data/lib/active_record/middleware/shard_selector.rb +62 -0
  191. data/lib/active_record/migration/command_recorder.rb +113 -16
  192. data/lib/active_record/migration/compatibility.rb +235 -46
  193. data/lib/active_record/migration/default_strategy.rb +22 -0
  194. data/lib/active_record/migration/execution_strategy.rb +19 -0
  195. data/lib/active_record/migration/join_table.rb +1 -1
  196. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  197. data/lib/active_record/migration.rb +374 -177
  198. data/lib/active_record/model_schema.rb +143 -159
  199. data/lib/active_record/nested_attributes.rb +48 -21
  200. data/lib/active_record/no_touching.rb +3 -3
  201. data/lib/active_record/normalization.rb +163 -0
  202. data/lib/active_record/persistence.rb +282 -283
  203. data/lib/active_record/promise.rb +84 -0
  204. data/lib/active_record/query_cache.rb +19 -25
  205. data/lib/active_record/query_logs.rb +189 -0
  206. data/lib/active_record/query_logs_formatter.rb +41 -0
  207. data/lib/active_record/querying.rb +44 -9
  208. data/lib/active_record/railtie.rb +234 -71
  209. data/lib/active_record/railties/controller_runtime.rb +25 -11
  210. data/lib/active_record/railties/databases.rake +189 -256
  211. data/lib/active_record/railties/job_runtime.rb +23 -0
  212. data/lib/active_record/readonly_attributes.rb +41 -3
  213. data/lib/active_record/reflection.rb +325 -103
  214. data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
  215. data/lib/active_record/relation/batches.rb +198 -63
  216. data/lib/active_record/relation/calculations.rb +300 -111
  217. data/lib/active_record/relation/delegation.rb +33 -22
  218. data/lib/active_record/relation/finder_methods.rb +123 -52
  219. data/lib/active_record/relation/merger.rb +26 -19
  220. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  221. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  222. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  223. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  224. data/lib/active_record/relation/predicate_builder.rb +29 -22
  225. data/lib/active_record/relation/query_attribute.rb +30 -12
  226. data/lib/active_record/relation/query_methods.rb +842 -150
  227. data/lib/active_record/relation/record_fetch_warning.rb +10 -9
  228. data/lib/active_record/relation/spawn_methods.rb +7 -6
  229. data/lib/active_record/relation/where_clause.rb +15 -36
  230. data/lib/active_record/relation.rb +736 -145
  231. data/lib/active_record/result.rb +67 -54
  232. data/lib/active_record/runtime_registry.rb +71 -13
  233. data/lib/active_record/sanitization.rb +84 -34
  234. data/lib/active_record/schema.rb +39 -23
  235. data/lib/active_record/schema_dumper.rb +90 -31
  236. data/lib/active_record/schema_migration.rb +74 -23
  237. data/lib/active_record/scoping/default.rb +72 -15
  238. data/lib/active_record/scoping/named.rb +5 -13
  239. data/lib/active_record/scoping.rb +65 -34
  240. data/lib/active_record/secure_password.rb +60 -0
  241. data/lib/active_record/secure_token.rb +21 -3
  242. data/lib/active_record/serialization.rb +6 -1
  243. data/lib/active_record/signed_id.rb +30 -9
  244. data/lib/active_record/statement_cache.rb +7 -7
  245. data/lib/active_record/store.rb +10 -10
  246. data/lib/active_record/suppressor.rb +13 -15
  247. data/lib/active_record/table_metadata.rb +7 -3
  248. data/lib/active_record/tasks/database_tasks.rb +277 -149
  249. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  250. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  251. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  252. data/lib/active_record/test_databases.rb +1 -1
  253. data/lib/active_record/test_fixtures.rb +173 -155
  254. data/lib/active_record/testing/query_assertions.rb +121 -0
  255. data/lib/active_record/timestamp.rb +32 -19
  256. data/lib/active_record/token_for.rb +123 -0
  257. data/lib/active_record/touch_later.rb +12 -7
  258. data/lib/active_record/transaction.rb +132 -0
  259. data/lib/active_record/transactions.rb +118 -41
  260. data/lib/active_record/translation.rb +3 -5
  261. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  262. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  263. data/lib/active_record/type/internal/timezone.rb +7 -2
  264. data/lib/active_record/type/serialized.rb +9 -7
  265. data/lib/active_record/type/time.rb +4 -0
  266. data/lib/active_record/type/type_map.rb +17 -20
  267. data/lib/active_record/type.rb +1 -2
  268. data/lib/active_record/type_caster/connection.rb +4 -4
  269. data/lib/active_record/validations/absence.rb +1 -1
  270. data/lib/active_record/validations/associated.rb +13 -7
  271. data/lib/active_record/validations/numericality.rb +5 -4
  272. data/lib/active_record/validations/presence.rb +5 -28
  273. data/lib/active_record/validations/uniqueness.rb +64 -15
  274. data/lib/active_record/validations.rb +12 -5
  275. data/lib/active_record/version.rb +1 -1
  276. data/lib/active_record.rb +444 -32
  277. data/lib/arel/alias_predication.rb +1 -1
  278. data/lib/arel/attributes/attribute.rb +0 -8
  279. data/lib/arel/collectors/bind.rb +2 -0
  280. data/lib/arel/collectors/composite.rb +7 -0
  281. data/lib/arel/collectors/sql_string.rb +1 -1
  282. data/lib/arel/collectors/substitute_binds.rb +1 -1
  283. data/lib/arel/crud.rb +28 -22
  284. data/lib/arel/delete_manager.rb +18 -4
  285. data/lib/arel/errors.rb +10 -0
  286. data/lib/arel/factory_methods.rb +4 -0
  287. data/lib/arel/filter_predications.rb +9 -0
  288. data/lib/arel/insert_manager.rb +2 -3
  289. data/lib/arel/nodes/binary.rb +6 -7
  290. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  291. data/lib/arel/nodes/casted.rb +1 -1
  292. data/lib/arel/nodes/cte.rb +36 -0
  293. data/lib/arel/nodes/delete_statement.rb +12 -13
  294. data/lib/arel/nodes/filter.rb +10 -0
  295. data/lib/arel/nodes/fragments.rb +35 -0
  296. data/lib/arel/nodes/function.rb +1 -0
  297. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  298. data/lib/arel/nodes/insert_statement.rb +2 -2
  299. data/lib/arel/nodes/leading_join.rb +8 -0
  300. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  301. data/lib/arel/nodes/node.rb +115 -5
  302. data/lib/arel/nodes/select_core.rb +2 -2
  303. data/lib/arel/nodes/select_statement.rb +2 -2
  304. data/lib/arel/nodes/sql_literal.rb +13 -0
  305. data/lib/arel/nodes/table_alias.rb +4 -0
  306. data/lib/arel/nodes/update_statement.rb +8 -3
  307. data/lib/arel/nodes.rb +7 -2
  308. data/lib/arel/predications.rb +14 -4
  309. data/lib/arel/select_manager.rb +11 -5
  310. data/lib/arel/table.rb +9 -6
  311. data/lib/arel/tree_manager.rb +8 -15
  312. data/lib/arel/update_manager.rb +20 -5
  313. data/lib/arel/visitors/dot.rb +81 -90
  314. data/lib/arel/visitors/mysql.rb +23 -5
  315. data/lib/arel/visitors/postgresql.rb +1 -22
  316. data/lib/arel/visitors/to_sql.rb +170 -36
  317. data/lib/arel/visitors/visitor.rb +2 -2
  318. data/lib/arel.rb +23 -4
  319. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  320. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  321. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  322. data/lib/rails/generators/active_record/migration.rb +3 -1
  323. data/lib/rails/generators/active_record/model/USAGE +113 -0
  324. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  325. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  326. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  327. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  328. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  329. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  330. metadata +100 -14
  331. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  332. data/lib/active_record/null_relation.rb +0 -67
@@ -15,41 +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
- if preventing_writes? && write_query?(sql)
25
- raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
26
- end
24
+ def internal_exec_query(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
25
+ sql = transform_query(sql)
26
+ check_if_write_query(sql)
27
27
 
28
- materialize_transactions
29
- mark_transaction_written_if_write(sql)
30
-
31
- log(sql, name) do
32
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
33
- @connection.execute(sql)
34
- end
35
- end
36
- end
37
-
38
- def exec_query(sql, name = nil, binds = [], prepare: false)
39
- if preventing_writes? && write_query?(sql)
40
- raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
41
- end
42
-
43
- materialize_transactions
44
28
  mark_transaction_written_if_write(sql)
45
29
 
46
30
  type_casted_binds = type_casted_binds(binds)
47
31
 
48
- log(sql, name, binds, type_casted_binds) do
49
- 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|
50
34
  # Don't cache statements if they are not prepared
51
35
  unless prepare
52
- stmt = @connection.prepare(sql)
36
+ stmt = conn.prepare(sql)
53
37
  begin
54
38
  cols = stmt.columns
55
39
  unless without_prepared_statement?(binds)
@@ -60,76 +44,111 @@ module ActiveRecord
60
44
  stmt.close
61
45
  end
62
46
  else
63
- stmt = @statements[sql] ||= @connection.prepare(sql)
47
+ stmt = @statements[sql] ||= conn.prepare(sql)
64
48
  cols = stmt.columns
65
49
  stmt.reset!
66
50
  stmt.bind_params(type_casted_binds)
67
51
  records = stmt.to_a
68
52
  end
53
+ verified!
69
54
 
70
- build_result(columns: cols, rows: records)
55
+ result = build_result(columns: cols, rows: records)
56
+ notification_payload[:row_count] = result.length
57
+ result
71
58
  end
72
59
  end
73
60
  end
74
61
 
75
- def exec_delete(sql, name = "SQL", binds = [])
76
- exec_query(sql, name, binds)
77
- @connection.changes
62
+ def exec_delete(sql, name = "SQL", binds = []) # :nodoc:
63
+ internal_exec_query(sql, name, binds)
64
+ @raw_connection.changes
78
65
  end
79
66
  alias :exec_update :exec_delete
80
67
 
81
- def begin_isolated_db_transaction(isolation) #:nodoc
68
+ def begin_isolated_db_transaction(isolation) # :nodoc:
82
69
  raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
83
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?
84
71
 
85
- Thread.current.thread_variable_set("read_uncommitted", @connection.get_first_value("PRAGMA read_uncommitted"))
86
- @connection.read_uncommitted = true
87
- 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
88
77
  end
89
78
 
90
- def begin_db_transaction #:nodoc:
91
- log("begin transaction", "TRANSACTION") { @connection.transaction }
79
+ def begin_db_transaction # :nodoc:
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
92
87
  end
93
88
 
94
- def commit_db_transaction #:nodoc:
95
- log("commit transaction", "TRANSACTION") { @connection.commit }
89
+ def commit_db_transaction # :nodoc:
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
96
95
  reset_read_uncommitted
97
96
  end
98
97
 
99
- def exec_rollback_db_transaction #:nodoc:
100
- log("rollback transaction", "TRANSACTION") { @connection.rollback }
98
+ def exec_rollback_db_transaction # :nodoc:
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
101
104
  reset_read_uncommitted
102
105
  end
103
106
 
107
+ # https://stackoverflow.com/questions/17574784
108
+ # https://www.sqlite.org/lang_datefunc.html
109
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')", retryable: true).freeze # :nodoc:
110
+ private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
111
+
112
+ def high_precision_current_timestamp
113
+ HIGH_PRECISION_CURRENT_TIMESTAMP
114
+ end
115
+
104
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
+
105
128
  def reset_read_uncommitted
106
- read_uncommitted = Thread.current.thread_variable_get("read_uncommitted")
129
+ read_uncommitted = ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted]
107
130
  return unless read_uncommitted
108
131
 
109
- @connection.read_uncommitted = read_uncommitted
132
+ @raw_connection&.read_uncommitted = read_uncommitted
110
133
  end
111
134
 
112
135
  def execute_batch(statements, name = nil)
136
+ statements = statements.map { |sql| transform_query(sql) }
113
137
  sql = combine_multi_statements(statements)
114
138
 
115
- if preventing_writes? && write_query?(sql)
116
- raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
117
- end
118
-
119
- materialize_transactions
139
+ check_if_write_query(sql)
120
140
  mark_transaction_written_if_write(sql)
121
141
 
122
- log(sql, name) do
123
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
124
- @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
125
148
  end
126
149
  end
127
150
  end
128
151
 
129
- def last_inserted_id(result)
130
- @connection.last_insert_row_id
131
- end
132
-
133
152
  def build_fixture_statements(fixture_set)
134
153
  fixture_set.flat_map do |table_name, fixtures|
135
154
  next if fixtures.empty?
@@ -140,6 +159,10 @@ module ActiveRecord
140
159
  def build_truncate_statement(table_name)
141
160
  "DELETE FROM #{quote_table_name(table_name)}"
142
161
  end
162
+
163
+ def returning_column_values(result)
164
+ result.rows.first
165
+ end
143
166
  end
144
167
  end
145
168
  end
@@ -4,20 +4,58 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLite3
6
6
  module Quoting # :nodoc:
7
- def quote_string(s)
8
- @connection.class.quote(s)
9
- end
7
+ extend ActiveSupport::Concern
8
+
9
+ QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
10
+ QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
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
10
27
 
11
- def quote_table_name_for_assignment(table, attr)
12
- quote_column_name(attr)
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
13
51
  end
14
52
 
15
- def quote_table_name(name)
16
- self.class.quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
53
+ def quote_string(s)
54
+ ::SQLite3::Database.quote(s)
17
55
  end
18
56
 
19
- def quote_column_name(name)
20
- self.class.quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
57
+ def quote_table_name_for_assignment(table, attr)
58
+ quote_column_name(attr)
21
59
  end
22
60
 
23
61
  def quoted_time(value)
@@ -45,57 +83,33 @@ module ActiveRecord
45
83
  0
46
84
  end
47
85
 
48
- def column_name_matcher
49
- COLUMN_NAME
50
- end
51
-
52
- def column_name_with_order_matcher
53
- COLUMN_NAME_WITH_ORDER
86
+ def quote_default_expression(value, column) # :nodoc:
87
+ if value.is_a?(Proc)
88
+ value = value.call
89
+ if value.match?(/\A\w+\(.*\)\z/)
90
+ "(#{value})"
91
+ else
92
+ value
93
+ end
94
+ else
95
+ super
96
+ end
54
97
  end
55
98
 
56
- COLUMN_NAME = /
57
- \A
58
- (
59
- (?:
60
- # "table_name"."column_name" | function(one or no argument)
61
- ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
62
- )
63
- (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
64
- )
65
- (?:\s*,\s*\g<1>)*
66
- \z
67
- /ix
68
-
69
- COLUMN_NAME_WITH_ORDER = /
70
- \A
71
- (
72
- (?:
73
- # "table_name"."column_name" | function(one or no argument)
74
- ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
75
- )
76
- (?:\s+ASC|\s+DESC)?
77
- )
78
- (?:\s*,\s*\g<1>)*
79
- \z
80
- /ix
81
-
82
- private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
83
-
84
- private
85
- def _type_cast(value)
86
- case value
87
- when BigDecimal
88
- value.to_f
89
- when String
90
- if value.encoding == Encoding::ASCII_8BIT
91
- super(value.encode(Encoding::UTF_8))
92
- else
93
- super
94
- end
99
+ def type_cast(value) # :nodoc:
100
+ case value
101
+ when BigDecimal, Rational
102
+ value.to_f
103
+ when String
104
+ if value.encoding == Encoding::ASCII_8BIT
105
+ super(value.encode(Encoding::UTF_8))
95
106
  else
96
107
  super
97
108
  end
109
+ else
110
+ super
98
111
  end
112
+ end
99
113
  end
100
114
  end
101
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
@@ -3,16 +3,36 @@
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
8
+ def change_column(column_name, type, **options)
9
+ name = column_name.to_s
10
+ @columns_hash[name] = nil
11
+ column(name, type, **options)
12
+ end
13
+
7
14
  def references(*args, **options)
8
15
  super(*args, type: :integer, **options)
9
16
  end
10
17
  alias :belongs_to :references
11
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
+
12
28
  private
13
29
  def integer_like_primary_key_type(type, options)
14
30
  :primary_key
15
31
  end
32
+
33
+ def valid_column_definition_options
34
+ super + [:as, :type, :stored]
35
+ end
16
36
  end
17
37
  end
18
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").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_")
@@ -21,9 +21,9 @@ module ActiveRecord
21
21
  WHERE name = #{quote(row['name'])} AND type = 'index'
22
22
  SQL
23
23
 
24
- /\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+))?\z/i =~ index_sql
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
 
@@ -49,10 +49,12 @@ module ActiveRecord
49
49
  where: where,
50
50
  orders: orders
51
51
  )
52
- end.compact
52
+ end
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)
@@ -60,6 +62,8 @@ module ActiveRecord
60
62
  end
61
63
 
62
64
  def remove_foreign_key(from_table, to_table = nil, **options)
65
+ return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
66
+
63
67
  to_table ||= options[:to_table]
64
68
  options = options.except(:name, :to_table, :validate)
65
69
  foreign_keys = foreign_keys(from_table)
@@ -82,11 +86,11 @@ module ActiveRecord
82
86
  table_sql = query_value(<<-SQL, "SCHEMA")
83
87
  SELECT sql
84
88
  FROM sqlite_master
85
- WHERE name = #{quote_table_name(table_name)} AND type = 'table'
89
+ WHERE name = #{quote(table_name)} AND type = 'table'
86
90
  UNION ALL
87
91
  SELECT sql
88
92
  FROM sqlite_temp_master
89
- WHERE name = #{quote_table_name(table_name)} AND type = 'table'
93
+ WHERE name = #{quote(table_name)} AND type = 'table'
90
94
  SQL
91
95
 
92
96
  table_sql.to_s.scan(/CONSTRAINT\s+(?<name>\w+)\s+CHECK\s+\((?<expression>(:?[^()]|\(\g<expression>\))+)\)/i).map do |name, expression|
@@ -100,7 +104,9 @@ module ActiveRecord
100
104
  end
101
105
  end
102
106
 
103
- def remove_check_constraint(table_name, expression = nil, **options)
107
+ def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
108
+ return if if_exists && !check_constraint_exists?(table_name, **options)
109
+
104
110
  check_constraints = check_constraints(table_name)
105
111
  chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
106
112
  check_constraints.delete_if { |chk| chk.name == chk_name_to_delete }
@@ -111,9 +117,13 @@ module ActiveRecord
111
117
  SQLite3::SchemaDumper.create(self, options)
112
118
  end
113
119
 
120
+ def schema_creation # :nodoc
121
+ SQLite3::SchemaCreation.new(self)
122
+ end
123
+
114
124
  private
115
- def schema_creation
116
- SQLite3::SchemaCreation.new(self)
125
+ def valid_table_definition_options
126
+ super + [:rename]
117
127
  end
118
128
 
119
129
  def create_table_definition(name, **options)
@@ -124,21 +134,42 @@ module ActiveRecord
124
134
  super unless internal
125
135
  end
126
136
 
127
- def new_column_from_field(table_name, field)
128
- default = \
129
- case field["dflt_value"]
130
- when /^null$/i
131
- nil
132
- when /^'(.*)'$/m
133
- $1.gsub("''", "'")
134
- when /^"(.*)"$/m
135
- $1.gsub('""', '"')
136
- else
137
- field["dflt_value"]
138
- end
137
+ def new_column_from_field(table_name, field, definitions)
138
+ default = field["dflt_value"]
139
139
 
140
140
  type_metadata = fetch_type_metadata(field["type"])
141
- Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, collation: field["collation"])
141
+ default_value = extract_value_from_default(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
+
150
+ rowid = is_column_the_rowid?(field, definitions)
151
+
152
+ Column.new(
153
+ field["name"],
154
+ default_value,
155
+ type_metadata,
156
+ field["notnull"].to_i == 0,
157
+ default_function,
158
+ collation: field["collation"],
159
+ auto_increment: field["auto_increment"],
160
+ rowid: rowid,
161
+ generated_type: generated_type
162
+ )
163
+ end
164
+
165
+ INTEGER_REGEX = /integer/i
166
+ # if a rowid table has a primary key that consists of a single column
167
+ # and the declared type of that column is "INTEGER" in any mixture of upper and lower case,
168
+ # then the column becomes an alias for the rowid.
169
+ def is_column_the_rowid?(field, column_definitions)
170
+ return false unless INTEGER_REGEX.match?(field["type"]) && field["pk"] == 1
171
+ # is the primary key a single column?
172
+ column_definitions.one? { |c| c["pk"] > 0 }
142
173
  end
143
174
 
144
175
  def data_source_sql(name = nil, type: nil)
@@ -164,6 +195,19 @@ module ActiveRecord
164
195
  scope[:type] = type if type
165
196
  scope
166
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
167
211
  end
168
212
  end
169
213
  end