activerecord 5.1.7 → 5.2.4.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 (261) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +556 -685
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -5
  5. data/examples/performance.rb +2 -0
  6. data/examples/simple.rb +2 -0
  7. data/lib/active_record.rb +11 -4
  8. data/lib/active_record/aggregations.rb +6 -5
  9. data/lib/active_record/association_relation.rb +7 -5
  10. data/lib/active_record/associations.rb +40 -63
  11. data/lib/active_record/associations/alias_tracker.rb +19 -27
  12. data/lib/active_record/associations/association.rb +41 -37
  13. data/lib/active_record/associations/association_scope.rb +38 -50
  14. data/lib/active_record/associations/belongs_to_association.rb +27 -8
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
  16. data/lib/active_record/associations/builder/association.rb +4 -7
  17. data/lib/active_record/associations/builder/belongs_to.rb +12 -4
  18. data/lib/active_record/associations/builder/collection_association.rb +3 -3
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -1
  20. data/lib/active_record/associations/builder/has_many.rb +2 -0
  21. data/lib/active_record/associations/builder/has_one.rb +2 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  23. data/lib/active_record/associations/collection_association.rb +59 -47
  24. data/lib/active_record/associations/collection_proxy.rb +20 -49
  25. data/lib/active_record/associations/foreign_association.rb +2 -0
  26. data/lib/active_record/associations/has_many_association.rb +12 -1
  27. data/lib/active_record/associations/has_many_through_association.rb +36 -30
  28. data/lib/active_record/associations/has_one_association.rb +12 -1
  29. data/lib/active_record/associations/has_one_through_association.rb +13 -8
  30. data/lib/active_record/associations/join_dependency.rb +48 -93
  31. data/lib/active_record/associations/join_dependency/join_association.rb +39 -63
  32. data/lib/active_record/associations/join_dependency/join_base.rb +9 -8
  33. data/lib/active_record/associations/join_dependency/join_part.rb +9 -9
  34. data/lib/active_record/associations/preloader.rb +18 -38
  35. data/lib/active_record/associations/preloader/association.rb +45 -61
  36. data/lib/active_record/associations/preloader/through_association.rb +71 -79
  37. data/lib/active_record/associations/singular_association.rb +14 -16
  38. data/lib/active_record/associations/through_association.rb +26 -11
  39. data/lib/active_record/attribute_assignment.rb +2 -5
  40. data/lib/active_record/attribute_decorators.rb +3 -2
  41. data/lib/active_record/attribute_methods.rb +65 -24
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +30 -214
  44. data/lib/active_record/attribute_methods/primary_key.rb +7 -6
  45. data/lib/active_record/attribute_methods/query.rb +2 -0
  46. data/lib/active_record/attribute_methods/read.rb +9 -3
  47. data/lib/active_record/attribute_methods/serialization.rb +23 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +6 -8
  49. data/lib/active_record/attribute_methods/write.rb +21 -9
  50. data/lib/active_record/attributes.rb +6 -5
  51. data/lib/active_record/autosave_association.rb +35 -19
  52. data/lib/active_record/base.rb +2 -0
  53. data/lib/active_record/callbacks.rb +8 -6
  54. data/lib/active_record/coders/json.rb +2 -0
  55. data/lib/active_record/coders/yaml_column.rb +2 -0
  56. data/lib/active_record/collection_cache_key.rb +12 -8
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +139 -41
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +174 -33
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +15 -5
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +13 -31
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +2 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +14 -5
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +64 -6
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +31 -53
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +152 -81
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +66 -21
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +84 -97
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +92 -165
  70. data/lib/active_record/connection_adapters/column.rb +3 -1
  71. data/lib/active_record/connection_adapters/connection_specification.rb +17 -3
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +13 -2
  73. data/lib/active_record/connection_adapters/mysql/column.rb +2 -0
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +47 -2
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +2 -0
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +9 -10
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +5 -3
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +7 -10
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +30 -30
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +106 -1
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +2 -0
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -2
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +2 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +6 -0
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +2 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +3 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -1
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -1
  101. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +2 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +4 -2
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +4 -2
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +2 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +2 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +18 -0
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +19 -25
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +50 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +24 -11
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +20 -13
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +233 -111
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +2 -0
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +3 -1
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +57 -73
  118. data/lib/active_record/connection_adapters/schema_cache.rb +4 -2
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +2 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +2 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +2 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +6 -15
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +3 -2
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +75 -1
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +81 -94
  127. data/lib/active_record/connection_adapters/statement_pool.rb +2 -0
  128. data/lib/active_record/connection_handling.rb +4 -2
  129. data/lib/active_record/core.rb +41 -61
  130. data/lib/active_record/counter_cache.rb +10 -3
  131. data/lib/active_record/define_callbacks.rb +5 -3
  132. data/lib/active_record/dynamic_matchers.rb +9 -9
  133. data/lib/active_record/enum.rb +18 -13
  134. data/lib/active_record/errors.rb +42 -3
  135. data/lib/active_record/explain.rb +3 -1
  136. data/lib/active_record/explain_registry.rb +2 -0
  137. data/lib/active_record/explain_subscriber.rb +2 -0
  138. data/lib/active_record/fixture_set/file.rb +2 -0
  139. data/lib/active_record/fixtures.rb +67 -60
  140. data/lib/active_record/gem_version.rb +5 -3
  141. data/lib/active_record/inheritance.rb +49 -19
  142. data/lib/active_record/integration.rb +58 -19
  143. data/lib/active_record/internal_metadata.rb +2 -0
  144. data/lib/active_record/legacy_yaml_adapter.rb +3 -1
  145. data/lib/active_record/locking/optimistic.rb +14 -17
  146. data/lib/active_record/locking/pessimistic.rb +9 -6
  147. data/lib/active_record/log_subscriber.rb +43 -0
  148. data/lib/active_record/migration.rb +189 -139
  149. data/lib/active_record/migration/command_recorder.rb +11 -9
  150. data/lib/active_record/migration/compatibility.rb +47 -9
  151. data/lib/active_record/migration/join_table.rb +2 -0
  152. data/lib/active_record/model_schema.rb +16 -21
  153. data/lib/active_record/nested_attributes.rb +18 -6
  154. data/lib/active_record/no_touching.rb +3 -1
  155. data/lib/active_record/null_relation.rb +2 -0
  156. data/lib/active_record/persistence.rb +167 -16
  157. data/lib/active_record/query_cache.rb +6 -8
  158. data/lib/active_record/querying.rb +4 -2
  159. data/lib/active_record/railtie.rb +62 -6
  160. data/lib/active_record/railties/console_sandbox.rb +2 -0
  161. data/lib/active_record/railties/controller_runtime.rb +2 -0
  162. data/lib/active_record/railties/databases.rake +46 -36
  163. data/lib/active_record/readonly_attributes.rb +3 -2
  164. data/lib/active_record/reflection.rb +108 -194
  165. data/lib/active_record/relation.rb +120 -214
  166. data/lib/active_record/relation/batches.rb +20 -5
  167. data/lib/active_record/relation/batches/batch_enumerator.rb +2 -0
  168. data/lib/active_record/relation/calculations.rb +45 -19
  169. data/lib/active_record/relation/delegation.rb +45 -27
  170. data/lib/active_record/relation/finder_methods.rb +75 -76
  171. data/lib/active_record/relation/from_clause.rb +2 -8
  172. data/lib/active_record/relation/merger.rb +53 -23
  173. data/lib/active_record/relation/predicate_builder.rb +60 -79
  174. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -7
  175. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  176. data/lib/active_record/relation/predicate_builder/base_handler.rb +2 -2
  177. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +12 -1
  178. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  179. data/lib/active_record/relation/predicate_builder/range_handler.rb +26 -9
  180. data/lib/active_record/relation/predicate_builder/relation_handler.rb +6 -0
  181. data/lib/active_record/relation/query_attribute.rb +28 -2
  182. data/lib/active_record/relation/query_methods.rb +128 -99
  183. data/lib/active_record/relation/record_fetch_warning.rb +2 -0
  184. data/lib/active_record/relation/spawn_methods.rb +4 -2
  185. data/lib/active_record/relation/where_clause.rb +65 -68
  186. data/lib/active_record/relation/where_clause_factory.rb +5 -48
  187. data/lib/active_record/result.rb +2 -0
  188. data/lib/active_record/runtime_registry.rb +2 -0
  189. data/lib/active_record/sanitization.rb +129 -121
  190. data/lib/active_record/schema.rb +4 -2
  191. data/lib/active_record/schema_dumper.rb +36 -26
  192. data/lib/active_record/schema_migration.rb +2 -0
  193. data/lib/active_record/scoping.rb +9 -8
  194. data/lib/active_record/scoping/default.rb +8 -9
  195. data/lib/active_record/scoping/named.rb +23 -7
  196. data/lib/active_record/secure_token.rb +2 -0
  197. data/lib/active_record/serialization.rb +2 -0
  198. data/lib/active_record/statement_cache.rb +23 -13
  199. data/lib/active_record/store.rb +3 -1
  200. data/lib/active_record/suppressor.rb +2 -0
  201. data/lib/active_record/table_metadata.rb +12 -3
  202. data/lib/active_record/tasks/database_tasks.rb +25 -14
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +9 -48
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +10 -2
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -3
  206. data/lib/active_record/timestamp.rb +6 -6
  207. data/lib/active_record/touch_later.rb +2 -0
  208. data/lib/active_record/transactions.rb +33 -28
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type.rb +4 -1
  211. data/lib/active_record/type/adapter_specific_registry.rb +2 -0
  212. data/lib/active_record/type/date.rb +2 -0
  213. data/lib/active_record/type/date_time.rb +2 -0
  214. data/lib/active_record/type/decimal_without_scale.rb +2 -0
  215. data/lib/active_record/type/hash_lookup_type_map.rb +2 -0
  216. data/lib/active_record/type/internal/timezone.rb +2 -0
  217. data/lib/active_record/type/json.rb +30 -0
  218. data/lib/active_record/type/serialized.rb +2 -0
  219. data/lib/active_record/type/text.rb +2 -0
  220. data/lib/active_record/type/time.rb +2 -0
  221. data/lib/active_record/type/type_map.rb +2 -0
  222. data/lib/active_record/type/unsigned_integer.rb +2 -0
  223. data/lib/active_record/type_caster.rb +2 -0
  224. data/lib/active_record/type_caster/connection.rb +2 -0
  225. data/lib/active_record/type_caster/map.rb +3 -1
  226. data/lib/active_record/validations.rb +2 -0
  227. data/lib/active_record/validations/absence.rb +2 -0
  228. data/lib/active_record/validations/associated.rb +2 -0
  229. data/lib/active_record/validations/length.rb +2 -0
  230. data/lib/active_record/validations/presence.rb +2 -0
  231. data/lib/active_record/validations/uniqueness.rb +35 -5
  232. data/lib/active_record/version.rb +2 -0
  233. data/lib/rails/generators/active_record.rb +3 -1
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/{model/templates/application_record.rb → application_record/templates/application_record.rb.tt} +0 -0
  236. data/lib/rails/generators/active_record/migration.rb +2 -0
  237. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -1
  238. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +0 -0
  239. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +0 -0
  240. data/lib/rails/generators/active_record/model/model_generator.rb +2 -23
  241. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +0 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  243. metadata +23 -36
  244. data/lib/active_record/associations/preloader/belongs_to.rb +0 -15
  245. data/lib/active_record/associations/preloader/collection_association.rb +0 -17
  246. data/lib/active_record/associations/preloader/has_many.rb +0 -15
  247. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  248. data/lib/active_record/associations/preloader/has_one.rb +0 -15
  249. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  250. data/lib/active_record/associations/preloader/singular_association.rb +0 -18
  251. data/lib/active_record/attribute.rb +0 -240
  252. data/lib/active_record/attribute/user_provided_default.rb +0 -30
  253. data/lib/active_record/attribute_mutation_tracker.rb +0 -122
  254. data/lib/active_record/attribute_set.rb +0 -113
  255. data/lib/active_record/attribute_set/builder.rb +0 -126
  256. data/lib/active_record/attribute_set/yaml_encoder.rb +0 -41
  257. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -10
  258. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  259. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +0 -88
  260. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -59
  261. data/lib/active_record/type/internal/abstract_json.rb +0 -37
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module PostgreSQL
@@ -42,15 +44,8 @@ module ActiveRecord
42
44
  # a record (as primary keys cannot be +nil+). This might be done via the
