activerecord 6.0.3.4 → 6.1.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 (245) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +891 -695
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_record.rb +7 -14
  6. data/lib/active_record/aggregations.rb +5 -5
  7. data/lib/active_record/association_relation.rb +30 -12
  8. data/lib/active_record/associations.rb +118 -11
  9. data/lib/active_record/associations/alias_tracker.rb +19 -15
  10. data/lib/active_record/associations/association.rb +44 -28
  11. data/lib/active_record/associations/association_scope.rb +19 -15
  12. data/lib/active_record/associations/belongs_to_association.rb +22 -8
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
  14. data/lib/active_record/associations/builder/association.rb +32 -5
  15. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  16. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
  18. data/lib/active_record/associations/builder/has_many.rb +6 -2
  19. data/lib/active_record/associations/builder/has_one.rb +11 -14
  20. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  21. data/lib/active_record/associations/collection_association.rb +19 -6
  22. data/lib/active_record/associations/collection_proxy.rb +13 -5
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +24 -2
  25. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  26. data/lib/active_record/associations/has_one_association.rb +15 -1
  27. data/lib/active_record/associations/join_dependency.rb +72 -50
  28. data/lib/active_record/associations/join_dependency/join_association.rb +39 -16
  29. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  30. data/lib/active_record/associations/preloader.rb +11 -5
  31. data/lib/active_record/associations/preloader/association.rb +51 -25
  32. data/lib/active_record/associations/preloader/through_association.rb +2 -2
  33. data/lib/active_record/associations/singular_association.rb +1 -1
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/attribute_assignment.rb +10 -8
  36. data/lib/active_record/attribute_methods.rb +64 -54
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
  38. data/lib/active_record/attribute_methods/dirty.rb +1 -11
  39. data/lib/active_record/attribute_methods/primary_key.rb +6 -2
  40. data/lib/active_record/attribute_methods/query.rb +3 -6
  41. data/lib/active_record/attribute_methods/read.rb +8 -11
  42. data/lib/active_record/attribute_methods/serialization.rb +11 -5
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
  44. data/lib/active_record/attribute_methods/write.rb +12 -20
  45. data/lib/active_record/attributes.rb +33 -8
  46. data/lib/active_record/autosave_association.rb +57 -40
  47. data/lib/active_record/base.rb +2 -14
  48. data/lib/active_record/callbacks.rb +152 -22
  49. data/lib/active_record/coders/yaml_column.rb +1 -1
  50. data/lib/active_record/connection_adapters.rb +50 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +191 -134
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -8
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +116 -27
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +228 -83
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +80 -32
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +54 -72
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +133 -96
  64. data/lib/active_record/connection_adapters/column.rb +15 -1
  65. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  66. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  67. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -25
  68. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
  69. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  70. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -6
  71. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  72. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
  73. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +11 -7
  74. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  75. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
  76. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  77. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  78. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  79. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +13 -54
  80. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  82. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  83. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -5
  89. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
  90. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  91. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  92. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
  93. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
  94. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -58
  96. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  97. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +31 -6
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  101. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +37 -4
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +49 -50
  103. data/lib/active_record/connection_handling.rb +218 -71
  104. data/lib/active_record/core.rb +245 -61
  105. data/lib/active_record/database_configurations.rb +124 -85
  106. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  107. data/lib/active_record/database_configurations/database_config.rb +52 -9
  108. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  109. data/lib/active_record/database_configurations/url_config.rb +15 -40
  110. data/lib/active_record/delegated_type.rb +209 -0
  111. data/lib/active_record/destroy_association_async_job.rb +36 -0
  112. data/lib/active_record/enum.rb +82 -38
  113. data/lib/active_record/errors.rb +47 -12
  114. data/lib/active_record/explain.rb +9 -4
  115. data/lib/active_record/explain_subscriber.rb +1 -1
  116. data/lib/active_record/fixture_set/file.rb +10 -17
  117. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  118. data/lib/active_record/fixture_set/render_context.rb +1 -1
  119. data/lib/active_record/fixture_set/table_row.rb +2 -2
  120. data/lib/active_record/fixtures.rb +58 -9
  121. data/lib/active_record/gem_version.rb +3 -3
  122. data/lib/active_record/inheritance.rb +40 -18
  123. data/lib/active_record/insert_all.rb +35 -6
  124. data/lib/active_record/integration.rb +3 -5
  125. data/lib/active_record/internal_metadata.rb +16 -7
  126. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  127. data/lib/active_record/locking/optimistic.rb +33 -17
  128. data/lib/active_record/locking/pessimistic.rb +6 -2
  129. data/lib/active_record/log_subscriber.rb +27 -8
  130. data/lib/active_record/middleware/database_selector.rb +4 -1
  131. data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
  132. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  133. data/lib/active_record/migration.rb +113 -83
  134. data/lib/active_record/migration/command_recorder.rb +47 -27
  135. data/lib/active_record/migration/compatibility.rb +68 -17
  136. data/lib/active_record/model_schema.rb +117 -13
  137. data/lib/active_record/nested_attributes.rb +2 -3
  138. data/lib/active_record/no_touching.rb +1 -1
  139. data/lib/active_record/persistence.rb +50 -45
  140. data/lib/active_record/query_cache.rb +15 -5
  141. data/lib/active_record/querying.rb +11 -6
  142. data/lib/active_record/railtie.rb +64 -44
  143. data/lib/active_record/railties/console_sandbox.rb +2 -4
  144. data/lib/active_record/railties/databases.rake +276 -99
  145. data/lib/active_record/readonly_attributes.rb +4 -0
  146. data/lib/active_record/reflection.rb +71 -57
  147. data/lib/active_record/relation.rb +96 -67
  148. data/lib/active_record/relation/batches.rb +38 -31
  149. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  150. data/lib/active_record/relation/calculations.rb +101 -44
  151. data/lib/active_record/relation/delegation.rb +2 -1
  152. data/lib/active_record/relation/finder_methods.rb +45 -15
  153. data/lib/active_record/relation/from_clause.rb +1 -1
  154. data/lib/active_record/relation/merger.rb +27 -25
  155. data/lib/active_record/relation/predicate_builder.rb +59 -38
  156. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  157. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  158. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
  159. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  160. data/lib/active_record/relation/query_methods.rb +333 -195
  161. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  162. data/lib/active_record/relation/spawn_methods.rb +8 -7
  163. data/lib/active_record/relation/where_clause.rb +104 -57
  164. data/lib/active_record/result.rb +41 -33
  165. data/lib/active_record/runtime_registry.rb +2 -2
  166. data/lib/active_record/sanitization.rb +6 -17
  167. data/lib/active_record/schema_dumper.rb +34 -4
  168. data/lib/active_record/schema_migration.rb +2 -8
  169. data/lib/active_record/scoping/named.rb +6 -17
  170. data/lib/active_record/secure_token.rb +16 -8
  171. data/lib/active_record/serialization.rb +5 -3
  172. data/lib/active_record/signed_id.rb +116 -0
  173. data/lib/active_record/statement_cache.rb +20 -4
  174. data/lib/active_record/store.rb +2 -2
  175. data/lib/active_record/suppressor.rb +2 -2
  176. data/lib/active_record/table_metadata.rb +42 -51
  177. data/lib/active_record/tasks/database_tasks.rb +140 -113
  178. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
  179. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
  180. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
  181. data/lib/active_record/test_databases.rb +5 -4
  182. data/lib/active_record/test_fixtures.rb +37 -16
  183. data/lib/active_record/timestamp.rb +4 -6
  184. data/lib/active_record/touch_later.rb +21 -21
  185. data/lib/active_record/transactions.rb +19 -66
  186. data/lib/active_record/type.rb +8 -1
  187. data/lib/active_record/type/serialized.rb +6 -2
  188. data/lib/active_record/type/time.rb +10 -0
  189. data/lib/active_record/type_caster/connection.rb +0 -1
  190. data/lib/active_record/type_caster/map.rb +8 -5
  191. data/lib/active_record/validations.rb +1 -0
  192. data/lib/active_record/validations/numericality.rb +35 -0
  193. data/lib/active_record/validations/uniqueness.rb +24 -4
  194. data/lib/arel.rb +5 -13
  195. data/lib/arel/attributes/attribute.rb +4 -0
  196. data/lib/arel/collectors/bind.rb +5 -0
  197. data/lib/arel/collectors/composite.rb +8 -0
  198. data/lib/arel/collectors/sql_string.rb +7 -0
  199. data/lib/arel/collectors/substitute_binds.rb +7 -0
  200. data/lib/arel/nodes.rb +3 -1
  201. data/lib/arel/nodes/binary.rb +82 -8
  202. data/lib/arel/nodes/bind_param.rb +8 -0
  203. data/lib/arel/nodes/casted.rb +21 -9
  204. data/lib/arel/nodes/equality.rb +6 -9
  205. data/lib/arel/nodes/grouping.rb +3 -0
  206. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  207. data/lib/arel/nodes/in.rb +8 -1
  208. data/lib/arel/nodes/infix_operation.rb +13 -1
  209. data/lib/arel/nodes/join_source.rb +1 -1
  210. data/lib/arel/nodes/node.rb +7 -6
  211. data/lib/arel/nodes/ordering.rb +27 -0
  212. data/lib/arel/nodes/sql_literal.rb +3 -0
  213. data/lib/arel/nodes/table_alias.rb +7 -3
  214. data/lib/arel/nodes/unary.rb +0 -1
  215. data/lib/arel/predications.rb +12 -18
  216. data/lib/arel/select_manager.rb +1 -2
  217. data/lib/arel/table.rb +13 -5
  218. data/lib/arel/visitors.rb +0 -7
  219. data/lib/arel/visitors/dot.rb +14 -2
  220. data/lib/arel/visitors/mysql.rb +11 -1
  221. data/lib/arel/visitors/postgresql.rb +15 -4
  222. data/lib/arel/visitors/to_sql.rb +89 -78
  223. data/lib/rails/generators/active_record/migration.rb +6 -1
  224. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  225. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  226. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
  227. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  228. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  229. metadata +25 -26
  230. data/lib/active_record/advisory_lock_base.rb +0 -18
  231. data/lib/active_record/attribute_decorators.rb +0 -88
  232. data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
  233. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  234. data/lib/active_record/define_callbacks.rb +0 -22
  235. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  236. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  237. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  238. data/lib/arel/attributes.rb +0 -22
  239. data/lib/arel/visitors/depth_first.rb +0 -203
  240. data/lib/arel/visitors/ibm_db.rb +0 -34
  241. data/lib/arel/visitors/informix.rb +0 -62
  242. data/lib/arel/visitors/mssql.rb +0 -156
  243. data/lib/arel/visitors/oracle.rb +0 -158
  244. data/lib/arel/visitors/oracle12.rb +0 -65
  245. data/lib/arel/visitors/where_sql.rb +0 -22
