activerecord 6.0.0 → 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 (376) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +996 -594
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +34 -34
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +22 -20
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +41 -30
  9. data/lib/active_record/associations/association.rb +106 -41
  10. data/lib/active_record/associations/association_scope.rb +30 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +69 -14
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +20 -6
  13. data/lib/active_record/associations/builder/association.rb +39 -6
  14. data/lib/active_record/associations/builder/belongs_to.rb +47 -17
  15. data/lib/active_record/associations/builder/collection_association.rb +14 -6
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -10
  17. data/lib/active_record/associations/builder/has_many.rb +7 -3
  18. data/lib/active_record/associations/builder/has_one.rb +13 -16
  19. data/lib/active_record/associations/builder/singular_association.rb +7 -3
  20. data/lib/active_record/associations/collection_association.rb +90 -53
  21. data/lib/active_record/associations/collection_proxy.rb +54 -19
  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 +21 -1
  25. data/lib/active_record/associations/has_many_association.rb +41 -10
  26. data/lib/active_record/associations/has_many_through_association.rb +29 -12
  27. data/lib/active_record/associations/has_one_association.rb +33 -9
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +41 -17
  30. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  31. data/lib/active_record/associations/join_dependency.rb +97 -54
  32. data/lib/active_record/associations/nested_error.rb +47 -0
  33. data/lib/active_record/associations/preloader/association.rb +237 -54
  34. data/lib/active_record/associations/preloader/batch.rb +48 -0
  35. data/lib/active_record/associations/preloader/branch.rb +153 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +51 -17
  37. data/lib/active_record/associations/preloader.rb +55 -121
  38. data/lib/active_record/associations/singular_association.rb +16 -4
  39. data/lib/active_record/associations/through_association.rb +26 -15
  40. data/lib/active_record/associations.rb +454 -440
  41. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  42. data/lib/active_record/attribute_assignment.rb +11 -14
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +36 -11
  44. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  45. data/lib/active_record/attribute_methods/dirty.rb +75 -34
  46. data/lib/active_record/attribute_methods/primary_key.rb +53 -31
  47. data/lib/active_record/attribute_methods/query.rb +31 -22
  48. data/lib/active_record/attribute_methods/read.rb +16 -17
  49. data/lib/active_record/attribute_methods/serialization.rb +177 -35
  50. data/lib/active_record/attribute_methods/time_zone_conversion.rb +18 -15
  51. data/lib/active_record/attribute_methods/write.rb +16 -28
  52. data/lib/active_record/attribute_methods.rb +227 -100
  53. data/lib/active_record/attributes.rb +94 -56
  54. data/lib/active_record/autosave_association.rb +119 -73
  55. data/lib/active_record/base.rb +31 -21
  56. data/lib/active_record/callbacks.rb +168 -55
  57. data/lib/active_record/coders/column_serializer.rb +61 -0
  58. data/lib/active_record/coders/json.rb +1 -1
  59. data/lib/active_record/coders/yaml_column.rb +70 -25
  60. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +367 -565
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -57
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +277 -89
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +241 -69
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +122 -134
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +324 -72
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +17 -4
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +611 -211
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +425 -82
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +698 -211
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +464 -239
  76. data/lib/active_record/connection_adapters/column.rb +28 -1
  77. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  78. data/lib/active_record/connection_adapters/mysql/column.rb +2 -1
  79. data/lib/active_record/connection_adapters/mysql/database_statements.rb +32 -137
  80. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  81. data/lib/active_record/connection_adapters/mysql/quoting.rb +90 -43
  82. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +41 -7
  83. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +18 -1
  84. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +13 -4
  85. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +53 -15
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  87. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  88. data/lib/active_record/connection_adapters/mysql2_adapter.rb +127 -63
  89. data/lib/active_record/connection_adapters/pool_config.rb +83 -0
  90. data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
  91. data/lib/active_record/connection_adapters/postgresql/column.rb +54 -2
  92. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +127 -100
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +9 -5
  95. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  97. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -15
  99. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  101. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -4
  103. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +35 -8
  106. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  110. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -4
  111. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  112. data/lib/active_record/connection_adapters/postgresql/quoting.rb +139 -106
  113. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -2
  114. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +98 -4
  115. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +176 -4
  116. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -1
  117. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -118
  118. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  119. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -11
  120. data/lib/active_record/connection_adapters/postgresql_adapter.rb +585 -295
  121. data/lib/active_record/connection_adapters/schema_cache.rb +399 -60
  122. data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
  123. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  124. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +99 -48
  125. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +80 -54
  126. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +27 -1
  127. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  128. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  129. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +102 -24
  130. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +425 -174
  131. data/lib/active_record/connection_adapters/statement_pool.rb +7 -1
  132. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  133. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  134. data/lib/active_record/connection_adapters.rb +176 -0
  135. data/lib/active_record/connection_handling.rb +243 -115
  136. data/lib/active_record/core.rb +481 -199
  137. data/lib/active_record/counter_cache.rb +69 -32
  138. data/lib/active_record/database_configurations/connection_url_resolver.rb +107 -0
  139. data/lib/active_record/database_configurations/database_config.rb +77 -10
  140. data/lib/active_record/database_configurations/hash_config.rb +148 -26
  141. data/lib/active_record/database_configurations/url_config.rb +44 -45
  142. data/lib/active_record/database_configurations.rb +190 -114
  143. data/lib/active_record/delegated_type.rb +279 -0
  144. data/lib/active_record/deprecator.rb +7 -0
  145. data/lib/active_record/destroy_association_async_job.rb +38 -0
  146. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  147. data/lib/active_record/dynamic_matchers.rb +5 -6
  148. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  149. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  150. data/lib/active_record/encryption/cipher.rb +53 -0
  151. data/lib/active_record/encryption/config.rb +68 -0
  152. data/lib/active_record/encryption/configurable.rb +60 -0
  153. data/lib/active_record/encryption/context.rb +42 -0
  154. data/lib/active_record/encryption/contexts.rb +76 -0
  155. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  156. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  157. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  158. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  159. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  160. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  161. data/lib/active_record/encryption/encryptor.rb +171 -0
  162. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  163. data/lib/active_record/encryption/errors.rb +15 -0
  164. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  165. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  166. data/lib/active_record/encryption/key.rb +28 -0
  167. data/lib/active_record/encryption/key_generator.rb +53 -0
  168. data/lib/active_record/encryption/key_provider.rb +46 -0
  169. data/lib/active_record/encryption/message.rb +33 -0
  170. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  171. data/lib/active_record/encryption/message_serializer.rb +96 -0
  172. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  173. data/lib/active_record/encryption/properties.rb +76 -0
  174. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  175. data/lib/active_record/encryption/scheme.rb +100 -0
  176. data/lib/active_record/encryption.rb +58 -0
  177. data/lib/active_record/enum.rb +224 -73
  178. data/lib/active_record/errors.rb +254 -36
  179. data/lib/active_record/explain.rb +30 -17
  180. data/lib/active_record/explain_registry.rb +11 -6
  181. data/lib/active_record/explain_subscriber.rb +2 -2
  182. data/lib/active_record/fixture_set/file.rb +22 -15
  183. data/lib/active_record/fixture_set/model_metadata.rb +15 -6
  184. data/lib/active_record/fixture_set/render_context.rb +3 -1
  185. data/lib/active_record/fixture_set/table_row.rb +88 -16
  186. data/lib/active_record/fixture_set/table_rows.rb +4 -5
  187. data/lib/active_record/fixtures.rb +229 -116
  188. data/lib/active_record/future_result.rb +178 -0
  189. data/lib/active_record/gem_version.rb +4 -4
  190. data/lib/active_record/inheritance.rb +121 -48
  191. data/lib/active_record/insert_all.rb +178 -29
  192. data/lib/active_record/integration.rb +16 -14
  193. data/lib/active_record/internal_metadata.rb +132 -21
  194. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  195. data/lib/active_record/locking/optimistic.rb +64 -33
  196. data/lib/active_record/locking/pessimistic.rb +21 -8
  197. data/lib/active_record/log_subscriber.rb +61 -30
  198. data/lib/active_record/marshalling.rb +59 -0
  199. data/lib/active_record/message_pack.rb +124 -0
  200. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  201. data/lib/active_record/middleware/database_selector/resolver.rb +19 -19
  202. data/lib/active_record/middleware/database_selector.rb +25 -13
  203. data/lib/active_record/middleware/shard_selector.rb +62 -0
  204. data/lib/active_record/migration/command_recorder.rb +160 -55
  205. data/lib/active_record/migration/compatibility.rb +286 -43
  206. data/lib/active_record/migration/default_strategy.rb +22 -0
  207. data/lib/active_record/migration/execution_strategy.rb +19 -0
  208. data/lib/active_record/migration/join_table.rb +1 -2
  209. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  210. data/lib/active_record/migration.rb +421 -193
  211. data/lib/active_record/model_schema.rb +217 -125
  212. data/lib/active_record/nested_attributes.rb +62 -27
  213. data/lib/active_record/no_touching.rb +4 -4
  214. data/lib/active_record/normalization.rb +163 -0
  215. data/lib/active_record/persistence.rb +322 -319
  216. data/lib/active_record/promise.rb +84 -0
  217. data/lib/active_record/query_cache.rb +18 -15
  218. data/lib/active_record/query_logs.rb +193 -0
  219. data/lib/active_record/query_logs_formatter.rb +41 -0
  220. data/lib/active_record/querying.rb +54 -14
  221. data/lib/active_record/railtie.rb +250 -72
  222. data/lib/active_record/railties/console_sandbox.rb +2 -4
  223. data/lib/active_record/railties/controller_runtime.rb +25 -11
  224. data/lib/active_record/railties/databases.rake +312 -197
  225. data/lib/active_record/railties/job_runtime.rb +23 -0
  226. data/lib/active_record/readonly_attributes.rb +45 -3
  227. data/lib/active_record/reflection.rb +389 -146
  228. data/lib/active_record/relation/batches/batch_enumerator.rb +61 -16
  229. data/lib/active_record/relation/batches.rb +214 -73
  230. data/lib/active_record/relation/calculations.rb +379 -124
  231. data/lib/active_record/relation/delegation.rb +36 -23
  232. data/lib/active_record/relation/finder_methods.rb +159 -49
  233. data/lib/active_record/relation/from_clause.rb +5 -1
  234. data/lib/active_record/relation/merger.rb +41 -33
  235. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -11
  236. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -7
  237. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +20 -13
  238. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  239. data/lib/active_record/relation/predicate_builder.rb +79 -53
  240. data/lib/active_record/relation/query_attribute.rb +30 -12
  241. data/lib/active_record/relation/query_methods.rb +1156 -279
  242. data/lib/active_record/relation/record_fetch_warning.rb +12 -11
  243. data/lib/active_record/relation/spawn_methods.rb +10 -9
  244. data/lib/active_record/relation/where_clause.rb +100 -66
  245. data/lib/active_record/relation.rb +829 -194
  246. data/lib/active_record/result.rb +76 -56
  247. data/lib/active_record/runtime_registry.rb +71 -13
  248. data/lib/active_record/sanitization.rb +86 -47
  249. data/lib/active_record/schema.rb +39 -23
  250. data/lib/active_record/schema_dumper.rb +140 -33
  251. data/lib/active_record/schema_migration.rb +74 -29
  252. data/lib/active_record/scoping/default.rb +73 -19
  253. data/lib/active_record/scoping/named.rb +10 -28
  254. data/lib/active_record/scoping.rb +65 -35
  255. data/lib/active_record/secure_password.rb +60 -0
  256. data/lib/active_record/secure_token.rb +34 -8
  257. data/lib/active_record/serialization.rb +11 -4
  258. data/lib/active_record/signed_id.rb +138 -0
  259. data/lib/active_record/statement_cache.rb +26 -10
  260. data/lib/active_record/store.rb +19 -14
  261. data/lib/active_record/suppressor.rb +15 -17
  262. data/lib/active_record/table_metadata.rb +46 -36
  263. data/lib/active_record/tasks/database_tasks.rb +371 -205
  264. data/lib/active_record/tasks/mysql_database_tasks.rb +43 -36
  265. data/lib/active_record/tasks/postgresql_database_tasks.rb +54 -41
  266. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -13
  267. data/lib/active_record/test_databases.rb +5 -4
  268. data/lib/active_record/test_fixtures.rb +189 -104
  269. data/lib/active_record/testing/query_assertions.rb +121 -0
  270. data/lib/active_record/timestamp.rb +35 -25
  271. data/lib/active_record/token_for.rb +123 -0
  272. data/lib/active_record/touch_later.rb +31 -27
  273. data/lib/active_record/transaction.rb +132 -0
  274. data/lib/active_record/transactions.rb +131 -99
  275. data/lib/active_record/translation.rb +3 -5
  276. data/lib/active_record/type/adapter_specific_registry.rb +33 -18
  277. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  278. data/lib/active_record/type/internal/timezone.rb +7 -2
  279. data/lib/active_record/type/serialized.rb +11 -6
  280. data/lib/active_record/type/time.rb +14 -0
  281. data/lib/active_record/type/type_map.rb +17 -21
  282. data/lib/active_record/type/unsigned_integer.rb +0 -1
  283. data/lib/active_record/type.rb +7 -2
  284. data/lib/active_record/type_caster/connection.rb +4 -5
  285. data/lib/active_record/type_caster/map.rb +8 -5
  286. data/lib/active_record/validations/absence.rb +1 -1
  287. data/lib/active_record/validations/associated.rb +13 -8
  288. data/lib/active_record/validations/numericality.rb +36 -0
  289. data/lib/active_record/validations/presence.rb +5 -28
  290. data/lib/active_record/validations/uniqueness.rb +88 -18
  291. data/lib/active_record/validations.rb +15 -8
  292. data/lib/active_record/version.rb +1 -1
  293. data/lib/active_record.rb +446 -40
  294. data/lib/arel/alias_predication.rb +1 -1
  295. data/lib/arel/attributes/attribute.rb +4 -8
  296. data/lib/arel/collectors/bind.rb +8 -1
  297. data/lib/arel/collectors/composite.rb +15 -0
  298. data/lib/arel/collectors/sql_string.rb +7 -0
  299. data/lib/arel/collectors/substitute_binds.rb +7 -0
  300. data/lib/arel/crud.rb +30 -22
  301. data/lib/arel/delete_manager.rb +23 -4
  302. data/lib/arel/errors.rb +10 -0
  303. data/lib/arel/factory_methods.rb +4 -0
  304. data/lib/arel/filter_predications.rb +9 -0
  305. data/lib/arel/insert_manager.rb +2 -3
  306. data/lib/arel/nodes/binary.rb +82 -9
  307. data/lib/arel/nodes/bind_param.rb +8 -0
  308. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  309. data/lib/arel/nodes/casted.rb +22 -10
  310. data/lib/arel/nodes/cte.rb +36 -0
  311. data/lib/arel/nodes/delete_statement.rb +14 -13
  312. data/lib/arel/nodes/equality.rb +6 -9
  313. data/lib/arel/nodes/filter.rb +10 -0
  314. data/lib/arel/nodes/fragments.rb +35 -0
  315. data/lib/arel/nodes/function.rb +1 -0
  316. data/lib/arel/nodes/grouping.rb +3 -0
  317. data/lib/arel/nodes/homogeneous_in.rb +68 -0
  318. data/lib/arel/nodes/in.rb +8 -1
  319. data/lib/arel/nodes/infix_operation.rb +13 -1
  320. data/lib/arel/nodes/insert_statement.rb +2 -2
  321. data/lib/arel/nodes/join_source.rb +1 -1
  322. data/lib/arel/nodes/leading_join.rb +8 -0
  323. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  324. data/lib/arel/nodes/node.rb +122 -11
  325. data/lib/arel/nodes/ordering.rb +27 -0
  326. data/lib/arel/nodes/select_core.rb +2 -2
  327. data/lib/arel/nodes/select_statement.rb +2 -2
  328. data/lib/arel/nodes/sql_literal.rb +16 -0
  329. data/lib/arel/nodes/table_alias.rb +11 -3
  330. data/lib/arel/nodes/unary.rb +0 -1
  331. data/lib/arel/nodes/update_statement.rb +11 -4
  332. data/lib/arel/nodes.rb +10 -3
  333. data/lib/arel/predications.rb +31 -28
  334. data/lib/arel/select_manager.rb +18 -9
  335. data/lib/arel/table.rb +21 -10
  336. data/lib/arel/tree_manager.rb +8 -15
  337. data/lib/arel/update_manager.rb +25 -5
  338. data/lib/arel/visitors/dot.rb +94 -90
  339. data/lib/arel/visitors/mysql.rb +34 -6
  340. data/lib/arel/visitors/postgresql.rb +5 -16
  341. data/lib/arel/visitors/sqlite.rb +25 -1
  342. data/lib/arel/visitors/to_sql.rb +227 -81
  343. data/lib/arel/visitors/visitor.rb +2 -3
  344. data/lib/arel/visitors.rb +0 -7
  345. data/lib/arel.rb +37 -15
  346. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  347. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  348. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  349. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  350. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +6 -1
  351. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  352. data/lib/rails/generators/active_record/migration.rb +9 -3
  353. data/lib/rails/generators/active_record/model/USAGE +113 -0
  354. data/lib/rails/generators/active_record/model/model_generator.rb +49 -4
  355. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  356. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  357. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  358. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  359. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  360. metadata +117 -30
  361. data/lib/active_record/attribute_decorators.rb +0 -90
  362. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  363. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  364. data/lib/active_record/define_callbacks.rb +0 -22
  365. data/lib/active_record/null_relation.rb +0 -68
  366. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  367. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  368. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  369. data/lib/arel/attributes.rb +0 -22
  370. data/lib/arel/visitors/depth_first.rb +0 -204
  371. data/lib/arel/visitors/ibm_db.rb +0 -34
  372. data/lib/arel/visitors/informix.rb +0 -62
  373. data/lib/arel/visitors/mssql.rb +0 -157
  374. data/lib/arel/visitors/oracle.rb +0 -159
  375. data/lib/arel/visitors/oracle12.rb +0 -66
  376. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "active_record/connection_adapters/abstract_adapter"