43
45
  # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
44
46
  def primary_key(name, type = :primary_key, **options)
45
- options[:auto_increment] = true if [:integer, :bigint].include?(type) && !options.key?(:default)
46
47
  if type == :uuid
47
48
  options[:default] = options.fetch(:default, "gen_random_uuid()")
48
- elsif options.delete(:auto_increment) == true && %i(integer bigint).include?(type)
49
- type = if type == :bigint || options[:limit] == 8
50
- :bigserial
51
- else
52
- :serial
53
- end
54
49
  end
55
50
 
56
51
  super
@@ -100,10 +95,6 @@ module ActiveRecord
100
95
  args.each { |name| column(name, :int8range, options) }
101
96
  end
102
97
 
103
- def json(*args, **options)
104
- args.each { |name| column(name, :json, options) }
105
- end
106
-
107
98
  def jsonb(*args, **options)
108
99
  args.each { |name| column(name, :jsonb, options) }
109
100
  end
@@ -183,11 +174,33 @@ module ActiveRecord
183
174
 
184
175
  class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
185
176
  include ColumnMethods
177
+
178
+ private
179
+ def integer_like_primary_key_type(type, options)
180
+ if type == :bigint || options[:limit] == 8
181
+ :bigserial
182
+ else
183
+ :serial
184
+ end
185
+ end
186
186
  end