@@ -67,15 +67,34 @@ module ActiveRecord
67
67
  end
68
68
 
69
69
  def extract_bounds(value)
70
- from, to = value[1..-2].split(",")
70
+ from, to = value[1..-2].split(",", 2)
71
71
  {
72
- from: (value[1] == "," || from == "-infinity") ? infinity(negative: true) : from,
73
- to: (value[-2] == "," || to == "infinity") ? infinity : to,
74
- exclude_start: (value[0] == "("),
75
- exclude_end: (value[-1] == ")")
72
+ from: (from == "" || from == "-infinity") ? infinity(negative: true) : unquote(from),
73
+ to: (to == "" || to == "infinity") ? infinity : unquote(to),
74
+ exclude_start: value.start_with?("("),
75
+ exclude_end: value.end_with?(")")
76
76
  }
77
77
  end
78
78
 
79
+ # When formatting the bound values of range types, PostgreSQL quotes
80
+ # the bound value using double-quotes in certain conditions. Within
81
+ # a double-quoted string, literal " and \ characters are themselves
82
+ # escaped. In input, PostgreSQL accepts multiple escape styles for "
83
+ # (either \" or "") but in output always uses "".
84
+ # See:
85
+ # * https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-IO
86
+ # * https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-IO-SYNTAX
87
+ def unquote(value)
88
+ if value.start_with?('"') && value.end_with?('"')
89
+ unquoted_value = value[1..-2]
90
+ unquoted_value.gsub!('""', '"')
91
+ unquoted_value.gsub!('\\\\', '\\')
92
+ unquoted_value
93
+ else
94
+ value
95
+ end
96
+ end
97
+
79
98
  def infinity(negative: false)
