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
@@ -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,44 +11,13 @@ 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] && !config[:database].to_s.start_with?("file:")
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:
18
+ module ConnectionAdapters # :nodoc:
19
+ # = Active Record SQLite3 Adapter
20
+ #
51
21
  # The SQLite3 adapter works with the sqlite3-ruby drivers
52
22
  # (available as gem from https://rubygems.org/gems/sqlite3).
53
23
  #
@@ -57,10 +27,42 @@ module ActiveRecord
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,26 +78,54 @@ module ActiveRecord
76
78
  json: { name: "json" },
77
79
  }
78
80
 
81
+ DEFAULT_PRAGMAS = {
82
+ "foreign_keys" => true,
83
+ "journal_mode" => :wal,
84
+ "synchronous" => :normal,
85
+ "mmap_size" => 134217728, # 128 megabytes
86
+ "journal_size_limit" => 67108864, # 64 megabytes
87
+ "cache_size" => 2000
88
+ }
89
+
79
90
  class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
91
+ alias reset clear
92
+
80
93
  private
81
94
  def dealloc(stmt)
82
95
  stmt.close unless stmt.closed?
83
96
  end
84
97
  end
85
98
 
86
- def initialize(connection, logger, connection_options, config)
87
- super(connection, logger, config)
88
- configure_connection
89
- end
99
+ def initialize(...)
100
+ super
90
101
 
91
- def self.database_exists?(config)
92
- config = config.symbolize_keys
93
- if config[:database] == ":memory:"
94
- 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:/
95
109
  else
96
- database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
97
- 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
98
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)
99
129
  end
100
130
 
101
131
  def supports_ddl_transactions?
@@ -146,6 +176,10 @@ module ActiveRecord
146
176
  database_version >= "3.8.3"
147
177
  end
148
178
 
179
+ def supports_insert_returning?
180
+ database_version >= "3.35.0"
181
+ end
182
+
149
183
  def supports_insert_on_conflict?
150
184
  database_version >= "3.24.0"
151
185
  end
@@ -153,33 +187,42 @@ module ActiveRecord
153
187
  alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
154
188
  alias supports_insert_conflict_target? supports_insert_on_conflict?
155
189
 
156
- def active?
157
- !@connection.closed?
190
+ def supports_concurrent_connections?
191
+ !@memory_database
158
192
  end
159
193
 
160
- def reconnect!
161
- super
162
- 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?)
163
200
  end
164
201
 
202
+ alias_method :active?, :connected?
203
+
204
+ alias :reset! :reconnect!
205
+
165
206
  # Disconnects from the database if already connected. Otherwise, this
166
207
  # method does nothing.
167
208
  def disconnect!
168
209
  super
169
- @connection.close rescue nil
210
+
211
+ @raw_connection&.close rescue nil
212
+ @raw_connection = nil
170
213
  end
171
214
 
172
215
  def supports_index_sort_order?
173
216
  true
174
217
  end
175
218
 
176
- def native_database_types #:nodoc:
219
+ def native_database_types # :nodoc:
177
220
  NATIVE_DATABASE_TYPES
178
221
  end
179
222
 
180
- # Returns the current database encoding format as a string, eg: 'UTF-8'
223
+ # Returns the current database encoding format as a string, e.g. 'UTF-8'
181
224
  def encoding
182
- @connection.encoding.to_s
225
+ any_raw_connection.encoding.to_s
183
226
  end
184
227
 
185
228
  def supports_explain?
@@ -190,6 +233,10 @@ module ActiveRecord
190
233
  true
191
234
  end
192
235
 
236
+ def supports_deferrable_constraints?
237
+ true
238
+ end
239
+
193
240
  # REFERENTIAL INTEGRITY ====================================
194
241
 
195
242
  def disable_referential_integrity # :nodoc:
@@ -206,6 +253,16 @@ module ActiveRecord
206
253
  end
207
254
  end
208
255
 
