activerecord 6.0.0 → 6.1.0

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 (268) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +872 -582
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_record.rb +7 -13
  6. data/lib/active_record/aggregations.rb +1 -2
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations.rb +116 -13
  9. data/lib/active_record/associations/alias_tracker.rb +19 -16
  10. data/lib/active_record/associations/association.rb +49 -29
  11. data/lib/active_record/associations/association_scope.rb +17 -15
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  14. data/lib/active_record/associations/builder/association.rb +9 -3
  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 -3
  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 +25 -8
  22. data/lib/active_record/associations/collection_proxy.rb +14 -7
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +24 -3
  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 +77 -42
  28. data/lib/active_record/associations/join_dependency/join_association.rb +36 -14
  29. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  30. data/lib/active_record/associations/preloader.rb +13 -8
  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 -9
  36. data/lib/active_record/attribute_methods.rb +64 -54
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -10
  38. data/lib/active_record/attribute_methods/dirty.rb +3 -13
  39. data/lib/active_record/attribute_methods/primary_key.rb +6 -4
  40. data/lib/active_record/attribute_methods/query.rb +3 -6
  41. data/lib/active_record/attribute_methods/read.rb +8 -12
  42. data/lib/active_record/attribute_methods/serialization.rb +11 -6
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  44. data/lib/active_record/attribute_methods/write.rb +12 -21
  45. data/lib/active_record/attributes.rb +32 -8
  46. data/lib/active_record/autosave_association.rb +63 -44
  47. data/lib/active_record/base.rb +2 -14
  48. data/lib/active_record/callbacks.rb +153 -24
  49. data/lib/active_record/coders/yaml_column.rb +1 -2
  50. data/lib/active_record/connection_adapters.rb +50 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +202 -138
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +86 -37
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +4 -9
  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 +152 -116
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -52
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +263 -107
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +82 -35
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +74 -76
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +149 -115
  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/column.rb +1 -1
  68. data/lib/active_record/connection_adapters/mysql/database_statements.rb +30 -36
  69. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  70. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  71. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -7
  72. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  73. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  74. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -13
  75. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -13
  77. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  78. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  79. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  80. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +21 -56
  81. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +0 -1
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +0 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  90. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  93. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -6
  94. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -2
  96. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  97. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  98. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +7 -3
  99. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +1 -1
  100. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  101. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +72 -54
  102. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  103. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  104. data/lib/active_record/connection_adapters/postgresql_adapter.rb +81 -57
  105. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  106. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  107. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +38 -12
  108. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -2
  109. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  110. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -5
  111. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -57
  112. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  113. data/lib/active_record/connection_handling.rb +211 -81
  114. data/lib/active_record/core.rb +237 -69
  115. data/lib/active_record/counter_cache.rb +4 -1
  116. data/lib/active_record/database_configurations.rb +124 -85
  117. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  118. data/lib/active_record/database_configurations/database_config.rb +52 -9
  119. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  120. data/lib/active_record/database_configurations/url_config.rb +15 -41
  121. data/lib/active_record/delegated_type.rb +209 -0
  122. data/lib/active_record/destroy_association_async_job.rb +36 -0
  123. data/lib/active_record/dynamic_matchers.rb +2 -3
  124. data/lib/active_record/enum.rb +40 -16
  125. data/lib/active_record/errors.rb +47 -12
  126. data/lib/active_record/explain.rb +9 -5
  127. data/lib/active_record/explain_subscriber.rb +1 -1
  128. data/lib/active_record/fixture_set/file.rb +10 -17
  129. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  130. data/lib/active_record/fixture_set/render_context.rb +1 -1
  131. data/lib/active_record/fixture_set/table_row.rb +2 -3
  132. data/lib/active_record/fixture_set/table_rows.rb +0 -1
  133. data/lib/active_record/fixtures.rb +54 -11
  134. data/lib/active_record/gem_version.rb +1 -1
  135. data/lib/active_record/inheritance.rb +40 -21
  136. data/lib/active_record/insert_all.rb +39 -10
  137. data/lib/active_record/integration.rb +3 -5
  138. data/lib/active_record/internal_metadata.rb +16 -7
  139. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  140. data/lib/active_record/locking/optimistic.rb +22 -17
  141. data/lib/active_record/locking/pessimistic.rb +6 -2
  142. data/lib/active_record/log_subscriber.rb +27 -9
  143. data/lib/active_record/middleware/database_selector.rb +4 -2
  144. data/lib/active_record/middleware/database_selector/resolver.rb +14 -14
  145. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  146. data/lib/active_record/migration.rb +114 -84
  147. data/lib/active_record/migration/command_recorder.rb +53 -45
  148. data/lib/active_record/migration/compatibility.rb +70 -20
  149. data/lib/active_record/migration/join_table.rb +0 -1
  150. data/lib/active_record/model_schema.rb +120 -15
  151. data/lib/active_record/nested_attributes.rb +2 -5
  152. data/lib/active_record/no_touching.rb +1 -1
  153. data/lib/active_record/null_relation.rb +0 -1
  154. data/lib/active_record/persistence.rb +50 -46
  155. data/lib/active_record/query_cache.rb +15 -5
  156. data/lib/active_record/querying.rb +12 -7
  157. data/lib/active_record/railtie.rb +65 -45
  158. data/lib/active_record/railties/databases.rake +267 -93
  159. data/lib/active_record/readonly_attributes.rb +4 -0
  160. data/lib/active_record/reflection.rb +77 -63
  161. data/lib/active_record/relation.rb +108 -67
  162. data/lib/active_record/relation/batches.rb +38 -32
  163. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  164. data/lib/active_record/relation/calculations.rb +102 -45
  165. data/lib/active_record/relation/delegation.rb +9 -7
  166. data/lib/active_record/relation/finder_methods.rb +55 -17
  167. data/lib/active_record/relation/from_clause.rb +5 -1
  168. data/lib/active_record/relation/merger.rb +27 -26
  169. data/lib/active_record/relation/predicate_builder.rb +55 -35
  170. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  171. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  172. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +3 -3
  173. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  174. data/lib/active_record/relation/query_methods.rb +340 -180
  175. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  176. data/lib/active_record/relation/spawn_methods.rb +8 -8
  177. data/lib/active_record/relation/where_clause.rb +104 -58
  178. data/lib/active_record/result.rb +41 -34
  179. data/lib/active_record/runtime_registry.rb +2 -2
  180. data/lib/active_record/sanitization.rb +6 -17
  181. data/lib/active_record/schema_dumper.rb +34 -4
  182. data/lib/active_record/schema_migration.rb +2 -8
  183. data/lib/active_record/scoping.rb +0 -1
  184. data/lib/active_record/scoping/default.rb +0 -1
  185. data/lib/active_record/scoping/named.rb +7 -18
  186. data/lib/active_record/secure_token.rb +16 -8
  187. data/lib/active_record/serialization.rb +5 -3
  188. data/lib/active_record/signed_id.rb +116 -0
  189. data/lib/active_record/statement_cache.rb +20 -4
  190. data/lib/active_record/store.rb +3 -3
  191. data/lib/active_record/suppressor.rb +2 -2
  192. data/lib/active_record/table_metadata.rb +39 -36
  193. data/lib/active_record/tasks/database_tasks.rb +139 -113
  194. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -36
  195. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -27
  196. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -10
  197. data/lib/active_record/test_databases.rb +5 -4
  198. data/lib/active_record/test_fixtures.rb +38 -16
  199. data/lib/active_record/timestamp.rb +4 -7
  200. data/lib/active_record/touch_later.rb +20 -21
  201. data/lib/active_record/transactions.rb +22 -71
  202. data/lib/active_record/type.rb +8 -2
  203. data/lib/active_record/type/adapter_specific_registry.rb +2 -5
  204. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  205. data/lib/active_record/type/serialized.rb +6 -3
  206. data/lib/active_record/type/time.rb +10 -0
  207. data/lib/active_record/type/type_map.rb +0 -1
  208. data/lib/active_record/type/unsigned_integer.rb +0 -1
  209. data/lib/active_record/type_caster/connection.rb +0 -1
  210. data/lib/active_record/type_caster/map.rb +8 -5
  211. data/lib/active_record/validations.rb +3 -3
  212. data/lib/active_record/validations/associated.rb +1 -2
  213. data/lib/active_record/validations/numericality.rb +35 -0
  214. data/lib/active_record/validations/uniqueness.rb +24 -4
  215. data/lib/arel.rb +15 -12
  216. data/lib/arel/attributes/attribute.rb +4 -0
  217. data/lib/arel/collectors/bind.rb +5 -0
  218. data/lib/arel/collectors/composite.rb +8 -0
  219. data/lib/arel/collectors/sql_string.rb +7 -0
  220. data/lib/arel/collectors/substitute_binds.rb +7 -0
  221. data/lib/arel/nodes.rb +3 -1
  222. data/lib/arel/nodes/binary.rb +82 -8
  223. data/lib/arel/nodes/bind_param.rb +8 -0
  224. data/lib/arel/nodes/casted.rb +21 -9
  225. data/lib/arel/nodes/equality.rb +6 -9
  226. data/lib/arel/nodes/grouping.rb +3 -0
  227. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  228. data/lib/arel/nodes/in.rb +8 -1
  229. data/lib/arel/nodes/infix_operation.rb +13 -1
  230. data/lib/arel/nodes/join_source.rb +1 -1
  231. data/lib/arel/nodes/node.rb +7 -6
  232. data/lib/arel/nodes/ordering.rb +27 -0
  233. data/lib/arel/nodes/sql_literal.rb +3 -0
  234. data/lib/arel/nodes/table_alias.rb +7 -3
  235. data/lib/arel/nodes/unary.rb +0 -1
  236. data/lib/arel/predications.rb +17 -24
  237. data/lib/arel/select_manager.rb +1 -2
  238. data/lib/arel/table.rb +13 -5
  239. data/lib/arel/visitors.rb +0 -7
  240. data/lib/arel/visitors/dot.rb +14 -3
  241. data/lib/arel/visitors/mysql.rb +11 -1
  242. data/lib/arel/visitors/postgresql.rb +15 -5
  243. data/lib/arel/visitors/sqlite.rb +0 -1
  244. data/lib/arel/visitors/to_sql.rb +89 -79
  245. data/lib/arel/visitors/visitor.rb +0 -1
  246. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  247. data/lib/rails/generators/active_record/migration.rb +6 -2
  248. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  249. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  250. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  251. data/lib/rails/generators/active_record/model/model_generator.rb +38 -2
  252. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  253. metadata +27 -24
  254. data/lib/active_record/attribute_decorators.rb +0 -90
  255. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  256. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  257. data/lib/active_record/define_callbacks.rb +0 -22
  258. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  259. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  260. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  261. data/lib/arel/attributes.rb +0 -22
  262. data/lib/arel/visitors/depth_first.rb +0 -204
  263. data/lib/arel/visitors/ibm_db.rb +0 -34
  264. data/lib/arel/visitors/informix.rb +0 -62
  265. data/lib/arel/visitors/mssql.rb +0 -157
  266. data/lib/arel/visitors/oracle.rb +0 -159
  267. data/lib/arel/visitors/oracle12.rb +0 -66
  268. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -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
