activerecord 6.1.4.1 → 7.0.2.3

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 (240) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1203 -922
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/association_relation.rb +0 -10
  7. data/lib/active_record/associations/association.rb +33 -17
  8. data/lib/active_record/associations/association_scope.rb +1 -3
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -4
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +8 -2
  12. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_many.rb +3 -2
  15. data/lib/active_record/associations/builder/has_one.rb +2 -1
  16. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  17. data/lib/active_record/associations/collection_association.rb +34 -27
  18. data/lib/active_record/associations/collection_proxy.rb +8 -3
  19. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  20. data/lib/active_record/associations/has_many_association.rb +1 -1
  21. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  22. data/lib/active_record/associations/has_one_association.rb +10 -7
  23. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  24. data/lib/active_record/associations/join_dependency.rb +6 -2
  25. data/lib/active_record/associations/preloader/association.rb +187 -55
  26. data/lib/active_record/associations/preloader/batch.rb +48 -0
  27. data/lib/active_record/associations/preloader/branch.rb +147 -0
  28. data/lib/active_record/associations/preloader/through_association.rb +49 -13
  29. data/lib/active_record/associations/preloader.rb +39 -113
  30. data/lib/active_record/associations/singular_association.rb +8 -2
  31. data/lib/active_record/associations/through_association.rb +3 -3
  32. data/lib/active_record/associations.rb +119 -90
  33. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  34. data/lib/active_record/attribute_assignment.rb +1 -1
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  36. data/lib/active_record/attribute_methods/dirty.rb +49 -16
  37. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  38. data/lib/active_record/attribute_methods/query.rb +2 -2
  39. data/lib/active_record/attribute_methods/read.rb +7 -5
  40. data/lib/active_record/attribute_methods/serialization.rb +66 -12
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  42. data/lib/active_record/attribute_methods/write.rb +7 -10
  43. data/lib/active_record/attribute_methods.rb +13 -14
  44. data/lib/active_record/attributes.rb +24 -35
  45. data/lib/active_record/autosave_association.rb +8 -23
  46. data/lib/active_record/base.rb +19 -1
  47. data/lib/active_record/callbacks.rb +2 -2
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +47 -561
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +46 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +42 -72
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +38 -13
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +78 -22
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +149 -74
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +97 -81
  63. data/lib/active_record/connection_adapters/column.rb +4 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +38 -24
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +35 -21
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +5 -1
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  69. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  70. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +21 -12
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  80. data/lib/active_record/connection_adapters/postgresql/quoting.rb +50 -50
  81. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  83. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  84. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  85. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +35 -19
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +207 -107
  87. data/lib/active_record/connection_adapters/schema_cache.rb +29 -4
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +27 -19
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +28 -16
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +16 -14
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +89 -30
  92. data/lib/active_record/connection_adapters.rb +6 -5
  93. data/lib/active_record/connection_handling.rb +47 -53
  94. data/lib/active_record/core.rb +122 -132
  95. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  96. data/lib/active_record/database_configurations/database_config.rb +12 -9
  97. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  98. data/lib/active_record/database_configurations/url_config.rb +2 -2
  99. data/lib/active_record/database_configurations.rb +16 -32
  100. data/lib/active_record/delegated_type.rb +52 -11
  101. data/lib/active_record/destroy_association_async_job.rb +1 -1
  102. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  103. data/lib/active_record/dynamic_matchers.rb +1 -1
  104. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  105. data/lib/active_record/encryption/cipher.rb +53 -0
  106. data/lib/active_record/encryption/config.rb +44 -0
  107. data/lib/active_record/encryption/configurable.rb +61 -0
  108. data/lib/active_record/encryption/context.rb +35 -0
  109. data/lib/active_record/encryption/contexts.rb +72 -0
  110. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  111. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  112. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  113. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  114. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  115. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  116. data/lib/active_record/encryption/encryptor.rb +155 -0
  117. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  118. data/lib/active_record/encryption/errors.rb +15 -0
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  121. data/lib/active_record/encryption/key.rb +28 -0
  122. data/lib/active_record/encryption/key_generator.rb +42 -0
  123. data/lib/active_record/encryption/key_provider.rb +46 -0
  124. data/lib/active_record/encryption/message.rb +33 -0
  125. data/lib/active_record/encryption/message_serializer.rb +90 -0
  126. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  127. data/lib/active_record/encryption/properties.rb +76 -0
  128. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  129. data/lib/active_record/encryption/scheme.rb +99 -0
  130. data/lib/active_record/encryption.rb +55 -0
  131. data/lib/active_record/enum.rb +49 -42
  132. data/lib/active_record/errors.rb +67 -4
  133. data/lib/active_record/explain_registry.rb +11 -6
  134. data/lib/active_record/fixture_set/file.rb +15 -1
  135. data/lib/active_record/fixture_set/table_row.rb +41 -6
  136. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  137. data/lib/active_record/fixtures.rb +17 -20
  138. data/lib/active_record/future_result.rb +139 -0
  139. data/lib/active_record/gem_version.rb +4 -4
  140. data/lib/active_record/inheritance.rb +55 -17
  141. data/lib/active_record/insert_all.rb +80 -14
  142. data/lib/active_record/integration.rb +4 -3
  143. data/lib/active_record/internal_metadata.rb +3 -5
  144. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  145. data/lib/active_record/locking/optimistic.rb +10 -9
  146. data/lib/active_record/locking/pessimistic.rb +9 -3
  147. data/lib/active_record/log_subscriber.rb +14 -3
  148. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  149. data/lib/active_record/middleware/database_selector.rb +8 -3
  150. data/lib/active_record/middleware/shard_selector.rb +60 -0
  151. data/lib/active_record/migration/command_recorder.rb +4 -4
  152. data/lib/active_record/migration/compatibility.rb +107 -3
  153. data/lib/active_record/migration/join_table.rb +1 -1
  154. data/lib/active_record/migration.rb +109 -79
  155. data/lib/active_record/model_schema.rb +45 -58
  156. data/lib/active_record/nested_attributes.rb +13 -12
  157. data/lib/active_record/no_touching.rb +3 -3
  158. data/lib/active_record/null_relation.rb +2 -6
  159. data/lib/active_record/persistence.rb +219 -52
  160. data/lib/active_record/query_cache.rb +2 -2
  161. data/lib/active_record/query_logs.rb +138 -0
  162. data/lib/active_record/querying.rb +15 -5
  163. data/lib/active_record/railtie.rb +127 -17
  164. data/lib/active_record/railties/controller_runtime.rb +1 -1
  165. data/lib/active_record/railties/databases.rake +66 -129
  166. data/lib/active_record/readonly_attributes.rb +11 -0
  167. data/lib/active_record/reflection.rb +67 -50
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  169. data/lib/active_record/relation/batches.rb +3 -3
  170. data/lib/active_record/relation/calculations.rb +43 -38
  171. data/lib/active_record/relation/delegation.rb +7 -7
  172. data/lib/active_record/relation/finder_methods.rb +31 -35
  173. data/lib/active_record/relation/merger.rb +20 -13
  174. data/lib/active_record/relation/predicate_builder.rb +1 -6
  175. data/lib/active_record/relation/query_attribute.rb +5 -11
  176. data/lib/active_record/relation/query_methods.rb +249 -61
  177. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  178. data/lib/active_record/relation/spawn_methods.rb +2 -2
  179. data/lib/active_record/relation/where_clause.rb +10 -19
  180. data/lib/active_record/relation.rb +184 -84
  181. data/lib/active_record/result.rb +17 -7
  182. data/lib/active_record/runtime_registry.rb +9 -13
  183. data/lib/active_record/sanitization.rb +11 -7
  184. data/lib/active_record/schema.rb +38 -23
  185. data/lib/active_record/schema_dumper.rb +25 -19
  186. data/lib/active_record/schema_migration.rb +4 -4
  187. data/lib/active_record/scoping/default.rb +61 -12
  188. data/lib/active_record/scoping/named.rb +3 -11
  189. data/lib/active_record/scoping.rb +64 -34
  190. data/lib/active_record/serialization.rb +1 -1
  191. data/lib/active_record/signed_id.rb +1 -1
  192. data/lib/active_record/suppressor.rb +11 -15
  193. data/lib/active_record/tasks/database_tasks.rb +120 -58
  194. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  195. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -12
  196. data/lib/active_record/test_databases.rb +1 -1
  197. data/lib/active_record/test_fixtures.rb +4 -4
  198. data/lib/active_record/timestamp.rb +3 -4
  199. data/lib/active_record/transactions.rb +9 -14
  200. data/lib/active_record/translation.rb +2 -2
  201. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  202. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  203. data/lib/active_record/type/internal/timezone.rb +2 -2
  204. data/lib/active_record/type/serialized.rb +1 -1
  205. data/lib/active_record/type/type_map.rb +17 -20
  206. data/lib/active_record/type.rb +1 -2
  207. data/lib/active_record/validations/associated.rb +1 -1
  208. data/lib/active_record/validations/uniqueness.rb +1 -1
  209. data/lib/active_record.rb +204 -28
  210. data/lib/arel/attributes/attribute.rb +0 -8
  211. data/lib/arel/crud.rb +28 -22
  212. data/lib/arel/delete_manager.rb +18 -4
  213. data/lib/arel/filter_predications.rb +9 -0
  214. data/lib/arel/insert_manager.rb +2 -3
  215. data/lib/arel/nodes/casted.rb +1 -1
  216. data/lib/arel/nodes/delete_statement.rb +12 -13
  217. data/lib/arel/nodes/filter.rb +10 -0
  218. data/lib/arel/nodes/function.rb +1 -0
  219. data/lib/arel/nodes/insert_statement.rb +2 -2
  220. data/lib/arel/nodes/select_core.rb +2 -2
  221. data/lib/arel/nodes/select_statement.rb +2 -2
  222. data/lib/arel/nodes/update_statement.rb +8 -3
  223. data/lib/arel/nodes.rb +1 -0
  224. data/lib/arel/predications.rb +11 -3
  225. data/lib/arel/select_manager.rb +10 -4
  226. data/lib/arel/table.rb +0 -1
  227. data/lib/arel/tree_manager.rb +0 -12
  228. data/lib/arel/update_manager.rb +18 -4
  229. data/lib/arel/visitors/dot.rb +80 -90
  230. data/lib/arel/visitors/mysql.rb +8 -2
  231. data/lib/arel/visitors/postgresql.rb +0 -10
  232. data/lib/arel/visitors/to_sql.rb +58 -2
  233. data/lib/arel.rb +2 -1
  234. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  235. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  236. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  237. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  238. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  239. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  240. metadata +56 -11