256
+ def check_all_foreign_keys_valid! # :nodoc:
257
+ sql = "PRAGMA foreign_key_check"
258
+ result = execute(sql)
259
+
260
+ unless result.blank?
261
+ tables = result.map { |row| row["table"] }
262
+ raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql, connection_pool: @pool)
263
+ end
264
+ end
265
+
209
266
  # SCHEMA STATEMENTS ========================================
210
267
 
211
268
  def primary_keys(table_name) # :nodoc:
@@ -225,14 +282,16 @@ module ActiveRecord
225
282
  #
226
283
  # Example:
227
284
  # rename_table('octopuses', 'octopi')
228
- def rename_table(table_name, new_name)
285
+ def rename_table(table_name, new_name, **options)
286
+ validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
229
287
  schema_cache.clear_data_source_cache!(table_name.to_s)
230
288
  schema_cache.clear_data_source_cache!(new_name.to_s)
231
289
  exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
232
- rename_table_indexes(table_name, new_name)
290
+ rename_table_indexes(table_name, new_name, **options)
233
291
  end
234
292
 
235
- def add_column(table_name, column_name, type, **options) #:nodoc:
293
+ def add_column(table_name, column_name, type, **options) # :nodoc:
294
+ type = type.to_sym
236
295
  if invalid_alter_table_type?(type, options)
237
296
  alter_table(table_name) do |definition|
238
297
  definition.column(column_name, type, **options)
@@ -242,16 +301,24 @@ module ActiveRecord
242
301
  end
243
302
  end
244
303
 
245
- def remove_column(table_name, column_name, type = nil, **options) #:nodoc:
304
+ def remove_column(table_name, column_name, type = nil, **options) # :nodoc:
246
305
  alter_table(table_name) do |definition|
247
306
  definition.remove_column column_name
248
- definition.foreign_keys.delete_if do |_, fk_options|
249
- fk_options[:column] == column_name.to_s
307
+ definition.foreign_keys.delete_if { |fk| fk.column == column_name.to_s }
308
+ end
309
+ end
310
+
311
+ def remove_columns(table_name, *column_names, type: nil, **options) # :nodoc:
312
+ alter_table(table_name) do |definition|
313
+ column_names.each do |column_name|
314
+ definition.remove_column column_name
250
315
  end
316
+ column_names = column_names.map(&:to_s)
317
+ definition.foreign_keys.delete_if { |fk| column_names.include?(fk.column) }
251
318
  end
252
319
  end
253
320
 
254
- def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
321
+ def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
255
322
  default = extract_new_default_value(default_or_changes)
256
323
 
257
324
  alter_table(table_name) do |definition|
@@ -259,44 +326,81 @@ module ActiveRecord
259
326
  end
260
327
  end
261
328
 
262
- def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
329
+ def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
330
+ validate_change_column_null_argument!(null)
331
+
263
332
  unless null || default.nil?
264
- exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
333
+ 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")
265
334
  end
266
335
  alter_table(table_name) do |definition|
267
336
  definition[column_name].null = null
268
337
  end
269
338
  end
270
339
 
271
- def change_column(table_name, column_name, type, **options) #:nodoc:
340
+ def change_column(table_name, column_name, type, **options) # :nodoc:
272
341
  alter_table(table_name) do |definition|
273
- definition[column_name].instance_eval do
274
- self.type = aliased_types(type.to_s, type)
275
- self.options.merge!(options)
276
- end
342
+ definition.change_column(column_name, type, **options)
277
343
  end
278
344
  end
279
345
 
280
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
346
+ def rename_column(table_name, column_name, new_column_name) # :nodoc:
281
347
  column = column_for(table_name, column_name)
282
348
  alter_table(table_name, rename: { column.name => new_column_name.to_s })
283
349
  rename_column_indexes(table_name, column.name, new_column_name)
284
350
  end
285
351
 