4
4
  require "active_record/connection_adapters/statement_pool"
5
+ require "active_record/connection_adapters/sqlite3/column"
5
6
  require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
6
7
  require "active_record/connection_adapters/sqlite3/quoting"
7
8
  require "active_record/connection_adapters/sqlite3/database_statements"
@@ -10,57 +11,58 @@ require "active_record/connection_adapters/sqlite3/schema_definitions"
10
11
  require "active_record/connection_adapters/sqlite3/schema_dumper"
11
12
  require "active_record/connection_adapters/sqlite3/schema_statements"
12
13
 
13
- gem "sqlite3", "~> 1.4"
14
+ gem "sqlite3", ">= 1.4"
14
15
  require "sqlite3"
15
16
 
16
17
  module ActiveRecord
17
- module ConnectionHandling # :nodoc:
18
- def sqlite3_connection(config)
19
- config = config.symbolize_keys
20
-
21
- # Require database.
22
- unless config[:database]
23
- raise ArgumentError, "No database file specified. Missing argument: database"
24
- end
25
-
26
- # Allow database path relative to Rails.root, but only if the database
27
- # path is not the special path that tells sqlite to build a database only
28
- # in memory.
29
- if ":memory:" != config[:database]
30
- config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
31
- dirname = File.dirname(config[:database])
32
- Dir.mkdir(dirname) unless File.directory?(dirname)
33
- end
34
-
35
- db = SQLite3::Database.new(
36
- config[:database].to_s,
37
- config.merge(results_as_hash: true)
38
- )
39
-
40
- ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
41
- rescue Errno::ENOENT => error
42
- if error.message.include?("No such file or directory")
43
- raise ActiveRecord::NoDatabaseError
44
- else
45
- raise
46
- end
47
- end
48
- end
49
-
50
- module ConnectionAdapters #:nodoc:
51
- # The SQLite3 adapter works SQLite 3.6.16 or newer
52
- # with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
18
+ module ConnectionAdapters # :nodoc:
19
+ # = Active Record SQLite3 Adapter
53
20
  #