@@ -52,7 +52,7 @@ module ActiveRecord
52
52
  # * <tt>:port</tt> - Defaults to 5432.
53
53
  # * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
54
54
  # * <tt>:password</tt> - Password to be used if the server demands password authentication.
55
- # * <tt>:database</tt> - Defaults to be the same as the user name.
55
+ # * <tt>:database</tt> - Defaults to be the same as the username.
56
56
  # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
57
57
  # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
58
58
  # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
@@ -75,10 +75,14 @@ module ActiveRecord
75
75
 
76
76
  class << self
77
77
  def new_client(conn_params)
78
- PG.connect(conn_params)
78
+ PG.connect(**conn_params)
79
79
  rescue ::PG::Error => error
80
80
  if conn_params && conn_params[:dbname] && error.message.include?(conn_params[:dbname])
81
- raise ActiveRecord::NoDatabaseError
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])
82
86
  else
83
87
  raise ActiveRecord::ConnectionNotEstablished, error.message
84
88
  end
@@ -98,14 +102,35 @@ module ActiveRecord
98
102
  # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
99
103
  class_attribute :create_unlogged_tables, default: false
100
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
122
+
101
123
  NATIVE_DATABASE_TYPES = {
102
124
  primary_key: "bigserial primary key",
103
125
  string: { name: "character varying" },
104
126
  text: { name: "text" },
105
127
  integer: { name: "integer", limit: 4 },
128
+ bigint: { name: "bigint" },
106
129
  float: { name: "float" },
107
130
  decimal: { name: "decimal" },
108
- datetime: { name: "timestamp" },
131
+ datetime: {}, # set dynamically based on datetime_type
132
+ timestamp: { name: "timestamp" },
133
+ timestamptz: { name: "timestamptz" },
109
134
  time: { name: "time" },
110
135
  date: { name: "date" },
111
136
  daterange: { name: "daterange" },
@@ -139,9 +164,10 @@ module ActiveRecord
139
164
  money: { name: "money" },
140
165
  interval: { name: "interval" },
141
166
  oid: { name: "oid" },
167
+ enum: {} # special type https://www.postgresql.org/docs/current/datatype-enum.html
142
168
  }