187
187
 
188
188
  class Table < ActiveRecord::ConnectionAdapters::Table
189
189
  include ColumnMethods
190
190
  end
191
+
192
+ class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
193
+ attr_reader :constraint_validations
194
+
195
+ def initialize(td)
196
+ super
197
+ @constraint_validations = []
198
+ end
199
+
200
+ def validate_constraint(name)
201
+ @constraint_validations << name
202
+ end
203
+ end
191
204
  end
192
205
  end
193
206
  end
@@ -1,21 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module PostgreSQL
4
- module ColumnDumper # :nodoc:
5
- # Adds +:array+ option to the default set
6
- def prepare_column_options(column)
7
- spec = super
8
- spec[:array] = "true" if column.array?
9
- spec
10
- end
11
-
12
- # Adds +:array+ as a valid migration key
13
- def migration_keys
14
- super + [:array]
15
- end
16
-
6
+ class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
17
7
  private
18
8
 
9
+ def extensions(stream)
10
+ extensions = @connection.extensions
11
+ if extensions.any?
12
+ stream.puts " # These are extensions that must be enabled in order to support this database"
13
+ extensions.sort.each do |extension|
14
+ stream.puts " enable_extension #{extension.inspect}"
15
+ end
16
+ stream.puts
17
+ end
18
+ end
19
+
20
+ def prepare_column_options(column)
21
+ spec = super
22
+ spec[:array] = "true" if column.array?
23
+ spec
24
+ end
25
+
19
26
  def default_primary_key?(column)