54
- # Options:
21
+ # The SQLite3 adapter works with the sqlite3-ruby drivers
22
+ # (available as gem from https://rubygems.org/gems/sqlite3).
23
+ #
24
+ # ==== Options
55
25
  #
56
26
  # * <tt>:database</tt> - Path to the database file.
57
27
  class SQLite3Adapter < AbstractAdapter
58
28
  ADAPTER_NAME = "SQLite"
59
29
 
30
+ class << self
31
+ def new_client(config)
32
+ ::SQLite3::Database.new(config[:database].to_s, config)
33
+ rescue Errno::ENOENT => error
34
+ if error.message.include?("No such file or directory")
35
+ raise ActiveRecord::NoDatabaseError
36
+ else
37
+ raise
38
+ end
39
+ end
40
+
41
+ def dbconsole(config, options = {})
42
+ args = []
43
+
44
+ args << "-#{options[:mode]}" if options[:mode]
45
+ args << "-header" if options[:header]
46
+ args << File.expand_path(config.database, Rails.respond_to?(:root) ? Rails.root : nil)
47
+
48
+ find_cmd_and_exec("sqlite3", *args)
49
+ end
50
+ end
51
+
60
52
  include SQLite3::Quoting
61
53
  include SQLite3::SchemaStatements
62
54
  include SQLite3::DatabaseStatements
