activerecord 5.2.8 → 7.0.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (364) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1393 -587
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +10 -9
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +19 -16
  9. data/lib/active_record/associations/association.rb +122 -47
  10. data/lib/active_record/associations/association_scope.rb +24 -24
  11. data/lib/active_record/associations/belongs_to_association.rb +67 -49
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -7
  13. data/lib/active_record/associations/builder/association.rb +52 -23
  14. data/lib/active_record/associations/builder/belongs_to.rb +44 -61
  15. data/lib/active_record/associations/builder/collection_association.rb +17 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  17. data/lib/active_record/associations/builder/has_many.rb +10 -3
  18. data/lib/active_record/associations/builder/has_one.rb +35 -3
  19. data/lib/active_record/associations/builder/singular_association.rb +5 -3
  20. data/lib/active_record/associations/collection_association.rb +59 -50
  21. data/lib/active_record/associations/collection_proxy.rb +32 -23
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/foreign_association.rb +20 -0
  24. data/lib/active_record/associations/has_many_association.rb +27 -14
  25. data/lib/active_record/associations/has_many_through_association.rb +26 -19
  26. data/lib/active_record/associations/has_one_association.rb +52 -37
  27. data/lib/active_record/associations/has_one_through_association.rb +6 -6
  28. data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  30. data/lib/active_record/associations/join_dependency.rb +97 -62
  31. data/lib/active_record/associations/preloader/association.rb +220 -60
  32. data/lib/active_record/associations/preloader/batch.rb +48 -0
  33. data/lib/active_record/associations/preloader/branch.rb +147 -0
  34. data/lib/active_record/associations/preloader/through_association.rb +85 -40
  35. data/lib/active_record/associations/preloader.rb +44 -105
  36. data/lib/active_record/associations/singular_association.rb +9 -17
  37. data/lib/active_record/associations/through_association.rb +4 -4
  38. data/lib/active_record/associations.rb +207 -66
  39. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  40. data/lib/active_record/attribute_assignment.rb +17 -19
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +19 -8
  42. data/lib/active_record/attribute_methods/dirty.rb +141 -47
  43. data/lib/active_record/attribute_methods/primary_key.rb +22 -27
  44. data/lib/active_record/attribute_methods/query.rb +6 -10
  45. data/lib/active_record/attribute_methods/read.rb +15 -55
  46. data/lib/active_record/attribute_methods/serialization.rb +77 -18
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +16 -18
  48. data/lib/active_record/attribute_methods/write.rb +18 -37
  49. data/lib/active_record/attribute_methods.rb +90 -153
  50. data/lib/active_record/attributes.rb +38 -12
  51. data/lib/active_record/autosave_association.rb +50 -50
  52. data/lib/active_record/base.rb +23 -18
  53. data/lib/active_record/callbacks.rb +159 -44
  54. data/lib/active_record/coders/yaml_column.rb +12 -3
  55. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  58. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +92 -464
  59. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -51
  60. data/lib/active_record/connection_adapters/abstract/database_statements.rb +209 -164
  61. data/lib/active_record/connection_adapters/abstract/query_cache.rb +38 -22
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +103 -82
  63. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  64. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +140 -110
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -94
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +16 -5
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +456 -159
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +169 -78
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +367 -162
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +311 -327
  71. data/lib/active_record/connection_adapters/column.rb +33 -11
  72. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  73. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  74. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  75. data/lib/active_record/connection_adapters/mysql/database_statements.rb +113 -45
  76. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  77. data/lib/active_record/connection_adapters/mysql/quoting.rb +71 -5
  78. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  79. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  80. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +25 -8
  81. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +143 -19
  82. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  83. data/lib/active_record/connection_adapters/mysql2_adapter.rb +63 -22
  84. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  85. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  86. data/lib/active_record/connection_adapters/postgresql/column.rb +53 -28
  87. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +56 -63
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +54 -16
  95. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  102. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +26 -12
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  106. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  107. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -52
  108. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -2
  109. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +39 -4
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +128 -91
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -1
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +149 -113
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +386 -182
  116. data/lib/active_record/connection_adapters/schema_cache.rb +161 -22
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +17 -6
  118. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +152 -0
  119. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +65 -18
  120. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  121. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +92 -26
  122. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +251 -204
  123. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  124. data/lib/active_record/connection_adapters.rb +53 -0
  125. data/lib/active_record/connection_handling.rb +292 -38
  126. data/lib/active_record/core.rb +385 -158
  127. data/lib/active_record/counter_cache.rb +8 -30
  128. data/lib/active_record/database_configurations/connection_url_resolver.rb +100 -0
  129. data/lib/active_record/database_configurations/database_config.rb +83 -0
  130. data/lib/active_record/database_configurations/hash_config.rb +154 -0
  131. data/lib/active_record/database_configurations/url_config.rb +53 -0
  132. data/lib/active_record/database_configurations.rb +256 -0
  133. data/lib/active_record/delegated_type.rb +250 -0
  134. data/lib/active_record/destroy_association_async_job.rb +36 -0
  135. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  136. data/lib/active_record/dynamic_matchers.rb +4 -5
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +44 -0
  140. data/lib/active_record/encryption/configurable.rb +61 -0
  141. data/lib/active_record/encryption/context.rb +35 -0
  142. data/lib/active_record/encryption/contexts.rb +72 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -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 +155 -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 +160 -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 +42 -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_serializer.rb +90 -0
  159. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  160. data/lib/active_record/encryption/properties.rb +76 -0
  161. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  162. data/lib/active_record/encryption/scheme.rb +99 -0
  163. data/lib/active_record/encryption.rb +55 -0
  164. data/lib/active_record/enum.rb +130 -51
  165. data/lib/active_record/errors.rb +129 -23
  166. data/lib/active_record/explain.rb +10 -6
  167. data/lib/active_record/explain_registry.rb +11 -6
  168. data/lib/active_record/explain_subscriber.rb +1 -1
  169. data/lib/active_record/fixture_set/file.rb +22 -15
  170. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  171. data/lib/active_record/fixture_set/render_context.rb +17 -0
  172. data/lib/active_record/fixture_set/table_row.rb +187 -0
  173. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  174. data/lib/active_record/fixtures.rb +206 -490
  175. data/lib/active_record/future_result.rb +139 -0
  176. data/lib/active_record/gem_version.rb +3 -3
  177. data/lib/active_record/inheritance.rb +104 -37
  178. data/lib/active_record/insert_all.rb +278 -0
  179. data/lib/active_record/integration.rb +69 -18
  180. data/lib/active_record/internal_metadata.rb +24 -9
  181. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  182. data/lib/active_record/locking/optimistic.rb +41 -26
  183. data/lib/active_record/locking/pessimistic.rb +18 -8
  184. data/lib/active_record/log_subscriber.rb +46 -35
  185. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  186. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  187. data/lib/active_record/middleware/database_selector.rb +82 -0
  188. data/lib/active_record/middleware/shard_selector.rb +60 -0
  189. data/lib/active_record/migration/command_recorder.rb +96 -44
  190. data/lib/active_record/migration/compatibility.rb +246 -64
  191. data/lib/active_record/migration/join_table.rb +1 -2
  192. data/lib/active_record/migration.rb +266 -187
  193. data/lib/active_record/model_schema.rb +165 -52
  194. data/lib/active_record/nested_attributes.rb +17 -19
  195. data/lib/active_record/no_touching.rb +11 -4
  196. data/lib/active_record/null_relation.rb +2 -7
  197. data/lib/active_record/persistence.rb +467 -92
  198. data/lib/active_record/query_cache.rb +21 -4
  199. data/lib/active_record/query_logs.rb +138 -0
  200. data/lib/active_record/querying.rb +51 -24
  201. data/lib/active_record/railtie.rb +224 -57
  202. data/lib/active_record/railties/console_sandbox.rb +2 -4
  203. data/lib/active_record/railties/controller_runtime.rb +31 -36
  204. data/lib/active_record/railties/databases.rake +369 -101
  205. data/lib/active_record/readonly_attributes.rb +15 -0
  206. data/lib/active_record/reflection.rb +170 -137
  207. data/lib/active_record/relation/batches/batch_enumerator.rb +44 -14
  208. data/lib/active_record/relation/batches.rb +46 -37
  209. data/lib/active_record/relation/calculations.rb +168 -96
  210. data/lib/active_record/relation/delegation.rb +37 -52
  211. data/lib/active_record/relation/finder_methods.rb +79 -58
  212. data/lib/active_record/relation/from_clause.rb +5 -1
  213. data/lib/active_record/relation/merger.rb +50 -51
  214. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  215. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  216. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  217. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
  218. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  219. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  220. data/lib/active_record/relation/predicate_builder.rb +58 -46
  221. data/lib/active_record/relation/query_attribute.rb +9 -10
  222. data/lib/active_record/relation/query_methods.rb +685 -208
  223. data/lib/active_record/relation/record_fetch_warning.rb +9 -11
  224. data/lib/active_record/relation/spawn_methods.rb +10 -10
  225. data/lib/active_record/relation/where_clause.rb +108 -64
  226. data/lib/active_record/relation.rb +515 -151
  227. data/lib/active_record/result.rb +78 -42
  228. data/lib/active_record/runtime_registry.rb +9 -13
  229. data/lib/active_record/sanitization.rb +29 -44
  230. data/lib/active_record/schema.rb +37 -31
  231. data/lib/active_record/schema_dumper.rb +74 -23
  232. data/lib/active_record/schema_migration.rb +7 -9
  233. data/lib/active_record/scoping/default.rb +62 -17
  234. data/lib/active_record/scoping/named.rb +17 -32
  235. data/lib/active_record/scoping.rb +70 -41
  236. data/lib/active_record/secure_token.rb +16 -8
  237. data/lib/active_record/serialization.rb +6 -4
  238. data/lib/active_record/signed_id.rb +116 -0
  239. data/lib/active_record/statement_cache.rb +49 -6
  240. data/lib/active_record/store.rb +88 -9
  241. data/lib/active_record/suppressor.rb +13 -17
  242. data/lib/active_record/table_metadata.rb +42 -43
  243. data/lib/active_record/tasks/database_tasks.rb +352 -94
  244. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  245. data/lib/active_record/tasks/postgresql_database_tasks.rb +41 -39
  246. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  247. data/lib/active_record/test_databases.rb +24 -0
  248. data/lib/active_record/test_fixtures.rb +287 -0
  249. data/lib/active_record/timestamp.rb +44 -34
  250. data/lib/active_record/touch_later.rb +23 -22
  251. data/lib/active_record/transactions.rb +67 -128
  252. data/lib/active_record/translation.rb +3 -3
  253. data/lib/active_record/type/adapter_specific_registry.rb +34 -19
  254. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  255. data/lib/active_record/type/internal/timezone.rb +2 -2
  256. data/lib/active_record/type/serialized.rb +7 -4
  257. data/lib/active_record/type/time.rb +10 -0
  258. data/lib/active_record/type/type_map.rb +17 -21
  259. data/lib/active_record/type/unsigned_integer.rb +0 -1
  260. data/lib/active_record/type.rb +9 -5
  261. data/lib/active_record/type_caster/connection.rb +15 -15
  262. data/lib/active_record/type_caster/map.rb +8 -8
  263. data/lib/active_record/validations/associated.rb +2 -3
  264. data/lib/active_record/validations/numericality.rb +35 -0
  265. data/lib/active_record/validations/uniqueness.rb +39 -31
  266. data/lib/active_record/validations.rb +4 -3
  267. data/lib/active_record.rb +209 -32
  268. data/lib/arel/alias_predication.rb +9 -0
  269. data/lib/arel/attributes/attribute.rb +33 -0
  270. data/lib/arel/collectors/bind.rb +29 -0
  271. data/lib/arel/collectors/composite.rb +39 -0
  272. data/lib/arel/collectors/plain_string.rb +20 -0
  273. data/lib/arel/collectors/sql_string.rb +27 -0
  274. data/lib/arel/collectors/substitute_binds.rb +35 -0
  275. data/lib/arel/crud.rb +48 -0
  276. data/lib/arel/delete_manager.rb +32 -0
  277. data/lib/arel/errors.rb +9 -0
  278. data/lib/arel/expressions.rb +29 -0
  279. data/lib/arel/factory_methods.rb +49 -0
  280. data/lib/arel/filter_predications.rb +9 -0
  281. data/lib/arel/insert_manager.rb +48 -0
  282. data/lib/arel/math.rb +45 -0
  283. data/lib/arel/nodes/and.rb +32 -0
  284. data/lib/arel/nodes/ascending.rb +23 -0
  285. data/lib/arel/nodes/binary.rb +126 -0
  286. data/lib/arel/nodes/bind_param.rb +44 -0
  287. data/lib/arel/nodes/case.rb +55 -0
  288. data/lib/arel/nodes/casted.rb +62 -0
  289. data/lib/arel/nodes/comment.rb +29 -0
  290. data/lib/arel/nodes/count.rb +12 -0
  291. data/lib/arel/nodes/delete_statement.rb +44 -0
  292. data/lib/arel/nodes/descending.rb +23 -0
  293. data/lib/arel/nodes/equality.rb +15 -0
  294. data/lib/arel/nodes/extract.rb +24 -0
  295. data/lib/arel/nodes/false.rb +16 -0
  296. data/lib/arel/nodes/filter.rb +10 -0
  297. data/lib/arel/nodes/full_outer_join.rb +8 -0
  298. data/lib/arel/nodes/function.rb +45 -0
  299. data/lib/arel/nodes/grouping.rb +11 -0
  300. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  301. data/lib/arel/nodes/in.rb +15 -0
  302. data/lib/arel/nodes/infix_operation.rb +92 -0
  303. data/lib/arel/nodes/inner_join.rb +8 -0
  304. data/lib/arel/nodes/insert_statement.rb +37 -0
  305. data/lib/arel/nodes/join_source.rb +20 -0
  306. data/lib/arel/nodes/matches.rb +18 -0
  307. data/lib/arel/nodes/named_function.rb +23 -0
  308. data/lib/arel/nodes/node.rb +51 -0
  309. data/lib/arel/nodes/node_expression.rb +13 -0
  310. data/lib/arel/nodes/ordering.rb +27 -0
  311. data/lib/arel/nodes/outer_join.rb +8 -0
  312. data/lib/arel/nodes/over.rb +15 -0
  313. data/lib/arel/nodes/regexp.rb +16 -0
  314. data/lib/arel/nodes/right_outer_join.rb +8 -0
  315. data/lib/arel/nodes/select_core.rb +67 -0
  316. data/lib/arel/nodes/select_statement.rb +41 -0
  317. data/lib/arel/nodes/sql_literal.rb +19 -0
  318. data/lib/arel/nodes/string_join.rb +11 -0
  319. data/lib/arel/nodes/table_alias.rb +31 -0
  320. data/lib/arel/nodes/terminal.rb +16 -0
  321. data/lib/arel/nodes/true.rb +16 -0
  322. data/lib/arel/nodes/unary.rb +44 -0
  323. data/lib/arel/nodes/unary_operation.rb +20 -0
  324. data/lib/arel/nodes/unqualified_column.rb +22 -0
  325. data/lib/arel/nodes/update_statement.rb +46 -0
  326. data/lib/arel/nodes/values_list.rb +9 -0
  327. data/lib/arel/nodes/window.rb +126 -0
  328. data/lib/arel/nodes/with.rb +11 -0
  329. data/lib/arel/nodes.rb +71 -0
  330. data/lib/arel/order_predications.rb +13 -0
  331. data/lib/arel/predications.rb +258 -0
  332. data/lib/arel/select_manager.rb +276 -0
  333. data/lib/arel/table.rb +117 -0
  334. data/lib/arel/tree_manager.rb +60 -0
  335. data/lib/arel/update_manager.rb +48 -0
  336. data/lib/arel/visitors/dot.rb +298 -0
  337. data/lib/arel/visitors/mysql.rb +99 -0
  338. data/lib/arel/visitors/postgresql.rb +110 -0
  339. data/lib/arel/visitors/sqlite.rb +38 -0
  340. data/lib/arel/visitors/to_sql.rb +955 -0
  341. data/lib/arel/visitors/visitor.rb +45 -0
  342. data/lib/arel/visitors.rb +13 -0
  343. data/lib/arel/window_predications.rb +9 -0
  344. data/lib/arel.rb +55 -0
  345. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  346. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  347. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  348. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  349. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  350. data/lib/rails/generators/active_record/migration.rb +19 -2
  351. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  352. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  353. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  354. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  355. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  356. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  357. metadata +162 -32
  358. data/lib/active_record/attribute_decorators.rb +0 -90
  359. data/lib/active_record/collection_cache_key.rb +0 -53
  360. data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
  361. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
  362. data/lib/active_record/define_callbacks.rb +0 -22
  363. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
  364. data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -1,17 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Make sure we're using pg high enough for type casts and Ruby 2.2+ compatibility