80
99
  if subtype.respond_to?(:infinity)
81
100
  subtype.infinity(negative: negative)
@@ -7,12 +7,22 @@ module ActiveRecord
7
7
  class Uuid < Type::Value # :nodoc:
8
8
  ACCEPTABLE_UUID = %r{\A(\{)?([a-fA-F0-9]{4}-?){8}(?(1)\}|)\z}
9
9
 
10
- alias_method :serialize, :deserialize
10
+ alias :serialize :deserialize
11
11
 
12
12
  def type
13
13
  :uuid
14
14
  end
15
15
 
16
+ def changed?(old_value, new_value, _new_value_before_type_cast)
17
+ old_value.class != new_value.class ||
18
+ new_value && old_value.casecmp(new_value) != 0
19
+ end
20
+
21
+ def changed_in_place?(raw_old_value, new_value)
22
+ raw_old_value.class != new_value.class ||
23
+ new_value && raw_old_value.casecmp(new_value) != 0
24
+ end
25
+
16
26
  private
17
27
  def cast_value(value)
18
28
  casted = value.to_s
@@ -18,7 +18,7 @@ module ActiveRecord
18
18
 
19
19
  # Quotes strings for use in SQL input.
20
20
  def quote_string(s) #:nodoc:
21
- @connection.escape(s)
21
+ PG::Connection.escape(s)
22
22
  end