352
+ def add_timestamps(table_name, **options)
353
+ options[:null] = false if options[:null].nil?
354
+
355
+ if !options.key?(:precision)
356
+ options[:precision] = 6
357
+ end
358
+
359
+ alter_table(table_name) do |definition|
360
+ definition.column :created_at, :datetime, **options
361
+ definition.column :updated_at, :datetime, **options
362
+ end
363
+ end
364
+
286
365
  def add_reference(table_name, ref_name, **options) # :nodoc:
287
366
  super(table_name, ref_name, type: :integer, **options)
288
367
  end
289
368
  alias :add_belongs_to :add_reference
290
369
 
370
+ FK_REGEX = /.*FOREIGN KEY\s+\("(\w+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
371
+ DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
291
372
  def foreign_keys(table_name)
292
- fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
293
- fk_info.map do |row|
373
+ # SQLite returns 1 row for each column of composite foreign keys.
374
+ fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
375
+ # Deferred or immediate foreign keys can only be seen in the CREATE TABLE sql
376
+ fk_defs = table_structure_sql(table_name)
377
+ .select do |column_string|
378
+ column_string.start_with?("CONSTRAINT") &&
379
+ column_string.include?("FOREIGN KEY")
380
+ end
381
+ .to_h do |fk_string|
382
+ _, from, table, to = fk_string.match(FK_REGEX).to_a
383
+ _, mode = fk_string.match(DEFERRABLE_REGEX).to_a
384
+ deferred = mode&.downcase&.to_sym || false
385
+ [[table, from, to], deferred]
386
+ end
387
+
388
+ grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
389
+ grouped_fk.map do |group|
390
+ row = group.first
294
391
  options = {
295
- column: row["from"],
296
- primary_key: row["to"],
297
392
  on_delete: extract_foreign_key_action(row["on_delete"]),
298
- on_update: extract_foreign_key_action(row["on_update"])
393
+ on_update: extract_foreign_key_action(row["on_update"]),
394
+ deferrable: fk_defs[[row["table"], row["from"], row["to"]]]
299
395
  }
396
+
397
+ if group.one?
398
+ options[:column] = row["from"]
399
+ options[:primary_key] = row["to"]
400
+ else
401
+ options[:column] = group.map { |row| row["from"] }
402
+ options[:primary_key] = group.map { |row| row["to"] }
403
+ end
300
404
  ForeignKeyDefinition.new(table_name, row["table"], options)
301
405
  end
302
406
  end
@@ -308,10 +412,15 @@ module ActiveRecord
308
412
  sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
309
413
  elsif insert.update_duplicates?
310
414
  sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
311
- sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" }
312
- sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
415
+ if insert.raw_update_sql?
416
+ sql << insert.raw_update_sql
417
+ else
418
+ sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" }
419
+ sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
420
+ end
313
421
  end
314
422
 
423
+ sql << " RETURNING #{insert.returning}" if insert.returning
315
424
  sql
316
425
  end
317
426
 
@@ -319,8 +428,12 @@ module ActiveRecord
319
428
  @config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
320
429
  end
321
430
 
431
+ def use_insert_returning?
432
+ @use_insert_returning
433
+ end
434
+
322
435
  def get_database_version # :nodoc:
323
- SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
436
+ SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
324
437
  end
325
438
 
326
439
  def check_version # :nodoc:
@@ -329,6 +442,28 @@ module ActiveRecord
329
442
  end
330
443
  end
331
444
 
445
+ class SQLite3Integer < Type::Integer # :nodoc:
446
+ private
447
+ def _limit
448
+ # INTEGER storage class can be stored 8 bytes value.
449
+ # See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
450
+ limit || 8
451
+ end
452
+ end
453
+
454
+ ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
455
+
456
+ class << self
457
+ private
458
+ def initialize_type_map(m)
459
+ super
460
+ register_class_with_limit m, %r(int)i, SQLite3Integer
461
+ end
462
+ end
463
+
464
+ TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
465
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
466
+
332
467
  private
333
468
  # See https://www.sqlite.org/limits.html,
334
469
  # the default value is 999 when not configured.
@@ -336,23 +471,50 @@ module ActiveRecord
336
471
  999
337
472
  end
338
473
 
339
- def initialize_type_map(m = type_map)
340
- super
341
- register_class_with_limit m, %r(int)i, SQLite3Integer
342
- end
343
-
344
474
  def table_structure(table_name)
345
- structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
346
- raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
475
+ structure = table_info(table_name)
476
+ raise ActiveRecord::StatementInvalid.new("Could not find table '#{table_name}'", connection_pool: @pool) if structure.empty?
347
477
  table_structure_with_collation(table_name, structure)
348
478
  end
349
479
  alias column_definitions table_structure
350
480
 
481
+ def extract_value_from_default(default)
482
+ case default
483
+ when /^null$/i
484
+ nil
485
+ # Quoted types
486
+ when /^'([^|]*)'$/m
487
+ $1.gsub("''", "'")
488
+ # Quoted types
489
+ when /^"([^|]*)"$/m
490
+ $1.gsub('""', '"')
491
+ # Numeric types
492
+ when /\A-?\d+(\.\d*)?\z/
493
+ $&
494
+ # Binary columns
495
+ when /x'(.*)'/
496
+ [ $1 ].pack("H*")
497
+ else
498
+ # Anything else is blank or some function
499
+ # and we can't know the value of that, so return nil.
500
+ nil
501
+ end
502
+ end
503
+
504
+ def extract_default_function(default_value, default)
505
+ default if has_default_function?(default_value, default)
506
+ end
507
+
508
+ def has_default_function?(default_value, default)
509
+ !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP|\|\|}.match?(default)
510
+ end
511
+
351
512
  # See: https://www.sqlite.org/lang_altertable.html