4
- gem "pg", ">= 0.18", "< 2.0"
3
+ gem "pg", "~> 1.1"
5
4
  require "pg"
6
5
 
7
- # Use async_exec instead of exec_params on pg versions before 1.1
8
- class ::PG::Connection
9
- unless self.public_method_defined?(:async_exec_params)
10
- remove_method :exec_params
11
- alias exec_params async_exec
12
- end
13
- end
14
-
6
+ require "active_support/core_ext/object/try"
15
7
  require "active_record/connection_adapters/abstract_adapter"
16
8
  require "active_record/connection_adapters/statement_pool"
17
9
  require "active_record/connection_adapters/postgresql/column"
@@ -31,9 +23,7 @@ module ActiveRecord
31
23
  module ConnectionHandling # :nodoc:
32
24
  # Establishes a connection to the database that's used by all Active Record objects
33
25
  def postgresql_connection(config)
34
- conn_params = config.symbolize_keys
35
-
36
- conn_params.delete_if { |_, v| v.nil? }
26
+ conn_params = config.symbolize_keys.compact
37
27
 
38
28
  # Map ActiveRecords param names to PGs.
39
29
  conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
@@ -43,14 +33,17 @@ module ActiveRecord
43
33
  valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
44
34
  conn_params.slice!(*valid_conn_param_keys)