20
27
  schema_type(column) == :bigserial
21
28
  end
@@ -1,4 +1,4 @@
1
- require "active_support/core_ext/string/strip"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
@@ -38,7 +38,7 @@ module ActiveRecord
38
38
  " TABLESPACE = \"#{value}\""
39
39
  when :connection_limit
40
40
  " CONNECTION LIMIT = #{value}"
41
- else
41
+ else
42
42
  ""
43
43
  end
44
44
  end
@@ -64,12 +64,7 @@ module ActiveRecord
64
64
  end
65
65
 
66
66
  # Verifies existence of an index with a given name.
67
- def index_name_exists?(table_name, index_name, default = nil)
68
- unless default.nil?
69
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
70
- Passing default to #index_name_exists? is deprecated without replacement.
71
- MSG
72
- end
67
+ def index_name_exists?(table_name, index_name)
73
68
  table = quoted_scope(table_name)
74
69
  index = quoted_scope(index_name)
75
70
 
@@ -87,21 +82,12 @@ module ActiveRecord
87
82
  end
88
83
 
89
84
  # Returns an array of indexes for the given table.
90
- def indexes(table_name, name = nil) # :nodoc:
91
- if name
92
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
93
- Passing name to #indexes is deprecated without replacement.
94
- MSG
95
- end
96
-
85
+ def indexes(table_name) # :nodoc:
97
86
  scope = quoted_scope(table_name)