23
23
 
24
24
  # Checks the following cases:
@@ -67,8 +67,8 @@ module ActiveRecord
67
67
  elsif column.type == :uuid && value.is_a?(String) && /\(\)/.match?(value)
68
68
  value # Does not quote function default values for UUID columns
69
69
  elsif column.respond_to?(:array?)
70
- value = type_cast_from_column(column, value)
71
- quote(value)
70
+ type = lookup_cast_type_from_column(column)
71
+ quote(type.serialize(value))
72
72
  else
73
73
  super
74
74
  end
@@ -93,7 +93,7 @@ module ActiveRecord
93
93
  # "table_name"."column_name"::type_name | function(one or no argument)::type_name
94
94
  ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
95
95
  )
96
- (?:\s+AS\s+(?:\w+|"\w+"))?
96
+ (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
97
97
  )
98
98
  (?:\s*,\s*\g<1>)*
99
99
  \z
@@ -24,7 +24,7 @@ WARNING: Rails was not able to disable referential integrity.
24
24
  This is most likely caused due to missing permissions.
25
25
  Rails needs superuser privileges to disable referential integrity.
26
26
 
27
- cause: #{original_exception.try(:message)}
27
+ cause: #{original_exception&.message}
28
28
 
29
29
  WARNING
30
30
  raise e
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module PostgreSQL
6
- class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
6
+ class SchemaCreation < SchemaCreation # :nodoc:
7
7
  private
8
8
  def visit_AlterTable(o)
9
9
  super << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
@@ -13,6 +13,10 @@ module ActiveRecord
13
13
  super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
14
14
  end
15
15
 
16
+ def visit_CheckConstraintDefinition(o)
17
+ super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
18
+ end
19
+
16
20
  def visit_ValidateConstraint(name)
17
21
  "VALIDATE CONSTRAINT #{quote_column_name(name)}"
18
22
  end
@@ -54,7 +54,8 @@ module ActiveRecord
54
54
  execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
55
55
  end
56
56
 
57
- def drop_table(table_name, options = {}) # :nodoc:
57
+ def drop_table(table_name, **options) # :nodoc:
58
+ schema_cache.clear_data_source_cache!(table_name.to_s)
58
59
  execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
59
60
  end
60
61
 
@@ -211,7 +212,7 @@ module ActiveRecord
211
212
  end
212
213
 
213
214
  # Drops the schema for the given schema name.
214
- def drop_schema(schema_name, options = {})
215
+ def drop_schema(schema_name, **options)
215
216
  execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
216
217
  end
217
218
 
@@ -376,6 +377,8 @@ module ActiveRecord
376
377
  # rename_table('octopuses', 'octopi')
377
378
  def rename_table(table_name, new_name)
378
379
  clear_cache!
380
+ schema_cache.clear_data_source_cache!(table_name.to_s)
381
+ schema_cache.clear_data_source_cache!(new_name.to_s)
379
382
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
380
383
  pk, seq = pk_and_sequence_for(new_name)
381
384
  if pk
@@ -396,9 +399,9 @@ module ActiveRecord
396
399
  change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
397
400
  end
398
401
 
399
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
402
+ def change_column(table_name, column_name, type, **options) #:nodoc:
400
403
  clear_cache!
401
- sqls, procs = Array(change_column_for_alter(table_name, column_name, type, options)).partition { |v| v.is_a?(String) }
404
+ sqls, procs = Array(change_column_for_alter(table_name, column_name, type, **options)).partition { |v| v.is_a?(String) }
402
405
  execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
403
406
  procs.each(&:call)
404
407
  end
@@ -434,21 +437,24 @@ module ActiveRecord
434
437
  # Renames a column in a table.
435
438
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
436
439
  clear_cache!
437
- execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
440
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
438
441
  rename_column_indexes(table_name, column_name, new_column_name)