45
35
 
46
- # The postgres drivers don't allow the creation of an unconnected PG::Connection object,
47
- # so just pass a nil connection object for the time being.
48
- ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
36
+ ConnectionAdapters::PostgreSQLAdapter.new(
37
+ ConnectionAdapters::PostgreSQLAdapter.new_client(conn_params),
38
+ logger,
39
+ conn_params,
40
+ config,
41
+ )
49
42
  end
50
43
  end
51
44
 
52
45
  module ConnectionAdapters
53
- # The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
46
+ # The PostgreSQL adapter works with the native C (https://github.com/ged/ruby-pg) driver.
54
47
  #
55
48
  # Options:
56
49
  #
@@ -59,7 +52,7 @@ module ActiveRecord
59
52
  # * <tt>:port</tt> - Defaults to 5432.
60
53
  # * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
61
54
  # * <tt>:password</tt> - Password to be used if the server demands password authentication.
62
- # * <tt>:database</tt> - Defaults to be the same as the user name.
55
+ # * <tt>:database</tt> - Defaults to be the same as the username.
63
56
  # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
64
57
  # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
65
58
  # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
@@ -78,16 +71,66 @@ module ActiveRecord
78
71
  # In addition, default connection parameters of libpq can be set per environment variables.
79
72
  # See https://www.postgresql.org/docs/current/static/libpq-envars.html .
80
73
  class PostgreSQLAdapter < AbstractAdapter