143
169
 
144
- OID = PostgreSQL::OID #:nodoc:
170
+ OID = PostgreSQL::OID # :nodoc:
145
171
 
146
172
  include PostgreSQL::Quoting
147
173
  include PostgreSQL::ReferentialIntegrity
@@ -157,7 +183,7 @@ module ActiveRecord
157
183
  end
158
184
 
159
185
  def supports_partitioned_indexes?
160
- database_version >= 110_000
186
+ database_version >= 110_000 # >= 11.0
161
187
  end
162
188
 
163
189
  def supports_partial_index?
@@ -184,6 +210,10 @@ module ActiveRecord
184
210
  true
185
211
  end
186
212
 
213
+ def supports_deferrable_constraints?
214
+ true
215
+ end
216
+
187
217
  def supports_views?
188
218
  true
189
219
  end
@@ -209,12 +239,16 @@ module ActiveRecord
209
239
  end
210
240
 
211
241
  def supports_insert_on_conflict?
212
- database_version >= 90500
242
+ database_version >= 90500 # >= 9.5
213
243
  end
214
244
  alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
215
245
  alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
216
246
  alias supports_insert_conflict_target? supports_insert_on_conflict?
217
247
 
248
+ def supports_virtual_columns?
249
+ database_version >= 120_000 # >= 12.0
250
+ end
251
+
218
252
  def index_algorithms