439
442
  end
440
443
 
441
- def add_index(table_name, column_name, options = {}) #:nodoc:
442
- index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, **options)
443
- execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns_and_opclasses})#{index_options}").tap do
444
- execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
445
- end
444
+ def add_index(table_name, column_name, **options) #:nodoc:
445
+ index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
446
+
447
+ create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
448
+ result = execute schema_creation.accept(create_index)
449
+
450
+ execute "COMMENT ON INDEX #{quote_column_name(index.name)} IS #{quote(index.comment)}" if index.comment
451
+ result
446
452
  end
447
453
 
448
- def remove_index(table_name, options = {}) #:nodoc:
454
+ def remove_index(table_name, column_name = nil, **options) # :nodoc:
449
455
  table = Utils.extract_schema_qualified_name(table_name.to_s)
450
456
 
451
- if options.is_a?(Hash) && options.key?(:name)
457
+ if options.key?(:name)
452
458
  provided_index = Utils.extract_schema_qualified_name(options[:name].to_s)
453
459
 
454
460
  options[:name] = provided_index.identifier
@@ -459,14 +465,11 @@ module ActiveRecord
459
465
  end
460
466
  end
461
467
 
462
- index_to_remove = PostgreSQL::Name.new(table.schema, index_name_for_remove(table.to_s, options))
463
- algorithm =
464
- if options.is_a?(Hash) && options.key?(:algorithm)
465
- index_algorithms.fetch(options[:algorithm]) do
466
- raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
467
- end
468
- end
469
- execute "DROP INDEX #{algorithm} #{quote_table_name(index_to_remove)}"
468
+ return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
469
+
470
+ index_to_remove = PostgreSQL::Name.new(table.schema, index_name_for_remove(table.to_s, column_name, options))
471
+
472
+ execute "DROP INDEX #{index_algorithm(options[:algorithm])} #{quote_table_name(index_to_remove)}"
470
473
  end
471
474
 
472
475
  # Renames an index of a table. Raises error if length of new
@@ -516,6 +519,28 @@ module ActiveRecord
516
519
  query_values(data_source_sql(table_name, type: "FOREIGN TABLE"), "SCHEMA").any? if table_name.present?
517
520
  end
518
521
 
522
+ def check_constraints(table_name) # :nodoc:
523
+ scope = quoted_scope(table_name)
524
+
525
+ check_info = exec_query(<<-SQL, "SCHEMA")
526
+ SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef, c.convalidated AS valid
527
+ FROM pg_constraint c
528
+ JOIN pg_class t ON c.conrelid = t.oid
529
+ WHERE c.contype = 'c'
530
+ AND t.relname = #{scope[:name]}
531
+ SQL
532
+
533
+ check_info.map do |row|
534
+ options = {
535
+ name: row["conname"],
536
+ validate: row["valid"]
537
+ }
538
+ expression = row["constraintdef"][/CHECK \({2}(.+)\){2}/, 1]
539
+
540
+ CheckConstraintDefinition.new(table_name, expression, options)
541
+ end
542
+ end
543
+
519
544
  # Maps logical Rails types to PostgreSQL-specific data types.
520
545
  def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
521
546
  sql = \
@@ -552,13 +577,13 @@ module ActiveRecord
552
577
  # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
553
578
  # requires that the ORDER BY include the distinct column.
554
579
  def columns_for_distinct(columns, orders) #:nodoc:
555
- order_columns = orders.reject(&:blank?).map { |s|
580
+ order_columns = orders.compact_blank.map { |s|
556
581
  # Convert Arel node to string
557
582
  s = visitor.compile(s) unless s.is_a?(String)
558
583
  # Remove any ASC/DESC modifiers
559
584
  s.gsub(/\s+(?:ASC|DESC)\b/i, "")
560
585
  .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
561
- }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
586
+ }.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" }
562
587
 
563
588
  (order_columns << super).join(", ")
564
589
  end
@@ -577,8 +602,6 @@ module ActiveRecord
577
602
  #
578
603
  # validate_constraint :accounts, :constraint_name
579
604
  def validate_constraint(table_name, constraint_name)
580
- return unless supports_validate_constraints?
581
-
582
605
  at = create_alter_table table_name
583
606
  at.validate_constraint constraint_name
584
607
 
@@ -601,20 +624,29 @@ module ActiveRecord
601
624
  #
602
625
  # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
603
626
  def validate_foreign_key(from_table, to_table = nil, **options)