98
87
 
99
88
  result = query(<<-SQL, "SCHEMA")
100
89
  SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
101
- pg_catalog.obj_description(i.oid, 'pg_class') AS comment,
102
- (SELECT COUNT(*) FROM pg_opclass o
103
- JOIN (SELECT unnest(string_to_array(d.indclass::text, ' '))::int oid) c
104
- ON o.oid = c.oid WHERE o.opcdefault = 'f')
90
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment
105
91
  FROM pg_class t
106
92
  INNER JOIN pg_index d ON t.oid = d.indrelid
107
93
  INNER JOIN pg_class i ON d.indexrelid = i.oid
@@ -120,11 +106,13 @@ module ActiveRecord
120
106
  inddef = row[3]
121
107
  oid = row[4]
122
108
  comment = row[5]
123
- opclass = row[6]
124
109
 
125
110
  using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten
126
111
 
127
- if indkey.include?(0) || opclass > 0
112
+ orders = {}
113
+ opclasses = {}
114
+
115
+ if indkey.include?(0)
128
116
  columns = expressions
129
117
  else
130
118
  columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
@@ -134,34 +122,30 @@ module ActiveRecord
134
122
  AND a.attnum IN (#{indkey.join(",")})
135
123
  SQL
136
124
 
137
- # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
138
- orders = Hash[
139
- expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] }
140
- ]
125
+ # add info on sort order (only desc order is explicitly specified, asc is the default)
126
+ # and non-default opclasses
127
+ expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
128
+ opclasses[column] = opclass.to_sym if opclass
129
+ if nulls
130
+ orders[column] = [desc, nulls].compact.join(" ")
131
+ else
132
+ orders[column] = :desc if desc
133
+ end
134
+ end
141
135
  end
142
136
 
143
- IndexDefinition.new(table_name, index_name, unique, columns, [], orders, where, nil, using.to_sym, comment.presence)
144
- end.compact
145
- end
146
-
147
- def new_column_from_field(table_name, field) # :nondoc:
148
- column_name, type, default, notnull, oid, fmod, collation, comment = field
149
- oid = oid.to_i
150
- fmod = fmod.to_i
151
- type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
152
- default_value = extract_value_from_default(default)
153
- default_function = extract_default_function(default_value, default)
154
- PostgreSQLColumn.new(
155
- column_name,
156
- default_value,
157
- type_metadata,
158
- !notnull,
159
- table_name,
160
- default_function,
161
- collation,
162
- comment: comment.presence,
163
- max_identifier_length: max_identifier_length
164
- )
137
+ IndexDefinition.new(
138
+ table_name,
139
+ index_name,
140
+ unique,
141
+ columns,
142
+ orders: orders,
143
+ opclasses: opclasses,
144
+ where: where,
145
+ using: using.to_sym,
146
+ comment: comment.presence
147
+ )
148
+ end
165
149
  end
166
150
 
167
151
  def table_options(table_name) # :nodoc:
@@ -233,7 +217,7 @@ module ActiveRecord
233
217
 
234
218
  # Sets the schema search path to a string of comma-separated schema names.
235
219
  # Names beginning with $ have to be quoted (e.g. $user => '$user').
236
- # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
220
+ # See: https://www.postgresql.org/docs/current/static/ddl-schemas.html
237
221
  #
238
222
  # This should be not be called manually but set in database.yml.
239
223
  def schema_search_path=(schema_csv)
@@ -334,7 +318,7 @@ module ActiveRecord
334
318
  AND seq.relnamespace = nsp.oid
335
319
  AND cons.contype = 'p'
336
320
  AND dep.classid = 'pg_class'::regclass
337
- AND dep.refobjid = '#{quote_table_name(table)}'::regclass
321
+ AND dep.refobjid = #{quote(quote_table_name(table))}::regclass
338
322
  end_sql
339
323
 
340
324
  if result.nil? || result.empty?