@@ -37,7 +37,6 @@ module ActiveRecord
37
37
  end
38
38
 
39
39
  protected
40
-
41
40
  def parts
42
41
  @parts ||= [@schema, @identifier].compact
43
42
  end
@@ -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
@@ -156,6 +156,10 @@ module ActiveRecord
156
156
  true
157
157
  end
158
158
 
159
+ def supports_partitioned_indexes?
160
+ database_version >= 110_000
161
+ end
162
+
159
163
  def supports_partial_index?
160
164
  true
161
165
  end
@@ -172,6 +176,10 @@ module ActiveRecord
172
176
  true
173
177
  end
174
178
 
179
+ def supports_check_constraints?
180
+ true
181
+ end
182
+
175
183
  def supports_validate_constraints?
176
184
  true
177
185
  end
@@ -337,11 +345,6 @@ module ActiveRecord
337
345
  true
338
346
  end
339
347
 
340
- def supports_ranges?
341
- true
342
- end
343
- deprecate :supports_ranges?
344
-
345
348
  def supports_materialized_views?
346
349
  true
347
350
  end
@@ -361,6 +364,10 @@ module ActiveRecord
361
364
  @has_pg_hint_plan
362
365
  end
363
366
 
367
+ def supports_common_table_expressions?
368
+ true
369
+ end
370
+
364
371
  def supports_lazy_transactions?