604
- return unless supports_validate_constraints?
605
-
606
627
  fk_name_to_validate = foreign_key_for!(from_table, to_table: to_table, **options).name
607
628
 
608
629
  validate_constraint from_table, fk_name_to_validate
609
630
  end
610
631
 
632
+ # Validates the given check constraint.
633
+ #
634
+ # validate_check_constraint :products, name: "price_check"
635
+ #
636
+ # The +options+ hash accepts the same keys as add_check_constraint[rdoc-ref:ConnectionAdapters::SchemaStatements#add_check_constraint].
637
+ def validate_check_constraint(table_name, **options)
638
+ chk_name_to_validate = check_constraint_for!(table_name, **options).name
639
+
640
+ validate_constraint table_name, chk_name_to_validate
641
+ end
642
+
611
643
  private
612
644
  def schema_creation
613
645
  PostgreSQL::SchemaCreation.new(self)
614
646
  end
615
647
 
616
- def create_table_definition(*args, **options)
617
- PostgreSQL::TableDefinition.new(self, *args, **options)
648
+ def create_table_definition(name, **options)
649
+ PostgreSQL::TableDefinition.new(self, name, **options)
618
650
  end
619
651
 
620
652
  def create_alter_table(name)
@@ -684,7 +716,7 @@ module ActiveRecord
684
716
  [super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
685
717
  end
686
718
 
687
- def change_column_for_alter(table_name, column_name, type, options = {})
719
+ def change_column_for_alter(table_name, column_name, type, **options)
688
720
  td = create_table_definition(table_name)
689
721
  cd = td.new_column_definition(column_name, type, **options)
690
722
  sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
@@ -7,6 +7,8 @@ module ActiveRecord
7
7
  class TypeMetadata < DelegateClass(SqlTypeMetadata)
8
8
  undef to_yaml if method_defined?(:to_yaml)
9
9
 
10
+ include Deduplicable
11
+
10
12
  attr_reader :oid, :fmod
11
13
 
12
14
  def initialize(type_metadata, oid: nil, fmod: nil)
@@ -29,6 +31,12 @@ module ActiveRecord
29
31
  oid.hash ^
30
32
  fmod.hash
31
33
  end
34
+
35
+ private
36
+ def deduplicated
37
+ __setobj__(__getobj__.deduplicate)
38
+ super
39
+ end
32
40
  end
33
41
  end
34
42
  PostgreSQLTypeMetadata = PostgreSQL::TypeMetadata
@@ -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 # :nodoc:
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,19 +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
- conn = PG.connect(conn_params)
47
- ConnectionAdapters::PostgreSQLAdapter.new(conn, logger, conn_params, config)
48
- rescue ::PG::Error => error
49
- if error.message.include?(conn_params[:dbname])
50
- raise ActiveRecord::NoDatabaseError
51
- else
52
- raise
53
- end
36
+ ConnectionAdapters::PostgreSQLAdapter.new(
37
+ ConnectionAdapters::PostgreSQLAdapter.new_client(conn_params),
38
+ logger,
39
+ conn_params,
40
+ config,
41
+ )
54
42
  end
55
43
  end
56
44
 
57
45
  module ConnectionAdapters
58
- # 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.
59
47
  #
60
48
  # Options:
61
49
  #
@@ -85,6 +73,18 @@ module ActiveRecord
85
73
  class PostgreSQLAdapter < AbstractAdapter
86
74
  ADAPTER_NAME = "PostgreSQL"
87
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
82
+ else
83
+ raise ActiveRecord::ConnectionNotEstablished, error.message
84
+ end
85
+ end
86
+ end
87
+
88
88
  ##
89
89
  # :singleton-method:
90
90
  # PostgreSQL allows the creation of "unlogged" tables, which do not record
@@ -176,6 +176,10 @@ module ActiveRecord
176
176
  true
177
177
  end
178
178
 
179
+ def supports_check_constraints?
180
+ true
181
+ end
182
+
179
183
  def supports_validate_constraints?
180
184
  true
181
185
  end
@@ -341,11 +345,6 @@ module ActiveRecord
341
345
  true
342
346
  end
343
347
 
344
- def supports_ranges?
345
- true
346
- end
347
- deprecate :supports_ranges?
348
-
349
348
  def supports_materialized_views?
350
349
  true
351
350
  end
@@ -426,16 +425,6 @@ module ActiveRecord
426
425
  @use_insert_returning
427
426
  end
428
427
 
429
- def column_name_for_operation(operation, node) # :nodoc:
430
- OPERATION_ALIASES.fetch(operation) { operation.downcase }
431
- end
432
-
433
- OPERATION_ALIASES = { # :nodoc:
434
- "maximum" => "max",
435
- "minimum" => "min",
436
- "average" => "avg",
437
- }
438
-
439
428
  # Returns the version of the connected PostgreSQL server.
440
429
  def get_database_version # :nodoc:
441
430
  @connection.server_version
@@ -453,6 +442,7 @@ module ActiveRecord
453
442
  sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
454
443
  elsif insert.update_duplicates?
455
444
  sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
445
+ sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column} IS NOT DISTINCT FROM excluded.#{column}" }
456
446
  sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