@@ -352,7 +336,7 @@ module ActiveRecord
352
336
  JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
353
337
  JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
354
338
  JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
355
- WHERE t.oid = '#{quote_table_name(table)}'::regclass
339
+ WHERE t.oid = #{quote(quote_table_name(table))}::regclass
356
340
  AND cons.contype = 'p'
357
341
  AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
358
342
  end_sql
@@ -384,6 +368,31 @@ module ActiveRecord
384
368
  SQL
385
369
  end
386
370
 
371
+ def bulk_change_table(table_name, operations)
372
+ sql_fragments = []
373
+ non_combinable_operations = []
374
+
375
+ operations.each do |command, args|
376
+ table, arguments = args.shift, args
377
+ method = :"#{command}_for_alter"
378
+
379
+ if respond_to?(method, true)
380
+ sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
381
+ sql_fragments << sqls
382
+ non_combinable_operations.concat(procs)
383
+ else
384
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
385
+ non_combinable_operations.each(&:call)
386
+ sql_fragments = []
387
+ non_combinable_operations = []
388
+ send(command, table, *arguments)
389
+ end
390
+ end
391
+
392
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
393
+ non_combinable_operations.each(&:call)
394
+ end
395
+
387
396
  # Renames a table.
388
397
  # Also renames a table's primary key sequence if the sequence name exists and
389
398
  # matches the Active Record default.
@@ -394,14 +403,15 @@ module ActiveRecord
394
403
  clear_cache!
395
404
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
396
405
  pk, seq = pk_and_sequence_for(new_name)
397
- if seq && seq.identifier == "#{table_name}_#{pk}_seq"
398
- new_seq = "#{new_name}_#{pk}_seq"
406
+ if pk
399
407
  idx = "#{table_name}_pkey"
400
408
  new_idx = "#{new_name}_pkey"
401
- execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
402
409
  execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
410
+ if seq && seq.identifier == "#{table_name}_#{pk}_seq"
411
+ new_seq = "#{new_name}_#{pk}_seq"
412
+ execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
413
+ end
403
414
  end
404
-
405
415
  rename_table_indexes(table_name, new_name)
406
416
  end
407
417
 
@@ -413,50 +423,23 @@ module ActiveRecord
413
423
 
414
424
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
415
425
  clear_cache!
416
- quoted_table_name = quote_table_name(table_name)
417
- quoted_column_name = quote_column_name(column_name)
418
- sql_type = type_to_sql(type, options)
419
- sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
420
- if options[:collation]
421
- sql << " COLLATE \"#{options[:collation]}\""
422
- end
423
- if options[:using]
424
- sql << " USING #{options[:using]}"
425
- elsif options[:cast_as]
426
- cast_as_type = type_to_sql(options[:cast_as], options)
427
- sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
428
- end
429
- execute sql
430
-
431
- change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
432
- change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
433
- change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
426
+ sqls, procs = Array(change_column_for_alter(table_name, column_name, type, options)).partition { |v| v.is_a?(String) }
427
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
428
+ procs.each(&:call)
434
429
  end
435
430
 
436
431
  # Changes the default value of a table column.
437
432
  def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
438
- clear_cache!
439
- column = column_for(table_name, column_name)
440
- return unless column
441
-
442
- default = extract_new_default_value(default_or_changes)
443
- alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
444
- if default.nil?
445
- # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
446
- # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
447
- execute alter_column_query % "DROP DEFAULT"
448
- else
449
- execute alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
450
- end
433
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
451
434
  end
452
435
 
453
436
  def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
454
437
  clear_cache!
455
438
  unless null || default.nil?
456
439
  column = column_for(table_name, column_name)
457
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
440
+ execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL" if column
458
441
  end
459
- execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
442
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_null_for_alter(table_name, column_name, null, default)}"
460
443
  end
461
444
 
462
445
  # Adds comment for given table column or drops it if +comment+ is a +nil+
@@ -479,8 +462,8 @@ module ActiveRecord
479
462
  end
480
463
 
481
464
  def add_index(table_name, column_name, options = {}) #:nodoc:
482
- index_name, index_type, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
483
- execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}").tap do
465
+ index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
466
+ 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
484
467
  execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
485
468
  end