81
- ADAPTER_NAME = "PostgreSQL".freeze
74
+ ADAPTER_NAME = "PostgreSQL"
75
+
76
+ class << self
77
+ def new_client(conn_params)
78
+ PG.connect(**conn_params)
79
+ rescue ::PG::Error => error
80
+ if conn_params && conn_params[:dbname] && error.message.include?(conn_params[:dbname])
81
+ raise ActiveRecord::NoDatabaseError.db_error(conn_params[:dbname])
82
+ elsif conn_params && conn_params[:user] && error.message.include?(conn_params[:user])
83
+ raise ActiveRecord::DatabaseConnectionError.username_error(conn_params[:user])
84
+ elsif conn_params && conn_params[:hostname] && error.message.include?(conn_params[:hostname])
85
+ raise ActiveRecord::DatabaseConnectionError.hostname_error(conn_params[:hostname])
86
+ else
87
+ raise ActiveRecord::ConnectionNotEstablished, error.message
88
+ end
89
+ end
90
+ end
91
+
92
+ ##
93
+ # :singleton-method:
94
+ # PostgreSQL allows the creation of "unlogged" tables, which do not record
95
+ # data in the PostgreSQL Write-Ahead Log. This can make the tables faster,
96
+ # but significantly increases the risk of data loss if the database
97
+ # crashes. As a result, this should not be used in production
98
+ # environments. If you would like all created tables to be unlogged in
99
+ # the test environment you can add the following line to your test.rb
100
+ # file:
101
+ #
102
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
103
+ class_attribute :create_unlogged_tables, default: false
104
+
105
+ ##
106
+ # :singleton-method:
107
+ # PostgreSQL supports multiple types for DateTimes. By default if you use `datetime`
108
+ # in migrations, Rails will translate this to a PostgreSQL "timestamp without time zone".
109
+ # Change this in an initializer to use another NATIVE_DATABASE_TYPES. For example, to
110
+ # store DateTimes as "timestamp with time zone":
111
+ #
112
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz
113
+ #
114
+ # Or if you are adding a custom type:
115
+ #
116
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:my_custom_type] = { name: "my_custom_type_name" }
117
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :my_custom_type
118
+ #
119
+ # If you're using :ruby as your config.active_record.schema_format and you change this
120
+ # setting, you should immediately run bin/rails db:migrate to update the types in your schema.rb.
121
+ class_attribute :datetime_type, default: :timestamp
82
122
 
83
123
  NATIVE_DATABASE_TYPES = {
84
124
  primary_key: "bigserial primary key",
85
125
  string: { name: "character varying" },
86
126
  text: { name: "text" },
87
127
  integer: { name: "integer", limit: 4 },
128
+ bigint: { name: "bigint" },
88
129
  float: { name: "float" },
89
130
  decimal: { name: "decimal" },
90
- datetime: { name: "timestamp" },
131
+ datetime: {}, # set dynamically based on datetime_type
132
+ timestamp: { name: "timestamp" },
133
+ timestamptz: { name: "timestamptz" },
91
134
  time: { name: "time" },
92
135
  date: { name: "date" },
93
136
  daterange: { name: "daterange" },
@@ -121,9 +164,10 @@ module ActiveRecord
121
164
  money: { name: "money" },
122
165
  interval: { name: "interval" },
123
166
  oid: { name: "oid" },
167
+ enum: {} # special type https://www.postgresql.org/docs/current/datatype-enum.html
124
168
  }
125
169
 
126
- OID = PostgreSQL::OID #:nodoc:
170
+ OID = PostgreSQL::OID # :nodoc:
127
171
 
128
172
  include PostgreSQL::Quoting
129
173
  include PostgreSQL::ReferentialIntegrity
@@ -138,6 +182,10 @@ module ActiveRecord
138
182
  true
139
183
  end
140
184
 
185
+ def supports_partitioned_indexes?
186
+ database_version >= 110_000 # >= 11.0
187
+ end
188
+
141
189
  def supports_partial_index?
142
190
  true
143
191
  end
@@ -154,10 +202,18 @@ module ActiveRecord
154
202
  true
155
203
  end
156
204
 
205
+ def supports_check_constraints?
206
+ true
207
+ end
208
+
157
209
  def supports_validate_constraints?
158
210
  true
159
211
  end
160
212
 
213
+ def supports_deferrable_constraints?
214
+ true
215
+ end
216
+
161
217
  def supports_views?
162
218
  true
163
219
  end
@@ -167,7 +223,7 @@ module ActiveRecord
167
223
  end
168
224
 
169
225
  def supports_json?
170
- postgresql_version >= 90200
226
+ true
171
227
  end
172
228
 
173
229
  def supports_comments?
@@ -178,6 +234,21 @@ module ActiveRecord
178
234
  true
179
235
  end
180
236
 
237
+ def supports_insert_returning?
238
+ true
239
+ end
240
+
241
+ def supports_insert_on_conflict?
242
+ database_version >= 90500 # >= 9.5
243
+ end
244
+ alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
245
+ alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
246
+ alias supports_insert_conflict_target? supports_insert_on_conflict?
247
+
248
+ def supports_virtual_columns?
249
+ database_version >= 120_000 # >= 12.0
250
+ end
251
+
181
252
  def index_algorithms
182
253
  { concurrently: "CONCURRENTLY" }
183
254
  end
@@ -190,11 +261,7 @@ module ActiveRecord
190
261
  end
191
262
 
192
263
  def next_key
193
- "a#{@counter + 1}"
194
- end
195
-
196
- def []=(sql, key)
197
- super.tap { @counter += 1 }
264
+ "a#{@counter += 1}"
198
265
  end
199
266
 
200
267
  private
@@ -214,21 +281,14 @@ module ActiveRecord
214
281
  def initialize(connection, logger, connection_parameters, config)
215
282
  super(connection, logger, config)
216
283
 
217
- @connection_parameters = connection_parameters
284
+ @connection_parameters = connection_parameters || {}
218
285
 
219
286
  # @local_tz is initialized as nil to avoid warnings when connect tries to use it
220
287
  @local_tz = nil
221
288
  @max_identifier_length = nil
222
289
 
223
- connect
290
+ configure_connection
224
291
  add_pg_encoders
225
- @statements = StatementPool.new @connection,
226
- self.class.type_cast_config_to_integer(config[:statement_limit])
227
-
228
- if postgresql_version < 90100
229
- raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1."
230
- end
231
-
232
292
  add_pg_decoders
233
293
 
234
294
  @type_map = Type::HashLookupTypeMap.new
@@ -237,33 +297,36 @@ module ActiveRecord
237
297
  @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
238
298
  end
239
299
 
240
- # Clears the prepared statements cache.
241
- def clear_cache!
242
- @lock.synchronize do
243
- @statements.clear
244
- end
245
- end
246
-
247
- def truncate(table_name, name = nil)
248
- exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, []
300
+ def self.database_exists?(config)
301
+ !!ActiveRecord::Base.postgresql_connection(config)
302
+ rescue ActiveRecord::NoDatabaseError
303
+ false
249
304
  end
250
305
 
251
306
  # Is this connection alive and ready for queries?
252
307
  def active?
253
308
  @lock.synchronize do
254
- @connection.query "SELECT 1"
309
+ @connection.query ";"
255
310
  end