365
372
  true
366
373
  end
@@ -418,16 +425,6 @@ module ActiveRecord
418
425
  @use_insert_returning
419
426
  end
420
427
 
421
- def column_name_for_operation(operation, node) # :nodoc:
422
- OPERATION_ALIASES.fetch(operation) { operation.downcase }
423
- end
424
-
425
- OPERATION_ALIASES = { # :nodoc:
426
- "maximum" => "max",
427
- "minimum" => "min",
428
- "average" => "avg",
429
- }
430
-
431
428
  # Returns the version of the connected PostgreSQL server.
432
429
  def get_database_version # :nodoc:
433
430
  @connection.server_version
@@ -445,6 +442,7 @@ module ActiveRecord
445
442
  sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
446
443
  elsif insert.update_duplicates?
447
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}" }
448
446
  sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
449
447
  end
450
448
 
@@ -459,7 +457,6 @@ module ActiveRecord
459
457
  end
460
458
 
461
459
  private
462
-
463
460
  # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
464
461
  VALUE_LIMIT_VIOLATION = "22001"
465
462
  NUMERIC_VALUE_OUT_OF_RANGE = "22003"
@@ -468,6 +465,7 @@ module ActiveRecord
468
465
  UNIQUE_VIOLATION = "23505"
469
466
  SERIALIZATION_FAILURE = "40001"