219
253
  { concurrently: "CONCURRENTLY" }
220
254
  end
@@ -247,7 +281,7 @@ module ActiveRecord
247
281
  def initialize(connection, logger, connection_parameters, config)
248
282
  super(connection, logger, config)
249
283
 
250
- @connection_parameters = connection_parameters
284
+ @connection_parameters = connection_parameters || {}
251
285
 
252
286
  # @local_tz is initialized as nil to avoid warnings when connect tries to use it
253
287
  @local_tz = nil
@@ -272,19 +306,25 @@ module ActiveRecord
272
306
  # Is this connection alive and ready for queries?
273
307
  def active?
274
308
  @lock.synchronize do
275
- @connection.query "SELECT 1"
309
+ @connection.query ";"
276
310
  end
277
311
  true
278
312
  rescue PG::Error
279
313
  false
280
314
  end
281
315
 
316
+ def reload_type_map # :nodoc:
317
+ type_map.clear
318
+ initialize_type_map
319
+ end
320
+
282
321
  # Close then reopen the connection.
283
322
  def reconnect!
284
323
  @lock.synchronize do
285
324
  super
286
325
  @connection.reset
287
326
  configure_connection
327
+ reload_type_map
288
328
  rescue PG::ConnectionBad
289
329
  connect
290
330
  end
@@ -317,8 +357,16 @@ module ActiveRecord
317
357
  @connection = nil
318
358
  end
319
359
 
320
- def native_database_types #:nodoc:
321
- 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
322
370
  end
323
371
 
324
372
  def set_standard_conforming_strings
@@ -350,7 +398,7 @@ module ActiveRecord
350
398
  end
351
399
 
352
400
  def supports_pgcrypto_uuid?
353
- database_version >= 90400
401
+ database_version >= 90400 # >= 9.4
354
402
  end
355
403
 
356
404
  def supports_optimizer_hints?
@@ -406,6 +454,38 @@ module ActiveRecord
406
454
  exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
407
455
  end
408
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
+
409
489
  # Returns the configured supported identifier length supported by PostgreSQL
410
490
  def max_identifier_length
411
491
  @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
@@ -438,8 +518,12 @@ module ActiveRecord
438
518
  sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
439
519
  elsif insert.update_duplicates?
440
520
  sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
441
- sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column} IS NOT DISTINCT FROM excluded.#{column}" }
442
- sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
521
+ if insert.raw_update_sql?
522
+ sql << insert.raw_update_sql
523
+ else
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(",")
526
+ end
443
527
  end
444
528
 
445
529
  sql << " RETURNING #{insert.returning}" if insert.returning
@@ -447,73 +531,13 @@ module ActiveRecord
447
531
  end
448
532
 
449
533
  def check_version # :nodoc:
450
- if database_version < 90300
534
+ if database_version < 90300 # < 9.3
451
535
  raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
452
536
  end
453
537
  end
454
538
 