256
311
  true
257
312
  rescue PG::Error
258
313
  false
259
314
  end
260
315
 
316
+ def reload_type_map # :nodoc:
317
+ type_map.clear
318
+ initialize_type_map
319
+ end
320
+
261
321
  # Close then reopen the connection.
262
322
  def reconnect!
263
323
  @lock.synchronize do
264
324
  super
265
325
  @connection.reset
266
326
  configure_connection
327
+ reload_type_map
328
+ rescue PG::ConnectionBad
329
+ connect
267
330
  end
268
331
  end
269
332
 
@@ -289,12 +352,21 @@ module ActiveRecord
289
352
  end
290
353
 
291
354
  def discard! # :nodoc:
355
+ super
292
356
  @connection.socket_io.reopen(IO::NULL) rescue nil
293
357
  @connection = nil
294
358
  end
295
359
 
296
- def native_database_types #:nodoc:
297
- NATIVE_DATABASE_TYPES
360
+ def native_database_types # :nodoc:
361
+ self.class.native_database_types
362
+ end
363
+
364
+ def self.native_database_types # :nodoc:
365
+ @native_database_types ||= begin
366
+ types = NATIVE_DATABASE_TYPES.dup
367
+ types[:datetime] = types[datetime_type]
368
+ types
369
+ end
298
370
  end
299
371
 
300
372
  def set_standard_conforming_strings
@@ -317,21 +389,31 @@ module ActiveRecord
317
389
  true
318
390
  end
319
391
 
320
- def supports_ranges?
321
- # Range datatypes weren't introduced until PostgreSQL 9.2
322
- postgresql_version >= 90200
323
- end
324
-
325
392
  def supports_materialized_views?
326
- postgresql_version >= 90300
393
+ true
327
394
  end
328
395
 
329
396
  def supports_foreign_tables?
330
- postgresql_version >= 90300
397
+ true
331
398
  end
332
399
 
333
400
  def supports_pgcrypto_uuid?
334
- postgresql_version >= 90400
401
+ database_version >= 90400 # >= 9.4
402
+ end
403
+
404
+ def supports_optimizer_hints?
405
+ unless defined?(@has_pg_hint_plan)
406
+ @has_pg_hint_plan = extension_available?("pg_hint_plan")
407
+ end
408
+ @has_pg_hint_plan
409
+ end
410
+
411
+ def supports_common_table_expressions?
412
+ true
413
+ end
414
+
415
+ def supports_lazy_transactions?
416
+ true
335
417
  end
336
418
 
337
419
  def get_advisory_lock(lock_id) # :nodoc:
@@ -360,21 +442,54 @@ module ActiveRecord
360
442
  }
361
443
  end
362
444
 