63
55
 
56
+ ##
57
+ # :singleton-method:
58
+ # Configure the SQLite3Adapter to be used in a strict strings mode.
59
+ # This will disable double-quoted string literals, because otherwise typos can silently go unnoticed.
60
+ # For example, it is possible to create an index for a non existing column.
61
+ # If you wish to enable this mode you can add the following line to your application.rb file:
62
+ #
63
+ # config.active_record.sqlite3_adapter_strict_strings_by_default = true
64
+ class_attribute :strict_strings_by_default, default: false
65
+
64
66
  NATIVE_DATABASE_TYPES = {
65
67
  primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
66
68
  string: { name: "varchar" },
@@ -76,36 +78,54 @@ module ActiveRecord
76
78
  json: { name: "json" },
77
79
  }
78
80
 
79
- def self.represent_boolean_as_integer=(value) # :nodoc:
80
- if value == false
81
- raise "`.represent_boolean_as_integer=` is now always true, so make sure your application can work with it and remove this settings."
82
- end
83
-
84
- ActiveSupport::Deprecation.warn(
85
- "`.represent_boolean_as_integer=` is now always true, so setting this is deprecated and will be removed in Rails 6.1."
86
- )
87
- end
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
+ }
88
89
 
89
90
  class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
91
+ alias reset clear
92
+
90
93
  private
91
94
  def dealloc(stmt)
92
95
  stmt.close unless stmt.closed?
93
96
  end
94
97
  end
95
98
 
96
- def initialize(connection, logger, connection_options, config)
97
- super(connection, logger, config)
98
- configure_connection
99
- end
99
+ def initialize(...)
100
+ super
100
101
 
101
- def self.database_exists?(config)
102
- config = config.symbolize_keys
103
- if config[:database] == ":memory:"
104
- return true
102
+ @memory_database = false
103
+ case @config[:database].to_s
104
+ when ""
105
+ raise ArgumentError, "No database file specified. Missing argument: database"
106
+ when ":memory:"
107
+ @memory_database = true
108
+ when /\Afile:/
105
109
  else
106
- database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
107
- File.exist?(database_file)
110
+ # Otherwise we have a path relative to Rails.root
111
+ @config[:database] = File.expand_path(@config[:database], Rails.root) if defined?(Rails.root)
112
+ dirname = File.dirname(@config[:database])
113
+ unless File.directory?(dirname)
114
+ begin
115
+ FileUtils.mkdir_p(dirname)
116
+ rescue SystemCallError
117
+ raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool)
118
+ end
119
+ end
108
120
  end
121
+
122
+ @config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
123
+ @connection_parameters = @config.merge(database: @config[:database].to_s, results_as_hash: true)
124
+ @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
125
+ end
126
+
127
+ def database_exists?
128
+ @config[:database] == ":memory:" || File.exist?(@config[:database].to_s)
109
129
  end
110
130
 
111
131
  def supports_ddl_transactions?
@@ -116,6 +136,10 @@ module ActiveRecord
116
136
  true
117
137
  end
118
138
 
139
+ def supports_transaction_isolation?
140
+ true
141
+ end
142
+
119
143
  def supports_partial_index?
120
144
  true
121
145
  end
@@ -132,6 +156,10 @@ module ActiveRecord
132
156
  true
133
157
  end
134
158
 
159
+ def supports_check_constraints?
160
+ true
161
+ end
162
+
135
163
  def supports_views?
136
164
  true