470
467
  DEADLOCK_DETECTED = "40P01"
468
+ DUPLICATE_DATABASE = "42P04"
471
469
  LOCK_NOT_AVAILABLE = "55P03"
472
470
  QUERY_CANCELED = "57014"
473
471
 
@@ -475,6 +473,12 @@ module ActiveRecord
475
473
  return exception unless exception.respond_to?(:result)
476
474
 
477
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
478
482
  when UNIQUE_VIOLATION
479
483
  RecordNotUnique.new(message, sql: sql, binds: binds)
480
484
  when FOREIGN_KEY_VIOLATION
@@ -489,6 +493,8 @@ module ActiveRecord
489
493
  SerializationFailure.new(message, sql: sql, binds: binds)
490
494
  when DEADLOCK_DETECTED
491
495
  Deadlocked.new(message, sql: sql, binds: binds)
496
+ when DUPLICATE_DATABASE
497
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
492
498
  when LOCK_NOT_AVAILABLE
493
499
  LockWaitTimeout.new(message, sql: sql, binds: binds)
494
500
  when QUERY_CANCELED
@@ -540,7 +546,7 @@ module ActiveRecord
540
546
  m.register_type "uuid", OID::Uuid.new
541
547
  m.register_type "xml", OID::Xml.new
542
548
  m.register_type "tsvector", OID::SpecializedString.new(:tsvector)
543
- m.register_type "macaddr", OID::SpecializedString.new(:macaddr)
549
+ m.register_type "macaddr", OID::Macaddr.new
544
550
  m.register_type "citext", OID::SpecializedString.new(:citext)
545
551
  m.register_type "ltree", OID::SpecializedString.new(:ltree)
546
552
  m.register_type "line", OID::SpecializedString.new(:line)
@@ -550,11 +556,6 @@ module ActiveRecord
550
556
  m.register_type "polygon", OID::SpecializedString.new(:polygon)
551
557
  m.register_type "circle", OID::SpecializedString.new(:circle)
552
558
 
553
- m.register_type "interval" do |_, _, sql_type|
554
- precision = extract_precision(sql_type)
555
- OID::SpecializedString.new(:interval, precision: precision)
556
- end
557
-
558
559
  register_class_with_precision m, "time", Type::Time