352
513
  # SQLite has an additional restriction on the ALTER TABLE statement
353
514
  def invalid_alter_table_type?(type, options)
354
- type.to_sym == :primary_key || options[:primary_key] ||
355
- options[:null] == false && options[:default].nil?
515
+ type == :primary_key || options[:primary_key] ||
516
+ options[:null] == false && options[:default].nil? ||
517
+ (type == :virtual && options[:stored])
356
518
  end
357
519
 
358
520
  def alter_table(
@@ -408,19 +570,40 @@ module ActiveRecord
408
570
  options[:rename][column.name.to_sym] ||
409
571
  column.name) : column.name
410
572
 
411
- @definition.column(column_name, column.type,
412
- limit: column.limit, default: column.default,
413
- precision: column.precision, scale: column.scale,
414
- null: column.null, collation: column.collation,
573
+ column_options = {
574
+ limit: column.limit,
575
+ precision: column.precision,
576
+ scale: column.scale,
577
+ null: column.null,
578
+ collation: column.collation,
415
579
  primary_key: column_name == from_primary_key
416
- )
580
+ }
581
+
582
+ if column.virtual?
583
+ column_options[:as] = column.default_function
584
+ column_options[:stored] = column.virtual_stored?
585
+ column_options[:type] = column.type
586
+ elsif column.has_default?
587
+ type = lookup_cast_type_from_column(column)
588
+ default = type.deserialize(column.default)
589
+ default = -> { column.default_function } if default.nil?
590
+
591
+ unless column.auto_increment?
592
+ column_options[:default] = default
593
+ end
594
+ end
595
+
596
+ column_type = column.virtual? ? :virtual : (column.bigint? ? :bigint : column.type)
597
+ @definition.column(column_name, column_type, **column_options)
417
598
  end
418
599
 
419
600
  yield @definition if block_given?
420
601
  end
421
602
  copy_table_indexes(from, to, options[:rename] || {})