457
447
  end
458
448
 
@@ -475,6 +465,7 @@ module ActiveRecord
475
465
  UNIQUE_VIOLATION = "23505"
476
466
  SERIALIZATION_FAILURE = "40001"
477
467
  DEADLOCK_DETECTED = "40P01"
468
+ DUPLICATE_DATABASE = "42P04"
478
469
  LOCK_NOT_AVAILABLE = "55P03"
479
470
  QUERY_CANCELED = "57014"
480
471
 
@@ -482,6 +473,12 @@ module ActiveRecord
482
473
  return exception unless exception.respond_to?(:result)
483
474
 
484
475
  case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
476
+ when nil
477
+ if exception.message.match?(/connection is closed/i)
478
+ ConnectionNotEstablished.new(exception)
479
+ else
480
+ super
481
+ end
485
482
  when UNIQUE_VIOLATION
486
483
  RecordNotUnique.new(message, sql: sql, binds: binds)
487
484
  when FOREIGN_KEY_VIOLATION
@@ -496,6 +493,8 @@ module ActiveRecord
496
493
  SerializationFailure.new(message, sql: sql, binds: binds)
497
494
  when DEADLOCK_DETECTED
498
495
  Deadlocked.new(message, sql: sql, binds: binds)
496
+ when DUPLICATE_DATABASE
497
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
499
498
  when LOCK_NOT_AVAILABLE
500
499
  LockWaitTimeout.new(message, sql: sql, binds: binds)
501
500
  when QUERY_CANCELED
@@ -547,7 +546,7 @@ module ActiveRecord
547
546
  m.register_type "uuid", OID::Uuid.new
548
547
  m.register_type "xml", OID::Xml.new
549
548
  m.register_type "tsvector", OID::SpecializedString.new(:tsvector)
550
- m.register_type "macaddr", OID::SpecializedString.new(:macaddr)
549
+ m.register_type "macaddr", OID::Macaddr.new
551
550
  m.register_type "citext", OID::SpecializedString.new(:citext)
552
551
  m.register_type "ltree", OID::SpecializedString.new(:ltree)
553
552
  m.register_type "line", OID::SpecializedString.new(:line)
@@ -557,11 +556,6 @@ module ActiveRecord
557
556
  m.register_type "polygon", OID::SpecializedString.new(:polygon)
558
557
  m.register_type "circle", OID::SpecializedString.new(:circle)
559
558
 
560
- m.register_type "interval" do |_, _, sql_type|
561
- precision = extract_precision(sql_type)
562
- OID::SpecializedString.new(:interval, precision: precision)
563
- end
564
-
565
559
  register_class_with_precision m, "time", Type::Time
566
560
  register_class_with_precision m, "timestamp", OID::DateTime
567
561
 
@@ -585,6 +579,11 @@ module ActiveRecord
585
579
  end
586
580
  end
587
581
 
582
+ m.register_type "interval" do |*args, sql_type|
583
+ precision = extract_precision(sql_type)
584
+ OID::Interval.new(precision: precision)
585
+ end
586
+
588
587
  load_additional_types
589
588
  end
590
589
 
@@ -650,20 +649,22 @@ module ActiveRecord
650
649
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
651
650
  end
652
651
 
653
- if without_prepared_statement?(binds)
654
- result = exec_no_cache(sql, name, [])
655
- elsif !prepare
652
+ if !prepare || without_prepared_statement?(binds)
656
653
  result = exec_no_cache(sql, name, binds)
657
654
  else
658
655
  result = exec_cache(sql, name, binds)
659
656
  end
660
- ret = yield result
661
- result.clear
657
+ begin
658
+ ret = yield result
659
+ ensure
660
+ result.clear
661
+ end
662
662
  ret
663
663
  end
664
664
 