559
560
  register_class_with_precision m, "timestamp", OID::DateTime
560
561
 
@@ -578,6 +579,11 @@ module ActiveRecord
578
579
  end
579
580
  end
580
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
+
581
587
  load_additional_types
582
588
  end
583
589
 
@@ -626,7 +632,7 @@ module ActiveRecord
626
632
  SQL
627
633
 
628
634
  if oids
629
- query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
635
+ query += "WHERE t.oid IN (%s)" % oids.join(", ")
630
636
  else
631
637
  query += initializer.query_conditions_for_initial_load
632
638
  end
@@ -650,13 +656,17 @@ module ActiveRecord
650
656
  else
651
657
  result = exec_cache(sql, name, binds)
652
658
  end
653
- ret = yield result
654
- result.clear
659
+ begin
660
+ ret = yield result
661
+ ensure
662
+ result.clear
663
+ end
655
664
  ret
656
665
  end
657
666
 
658
667
  def exec_no_cache(sql, name, binds)
659
668
  materialize_transactions
669
+ mark_transaction_written_if_write(sql)
660
670
 
661
671
  # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
662
672
  # made since we established the connection
@@ -672,6 +682,7 @@ module ActiveRecord
672
682
 
673
683
  def exec_cache(sql, name, binds)
674
684
  materialize_transactions
685
+ mark_transaction_written_if_write(sql)
675
686
  update_typemap_for_default_timezone
676
687
 
677
688
  stmt_key = prepare_statement(sql, binds)
@@ -707,11 +718,10 @@ module ActiveRecord
707
718
  #
708
719
  # Check here for more details:
709
720
  # https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
710
- CACHED_PLAN_HEURISTIC = "cached plan must not change result type"
711
721
  def is_cached_plan_failure?(e)
712
722
  pgerror = e.cause
713
- code = pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE)
714
- code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC)
723
+ pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
724
+ pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "RevalidateCachedQuery"
715
725
  rescue
716
726
  false
717
727
  end
@@ -749,7 +759,7 @@ module ActiveRecord
749
759
  # Connects to a PostgreSQL server and sets up the adapter depending on the
750
760
  # connected server's characteristics.
751
761
  def connect
752
- @connection = PG.connect(@connection_parameters)
762
+ @connection = self.class.new_client(@connection_parameters)
753
763
  configure_connection
754
764
  add_pg_encoders
755
765
  add_pg_decoders
@@ -779,6 +789,9 @@ module ActiveRecord
779
789
  end
780
790
  end
781
791
 
792
+ # Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
793
+ execute("SET intervalstyle = iso_8601", "SCHEMA")
794
+
782
795
  # SET statements from :variables config hash
783
796
  # https://www.postgresql.org/docs/current/static/sql-set.html
784
797
  variables.map do |k, v|
@@ -890,15 +903,12 @@ module ActiveRecord
890
903
  "oid" => PG::TextDecoder::Integer,
891
904
  "float4" => PG::TextDecoder::Float,
892
905
  "float8" => PG::TextDecoder::Float,
906
+ "numeric" => PG::TextDecoder::Numeric,
893
907
  "bool" => PG::TextDecoder::Boolean,
908
+ "timestamp" => PG::TextDecoder::TimestampUtc,
909
+ "timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
894
910
  }
895
911
 
896
- if defined?(PG::TextDecoder::TimestampUtc)
897
- # Use native PG encoders available since pg-1.1
898
- coders_by_name["timestamp"] = PG::TextDecoder::TimestampUtc
899
- coders_by_name["timestamptz"] = PG::TextDecoder::TimestampWithTimeZone
900
- end
901
-
902
912
  known_coder_types = coders_by_name.keys.map { |n| quote(n) }
903
913
  query = <<~SQL % known_coder_types.join(", ")
904
914
  SELECT t.oid, t.typname
@@ -915,6 +925,11 @@ module ActiveRecord
915
925
  coders.each { |coder| map.add_coder(coder) }
916
926
  @connection.type_map_for_results = map