137
165
  end
@@ -144,6 +172,14 @@ module ActiveRecord
144
172
  true
145
173
  end
146
174
 
175
+ def supports_common_table_expressions?
176
+ database_version >= "3.8.3"
177
+ end
178
+
179
+ def supports_insert_returning?
180
+ database_version >= "3.35.0"
181
+ end
182
+
147
183
  def supports_insert_on_conflict?
148
184
  database_version >= "3.24.0"
149
185
  end
@@ -151,40 +187,47 @@ module ActiveRecord
151
187
  alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
152
188
  alias supports_insert_conflict_target? supports_insert_on_conflict?
153
189
 
154
- def active?
155
- !@connection.closed?
190
+ def supports_concurrent_connections?
191
+ !@memory_database
156
192
  end
157
193
 
158
- def reconnect!
159
- super
160
- connect if @connection.closed?
194
+ def supports_virtual_columns?
195
+ database_version >= "3.31.0"
196
+ end
197
+
198
+ def connected?
199
+ !(@raw_connection.nil? || @raw_connection.closed?)
161
200
  end
162
201
 
202
+ def active?
203
+ if connected?
204
+ verified!
205
+ true
206
+ end
207
+ end
208
+
209
+ alias :reset! :reconnect!
210
+
163
211
  # Disconnects from the database if already connected. Otherwise, this
164
212
  # method does nothing.
165
213
  def disconnect!
166
214
  super
167
- @connection.close rescue nil
215
+
216
+ @raw_connection&.close rescue nil
217
+ @raw_connection = nil
168
218
  end
169
219
 
170
220
  def supports_index_sort_order?
171
221
  true
172
222
  end
173
223
 
174
- # Returns 62. SQLite supports index names up to 64
175
- # characters. The rest is used by Rails internally to perform
176
- # temporary rename operations
177
- def allowed_index_name_length
178
- index_name_length - 2
179
- end
180
-
181
- def native_database_types #:nodoc:
224
+ def native_database_types # :nodoc:
182
225
  NATIVE_DATABASE_TYPES
183
226
  end
184
227
 
185
- # Returns the current database encoding format as a string, eg: 'UTF-8'
228
+ # Returns the current database encoding format as a string, e.g. 'UTF-8'
186
229
  def encoding
187
- @connection.encoding.to_s
230
+ any_raw_connection.encoding.to_s
188
231
  end
189
232
 
190
233
  def supports_explain?
@@ -195,6 +238,10 @@ module ActiveRecord
195
238
  true
196
239
  end
197
240
 
241
+ def supports_deferrable_constraints?
242
+ true
243
+ end
244
+
198
245
  # REFERENTIAL INTEGRITY ====================================
199
246
 
200
247
  def disable_referential_integrity # :nodoc:
@@ -211,12 +258,14 @@ module ActiveRecord
211
258
  end
212
259
  end
213
260
 
214
- #--
215
- # DATABASE STATEMENTS ======================================
216
- #++
217
- def explain(arel, binds = [])
218
- sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
219
- SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
261
+ def check_all_foreign_keys_valid! # :nodoc:
262
+ sql = "PRAGMA foreign_key_check"
263
+ result = execute(sql)
264
+
265
+ unless result.blank?
266
+ tables = result.map { |row| row["table"] }
267
+ raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql, connection_pool: @pool)
268
+ end
220
269
  end
221
270
 
222
271
  # SCHEMA STATEMENTS ========================================
@@ -226,8 +275,11 @@ module ActiveRecord
226
275
  pks.sort_by { |f| f["pk"] }.map { |f| f["name"] }
227
276
  end
228
277
 
229
- def remove_index(table_name, options = {}) #:nodoc:
230
- index_name = index_name_for_remove(table_name, options)
278
+ def remove_index(table_name, column_name = nil, **options) # :nodoc:
279
+ return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
280
+
281
+ index_name = index_name_for_remove(table_name, column_name, options)
282
+
231
283
  exec_query "DROP INDEX #{quote_column_name(index_name)}"
232
284
  end
233
285
 
@@ -235,31 +287,43 @@ module ActiveRecord
235
287
  #
236
288
  # Example:
237
289
  # rename_table('octopuses', 'octopi')
238
- def rename_table(table_name, new_name)
290
+ def rename_table(table_name, new_name, **options)
291
+ validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
292
+ schema_cache.clear_data_source_cache!(table_name.to_s)
293
+ schema_cache.clear_data_source_cache!(new_name.to_s)
239
294
  exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
240
- rename_table_indexes(table_name, new_name)
295
+ rename_table_indexes(table_name, new_name, **options)
241
296
  end
242
297
 
243
- def add_column(table_name, column_name, type, options = {}) #:nodoc:
298
+ def add_column(table_name, column_name, type, **options) # :nodoc:
299
+ type = type.to_sym
244
300
  if invalid_alter_table_type?(type, options)
245
301
  alter_table(table_name) do |definition|
246
- definition.column(column_name, type, options)
302
+ definition.column(column_name, type, **options)
247
303
  end
248
304
  else
249
305
  super
250
306
  end
251
307
  end
252
308
 
253
- def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
309
+ def remove_column(table_name, column_name, type = nil, **options) # :nodoc:
254
310
  alter_table(table_name) do |definition|
255
311
  definition.remove_column column_name
256
- definition.foreign_keys.delete_if do |_, fk_options|
257
- fk_options[:column] == column_name.to_s
312
+ definition.foreign_keys.delete_if { |fk| fk.column == column_name.to_s }
313
+ end
314
+ end
315
+
316
+ def remove_columns(table_name, *column_names, type: nil, **options) # :nodoc:
317
+ alter_table(table_name) do |definition|
318
+ column_names.each do |column_name|
319
+ definition.remove_column column_name
258
320
  end
321
+ column_names = column_names.map(&:to_s)
322
+ definition.foreign_keys.delete_if { |fk| column_names.include?(fk.column) }
259
323
  end
260
324
  end
261
325
 
262
- def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
326
+ def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
263
327
  default = extract_new_default_value(default_or_changes)
264
328
 
265
329
  alter_table(table_name) do |definition|
@@ -267,49 +331,81 @@ module ActiveRecord
267
331
  end
268
332
  end
269
333
 
270
- def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
334
+ def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
335
+ validate_change_column_null_argument!(null)
336
+
271
337
  unless null || default.nil?