455
- private
456
- # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
457
- VALUE_LIMIT_VIOLATION = "22001"
458
- NUMERIC_VALUE_OUT_OF_RANGE = "22003"
459
- NOT_NULL_VIOLATION = "23502"
460
- FOREIGN_KEY_VIOLATION = "23503"
461
- UNIQUE_VIOLATION = "23505"
462
- SERIALIZATION_FAILURE = "40001"
463
- DEADLOCK_DETECTED = "40P01"
464
- DUPLICATE_DATABASE = "42P04"
465
- LOCK_NOT_AVAILABLE = "55P03"
466
- QUERY_CANCELED = "57014"
467
-
468
- def translate_exception(exception, message:, sql:, binds:)
469
- return exception unless exception.respond_to?(:result)
470
-
471
- case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
472
- when nil
473
- if exception.message.match?(/connection is closed/i)
474
- ConnectionNotEstablished.new(exception)
475
- else
476
- super
477
- end
478
- when UNIQUE_VIOLATION
479
- RecordNotUnique.new(message, sql: sql, binds: binds)
480
- when FOREIGN_KEY_VIOLATION
481
- InvalidForeignKey.new(message, sql: sql, binds: binds)
482
- when VALUE_LIMIT_VIOLATION
483
- ValueTooLong.new(message, sql: sql, binds: binds)
484
- when NUMERIC_VALUE_OUT_OF_RANGE
485
- RangeError.new(message, sql: sql, binds: binds)
486
- when NOT_NULL_VIOLATION
487
- NotNullViolation.new(message, sql: sql, binds: binds)
488
- when SERIALIZATION_FAILURE
489
- SerializationFailure.new(message, sql: sql, binds: binds)
490
- when DEADLOCK_DETECTED
491
- Deadlocked.new(message, sql: sql, binds: binds)
492
- when DUPLICATE_DATABASE
493
- DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
494
- when LOCK_NOT_AVAILABLE
495
- LockWaitTimeout.new(message, sql: sql, binds: binds)
496
- when QUERY_CANCELED
497
- QueryCanceled.new(message, sql: sql, binds: binds)
498
- else
499
- super
500
- end
501
- end
502
-
503
- def get_oid_type(oid, fmod, column_name, sql_type = "")
504
- if !type_map.key?(oid)
505
- load_additional_types([oid])
506
- end
507
-
508
- type_map.fetch(oid, fmod, sql_type) {
509
- warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
510
- Type.default_value.tap do |cast_type|
511
- type_map.register_type(oid, cast_type)
512
- end
513
- }
514
- end
515
-
516
- def initialize_type_map(m = type_map)
539
+ class << self
540
+ def initialize_type_map(m) # :nodoc:
517
541
  m.register_type "int2", Type::Integer.new(limit: 2)
518
542
  m.register_type "int4", Type::Integer.new(limit: 4)
519
543
  m.register_type "int8", Type::Integer.new(limit: 8)
@@ -528,7 +552,6 @@ module ActiveRecord
528
552
  m.register_type "bool", Type::Boolean.new
529
553
  register_class_with_limit m, "bit", OID::Bit
530
554
  register_class_with_limit m, "varbit", OID::BitVarying
531
- m.alias_type "timestamptz", "timestamp"
532
555
  m.register_type "date", OID::Date.new
533
556
 
534
557
  m.register_type "money", OID::Money.new
@@ -553,7 +576,8 @@ module ActiveRecord
553
576
  m.register_type "circle", OID::SpecializedString.new(:circle)
554
577
 
555
578
  register_class_with_precision m, "time", Type::Time
556
- 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
557
581
 
558
582
  m.register_type "numeric" do |_, fmod, sql_type|
559
583
  precision = extract_precision(sql_type)
@@ -579,7 +603,16 @@ module ActiveRecord
579
603
  precision = extract_precision(sql_type)
580
604
  OID::Interval.new(precision: precision)
581
605
  end
606
+ end
607
+ end
608
+
609
+ private
610
+ def type_map
611
+ @type_map ||= Type::HashLookupTypeMap.new
612
+ end
582
613
 
614
+ def initialize_type_map(m = type_map)
615
+ self.class.initialize_type_map(m)
583
616
  load_additional_types
584
617
  end
585
618
 
@@ -587,7 +620,7 @@ module ActiveRecord
587
620
  def extract_value_from_default(default)
588
621
  case default
589
622
  # Quoted types