445
+ def extension_available?(name)
446
+ query_value("SELECT true FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
447
+ end
448
+
363
449
  def extension_enabled?(name)
364
- res = exec_query("SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", "SCHEMA")
365
- res.cast_values.first
450
+ query_value("SELECT installed_version IS NOT NULL FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
366
451
  end
367
452
 
368
453
  def extensions
369
454
  exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
370
455
  end
371
456
 
457
+ # Returns a list of defined enum types, and their values.
458
+ def enum_types
459
+ query = <<~SQL
460
+ SELECT
461
+ type.typname AS name,
462
+ string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
463
+ FROM pg_enum AS enum
464
+ JOIN pg_type AS type
465
+ ON (type.oid = enum.enumtypid)
466
+ GROUP BY type.typname;
467
+ SQL
468
+ exec_query(query, "SCHEMA").cast_values
469
+ end
470
+
471
+ # Given a name and an array of values, creates an enum type.
472
+ def create_enum(name, values)
473
+ sql_values = values.map { |s| "'#{s}'" }.join(", ")
474
+ query = <<~SQL
475
+ DO $$
476
+ BEGIN
477
+ IF NOT EXISTS (
478
+ SELECT 1 FROM pg_type t
479
+ WHERE t.typname = '#{name}'
480
+ ) THEN
481
+ CREATE TYPE \"#{name}\" AS ENUM (#{sql_values});
482
+ END IF;
483
+ END
484
+ $$;
485
+ SQL
486
+ exec_query(query)
487
+ end
488
+
372
489
  # Returns the configured supported identifier length supported by PostgreSQL
373
490
  def max_identifier_length
374
491
  @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
375
492
  end
376
- alias table_alias_length max_identifier_length
377
- alias index_name_length max_identifier_length
378
493
 
379
494
  # Set the authorized user for this session
380
495
  def session_auth=(user)
@@ -386,78 +501,43 @@ module ActiveRecord
386
501
  @use_insert_returning
387
502
  end
388
503
 
389
- def column_name_for_operation(operation, node) # :nodoc:
390
- OPERATION_ALIASES.fetch(operation) { operation.downcase }
391
- end
392
-
393
- OPERATION_ALIASES = { # :nodoc:
394
- "maximum" => "max",
395
- "minimum" => "min",
396
- "average" => "avg",
397
- }
398
-
399
504
  # Returns the version of the connected PostgreSQL server.
400
- def postgresql_version
505
+ def get_database_version # :nodoc:
401
506
  @connection.server_version
402
507
  end
508
+ alias :postgresql_version :database_version
403
509
 
404
510
  def default_index_type?(index) # :nodoc:
405
511
  index.using == :btree || super
406
512
  end
407
513
 
408
- private
409
- # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
410
- VALUE_LIMIT_VIOLATION = "22001"
411
- NUMERIC_VALUE_OUT_OF_RANGE = "22003"
412
- NOT_NULL_VIOLATION = "23502"
413
- FOREIGN_KEY_VIOLATION = "23503"
414
- UNIQUE_VIOLATION = "23505"
415
- SERIALIZATION_FAILURE = "40001"
416
- DEADLOCK_DETECTED = "40P01"
417
- LOCK_NOT_AVAILABLE = "55P03"
418
- QUERY_CANCELED = "57014"
419
-
420
- def translate_exception(exception, message)
421
- return exception unless exception.respond_to?(:result)
514
+ def build_insert_sql(insert) # :nodoc:
515
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
422
516
 
423
- case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
424
- when UNIQUE_VIOLATION
425
- RecordNotUnique.new(message)
426
- when FOREIGN_KEY_VIOLATION
427
- InvalidForeignKey.new(message)
428
- when VALUE_LIMIT_VIOLATION
429
- ValueTooLong.new(message)
430
- when NUMERIC_VALUE_OUT_OF_RANGE
431
- RangeError.new(message)
432
- when NOT_NULL_VIOLATION
433
- NotNullViolation.new(message)
434
- when SERIALIZATION_FAILURE
435
- SerializationFailure.new(message)
436
- when DEADLOCK_DETECTED
437
- Deadlocked.new(message)
438
- when LOCK_NOT_AVAILABLE
439
- LockWaitTimeout.new(message)
440
- when QUERY_CANCELED
441
- QueryCanceled.new(message)
517
+ if insert.skip_duplicates?
518
+ sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
519
+ elsif insert.update_duplicates?
520
+ sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
521
+ if insert.raw_update_sql?
522
+ sql << insert.raw_update_sql
442
523
  else
443
- super
524
+ sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column} IS NOT DISTINCT FROM excluded.#{column}" }
525
+ sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
444
526
  end
445
527
  end
446
528
 
447
- def get_oid_type(oid, fmod, column_name, sql_type = "".freeze)
448
- if !type_map.key?(oid)
449
- load_additional_types([oid])
450
- end
529
+ sql << " RETURNING #{insert.returning}" if insert.returning
530
+ sql
531
+ end
451
532
 
452
- type_map.fetch(oid, fmod, sql_type) {
453
- warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
454
- Type.default_value.tap do |cast_type|
455
- type_map.register_type(oid, cast_type)
456
- end
457
- }
533
+ def check_version # :nodoc:
534
+ if database_version < 90300 # < 9.3
535
+ raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
458
536
  end
537
+ end
459
538
 
460
- def initialize_type_map(m = type_map)
539
+ class << self
540
+ def initialize_type_map(m) # :nodoc:
461
541
  m.register_type "int2", Type::Integer.new(limit: 2)
462
542
  m.register_type "int4", Type::Integer.new(limit: 4)
463
543
  m.register_type "int8", Type::Integer.new(limit: 8)
@@ -472,7 +552,6 @@ module ActiveRecord
472
552
  m.register_type "bool", Type::Boolean.new
473
553
  register_class_with_limit m, "bit", OID::Bit
474
554
  register_class_with_limit m, "varbit", OID::BitVarying
475
- m.alias_type "timestamptz", "timestamp"
476
555
  m.register_type "date", OID::Date.new
477
556
 
478
557
  m.register_type "money", OID::Money.new
@@ -486,7 +565,7 @@ module ActiveRecord
486
565
  m.register_type "uuid", OID::Uuid.new
487
566
  m.register_type "xml", OID::Xml.new
488
567
  m.register_type "tsvector", OID::SpecializedString.new(:tsvector)
489
- m.register_type "macaddr", OID::SpecializedString.new(:macaddr)
568
+ m.register_type "macaddr", OID::Macaddr.new
490
569
  m.register_type "citext", OID::SpecializedString.new(:citext)
491
570
  m.register_type "ltree", OID::SpecializedString.new(:ltree)
492
571
  m.register_type "line", OID::SpecializedString.new(:line)
@@ -496,13 +575,9 @@ module ActiveRecord
496
575
  m.register_type "polygon", OID::SpecializedString.new(:polygon)
497
576
  m.register_type "circle", OID::SpecializedString.new(:circle)
498
577
 
499
- m.register_type "interval" do |_, _, sql_type|
500
- precision = extract_precision(sql_type)
501
- OID::SpecializedString.new(:interval, precision: precision)
502
- end
503
-
504
578
  register_class_with_precision m, "time", Type::Time
505
- register_class_with_precision m, "timestamp", OID::DateTime
579
+ register_class_with_precision m, "timestamp", OID::Timestamp
580
+ register_class_with_precision m, "timestamptz", OID::TimestampWithTimeZone
506
581
 
507
582
  m.register_type "numeric" do |_, fmod, sql_type|
508
583
  precision = extract_precision(sql_type)
@@ -524,6 +599,20 @@ module ActiveRecord
524
599
  end
525
600
  end
526
601
 
602
+ m.register_type "interval" do |*args, sql_type|
603
+ precision = extract_precision(sql_type)
604
+ OID::Interval.new(precision: precision)
605
+ end
606
+ end
607
+ end
608
+
609
+ private
610
+ def type_map
611
+ @type_map ||= Type::HashLookupTypeMap.new
612
+ end
613
+
614
+ def initialize_type_map(m = type_map)
615
+ self.class.initialize_type_map(m)
527
616
  load_additional_types
528
617
  end
529
618
 
@@ -531,15 +620,15 @@ module ActiveRecord
531
620
  def extract_value_from_default(default)
532
621
  case default
533
622
  # Quoted types
534
- when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
623
+ when /\A[(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
535
624
  # The default 'now'::date is CURRENT_DATE
536
- if $1 == "now".freeze && $2 == "date".freeze
625
+ if $1 == "now" && $2 == "date"
537
626
  nil
538
627
  else
539
- $1.gsub("''".freeze, "'".freeze)
628
+ $1.gsub("''", "'")
540
629
  end
541
630
  # Boolean types
542
- when "true".freeze, "false".freeze
631
+ when "true", "false"
543
632
  default
544
633
  # Numeric types
545
634
  when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
@@ -562,62 +651,134 @@ module ActiveRecord
562
651
  !default_value && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
563
652
  end
564
653
 
565
- def load_additional_types(oids = nil)
566
- initializer = OID::TypeMapInitializer.new(type_map)
654
+ # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
655
+ VALUE_LIMIT_VIOLATION = "22001"
656
+ NUMERIC_VALUE_OUT_OF_RANGE = "22003"
657
+ NOT_NULL_VIOLATION = "23502"
658
+ FOREIGN_KEY_VIOLATION = "23503"
659
+ UNIQUE_VIOLATION = "23505"
660
+ SERIALIZATION_FAILURE = "40001"
661
+ DEADLOCK_DETECTED = "40P01"
662
+ DUPLICATE_DATABASE = "42P04"
663
+ LOCK_NOT_AVAILABLE = "55P03"
664
+ QUERY_CANCELED = "57014"
567
665
 
568
- if supports_ranges?
569
- query = <<-SQL
570
- SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
571
- FROM pg_type as t
572
- LEFT JOIN pg_range as r ON oid = rngtypid
573
- SQL
666
+ def translate_exception(exception, message:, sql:, binds:)
667
+ return exception unless exception.respond_to?(:result)
668
+
669
+ case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
670
+ when nil
671
+ if exception.message.match?(/connection is closed/i)
672
+ ConnectionNotEstablished.new(exception)
673
+ else
674
+ super
675
+ end
676
+ when UNIQUE_VIOLATION
677
+ RecordNotUnique.new(message, sql: sql, binds: binds)
678
+ when FOREIGN_KEY_VIOLATION
679
+ InvalidForeignKey.new(message, sql: sql, binds: binds)
680
+ when VALUE_LIMIT_VIOLATION
681
+ ValueTooLong.new(message, sql: sql, binds: binds)
682
+ when NUMERIC_VALUE_OUT_OF_RANGE
683
+ RangeError.new(message, sql: sql, binds: binds)
684
+ when NOT_NULL_VIOLATION
685
+ NotNullViolation.new(message, sql: sql, binds: binds)
686
+ when SERIALIZATION_FAILURE
687
+ SerializationFailure.new(message, sql: sql, binds: binds)
688
+ when DEADLOCK_DETECTED
689
+ Deadlocked.new(message, sql: sql, binds: binds)
690
+ when DUPLICATE_DATABASE
691
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
692
+ when LOCK_NOT_AVAILABLE
693
+ LockWaitTimeout.new(message, sql: sql, binds: binds)
694
+ when QUERY_CANCELED
695
+ QueryCanceled.new(message, sql: sql, binds: binds)
574
696
  else
575
- query = <<-SQL
576
- SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
577
- FROM pg_type as t
578
- SQL
697
+ super
579
698
  end
699
+ end
580
700
 
581
- if oids
582
- query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
583
- else
584
- query += initializer.query_conditions_for_initial_load
701
+ def get_oid_type(oid, fmod, column_name, sql_type = "")
702
+ if !type_map.key?(oid)
703
+ load_additional_types([oid])
704
+ end
705
+
706
+ type_map.fetch(oid, fmod, sql_type) {
707
+ warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
708
+ Type.default_value.tap do |cast_type|
709
+ type_map.register_type(oid, cast_type)
710
+ end
711
+ }
712
+ end
713
+
714
+ def load_additional_types(oids = nil)
715
+ initializer = OID::TypeMapInitializer.new(type_map)
716
+ load_types_queries(initializer, oids) do |query|
717
+ execute_and_clear(query, "SCHEMA", []) do |records|
718
+ initializer.run(records)
719
+ end
585
720
  end
721
+ end
586
722
 
587
- execute_and_clear(query, "SCHEMA", []) do |records|
588
- initializer.run(records)
723
+ def load_types_queries(initializer, oids)
724
+ query = <<~SQL
725
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
726
+ FROM pg_type as t
727
+ LEFT JOIN pg_range as r ON oid = rngtypid
728
+ SQL
729
+ if oids
730
+ yield query + "WHERE t.oid IN (%s)" % oids.join(", ")
731
+ else
732
+ yield query + initializer.query_conditions_for_known_type_names
733
+ yield query + initializer.query_conditions_for_known_type_types
734
+ yield query + initializer.query_conditions_for_array_types
589
735
  end
590
736
  end
591
737
 
592
- FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
738
+ FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
593
739
 
594
- def execute_and_clear(sql, name, binds, prepare: false)
595
- if without_prepared_statement?(binds)
596
- result = exec_no_cache(sql, name, [])
597
- elsif !prepare
598
- result = exec_no_cache(sql, name, binds)
740
+ def execute_and_clear(sql, name, binds, prepare: false, async: false)
741
+ sql = transform_query(sql)
742
+ check_if_write_query(sql)
743
+
744
+ if !prepare || without_prepared_statement?(binds)
745
+ result = exec_no_cache(sql, name, binds, async: async)
599
746
  else
600
- result = exec_cache(sql, name, binds)
747
+ result = exec_cache(sql, name, binds, async: async)
748
+ end
749
+ begin
750
+ ret = yield result
751
+ ensure
752
+ result.clear
601
753
  end
602
- ret = yield result
603
- result.clear
604
754
  ret
605
755
  end
606
756
 
607
- def exec_no_cache(sql, name, binds)
757
+ def exec_no_cache(sql, name, binds, async: false)
758
+ materialize_transactions
759
+ mark_transaction_written_if_write(sql)
760
+
761
+ # make sure we carry over any changes to ActiveRecord.default_timezone that have been
762
+ # made since we established the connection
763
+ update_typemap_for_default_timezone
764
+
608
765
  type_casted_binds = type_casted_binds(binds)
609
- log(sql, name, binds, type_casted_binds) do
766
+ log(sql, name, binds, type_casted_binds, async: async) do
610
767
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
611
768
  @connection.exec_params(sql, type_casted_binds)
612
769
  end
613
770
  end
614
771
  end
615
772
 
616
- def exec_cache(sql, name, binds)
617
- stmt_key = prepare_statement(sql)
773
+ def exec_cache(sql, name, binds, async: false)
774
+ materialize_transactions
775
+ mark_transaction_written_if_write(sql)
776
+ update_typemap_for_default_timezone
777
+
778
+ stmt_key = prepare_statement(sql, binds)
618
779
  type_casted_binds = type_casted_binds(binds)
619
780
 
620
- log(sql, name, binds, type_casted_binds, stmt_key) do
781
+ log(sql, name, binds, type_casted_binds, stmt_key, async: async) do
621
782
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
622
783
  @connection.exec_prepared(stmt_key, type_casted_binds)
623
784
  end
@@ -647,11 +808,10 @@ module ActiveRecord
647
808
  #
648
809
  # Check here for more details:
649
810
  # https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
650
- CACHED_PLAN_HEURISTIC = "cached plan must not change result type".freeze
651
811
  def is_cached_plan_failure?(e)
652
812
  pgerror = e.cause
653
- code = pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE)
654
- code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC)
813
+ pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
814
+ pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "RevalidateCachedQuery"
655
815
  rescue
656
816
  false
657
817
  end
@@ -668,7 +828,7 @@ module ActiveRecord
668
828
 
669
829
  # Prepare the statement if it hasn't been prepared, return
670
830
  # the statement key.
671
- def prepare_statement(sql)
831
+ def prepare_statement(sql, binds)
672
832
  @lock.synchronize do
673
833
  sql_key = sql_key(sql)
674
834
  unless @statements.key? sql_key
@@ -676,7 +836,7 @@ module ActiveRecord
676
836
  begin
677
837
  @connection.prepare nextkey, sql
678
838
  rescue => e
679
- raise translate_exception_class(e, sql)
839
+ raise translate_exception_class(e, sql, binds)
680
840
  end
681
841
  # Clear the queue
682
842
  @connection.get_last_result
@@ -689,14 +849,10 @@ module ActiveRecord
689
849
  # Connects to a PostgreSQL server and sets up the adapter depending on the
690
850
  # connected server's characteristics.
691
851
  def connect
692
- @connection = PG.connect(@connection_parameters)
852
+ @connection = self.class.new_client(@connection_parameters)
693
853
  configure_connection
694
- rescue ::PG::Error => error
695
- if error.message.include?("does not exist")
696
- raise ActiveRecord::NoDatabaseError
697
- else
698
- raise
699
- end
854
+ add_pg_encoders
855
+ add_pg_decoders
700
856
  end
701
857
 
702
858
  # Configures the encoding, verbosity, schema search path, and time zone of the connection.
@@ -716,13 +872,16 @@ module ActiveRecord
716
872
  # If using Active Record's time zone support configure the connection to return
717
873
  # TIMESTAMP WITH ZONE types in UTC.
718
874
  unless variables["timezone"]
719
- if ActiveRecord::Base.default_timezone == :utc
875
+ if ActiveRecord.default_timezone == :utc
720
876
  variables["timezone"] = "UTC"
721
877
  elsif @local_tz
722
878
  variables["timezone"] = @local_tz
723
879
  end
724
880
  end
725
881
 
882
+ # Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
883
+ execute("SET intervalstyle = iso_8601", "SCHEMA")
884
+
726
885
  # SET statements from :variables config hash
727
886
  # https://www.postgresql.org/docs/current/static/sql-set.html
728
887
  variables.map do |k, v|
@@ -754,10 +913,11 @@ module ActiveRecord
754
913
  # - format_type includes the column size constraint, e.g. varchar(50)
755
914
  # - ::regclass is a function that gives the id for a table name
756
915
  def column_definitions(table_name)
757
- query(<<-end_sql, "SCHEMA")
916
+ query(<<~SQL, "SCHEMA")
758
917
  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
759
918
  pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
760
- c.collname, col_description(a.attrelid, a.attnum) AS comment
919
+ c.collname, col_description(a.attrelid, a.attnum) AS comment,
920
+ #{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated
761
921
  FROM pg_attribute a
762
922
  LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
763
923
  LEFT JOIN pg_type t ON a.atttypid = t.oid
@@ -765,7 +925,7 @@ module ActiveRecord
765
925
  WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
766
926
  AND a.attnum > 0 AND NOT a.attisdropped
767
927
  ORDER BY a.attnum
768
- end_sql
928
+ SQL
769
929
  end
770
930
 
771
931
  def extract_table_ref_from_insert_sql(sql)
@@ -777,10 +937,14 @@ module ActiveRecord
777
937
  Arel::Visitors::PostgreSQL.new(self)
778
938
  end
779
939
 
940
+ def build_statement_pool
941
+ StatementPool.new(@connection, self.class.type_cast_config_to_integer(@config[:statement_limit]))
942
+ end
943
+
780
944
  def can_perform_case_insensitive_comparison_for?(column)
781
945
  @case_insensitive_cache ||= {}
782
946
  @case_insensitive_cache[column.sql_type] ||= begin
783
- sql = <<-end_sql
947
+ sql = <<~SQL
784
948
  SELECT exists(
785
949
  SELECT * FROM pg_proc
786
950
  WHERE proname = 'lower'
@@ -792,7 +956,7 @@ module ActiveRecord
792
956
  WHERE proname = 'lower'
793
957
  AND castsource = #{quote column.sql_type}::regtype
794
958
  )
795
- end_sql
959
+ SQL
796
960
  execute_and_clear(sql, "SCHEMA", []) do |result|
797
961
  result.getvalue(0, 0)
798
962
  end
@@ -807,7 +971,27 @@ module ActiveRecord
807
971
  @connection.type_map_for_queries = map
808
972
  end
809
973
 
974
+ def update_typemap_for_default_timezone
975
+ if @default_timezone != ActiveRecord.default_timezone && @timestamp_decoder
976
+ decoder_class = ActiveRecord.default_timezone == :utc ?
977
+ PG::TextDecoder::TimestampUtc :
978
+ PG::TextDecoder::TimestampWithoutTimeZone
979
+
980
+ @timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h)
981
+ @connection.type_map_for_results.add_coder(@timestamp_decoder)
982
+
983
+ @default_timezone = ActiveRecord.default_timezone
984
+
985
+ # if default timezone has changed, we need to reconfigure the connection
986
+ # (specifically, the session time zone)
987
+ configure_connection
988
+ end
989
+ end
990
+
810
991
  def add_pg_decoders