603
+
604
+ columns_to_copy = @definition.columns.reject { |col| col.options.key?(:as) }.map(&:name)
422
605
  copy_table_contents(from, to,
423
- @definition.columns.map(&:name),
606
+ columns_to_copy,
424
607
  options[:rename] || {})
425
608
  end
426
609
 
@@ -446,6 +629,7 @@ module ActiveRecord
446
629
  options = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
447
630
  options[:unique] = true if index.unique
448
631
  options[:where] = index.where if index.where
632
+ options[:order] = index.orders if index.orders
449
633
  add_index(to, columns, **options)
450
634
  end
451
635
  end
@@ -460,7 +644,7 @@ module ActiveRecord
460
644
  quoted_columns = columns.map { |col| quote_column_name(col) } * ","
461
645
  quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
462
646
 
463
- exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
647
+ internal_exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
464
648
  SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
465
649
  end
466
650
 
@@ -470,43 +654,36 @@ module ActiveRecord
470
654
  # Older versions of SQLite return:
471
655
  # column *column_name* is not unique
472
656
  if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
473
- RecordNotUnique.new(message, sql: sql, binds: binds)
657
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
474
658
  elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
475
- NotNullViolation.new(message, sql: sql, binds: binds)
659
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
476
660
  elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
477
- InvalidForeignKey.new(message, sql: sql, binds: binds)
661
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
478
662
  elsif exception.message.match?(/called on a closed database/i)
479
- ConnectionNotEstablished.new(exception)
663
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
480
664
  else
481
665
  super
482
666
  end
483
667
  end
484
668
 
485
- COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
669
+ COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i
670
+ PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*"(\w+)".+PRIMARY KEY AUTOINCREMENT/i
671
+ GENERATED_ALWAYS_AS_REGEX = /.*"(\w+)".+GENERATED ALWAYS AS \((.+)\) (?:STORED|VIRTUAL)/i
486
672
 
487
673
  def table_structure_with_collation(table_name, basic_structure)
488
674
  collation_hash = {}
489
- sql = <<~SQL
490
- SELECT sql FROM
491
- (SELECT * FROM sqlite_master UNION ALL
492
- SELECT * FROM sqlite_temp_master)
493
- WHERE type = 'table' AND name = #{quote(table_name)}
494
- SQL
495
-
496
- # Result will have following sample string
497
- # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
498
- # "password_digest" varchar COLLATE "NOCASE");
499
- result = query_value(sql, "SCHEMA")
675
+ auto_increments = {}
676
+ generated_columns = {}
500
677
 
501
- if result
502
- # Splitting with left parentheses and discarding the first part will return all
503
- # columns separated with comma(,).
504
- columns_string = result.split("(", 2).last
678
+ column_strings = table_structure_sql(table_name, basic_structure.map { |column| column["name"] })
505
679
 
506
- columns_string.split(",").each do |column_string|
680
+ if column_strings.any?
681
+ column_strings.each do |column_string|
507
682
  # This regex will match the column name and collation type and will save
508
683
  # the value in $1 and $2 respectively.
509
684
  collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
685
+ auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
686
+ generated_columns[$1] = $2 if GENERATED_ALWAYS_AS_REGEX =~ column_string
510
687
  end
511
688
 
512
689
  basic_structure.map do |column|
@@ -516,6 +693,14 @@ module ActiveRecord
516
693
  column["collation"] = collation_hash[column_name]
517
694
  end
518
695
 
696
+ if auto_increments.has_key?(column_name)
697
+ column["auto_increment"] = true
698
+ end
699
+
700
+ if generated_columns.has_key?(column_name)
701
+ column["dflt_value"] = generated_columns[column_name]
702
+ end
703
+
519
704
  column
520
705
  end
521
706
  else
@@ -523,6 +708,50 @@ module ActiveRecord
523
708
  end
524
709
  end
525
710
 