590
- when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
623
+ when /\A[(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
591
624
  # The default 'now'::date is CURRENT_DATE
592
625
  if $1 == "now" && $2 == "date"
593
626
  nil
@@ -618,37 +651,100 @@ module ActiveRecord
618
651
  !default_value && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
619
652
  end
620
653
 
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"
665
+
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)
696
+ else
697
+ super
698
+ end
699
+ end
700
+
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
+
621
714
  def load_additional_types(oids = nil)
622
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
720
+ end
721
+ end
623
722
 
723
+ def load_types_queries(initializer, oids)
624
724
  query = <<~SQL
625
725
  SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
626
726
  FROM pg_type as t
627
727
  LEFT JOIN pg_range as r ON oid = rngtypid
628
728
  SQL
629
-
630
729
  if oids
631
- query += "WHERE t.oid IN (%s)" % oids.join(", ")
730
+ yield query + "WHERE t.oid IN (%s)" % oids.join(", ")
632
731
  else
633
- query += initializer.query_conditions_for_initial_load
634
- end
635
-
636
- execute_and_clear(query, "SCHEMA", []) do |records|
637
- initializer.run(records)
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
638
735
  end
639
736
  end
640
737
 
641
- FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
738
+ FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
642
739
 
643
- def execute_and_clear(sql, name, binds, prepare: false)
644
- if preventing_writes? && write_query?(sql)
645
- raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
646
- end
740
+ def execute_and_clear(sql, name, binds, prepare: false, async: false)
741
+ sql = transform_query(sql)
742
+ check_if_write_query(sql)
647
743
 
648
744
  if !prepare || without_prepared_statement?(binds)
649
- result = exec_no_cache(sql, name, binds)
745
+ result = exec_no_cache(sql, name, binds, async: async)
650
746
  else
651
- result = exec_cache(sql, name, binds)
747
+ result = exec_cache(sql, name, binds, async: async)
652
748
  end
653
749
  begin
654
750
  ret = yield result
@@ -658,23 +754,23 @@ module ActiveRecord
658
754
  ret
659
755
  end
660
756
 
661
- def exec_no_cache(sql, name, binds)
757
+ def exec_no_cache(sql, name, binds, async: false)
662
758
  materialize_transactions
663
759
  mark_transaction_written_if_write(sql)
664
760
 
665
- # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
761
+ # make sure we carry over any changes to ActiveRecord.default_timezone that have been
666
762
  # made since we established the connection
667
763
  update_typemap_for_default_timezone
668
764
 
669
765
  type_casted_binds = type_casted_binds(binds)
670
- log(sql, name, binds, type_casted_binds) do
766
+ log(sql, name, binds, type_casted_binds, async: async) do
671
767
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
672
768
  @connection.exec_params(sql, type_casted_binds)
673
769
  end
674
770
  end
675
771
  end
676
772
 
677
- def exec_cache(sql, name, binds)
773
+ def exec_cache(sql, name, binds, async: false)
678
774
  materialize_transactions
679
775
  mark_transaction_written_if_write(sql)
680
776
  update_typemap_for_default_timezone
@@ -682,7 +778,7 @@ module ActiveRecord
682
778
  stmt_key = prepare_statement(sql, binds)
683
779
  type_casted_binds = type_casted_binds(binds)
684
780
 
685
- log(sql, name, binds, type_casted_binds, stmt_key) do
781
+ log(sql, name, binds, type_casted_binds, stmt_key, async: async) do
686
782
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
687
783
  @connection.exec_prepared(stmt_key, type_casted_binds)
688
784
  end
@@ -776,7 +872,7 @@ module ActiveRecord
776
872
  # If using Active Record's time zone support configure the connection to return
777
873
  # TIMESTAMP WITH ZONE types in UTC.
778
874
  unless variables["timezone"]
779
- if ActiveRecord::Base.default_timezone == :utc
875
+ if ActiveRecord.default_timezone == :utc
780
876
  variables["timezone"] = "UTC"
781
877
  elsif @local_tz
782
878
  variables["timezone"] = @local_tz
@@ -820,7 +916,8 @@ module ActiveRecord
820
916
  query(<<~SQL, "SCHEMA")
821
917
  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
822
918
  pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
823
- 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
824
921
  FROM pg_attribute a
825
922
  LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
826
923
  LEFT JOIN pg_type t ON a.atttypid = t.oid
@@ -875,14 +972,19 @@ module ActiveRecord
875
972
  end
876
973
 
877
974
  def update_typemap_for_default_timezone
878
- if @default_timezone != ActiveRecord::Base.default_timezone && @timestamp_decoder
879
- decoder_class = ActiveRecord::Base.default_timezone == :utc ?
975
+ if @default_timezone != ActiveRecord.default_timezone && @timestamp_decoder
976
+ decoder_class = ActiveRecord.default_timezone == :utc ?
880
977
  PG::TextDecoder::TimestampUtc :
881
978
  PG::TextDecoder::TimestampWithoutTimeZone
882
979
 
883
980
  @timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h)