992
+ @default_timezone = nil
993
+ @timestamp_decoder = nil
994
+
811
995
  coders_by_name = {
812
996
  "int2" => PG::TextDecoder::Integer,
813
997
  "int4" => PG::TextDecoder::Integer,
@@ -815,23 +999,34 @@ module ActiveRecord
815
999
  "oid" => PG::TextDecoder::Integer,
816
1000
  "float4" => PG::TextDecoder::Float,
817
1001
  "float8" => PG::TextDecoder::Float,
1002
+ "numeric" => PG::TextDecoder::Numeric,
818
1003
  "bool" => PG::TextDecoder::Boolean,
1004
+ "timestamp" => PG::TextDecoder::TimestampUtc,
1005
+ "timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
819
1006
  }
1007
+
820
1008
  known_coder_types = coders_by_name.keys.map { |n| quote(n) }
821
- query = <<-SQL % known_coder_types.join(", ")
1009
+ query = <<~SQL % known_coder_types.join(", ")
822
1010
  SELECT t.oid, t.typname
823
1011
  FROM pg_type as t
824
1012
  WHERE t.typname IN (%s)
825
1013
  SQL
826
1014
  coders = execute_and_clear(query, "SCHEMA", []) do |result|
827
- result
828
- .map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
829
- .compact
1015
+ result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
830
1016
  end