486
469
  end
@@ -520,7 +503,7 @@ module ActiveRecord
520
503
  def foreign_keys(table_name)
521
504
  scope = quoted_scope(table_name)
522
505
  fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
523
- SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
506
+ SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid
524
507
  FROM pg_constraint c
525
508
  JOIN pg_class t1 ON c.conrelid = t1.oid
526
509
  JOIN pg_class t2 ON c.confrelid = t2.oid
@@ -542,17 +525,18 @@ module ActiveRecord
542
525
 
543
526
  options[:on_delete] = extract_foreign_key_action(row["on_delete"])
544
527
  options[:on_update] = extract_foreign_key_action(row["on_update"])
528
+ options[:validate] = row["valid"]
545
529
 
546
530
  ForeignKeyDefinition.new(table_name, row["to_table"], options)
547
531
  end
548
532
  end
549
533
 
550
- def extract_foreign_key_action(specifier) # :nodoc:
551
- case specifier
552
- when "c"; :cascade
553
- when "n"; :nullify
554
- when "r"; :restrict
555
- end
534
+ def foreign_tables
535
+ query_values(data_source_sql(type: "FOREIGN TABLE"), "SCHEMA")
536
+ end
537
+
538
+ def foreign_table_exists?(table_name)
539
+ query_values(data_source_sql(table_name, type: "FOREIGN TABLE"), "SCHEMA").any? if table_name.present?
556
540
  end
557
541
 
558
542
  # Maps logical Rails types to PostgreSQL-specific data types.
@@ -584,7 +568,7 @@ module ActiveRecord
584
568
  super
585
569
  end
586
570
 
587
- sql << "[]" if array && type != :primary_key
571
+ sql = "#{sql}[]" if array && type != :primary_key
588
572
  sql
589
573
  end
590
574
 
@@ -599,27 +583,163 @@ module ActiveRecord
599
583
  .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
600
584
  }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
601
585
 
602
- [super, *order_columns].join(", ")
586
+ (order_columns << super).join(", ")
603
587
  end
604
588
 
605
- def fetch_type_metadata(column_name, sql_type, oid, fmod)
606
- cast_type = get_oid_type(oid, fmod, column_name, sql_type)
607
- simple_type = SqlTypeMetadata.new(
608
- sql_type: sql_type,
609
- type: cast_type.type,
610
- limit: cast_type.limit,
611
- precision: cast_type.precision,
612
- scale: cast_type.scale,
613
- )
614
- PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
589
+ def update_table_definition(table_name, base) # :nodoc:
590
+ PostgreSQL::Table.new(table_name, base)
591
+ end
592
+
593
+ def create_schema_dumper(options) # :nodoc:
594
+ PostgreSQL::SchemaDumper.create(self, options)
595
+ end
596
+
597
+ # Validates the given constraint.
598
+ #
599
+ # Validates the constraint named +constraint_name+ on +accounts+.
600
+ #
601
+ # validate_constraint :accounts, :constraint_name
602
+ def validate_constraint(table_name, constraint_name)
603
+ return unless supports_validate_constraints?
604
+
605
+ at = create_alter_table table_name
606
+ at.validate_constraint constraint_name
607
+
608
+ execute schema_creation.accept(at)
609
+ end
610
+
611
+ # Validates the given foreign key.
612
+ #
613
+ # Validates the foreign key on +accounts.branch_id+.
614
+ #
615
+ # validate_foreign_key :accounts, :branches
616
+ #
617
+ # Validates the foreign key on +accounts.owner_id+.
618
+ #
619
+ # validate_foreign_key :accounts, column: :owner_id
620
+ #
621
+ # Validates the foreign key named +special_fk_name+ on the +accounts+ table.
622
+ #
623
+ # validate_foreign_key :accounts, name: :special_fk_name
624
+ #
625
+ # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
626
+ def validate_foreign_key(from_table, options_or_to_table = {})
627
+ return unless supports_validate_constraints?
628
+
629
+ fk_name_to_validate = foreign_key_for!(from_table, options_or_to_table).name
630
+
631
+ validate_constraint from_table, fk_name_to_validate
615
632
  end
616
633
 
617
634
  private