711
+ UNQUOTED_OPEN_PARENS_REGEX = /\((?![^'"]*['"][^'"]*$)/
712
+ FINAL_CLOSE_PARENS_REGEX = /\);*\z/
713
+
714
+ def table_structure_sql(table_name, column_names = nil)
715
+ unless column_names
716
+ column_info = table_info(table_name)
717
+ column_names = column_info.map { |column| column["name"] }
718
+ end
719
+
720
+ sql = <<~SQL
721
+ SELECT sql FROM
722
+ (SELECT * FROM sqlite_master UNION ALL
723
+ SELECT * FROM sqlite_temp_master)
724
+ WHERE type = 'table' AND name = #{quote(table_name)}
725
+ SQL
726
+
727
+ # Result will have following sample string
728
+ # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
729
+ # "password_digest" varchar COLLATE "NOCASE",
730
+ # "o_id" integer,
731
+ # CONSTRAINT "fk_rails_78146ddd2e" FOREIGN KEY ("o_id") REFERENCES "os" ("id"));
732
+ result = query_value(sql, "SCHEMA")
733
+
734
+ return [] unless result
735
+
736
+ # Splitting with left parentheses and discarding the first part will return all
737
+ # columns separated with comma(,).
738
+ result.partition(UNQUOTED_OPEN_PARENS_REGEX)
739
+ .last
740
+ .sub(FINAL_CLOSE_PARENS_REGEX, "")
741
+ # column definitions can have a comma in them, so split on commas followed
742
+ # by a space and a column name in quotes or followed by the keyword CONSTRAINT
743
+ .split(/,(?=\s(?:CONSTRAINT|"(?:#{Regexp.union(column_names).source})"))/i)
744
+ .map(&:strip)
745
+ end
746
+
747
+ def table_info(table_name)
748
+ if supports_virtual_columns?
749
+ internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
750
+ else
751
+ internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
752
+ end
753
+ end
754
+
526
755
  def arel_visitor
527
756
  Arel::Visitors::SQLite.new(self)
528
757
  end
@@ -532,29 +761,42 @@ module ActiveRecord
532
761
  end
533
762
 
534
763
  def connect
535
- @connection = ::SQLite3::Database.new(
536
- @config[:database].to_s,
537
- @config.merge(results_as_hash: true)
538
- )
539
- configure_connection
764
+ @raw_connection = self.class.new_client(@connection_parameters)
765
+ rescue ConnectionNotEstablished => ex
766
+ raise ex.set_pool(@pool)
767
+ end
768
+
769
+ def reconnect
770
+ if active?
771
+ @raw_connection.rollback rescue nil
772
+ else
773
+ connect
774
+ end
540
775
  end
541
776
 
542
777
  def configure_connection
543
- @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
778
+ if @config[:timeout] && @config[:retries]
779
+ raise ArgumentError, "Cannot specify both timeout and retries arguments"
780
+ elsif @config[:timeout]
781
+ @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout]))
782
+ elsif @config[:retries]
783
+ retries = self.class.type_cast_config_to_integer(@config[:retries])
784
+ raw_connection.busy_handler do |count|
785
+ count <= retries
786
+ end
787
+ end
544
788
 
545
- execute("PRAGMA foreign_keys = ON", "SCHEMA")
546
- end
789
+ super
547
790
 
548
- class SQLite3Integer < Type::Integer # :nodoc:
549
- private
550
- def _limit
551
- # INTEGER storage class can be stored 8 bytes value.
552
- # See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
553
- limit || 8
791
+ pragmas = @config.fetch(:pragmas, {}).stringify_keys
792
+ DEFAULT_PRAGMAS.merge(pragmas).each do |pragma, value|
793
+ if ::SQLite3::Pragmas.method_defined?("#{pragma}=")
794
+ @raw_connection.public_send("#{pragma}=", value)
795
+ else
796
+ warn "Unknown SQLite pragma: #{pragma}"
554
797
  end
798
+ end
555
799
  end
556
-
557
- ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
558
800
  end
559
801
  ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
560
802
  end