884
981
  @connection.type_map_for_results.add_coder(@timestamp_decoder)
885
- @default_timezone = ActiveRecord::Base.default_timezone
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
886
988
  end
887
989
  end
888
990
 
@@ -910,9 +1012,7 @@ module ActiveRecord
910
1012
  WHERE t.typname IN (%s)
911
1013
  SQL
912
1014
  coders = execute_and_clear(query, "SCHEMA", []) do |result|
913
- result
914
- .map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
915
- .compact
1015
+ result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
916
1016
  end
917
1017
 
918
1018
  map = PG::TypeMapByOid.new
@@ -86,6 +86,7 @@ module ActiveRecord
86
86
 
87
87
  # A cached lookup for table existence.
88
88
  def data_source_exists?(name)
89
+ return if ignored_table?(name)
89
90
  prepare_data_sources if @data_sources.empty?
90
91
  return @data_sources[name] if @data_sources.key? name
91
92
 
@@ -108,6 +109,10 @@ module ActiveRecord
108
109
 
109
110
  # Get the columns for a table
110
111
  def columns(table_name)
112
+ if ignored_table?(table_name)
113
+ raise ActiveRecord::StatementInvalid, "Table '#{table_name}' doesn't exist"
114
+ end
115
+
111
116
  @columns.fetch(table_name) do
112
117
  @columns[deep_deduplicate(table_name)] = deep_deduplicate(connection.columns(table_name))
113
118
  end
@@ -128,7 +133,11 @@ module ActiveRecord
128
133
 
129
134
  def indexes(table_name)
130
135
  @indexes.fetch(table_name) do
131
- @indexes[deep_deduplicate(table_name)] = deep_deduplicate(connection.indexes(table_name))
136
+ if data_source_exists?(table_name)
137
+ @indexes[deep_deduplicate(table_name)] = deep_deduplicate(connection.indexes(table_name))
138
+ else
139
+ []
140
+ end
132
141
  end
133
142
  end
134
143
 
@@ -162,7 +171,7 @@ module ActiveRecord
162
171
 
163
172
  def dump_to(filename)
164
173
  clear!
165
- connection.data_sources.each { |table| add(table) }
174
+ tables_to_cache.each { |table| add(table) }
166
175
  open(filename) { |f|
167
176
  if filename.include?(".dump")
168
177
  f.write(Marshal.dump(self))
@@ -186,8 +195,20 @@ module ActiveRecord
186
195
  end
187
196
 
188
197
  private
198
+ def tables_to_cache
199
+ connection.data_sources.reject do |table|
200
+ ignored_table?(table)
201
+ end
202
+ end
203
+
204
+ def ignored_table?(table_name)
205
+ ActiveRecord.schema_cache_ignored_tables.any? do |ignored|
206
+ ignored === table_name
207
+ end
208
+ end
209
+
189
210
  def reset_version!
190
- @version = connection.migration_context.current_version
211
+ @version = connection.schema_version
191
212
  end
192
213
 
193
214
  def derive_columns_hash_and_deduplicate_values
@@ -212,10 +233,14 @@ module ActiveRecord
212
233
  end
213
234
 
214
235
  def prepare_data_sources
215
- connection.data_sources.each { |source| @data_sources[source] = true }
236
+ tables_to_cache.each do |source|
237
+ @data_sources[source] = true
238
+ end
216
239
  end
217
240
 
218
241
  def open(filename)
242
+ FileUtils.mkdir_p(File.dirname(filename))
243
+
219
244
  File.atomic_write(filename) do |file|
220
245
  if File.extname(filename) == ".gz"
221
246
  zipper = Zlib::GzipWriter.new file