272
- exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
338
+ internal_exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
273
339
  end
274
340
  alter_table(table_name) do |definition|
275
341
  definition[column_name].null = null
276
342
  end
277
343
  end
278
344
 
279
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
345
+ def change_column(table_name, column_name, type, **options) # :nodoc:
280
346
  alter_table(table_name) do |definition|
281
- definition[column_name].instance_eval do
282
- self.type = type
283
- self.limit = options[:limit] if options.include?(:limit)
284
- self.default = options[:default] if options.include?(:default)
285
- self.null = options[:null] if options.include?(:null)
286
- self.precision = options[:precision] if options.include?(:precision)
287
- self.scale = options[:scale] if options.include?(:scale)
288
- self.collation = options[:collation] if options.include?(:collation)
289
- end
347
+ definition.change_column(column_name, type, **options)
290
348
  end
291
349
  end
292
350
 
293
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
351
+ def rename_column(table_name, column_name, new_column_name) # :nodoc:
294
352
  column = column_for(table_name, column_name)
295
353
  alter_table(table_name, rename: { column.name => new_column_name.to_s })
296
354
  rename_column_indexes(table_name, column.name, new_column_name)
297
355
  end
298
356
 
357
+ def add_timestamps(table_name, **options)
358
+ options[:null] = false if options[:null].nil?
359
+
360
+ if !options.key?(:precision)
361
+ options[:precision] = 6
362
+ end
363
+
364
+ alter_table(table_name) do |definition|
365
+ definition.column :created_at, :datetime, **options
366
+ definition.column :updated_at, :datetime, **options
367
+ end
368
+ end
369
+
299
370
  def add_reference(table_name, ref_name, **options) # :nodoc:
300
371
  super(table_name, ref_name, type: :integer, **options)
301
372
  end
302
373
  alias :add_belongs_to :add_reference
303
374
 
375
+ FK_REGEX = /.*FOREIGN KEY\s+\("([^"]+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
376
+ DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
304
377
  def foreign_keys(table_name)
305
- fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
306
- fk_info.map do |row|
378
+ # SQLite returns 1 row for each column of composite foreign keys.
379
+ fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
380
+ # Deferred or immediate foreign keys can only be seen in the CREATE TABLE sql
381
+ fk_defs = table_structure_sql(table_name)
382
+ .select do |column_string|
383
+ column_string.start_with?("CONSTRAINT") &&
384
+ column_string.include?("FOREIGN KEY")
385
+ end
386
+ .to_h do |fk_string|
387
+ _, from, table, to = fk_string.match(FK_REGEX).to_a
388
+ _, mode = fk_string.match(DEFERRABLE_REGEX).to_a
389
+ deferred = mode&.downcase&.to_sym || false
390
+ [[table, from, to], deferred]
391
+ end
392
+
393
+ grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
394
+ grouped_fk.map do |group|
395
+ row = group.first
307
396
  options = {
308
- column: row["from"],
309
- primary_key: row["to"],
310
397
  on_delete: extract_foreign_key_action(row["on_delete"]),
311
- on_update: extract_foreign_key_action(row["on_update"])
398
+ on_update: extract_foreign_key_action(row["on_update"]),
399
+ deferrable: fk_defs[[row["table"], row["from"], row["to"]]]
312
400
  }
401
+
402
+ if group.one?
403
+ options[:column] = row["from"]
404
+ options[:primary_key] = row["to"]
405
+ else
406
+ options[:column] = group.map { |row| row["from"] }
407
+ options[:primary_key] = group.map { |row| row["to"] }
408
+ end
313
409
  ForeignKeyDefinition.new(table_name, row["table"], options)
314
410
  end
315
411
  end
@@ -321,14 +417,28 @@ module ActiveRecord
321
417
  sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
322
418
  elsif insert.update_duplicates?
323
419
  sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
324
- sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
420
+ if insert.raw_update_sql?
421
+ sql << insert.raw_update_sql
422
+ else
423
+ sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" }
424
+ sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
425
+ end
325
426
  end
326
427
 
428
+ sql << " RETURNING #{insert.returning}" if insert.returning
327
429
  sql
328
430
  end
329
431
 
432
+ def shared_cache? # :nodoc:
433
+ @config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
434
+ end
435
+
436
+ def use_insert_returning?
437
+ @use_insert_returning
438
+ end
439
+
330
440
  def get_database_version # :nodoc:
331
- SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
441
+ SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
332
442
  end
333
443
 
334
444
  def check_version # :nodoc:
@@ -337,6 +447,28 @@ module ActiveRecord
337
447
  end
338
448
  end
339
449
 
450
+ class SQLite3Integer < Type::Integer # :nodoc:
451
+ private
452
+ def _limit
453
+ # INTEGER storage class can be stored 8 bytes value.
454
+ # See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
455
+ limit || 8
456
+ end
457
+ end
458
+
459
+ ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
460
+
461
+ class << self
462
+ private
463
+ def initialize_type_map(m)
464
+ super
465
+ register_class_with_limit m, %r(int)i, SQLite3Integer
466
+ end
467
+ end
468
+
469
+ TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
470
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
471
+
340
472
  private
341
473
  # See https://www.sqlite.org/limits.html,
342
474
  # the default value is 999 when not configured.
@@ -344,25 +476,58 @@ module ActiveRecord
344
476
  999
345
477
  end
346
478
 
347
- def initialize_type_map(m = type_map)
348
- super
349
- register_class_with_limit m, %r(int)i, SQLite3Integer
350
- end
351
-
352
479
  def table_structure(table_name)
353
- structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
354
- raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
480
+ structure = table_info(table_name)
481
+ raise ActiveRecord::StatementInvalid.new("Could not find table '#{table_name}'", connection_pool: @pool) if structure.empty?
355
482
  table_structure_with_collation(table_name, structure)
356
483
  end
357
484
  alias column_definitions table_structure
358
485
 