665
665
  def exec_no_cache(sql, name, binds)
666
666
  materialize_transactions
667
+ mark_transaction_written_if_write(sql)
667
668
 
668
669
  # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
669
670
  # made since we established the connection
@@ -679,6 +680,7 @@ module ActiveRecord
679
680
 
680
681
  def exec_cache(sql, name, binds)
681
682
  materialize_transactions
683
+ mark_transaction_written_if_write(sql)
682
684
  update_typemap_for_default_timezone
683
685
 
684
686
  stmt_key = prepare_statement(sql, binds)
@@ -714,11 +716,10 @@ module ActiveRecord
714
716
  #
715
717
  # Check here for more details:
716
718
  # https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
717
- CACHED_PLAN_HEURISTIC = "cached plan must not change result type"
718
719
  def is_cached_plan_failure?(e)
719
720
  pgerror = e.cause
720
- code = pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE)
721
- code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC)
721
+ pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
722
+ pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "RevalidateCachedQuery"
722
723
  rescue
723
724
  false
724
725
  end
@@ -756,7 +757,7 @@ module ActiveRecord
756
757
  # Connects to a PostgreSQL server and sets up the adapter depending on the
757
758
  # connected server's characteristics.
758
759
  def connect
759
- @connection = PG.connect(@connection_parameters)
760
+ @connection = self.class.new_client(@connection_parameters)
760
761
  configure_connection
761
762
  add_pg_encoders
762
763
  add_pg_decoders
@@ -786,6 +787,9 @@ module ActiveRecord
786
787
  end
787
788
  end
788
789
 
790
+ # Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
791
+ execute("SET intervalstyle = iso_8601", "SCHEMA")
792
+
789
793
  # SET statements from :variables config hash
790
794
  # https://www.postgresql.org/docs/current/static/sql-set.html
791
795
  variables.map do |k, v|
@@ -897,15 +901,12 @@ module ActiveRecord
897
901
  "oid" => PG::TextDecoder::Integer,
898
902
  "float4" => PG::TextDecoder::Float,
899
903
  "float8" => PG::TextDecoder::Float,
904
+ "numeric" => PG::TextDecoder::Numeric,
900
905
  "bool" => PG::TextDecoder::Boolean,
906
+ "timestamp" => PG::TextDecoder::TimestampUtc,
907
+ "timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
901
908
  }
902
909
 
903
- if defined?(PG::TextDecoder::TimestampUtc)
904
- # Use native PG encoders available since pg-1.1
905
- coders_by_name["timestamp"] = PG::TextDecoder::TimestampUtc
906
- coders_by_name["timestamptz"] = PG::TextDecoder::TimestampWithTimeZone
907
- end
908
-
909
910
  known_coder_types = coders_by_name.keys.map { |n| quote(n) }
910
911
  query = <<~SQL % known_coder_types.join(", ")
911
912
  SELECT t.oid, t.typname
@@ -922,6 +923,11 @@ module ActiveRecord
922
923
  coders.each { |coder| map.add_coder(coder) }
923
924
  @connection.type_map_for_results = map
924
925
 
926
+ @type_map_for_results = PG::TypeMapByOid.new
927
+ @type_map_for_results.default_type_map = map
928
+ @type_map_for_results.add_coder(PG::TextDecoder::Bytea.new(oid: 17, name: "bytea"))
929
+ @type_map_for_results.add_coder(MoneyDecoder.new(oid: 790, name: "money"))
930
+
925
931
  # extract timestamp decoder for use in update_typemap_for_default_timezone
926
932
  @timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
927
933
  update_typemap_for_default_timezone
@@ -932,6 +938,14 @@ module ActiveRecord
932
938
  coder_class.new(oid: row["oid"].to_i, name: row["typname"])
933
939
  end
934
940
 
941
+ class MoneyDecoder < PG::SimpleDecoder # :nodoc:
942
+ TYPE = OID::Money.new
943
+
944
+ def decode(value, tuple = nil, field = nil)
945
+ TYPE.deserialize(value)
946
+ end
947
+ end
948
+
935
949
  ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql)
936
950
  ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql)
937
951
  ActiveRecord::Type.register(:bit, OID::Bit, adapter: :postgresql)
@@ -944,6 +958,7 @@ module ActiveRecord
944
958
  ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
945
959
  ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
946
960
  ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql)
961
+ ActiveRecord::Type.register(:interval, OID::Interval, adapter: :postgresql)
947
962
  ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
948
963
  ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
949
964
  ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)