831
1017
 
832
1018
  map = PG::TypeMapByOid.new
833
1019
  coders.each { |coder| map.add_coder(coder) }
834
1020
  @connection.type_map_for_results = map
1021
+
1022
+ @type_map_for_results = PG::TypeMapByOid.new
1023
+ @type_map_for_results.default_type_map = map
1024
+ @type_map_for_results.add_coder(PG::TextDecoder::Bytea.new(oid: 17, name: "bytea"))
1025
+ @type_map_for_results.add_coder(MoneyDecoder.new(oid: 790, name: "money"))
1026
+
1027
+ # extract timestamp decoder for use in update_typemap_for_default_timezone
1028
+ @timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
1029
+ update_typemap_for_default_timezone
835
1030
  end
836
1031
 
837
1032
  def construct_coder(row, coder_class)
@@ -839,6 +1034,14 @@ module ActiveRecord
839
1034
  coder_class.new(oid: row["oid"].to_i, name: row["typname"])
840
1035
  end
841
1036
 
1037
+ class MoneyDecoder < PG::SimpleDecoder # :nodoc:
1038
+ TYPE = OID::Money.new
1039
+
1040
+ def decode(value, tuple = nil, field = nil)
1041
+ TYPE.deserialize(value)
1042
+ end
1043
+ end
1044
+
842
1045
  ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql)
843
1046
  ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql)
844
1047
  ActiveRecord::Type.register(:bit, OID::Bit, adapter: :postgresql)
@@ -851,6 +1054,7 @@ module ActiveRecord
851
1054
  ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
852
1055
  ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
853
1056
  ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql)
1057
+ ActiveRecord::Type.register(:interval, OID::Interval, adapter: :postgresql)
854
1058
  ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
855
1059
  ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
856
1060
  ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)