486
+ def extract_value_from_default(default)
487
+ case default
488
+ when /^null$/i
489
+ nil
490
+ # Quoted types
491
+ when /^'([^|]*)'$/m
492
+ $1.gsub("''", "'")
493
+ # Quoted types
494
+ when /^"([^|]*)"$/m
495
+ $1.gsub('""', '"')
496
+ # Numeric types
497
+ when /\A-?\d+(\.\d*)?\z/
498
+ $&
499
+ # Binary columns
500
+ when /x'(.*)'/
501
+ [ $1 ].pack("H*")
502
+ else
503
+ # Anything else is blank or some function
504
+ # and we can't know the value of that, so return nil.
505
+ nil
506
+ end
507
+ end
508
+
509
+ def extract_default_function(default_value, default)
510
+ default if has_default_function?(default_value, default)
511
+ end
512
+
513
+ def has_default_function?(default_value, default)
514
+ !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP|\|\|}.match?(default)
515
+ end
516
+
359
517
  # See: https://www.sqlite.org/lang_altertable.html
360
518
  # SQLite has an additional restriction on the ALTER TABLE statement
361
519
  def invalid_alter_table_type?(type, options)
362
- type.to_sym == :primary_key || options[:primary_key]
520
+ type == :primary_key || options[:primary_key] ||
521
+ options[:null] == false && options[:default].nil? ||
522
+ (type == :virtual && options[:stored])
363
523
  end
364
524
 
365
- def alter_table(table_name, foreign_keys = foreign_keys(table_name), **options)
525
+ def alter_table(
526
+ table_name,
527
+ foreign_keys = foreign_keys(table_name),
528
+ check_constraints = check_constraints(table_name),
529
+ **options
530
+ )
366
531
  altered_table_name = "a#{table_name}"
367
532
 
368
533
  caller = lambda do |definition|
@@ -372,14 +537,18 @@ module ActiveRecord
372
537
  fk.options[:column] = column
373
538
  end
374
539
  to_table = strip_table_name_prefix_and_suffix(fk.to_table)
375
- definition.foreign_key(to_table, fk.options)
540
+ definition.foreign_key(to_table, **fk.options)
541
+ end
542
+
543
+ check_constraints.each do |chk|
544
+ definition.check_constraint(chk.expression, **chk.options)
376
545
  end
377
546
 
378
547
  yield definition if block_given?
379
548
  end
380
549
 
381
- transaction do
382
- disable_referential_integrity do
550
+ disable_referential_integrity do
551
+ transaction do
383
552
  move_table(table_name, altered_table_name, options.merge(temporary: true))
384
553
  move_table(altered_table_name, table_name, &caller)
385
554
  end
@@ -394,30 +563,52 @@ module ActiveRecord
394
563
  def copy_table(from, to, options = {})
395
564
  from_primary_key = primary_key(from)
396
565
  options[:id] = false
397
- create_table(to, options) do |definition|
566
+ create_table(to, **options) do |definition|
398
567
  @definition = definition
399
568
  if from_primary_key.is_a?(Array)
400
569
  @definition.primary_keys from_primary_key
401
570
  end
571
+
402
572
  columns(from).each do |column|
403
573
  column_name = options[:rename] ?
404
574
  (options[:rename][column.name] ||
405
575
  options[:rename][column.name.to_sym] ||
406
576
  column.name) : column.name
407
577
 
408
- @definition.column(column_name, column.type,
409
- limit: column.limit, default: column.default,
410
- precision: column.precision, scale: column.scale,
411
- null: column.null, collation: column.collation,
578
+ column_options = {
579
+ limit: column.limit,
580
+ precision: column.precision,
581
+ scale: column.scale,
582
+ null: column.null,
583
+ collation: column.collation,
412
584
  primary_key: column_name == from_primary_key
413
- )
585
+ }
586
+
587
+ if column.virtual?
588
+ column_options[:as] = column.default_function
589
+ column_options[:stored] = column.virtual_stored?
590
+ column_options[:type] = column.type
591
+ elsif column.has_default?
592
+ type = lookup_cast_type_from_column(column)
593
+ default = type.deserialize(column.default)
594
+ default = -> { column.default_function } if default.nil?
595
+
596
+ unless column.auto_increment?
597
+ column_options[:default] = default
598
+ end
599
+ end
600
+
601
+ column_type = column.virtual? ? :virtual : (column.bigint? ? :bigint : column.type)
602
+ @definition.column(column_name, column_type, **column_options)
414
603
  end
415
604
 
416
605
  yield @definition if block_given?
417
606
  end
418
607
  copy_table_indexes(from, to, options[:rename] || {})
608
+
609
+ columns_to_copy = @definition.columns.reject { |col| col.options.key?(:as) }.map(&:name)
419
610
  copy_table_contents(from, to,
420
- @definition.columns.map(&:name),
611
+ columns_to_copy,
421
612
  options[:rename] || {})
422
613
  end
423
614
 
@@ -440,10 +631,11 @@ module ActiveRecord
440
631
 
441
632
  unless columns.empty?
442
633
  # index name can't be the same
443
- opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
444
- opts[:unique] = true if index.unique
445
- opts[:where] = index.where if index.where
446
- add_index(to, columns, opts)
634
+ options = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
635
+ options[:unique] = true if index.unique
636
+ options[:where] = index.where if index.where
637
+ options[:order] = index.orders if index.orders
638
+ add_index(to, columns, **options)
447
639
  end
448
640
  end
449
641
  end
@@ -457,61 +649,63 @@ module ActiveRecord
457
649
  quoted_columns = columns.map { |col| quote_column_name(col) } * ","
458
650
  quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
459
651
 
460
- exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
652
+ internal_exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
461
653
  SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
462
654
  end
463
655
 
464
656
  def translate_exception(exception, message:, sql:, binds:)
465
- case exception.message
466
657
  # SQLite 3.8.2 returns a newly formatted error message:
467
658
  # UNIQUE constraint failed: *table_name*.*column_name*
468
659
  # Older versions of SQLite return:
469
660
  # column *column_name* is not unique
470
- when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
471
- RecordNotUnique.new(message, sql: sql, binds: binds)
472
- when /.* may not be NULL/, /NOT NULL constraint failed: .*/
473
- NotNullViolation.new(message, sql: sql, binds: binds)
474
- when /FOREIGN KEY constraint failed/i
475
- InvalidForeignKey.new(message, sql: sql, binds: binds)
661
+ if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
662
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
663
+ elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
664
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
665
+ elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
666
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
667
+ elsif exception.message.match?(/called on a closed database/i)
668
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
476
669
  else
477
670
  super
478
671
  end
479
672
  end
480
673
 