635
+ def schema_creation
636
+ PostgreSQL::SchemaCreation.new(self)
637
+ end
638
+
639
+ def create_table_definition(*args)
640
+ PostgreSQL::TableDefinition.new(*args)
641
+ end
642
+
643
+ def create_alter_table(name)
644
+ PostgreSQL::AlterTable.new create_table_definition(name)
645
+ end
646
+
647
+ def new_column_from_field(table_name, field)
648
+ column_name, type, default, notnull, oid, fmod, collation, comment = field
649
+ type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
650
+ default_value = extract_value_from_default(default)
651
+ default_function = extract_default_function(default_value, default)
652
+
653
+ PostgreSQLColumn.new(
654
+ column_name,
655
+ default_value,
656
+ type_metadata,
657
+ !notnull,
658
+ table_name,
659
+ default_function,
660
+ collation,
661
+ comment: comment.presence,
662
+ max_identifier_length: max_identifier_length
663
+ )
664
+ end
665
+
666
+ def fetch_type_metadata(column_name, sql_type, oid, fmod)
667
+ cast_type = get_oid_type(oid, fmod, column_name, sql_type)
668
+ simple_type = SqlTypeMetadata.new(
669
+ sql_type: sql_type,
670
+ type: cast_type.type,
671
+ limit: cast_type.limit,
672
+ precision: cast_type.precision,
673
+ scale: cast_type.scale,
674
+ )
675
+ PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
676
+ end
677
+
678
+ def extract_foreign_key_action(specifier)
679
+ case specifier
680
+ when "c"; :cascade
681
+ when "n"; :nullify
682
+ when "r"; :restrict
683
+ end
684
+ end
685
+
686
+ def add_column_for_alter(table_name, column_name, type, options = {})
687
+ return super unless options.key?(:comment)
688
+ [super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
689
+ end
690
+
691
+ def change_column_for_alter(table_name, column_name, type, options = {})
692
+ td = create_table_definition(table_name)
693
+ cd = td.new_column_definition(column_name, type, options)
694
+ sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
695
+ sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
696
+ sqls
697
+ end
698
+
699
+ def change_column_default_for_alter(table_name, column_name, default_or_changes)
700
+ column = column_for(table_name, column_name)
701
+ return unless column
702
+
703
+ default = extract_new_default_value(default_or_changes)
704
+ alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s"
705
+ if default.nil?
706
+ # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
707
+ # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
708
+ alter_column_query % "DROP DEFAULT"
709
+ else
710
+ alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
711
+ end
712
+ end
713
+
714
+ def change_column_null_for_alter(table_name, column_name, null, default = nil)
715
+ "ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
716
+ end
717
+
718
+ def add_timestamps_for_alter(table_name, options = {})
719
+ [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
720
+ end
721
+
722
+ def remove_timestamps_for_alter(table_name, options = {})
723
+ [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
724
+ end
725
+
726
+ def add_index_opclass(quoted_columns, **options)
727
+ opclasses = options_for_index_columns(options[:opclass])
728
+ quoted_columns.each do |name, column|
729
+ column << " #{opclasses[name]}" if opclasses[name].present?
730
+ end
731
+ end
732
+
733
+ def add_options_for_index_columns(quoted_columns, **options)
734
+ quoted_columns = add_index_opclass(quoted_columns, options)
735
+ super
736
+ end
737
+
618
738
  def data_source_sql(name = nil, type: nil)
619
739
  scope = quoted_scope(name, type: type)
620
- scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view
740
+ scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
621
741
 
622
- sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace"
742
+ sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace".dup
623
743
  sql << " WHERE n.nspname = #{scope[:schema]}"
624
744
  sql << " AND c.relname = #{scope[:name]}" if scope[:name]
625
745
  sql << " AND c.relkind IN (#{scope[:type]})"
@@ -631,9 +751,11 @@ module ActiveRecord
631
751
  type = \
632
752
  case type
633
753
  when "BASE TABLE"
634
- "'r'"
754
+ "'r','p'"
635
755
  when "VIEW"
636
756
  "'v','m'"
757
+ when "FOREIGN TABLE"
758
+ "'f'"
637
759
  end
638
760
  scope = {}
639
761
  scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))"