917
927
 
928
+ @type_map_for_results = PG::TypeMapByOid.new
929
+ @type_map_for_results.default_type_map = map
930
+ @type_map_for_results.add_coder(PG::TextDecoder::Bytea.new(oid: 17, name: "bytea"))
931
+ @type_map_for_results.add_coder(MoneyDecoder.new(oid: 790, name: "money"))
932
+
918
933
  # extract timestamp decoder for use in update_typemap_for_default_timezone
919
934
  @timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
920
935
  update_typemap_for_default_timezone
@@ -925,6 +940,14 @@ module ActiveRecord
925
940
  coder_class.new(oid: row["oid"].to_i, name: row["typname"])
926
941
  end
927
942
 
943
+ class MoneyDecoder < PG::SimpleDecoder # :nodoc:
944
+ TYPE = OID::Money.new
945
+
946
+ def decode(value, tuple = nil, field = nil)
947
+ TYPE.deserialize(value)
948
+ end
949
+ end
950
+
928
951
  ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql)
929
952
  ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql)
930
953
  ActiveRecord::Type.register(:bit, OID::Bit, adapter: :postgresql)
@@ -937,6 +960,7 @@ module ActiveRecord
937
960
  ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
938
961
  ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
939
962
  ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql)
963
+ ActiveRecord::Type.register(:interval, OID::Interval, adapter: :postgresql)
940
964
  ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
941
965
  ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
942
966
  ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)
@@ -1,8 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/file/atomic"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  class SchemaCache
8
+ def self.load_from(filename)
9
+ return unless File.file?(filename)
10
+
11
+ read(filename) do |file|
12
+ filename.include?(".dump") ? Marshal.load(file) : YAML.load(file)
13
+ end
14
+ end
15
+
16
+ def self.read(filename, &block)
17
+ if File.extname(filename) == ".gz"
18
+ Zlib::GzipReader.open(filename) { |gz|
19
+ yield gz.read
20
+ }
21
+ else
22
+ yield File.read(filename)
23
+ end
24
+ end
25
+ private_class_method :read
26
+
6
27
  attr_reader :version
7
28
  attr_accessor :connection
8
29
 
@@ -26,27 +47,33 @@ module ActiveRecord
26
47
  end
27
48
 
28
49
  def encode_with(coder)
50
+ reset_version!
51
+
29
52
  coder["columns"] = @columns
30
- coder["columns_hash"] = @columns_hash
31
53
  coder["primary_keys"] = @primary_keys
32
54
  coder["data_sources"] = @data_sources
33
55
  coder["indexes"] = @indexes
34
- coder["version"] = connection.migration_context.current_version
56
+ coder["version"] = @version
35
57
  coder["database_version"] = database_version
36
58
  end
37
59
 
38
60
  def init_with(coder)
39
61
  @columns = coder["columns"]
40
- @columns_hash = coder["columns_hash"]
41
62
  @primary_keys = coder["primary_keys"]
42
63
  @data_sources = coder["data_sources"]
43
64
  @indexes = coder["indexes"] || {}
44
65
  @version = coder["version"]
45
66
  @database_version = coder["database_version"]
67
+
68
+ derive_columns_hash_and_deduplicate_values
46
69
  end
47
70
 
48
71
  def primary_keys(table_name)
49
- @primary_keys[table_name] ||= data_source_exists?(table_name) ? connection.primary_key(table_name) : nil
72
+ @primary_keys.fetch(table_name) do
73
+ if data_source_exists?(table_name)
74
+ @primary_keys[deep_deduplicate(table_name)] = deep_deduplicate(connection.primary_key(table_name))
75
+ end
76
+ end
50
77
  end
51
78
 
52
79
  # A cached lookup for table existence.
@@ -54,7 +81,7 @@ module ActiveRecord
54
81
  prepare_data_sources if @data_sources.empty?
55
82
  return @data_sources[name] if @data_sources.key? name
56
83
 
57
- @data_sources[name] = connection.data_source_exists?(name)
84
+ @data_sources[deep_deduplicate(name)] = connection.data_source_exists?(name)
58
85
  end