481
- COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
674
+ COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i
675
+ PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*"(\w+)".+PRIMARY KEY AUTOINCREMENT/i
676
+ GENERATED_ALWAYS_AS_REGEX = /.*"(\w+)".+GENERATED ALWAYS AS \((.+)\) (?:STORED|VIRTUAL)/i
482
677
 
483
678
  def table_structure_with_collation(table_name, basic_structure)
484
679
  collation_hash = {}
485
- sql = <<~SQL
486
- SELECT sql FROM
487
- (SELECT * FROM sqlite_master UNION ALL
488
- SELECT * FROM sqlite_temp_master)
489
- WHERE type = 'table' AND name = #{quote(table_name)}
490
- SQL
491
-
492
- # Result will have following sample string
493
- # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
494
- # "password_digest" varchar COLLATE "NOCASE");
495
- result = exec_query(sql, "SCHEMA").first
680
+ auto_increments = {}
681
+ generated_columns = {}
496
682
 
497
- if result
498
- # Splitting with left parentheses and discarding the first part will return all
499
- # columns separated with comma(,).
500
- columns_string = result["sql"].split("(", 2).last
683
+ column_strings = table_structure_sql(table_name, basic_structure.map { |column| column["name"] })
501
684
 
502
- columns_string.split(",").each do |column_string|
685
+ if column_strings.any?
686
+ column_strings.each do |column_string|
503
687
  # This regex will match the column name and collation type and will save
504
688
  # the value in $1 and $2 respectively.
505
689
  collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
690
+ auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
691
+ generated_columns[$1] = $2 if GENERATED_ALWAYS_AS_REGEX =~ column_string
506
692
  end
507
693
 
508
- basic_structure.map! do |column|
694
+ basic_structure.map do |column|
509
695
  column_name = column["name"]
510
696
 
511
697
  if collation_hash.has_key? column_name
512
698
  column["collation"] = collation_hash[column_name]
513
699
  end
514
700
 
701
+ if auto_increments.has_key?(column_name)
702
+ column["auto_increment"] = true
703
+ end
704
+
705
+ if generated_columns.has_key?(column_name)
706
+ column["dflt_value"] = generated_columns[column_name]
707
+ end
708
+
515
709
  column
516
710
  end
517
711
  else
@@ -519,6 +713,50 @@ module ActiveRecord
519
713
  end
520
714
  end
521
715
 
716
+ UNQUOTED_OPEN_PARENS_REGEX = /\((?![^'"]*['"][^'"]*$)/
717
+ FINAL_CLOSE_PARENS_REGEX = /\);*\z/
718
+
719
+ def table_structure_sql(table_name, column_names = nil)
720
+ unless column_names
721
+ column_info = table_info(table_name)
722
+ column_names = column_info.map { |column| column["name"] }
723
+ end
724
+
725
+ sql = <<~SQL
726
+ SELECT sql FROM
727
+ (SELECT * FROM sqlite_master UNION ALL
728
+ SELECT * FROM sqlite_temp_master)
729
+ WHERE type = 'table' AND name = #{quote(table_name)}
730
+ SQL
731
+
732
+ # Result will have following sample string
733
+ # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
734
+ # "password_digest" varchar COLLATE "NOCASE",
735
+ # "o_id" integer,
736
+ # CONSTRAINT "fk_rails_78146ddd2e" FOREIGN KEY ("o_id") REFERENCES "os" ("id"));
737
+ result = query_value(sql, "SCHEMA")
738
+
739
+ return [] unless result
740
+
741
+ # Splitting with left parentheses and discarding the first part will return all
742
+ # columns separated with comma(,).
743
+ result.partition(UNQUOTED_OPEN_PARENS_REGEX)
744
+ .last
745
+ .sub(FINAL_CLOSE_PARENS_REGEX, "")
746
+ # column definitions can have a comma in them, so split on commas followed
747
+ # by a space and a column name in quotes or followed by the keyword CONSTRAINT
748
+ .split(/,(?=\s(?:CONSTRAINT|"(?:#{Regexp.union(column_names).source})"))/i)
749
+ .map(&:strip)
750
+ end
751
+
752
+ def table_info(table_name)
753
+ if supports_virtual_columns?
754
+ internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
755
+ else
756
+ internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
757
+ end
758
+ end
759
+
522
760
  def arel_visitor
523
761
  Arel::Visitors::SQLite.new(self)
524
762
  end
@@ -528,29 +766,42 @@ module ActiveRecord
528
766
  end
529
767
 
530
768
  def connect
531
- @connection = ::SQLite3::Database.new(
532
- @config[:database].to_s,
533
- @config.merge(results_as_hash: true)
534
- )
535
- configure_connection
769
+ @raw_connection = self.class.new_client(@connection_parameters)
770
+ rescue ConnectionNotEstablished => ex
771
+ raise ex.set_pool(@pool)
772
+ end
773
+
774
+ def reconnect
775
+ if active?
776
+ @raw_connection.rollback rescue nil
777
+ else
778
+ connect
779
+ end
536
780
  end
537
781
 
538
782
  def configure_connection
539
- @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
783
+ if @config[:timeout] && @config[:retries]
784
+ raise ArgumentError, "Cannot specify both timeout and retries arguments"
785
+ elsif @config[:timeout]
786
+ @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout]))
787
+ elsif @config[:retries]
788
+ retries = self.class.type_cast_config_to_integer(@config[:retries])
789
+ raw_connection.busy_handler do |count|
790
+ count <= retries
791
+ end
792
+ end
540
793
 
541
- execute("PRAGMA foreign_keys = ON", "SCHEMA")
542
- end
794
+ super
543
795
 
544
- class SQLite3Integer < Type::Integer # :nodoc:
545
- private
546
- def _limit
547
- # INTEGER storage class can be stored 8 bytes value.
548
- # See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
549
- limit || 8
796
+ pragmas = @config.fetch(:pragmas, {}).stringify_keys
797
+ DEFAULT_PRAGMAS.merge(pragmas).each do |pragma, value|
798
+ if ::SQLite3::Pragmas.method_defined?("#{pragma}=")
799
+ @raw_connection.public_send("#{pragma}=", value)
800
+ else
801
+ warn "Unknown SQLite pragma: #{pragma}"
550
802
  end
803
+ end
551
804
  end
552
-
553
- ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
554
805
  end
555
806
  ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
556
807
  end