59
86
 
60
87
  # Add internal cache for table with +table_name+.
@@ -73,15 +100,17 @@ module ActiveRecord
73
100
 
74
101
  # Get the columns for a table
75
102
  def columns(table_name)
76
- @columns[table_name] ||= connection.columns(table_name)
103
+ @columns.fetch(table_name) do
104
+ @columns[deep_deduplicate(table_name)] = deep_deduplicate(connection.columns(table_name))
105
+ end
77
106
  end
78
107
 
79
108
  # Get the columns for a table as a hash, key is the column name
80
109
  # value is the column object.
81
110
  def columns_hash(table_name)
82
- @columns_hash[table_name] ||= Hash[columns(table_name).map { |col|
83
- [col.name, col]
84
- }]
111
+ @columns_hash.fetch(table_name) do
112
+ @columns_hash[deep_deduplicate(table_name)] = columns(table_name).index_by(&:name).freeze
113
+ end
85
114
  end
86
115
 
87
116
  # Checks whether the columns hash is already cached for a table.
@@ -90,7 +119,9 @@ module ActiveRecord
90
119
  end
91
120
 
92
121
  def indexes(table_name)
93
- @indexes[table_name] ||= connection.indexes(table_name)
122
+ @indexes.fetch(table_name) do
123
+ @indexes[deep_deduplicate(table_name)] = deep_deduplicate(connection.indexes(table_name))
124
+ end
94
125
  end
95
126
 
96
127
  def database_version # :nodoc:
@@ -121,21 +152,73 @@ module ActiveRecord
121
152
  @indexes.delete name
122
153
  end
123
154
 
155
+ def dump_to(filename)
156
+ clear!
157
+ connection.data_sources.each { |table| add(table) }
158
+ open(filename) { |f|
159
+ if filename.include?(".dump")
160
+ f.write(Marshal.dump(self))
161
+ else
162
+ f.write(YAML.dump(self))
163
+ end
164
+ }
165
+ end
166
+
124
167
  def marshal_dump
125
- # if we get current version during initialization, it happens stack over flow.
126
- @version = connection.migration_context.current_version
127
- [@version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, database_version]
168
+ reset_version!
169
+
170
+ [@version, @columns, {}, @primary_keys, @data_sources, @indexes, database_version]
128
171
  end
129
172
 
130
173
  def marshal_load(array)
131
- @version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
132
- @indexes = @indexes || {}
174
+ @version, @columns, _columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
175
+ @indexes ||= {}
176
+
177
+ derive_columns_hash_and_deduplicate_values
133
178
  end
134
179
 
135
180
  private
181
+ def reset_version!
182
+ @version = connection.migration_context.current_version
183
+ end
184
+
185
+ def derive_columns_hash_and_deduplicate_values
186
+ @columns = deep_deduplicate(@columns)
187
+ @columns_hash = @columns.transform_values { |columns| columns.index_by(&:name) }
188
+ @primary_keys = deep_deduplicate(@primary_keys)
189
+ @data_sources = deep_deduplicate(@data_sources)
190
+ @indexes = deep_deduplicate(@indexes)
191
+ end
192
+
193
+ def deep_deduplicate(value)
194
+ case value
195
+ when Hash
196
+ value.transform_keys { |k| deep_deduplicate(k) }.transform_values { |v| deep_deduplicate(v) }
197
+ when Array
198
+ value.map { |i| deep_deduplicate(i) }
199
+ when String, Deduplicable
200
+ -value
201
+ else
202
+ value
203
+ end
204
+ end
205
+
136
206
  def prepare_data_sources
137
207
  connection.data_sources.each { |source| @data_sources[source] = true }
138
208
  end
209
+
210
+ def open(filename)
211
+ File.atomic_write(filename) do |file|
212
+ if File.extname(filename) == ".gz"
213
+ zipper = Zlib::GzipWriter.new file
214
+ yield zipper
215
+ zipper.flush
216
+ zipper.close
217
+ else
218
+ yield file
219
+ end
220
+ end
221
+ end
139
222
  end
140
223
  end
141
224
  end