activerecord 4.2.11.3 → 5.0.7.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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1638 -1132
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +7 -8
  5. data/examples/performance.rb +2 -3
  6. data/examples/simple.rb +0 -1
  7. data/lib/active_record.rb +7 -2
  8. data/lib/active_record/aggregations.rb +34 -21
  9. data/lib/active_record/association_relation.rb +7 -4
  10. data/lib/active_record/associations.rb +347 -218
  11. data/lib/active_record/associations/alias_tracker.rb +19 -16
  12. data/lib/active_record/associations/association.rb +22 -10
  13. data/lib/active_record/associations/association_scope.rb +75 -104
  14. data/lib/active_record/associations/belongs_to_association.rb +21 -32
  15. data/lib/active_record/associations/builder/association.rb +28 -34
  16. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  17. data/lib/active_record/associations/builder/collection_association.rb +7 -19
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +16 -11
  19. data/lib/active_record/associations/builder/has_many.rb +4 -4
  20. data/lib/active_record/associations/builder/has_one.rb +11 -6
  21. data/lib/active_record/associations/builder/singular_association.rb +13 -11
  22. data/lib/active_record/associations/collection_association.rb +85 -69
  23. data/lib/active_record/associations/collection_proxy.rb +104 -46
  24. data/lib/active_record/associations/foreign_association.rb +1 -1
  25. data/lib/active_record/associations/has_many_association.rb +21 -78
  26. data/lib/active_record/associations/has_many_through_association.rb +6 -47
  27. data/lib/active_record/associations/has_one_association.rb +12 -5
  28. data/lib/active_record/associations/join_dependency.rb +38 -22
  29. data/lib/active_record/associations/join_dependency/join_association.rb +15 -14
  30. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  31. data/lib/active_record/associations/preloader.rb +14 -4
  32. data/lib/active_record/associations/preloader/association.rb +52 -71
  33. data/lib/active_record/associations/preloader/collection_association.rb +0 -7
  34. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +0 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +36 -17
  38. data/lib/active_record/associations/singular_association.rb +13 -1
  39. data/lib/active_record/associations/through_association.rb +12 -4
  40. data/lib/active_record/attribute.rb +69 -19
  41. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  42. data/lib/active_record/attribute_assignment.rb +19 -140
  43. data/lib/active_record/attribute_decorators.rb +6 -5
  44. data/lib/active_record/attribute_methods.rb +69 -44
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  46. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  47. data/lib/active_record/attribute_methods/primary_key.rb +16 -3
  48. data/lib/active_record/attribute_methods/query.rb +2 -2
  49. data/lib/active_record/attribute_methods/read.rb +31 -59
  50. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
  52. data/lib/active_record/attribute_methods/write.rb +13 -37
  53. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  54. data/lib/active_record/attribute_set.rb +32 -3
  55. data/lib/active_record/attribute_set/builder.rb +42 -16
  56. data/lib/active_record/attributes.rb +199 -81
  57. data/lib/active_record/autosave_association.rb +54 -17
  58. data/lib/active_record/base.rb +32 -23
  59. data/lib/active_record/callbacks.rb +39 -43
  60. data/lib/active_record/coders/json.rb +1 -1
  61. data/lib/active_record/coders/yaml_column.rb +20 -8
  62. data/lib/active_record/collection_cache_key.rb +50 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +467 -189
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -62
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +39 -4
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +86 -13
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -188
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +407 -156
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -71
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +433 -399
  76. data/lib/active_record/connection_adapters/column.rb +28 -43
  77. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  78. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  79. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  80. data/lib/active_record/connection_adapters/mysql/database_statements.rb +108 -0
  81. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  82. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  83. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  84. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  87. data/lib/active_record/connection_adapters/mysql2_adapter.rb +25 -166
  88. data/lib/active_record/connection_adapters/postgresql/column.rb +33 -11
  89. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -72
  90. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  92. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +37 -57
  93. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +3 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -2
  95. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  97. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +13 -3
  98. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  99. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  102. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  106. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +56 -19
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +250 -154
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +2 -2
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +264 -170
  116. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  118. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  119. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  120. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  121. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +151 -194
  122. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  123. data/lib/active_record/connection_handling.rb +37 -14
  124. data/lib/active_record/core.rb +92 -108
  125. data/lib/active_record/counter_cache.rb +13 -24
  126. data/lib/active_record/dynamic_matchers.rb +1 -20
  127. data/lib/active_record/enum.rb +116 -76
  128. data/lib/active_record/errors.rb +87 -48
  129. data/lib/active_record/explain.rb +20 -9
  130. data/lib/active_record/explain_registry.rb +1 -1
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/file.rb +26 -5
  133. data/lib/active_record/fixtures.rb +77 -41
  134. data/lib/active_record/gem_version.rb +4 -4
  135. data/lib/active_record/inheritance.rb +32 -40
  136. data/lib/active_record/integration.rb +17 -14
  137. data/lib/active_record/internal_metadata.rb +56 -0
  138. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  139. data/lib/active_record/locale/en.yml +3 -2
  140. data/lib/active_record/locking/optimistic.rb +15 -15
  141. data/lib/active_record/locking/pessimistic.rb +1 -1
  142. data/lib/active_record/log_subscriber.rb +48 -24
  143. data/lib/active_record/migration.rb +362 -111
  144. data/lib/active_record/migration/command_recorder.rb +59 -18
  145. data/lib/active_record/migration/compatibility.rb +126 -0
  146. data/lib/active_record/model_schema.rb +270 -73
  147. data/lib/active_record/nested_attributes.rb +58 -29
  148. data/lib/active_record/no_touching.rb +4 -0
  149. data/lib/active_record/null_relation.rb +16 -8
  150. data/lib/active_record/persistence.rb +152 -90
  151. data/lib/active_record/query_cache.rb +18 -23
  152. data/lib/active_record/querying.rb +12 -11
  153. data/lib/active_record/railtie.rb +23 -16
  154. data/lib/active_record/railties/controller_runtime.rb +1 -1
  155. data/lib/active_record/railties/databases.rake +52 -41
  156. data/lib/active_record/readonly_attributes.rb +1 -1
  157. data/lib/active_record/reflection.rb +302 -115
  158. data/lib/active_record/relation.rb +187 -120
  159. data/lib/active_record/relation/batches.rb +141 -36
  160. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  161. data/lib/active_record/relation/calculations.rb +92 -117
  162. data/lib/active_record/relation/delegation.rb +8 -20
  163. data/lib/active_record/relation/finder_methods.rb +173 -89
  164. data/lib/active_record/relation/from_clause.rb +32 -0
  165. data/lib/active_record/relation/merger.rb +16 -42
  166. data/lib/active_record/relation/predicate_builder.rb +120 -107
  167. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  168. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  169. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  170. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  171. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  172. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  173. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  174. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  175. data/lib/active_record/relation/query_attribute.rb +19 -0
  176. data/lib/active_record/relation/query_methods.rb +308 -244
  177. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  178. data/lib/active_record/relation/spawn_methods.rb +4 -7
  179. data/lib/active_record/relation/where_clause.rb +174 -0
  180. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  181. data/lib/active_record/result.rb +11 -4
  182. data/lib/active_record/runtime_registry.rb +1 -1
  183. data/lib/active_record/sanitization.rb +105 -66
  184. data/lib/active_record/schema.rb +26 -22
  185. data/lib/active_record/schema_dumper.rb +54 -37
  186. data/lib/active_record/schema_migration.rb +11 -14
  187. data/lib/active_record/scoping.rb +34 -16
  188. data/lib/active_record/scoping/default.rb +28 -10
  189. data/lib/active_record/scoping/named.rb +59 -26
  190. data/lib/active_record/secure_token.rb +38 -0
  191. data/lib/active_record/serialization.rb +3 -5
  192. data/lib/active_record/statement_cache.rb +17 -15
  193. data/lib/active_record/store.rb +8 -3
  194. data/lib/active_record/suppressor.rb +58 -0
  195. data/lib/active_record/table_metadata.rb +69 -0
  196. data/lib/active_record/tasks/database_tasks.rb +66 -49
  197. data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
  198. data/lib/active_record/tasks/postgresql_database_tasks.rb +12 -3
  199. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  200. data/lib/active_record/timestamp.rb +20 -9
  201. data/lib/active_record/touch_later.rb +63 -0
  202. data/lib/active_record/transactions.rb +139 -57
  203. data/lib/active_record/type.rb +66 -17
  204. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  205. data/lib/active_record/type/date.rb +2 -45
  206. data/lib/active_record/type/date_time.rb +2 -49
  207. data/lib/active_record/type/internal/abstract_json.rb +33 -0
  208. data/lib/active_record/type/internal/timezone.rb +15 -0
  209. data/lib/active_record/type/serialized.rb +15 -14
  210. data/lib/active_record/type/time.rb +10 -16
  211. data/lib/active_record/type/type_map.rb +4 -4
  212. data/lib/active_record/type_caster.rb +7 -0
  213. data/lib/active_record/type_caster/connection.rb +29 -0
  214. data/lib/active_record/type_caster/map.rb +19 -0
  215. data/lib/active_record/validations.rb +33 -32
  216. data/lib/active_record/validations/absence.rb +23 -0
  217. data/lib/active_record/validations/associated.rb +10 -3
  218. data/lib/active_record/validations/length.rb +24 -0
  219. data/lib/active_record/validations/presence.rb +11 -12
  220. data/lib/active_record/validations/uniqueness.rb +33 -33
  221. data/lib/rails/generators/active_record/migration.rb +15 -0
  222. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -5
  223. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  224. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  225. data/lib/rails/generators/active_record/model/model_generator.rb +33 -16
  226. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  227. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  228. metadata +58 -34
  229. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  230. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  231. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  232. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  233. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  234. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  235. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  236. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  237. data/lib/active_record/type/big_integer.rb +0 -13
  238. data/lib/active_record/type/binary.rb +0 -50
  239. data/lib/active_record/type/boolean.rb +0 -31
  240. data/lib/active_record/type/decimal.rb +0 -64
  241. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  242. data/lib/active_record/type/decorator.rb +0 -14
  243. data/lib/active_record/type/float.rb +0 -19
  244. data/lib/active_record/type/integer.rb +0 -59
  245. data/lib/active_record/type/mutable.rb +0 -16
  246. data/lib/active_record/type/numeric.rb +0 -36
  247. data/lib/active_record/type/string.rb +0 -40
  248. data/lib/active_record/type/text.rb +0 -11
  249. data/lib/active_record/type/time_value.rb +0 -38
  250. data/lib/active_record/type/unsigned_integer.rb +0 -15
  251. data/lib/active_record/type/value.rb +0 -110
@@ -8,20 +8,39 @@ module ActiveRecord
8
8
 
9
9
  def disable_referential_integrity # :nodoc:
10
10
  if supports_disable_referential_integrity?
11
+ original_exception = nil
12
+
11
13
  begin
12
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
13
- rescue
14
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER USER" }.join(";"))
14
+ transaction(requires_new: true) do
15
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
16
+ end
17
+ rescue ActiveRecord::ActiveRecordError => e
18
+ original_exception = e
15
19
  end
16
- end
17
- yield
18
- ensure
19
- if supports_disable_referential_integrity?
20
+
21
+ begin
22
+ yield
23
+ rescue ActiveRecord::InvalidForeignKey => e
24
+ warn <<-WARNING
25
+ WARNING: Rails was not able to disable referential integrity.
26
+
27
+ This is most likely caused due to missing permissions.
28
+ Rails needs superuser privileges to disable referential integrity.
29
+
30
+ cause: #{original_exception.try(:message)}
31
+
32
+ WARNING
33
+ raise e
34
+ end
35
+
20
36
  begin
21
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
22
- rescue
23
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER USER" }.join(";"))
37
+ transaction(requires_new: true) do
38
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
39
+ end
40
+ rescue ActiveRecord::ActiveRecordError
24
41
  end
42
+ else
43
+ yield
25
44
  end
26
45
  end
27
46
  end
@@ -2,90 +2,153 @@ module ActiveRecord
2
2
  module ConnectionAdapters
3
3
  module PostgreSQL
4
4
  module ColumnMethods
5
- def xml(*args)
6
- options = args.extract_options!
7
- column(args[0], :xml, options)
5
+ # Defines the primary key field.
6
+ # Use of the native PostgreSQL UUID type is supported, and can be used
7
+ # by defining your tables as such:
8
+ #
9
+ # create_table :stuffs, id: :uuid do |t|
10
+ # t.string :content
11
+ # t.timestamps
12
+ # end
13
+ #
14
+ # By default, this will use the +uuid_generate_v4()+ function from the
15
+ # +uuid-ossp+ extension, which MUST be enabled on your database. To enable
16
+ # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
17
+ # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can
18
+ # set the +:default+ option to +nil+:
19
+ #
20
+ # create_table :stuffs, id: false do |t|
21
+ # t.primary_key :id, :uuid, default: nil
22
+ # t.uuid :foo_id
23
+ # t.timestamps
24
+ # end
25
+ #
26
+ # You may also pass a different UUID generation function from +uuid-ossp+
27
+ # or another library.
28
+ #
29
+ # Note that setting the UUID primary key default value to +nil+ will
30
+ # require you to assure that you always provide a UUID value before saving
31
+ # a record (as primary keys cannot be +nil+). This might be done via the
32
+ # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
33
+ def primary_key(name, type = :primary_key, **options)
34
+ options[:default] = options.fetch(:default, 'uuid_generate_v4()') if type == :uuid
35
+ super
36
+ end
37
+
38
+ def bigserial(*args, **options)
39
+ args.each { |name| column(name, :bigserial, options) }
40
+ end
41
+
42
+ def bit(*args, **options)
43
+ args.each { |name| column(name, :bit, options) }
44
+ end
45
+
46
+ def bit_varying(*args, **options)
47
+ args.each { |name| column(name, :bit_varying, options) }
48
+ end
49
+
50
+ def cidr(*args, **options)
51
+ args.each { |name| column(name, :cidr, options) }
52
+ end
53
+
54
+ def citext(*args, **options)
55
+ args.each { |name| column(name, :citext, options) }
8
56
  end
9
57
 
10
- def tsvector(*args)
11
- options = args.extract_options!
12
- column(args[0], :tsvector, options)
58
+ def daterange(*args, **options)
59
+ args.each { |name| column(name, :daterange, options) }
13
60
  end
14
61
 
15
- def int4range(name, options = {})
16
- column(name, :int4range, options)
62
+ def hstore(*args, **options)
63
+ args.each { |name| column(name, :hstore, options) }
17
64
  end
18
65
 
19
- def int8range(name, options = {})
20
- column(name, :int8range, options)
66
+ def inet(*args, **options)
67
+ args.each { |name| column(name, :inet, options) }
21
68
  end
22
69
 
23
- def tsrange(name, options = {})
24
- column(name, :tsrange, options)
70
+ def int4range(*args, **options)
71
+ args.each { |name| column(name, :int4range, options) }
25
72
  end
26
73
 
27
- def tstzrange(name, options = {})
28
- column(name, :tstzrange, options)
74
+ def int8range(*args, **options)
75
+ args.each { |name| column(name, :int8range, options) }
29
76
  end
30
77
 
31
- def numrange(name, options = {})
32
- column(name, :numrange, options)
78
+ def json(*args, **options)
79
+ args.each { |name| column(name, :json, options) }
33
80
  end
34
81
 
35
- def daterange(name, options = {})
36
- column(name, :daterange, options)
82
+ def jsonb(*args, **options)
83
+ args.each { |name| column(name, :jsonb, options) }
37
84
  end
38
85
 
39
- def hstore(name, options = {})
40
- column(name, :hstore, options)
86
+ def ltree(*args, **options)
87
+ args.each { |name| column(name, :ltree, options) }
41
88
  end
42
89
 
43
- def ltree(name, options = {})
44
- column(name, :ltree, options)
90
+ def macaddr(*args, **options)
91
+ args.each { |name| column(name, :macaddr, options) }
45
92
  end
46
93
 
47
- def inet(name, options = {})
48
- column(name, :inet, options)
94
+ def money(*args, **options)
95
+ args.each { |name| column(name, :money, options) }
49
96
  end
50
97
 
51
- def cidr(name, options = {})
52
- column(name, :cidr, options)
98
+ def numrange(*args, **options)
99
+ args.each { |name| column(name, :numrange, options) }
53
100
  end
54
101
 
55
- def macaddr(name, options = {})
56
- column(name, :macaddr, options)
102
+ def point(*args, **options)
103
+ args.each { |name| column(name, :point, options) }
57
104
  end
58
105
 
59
- def uuid(name, options = {})
60
- column(name, :uuid, options)
106
+ def line(*args, **options)
107
+ args.each { |name| column(name, :line, options) }
61
108
  end
62
109
 
63
- def json(name, options = {})
64
- column(name, :json, options)
110
+ def lseg(*args, **options)
111
+ args.each { |name| column(name, :lseg, options) }
65
112
  end
66
113
 
67
- def jsonb(name, options = {})
68
- column(name, :jsonb, options)
114
+ def box(*args, **options)
115
+ args.each { |name| column(name, :box, options) }
69
116
  end
70
117
 
71
- def citext(name, options = {})
72
- column(name, :citext, options)
118
+ def path(*args, **options)
119
+ args.each { |name| column(name, :path, options) }
73
120
  end
74
121
 
75
- def point(name, options = {})
76
- column(name, :point, options)
122
+ def polygon(*args, **options)
123
+ args.each { |name| column(name, :polygon, options) }
77
124
  end
78
125
 
79
- def bit(name, options = {})
80
- column(name, :bit, options)
126
+ def circle(*args, **options)
127
+ args.each { |name| column(name, :circle, options) }
81
128
  end
82
129
 
83
- def bit_varying(name, options = {})
84
- column(name, :bit_varying, options)
130
+ def serial(*args, **options)
131
+ args.each { |name| column(name, :serial, options) }
85
132
  end
86
133
 
87
- def money(name, options = {})
88
- column(name, :money, options)
134
+ def tsrange(*args, **options)
135
+ args.each { |name| column(name, :tsrange, options) }
136
+ end
137
+
138
+ def tstzrange(*args, **options)
139
+ args.each { |name| column(name, :tstzrange, options) }
140
+ end
141
+
142
+ def tsvector(*args, **options)
143
+ args.each { |name| column(name, :tsvector, options) }
144
+ end
145
+
146
+ def uuid(*args, **options)
147
+ args.each { |name| column(name, :uuid, options) }
148
+ end
149
+
150
+ def xml(*args, **options)
151
+ args.each { |name| column(name, :xml, options) }
89
152
  end
90
153
  end
91
154
 
@@ -96,41 +159,6 @@ module ActiveRecord
96
159
  class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
97
160
  include ColumnMethods
98
161
 
99
- # Defines the primary key field.
100
- # Use of the native PostgreSQL UUID type is supported, and can be used
101
- # by defining your tables as such:
102
- #
103
- # create_table :stuffs, id: :uuid do |t|
104
- # t.string :content
105
- # t.timestamps
106
- # end
107
- #
108
- # By default, this will use the +uuid_generate_v4()+ function from the
109
- # +uuid-ossp+ extension, which MUST be enabled on your database. To enable
110
- # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
111
- # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can
112
- # set the +:default+ option to +nil+:
113
- #
114
- # create_table :stuffs, id: false do |t|
115
- # t.primary_key :id, :uuid, default: nil
116
- # t.uuid :foo_id
117
- # t.timestamps
118
- # end
119
- #
120
- # You may also pass a different UUID generation function from +uuid-ossp+
121
- # or another library.
122
- #
123
- # Note that setting the UUID primary key default value to +nil+ will
124
- # require you to assure that you always provide a UUID value before saving
125
- # a record (as primary keys cannot be +nil+). This might be done via the
126
- # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
127
- def primary_key(name, type = :primary_key, options = {})
128
- return super unless type == :uuid
129
- options[:default] = options.fetch(:default, 'uuid_generate_v4()')
130
- options[:primary_key] = true
131
- column name, type, options
132
- end
133
-
134
162
  def new_column_definition(name, type, options) # :nodoc:
135
163
  column = super
136
164
  column.array = options[:array]
@@ -0,0 +1,47 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module PostgreSQL
4
+ module ColumnDumper
5
+ def column_spec_for_primary_key(column)
6
+ spec = super
7
+ if schema_type(column) == :uuid
8
+ spec[:default] ||= 'nil'
9
+ end
10
+ spec
11
+ end
12
+
13
+ # Adds +:array+ option to the default set
14
+ def prepare_column_options(column)
15
+ spec = super
16
+ spec[:array] = 'true' if column.array?
17
+ spec
18
+ end
19
+
20
+ # Adds +:array+ as a valid migration key
21
+ def migration_keys
22
+ super + [:array]
23
+ end
24
+
25
+ private
26
+
27
+ def default_primary_key?(column)
28
+ schema_type(column) == :serial
29
+ end
30
+
31
+ def schema_type(column)
32
+ return super unless column.serial?
33
+
34
+ if column.bigint?
35
+ :bigserial
36
+ else
37
+ :serial
38
+ end
39
+ end
40
+
41
+ def schema_expression(column)
42
+ super unless column.serial?
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/string/strip'
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module PostgreSQL
@@ -5,33 +7,15 @@ module ActiveRecord
5
7
  private
6
8
 
7
9
  def visit_ColumnDefinition(o)
8
- sql = super
9
- if o.primary_key? && o.type != :primary_key
10
- sql << " PRIMARY KEY "
11
- add_column_options!(sql, column_options(o))
12
- end
13
- sql
10
+ o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array)
11
+ super
14
12
  end
15
13
 
16
14
  def add_column_options!(sql, options)
17
- if options[:array] || options[:column].try(:array)
18
- sql << '[]'
19
- end
20
-
21
- column = options.fetch(:column) { return super }
22
- if column.type == :uuid && options[:default] =~ /\(\)/
23
- sql << " DEFAULT #{options[:default]}"
24
- else
25
- super
26
- end
27
- end
28
-
29
- def type_for_column(column)
30
- if column.array
31
- @conn.lookup_cast_type("#{column.sql_type}[]")
32
- else
33
- super
15
+ if options[:collation]
16
+ sql << " COLLATE \"#{options[:collation]}\""
34
17
  end
18
+ super
35
19
  end
36
20
  end
37
21
 
@@ -88,11 +72,13 @@ module ActiveRecord
88
72
 
89
73
  # Returns the list of all tables in the schema search path.
90
74
  def tables(name = nil)
91
- query(<<-SQL, 'SCHEMA').map { |row| row[0] }
92
- SELECT tablename
93
- FROM pg_tables
94
- WHERE schemaname = ANY (current_schemas(false))
95
- SQL
75
+ if name
76
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
77
+ Passing arguments to #tables is deprecated without replacement.
78
+ MSG
79
+ end
80
+
81
+ select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA')
96
82
  end
97
83
 
98
84
  def data_sources # :nodoc
@@ -109,10 +95,20 @@ module ActiveRecord
109
95
  # If the schema is not specified as part of +name+ then it will only find tables within
110
96
  # the current schema search path (regardless of permissions to access tables in other schemas)
111
97
  def table_exists?(name)
98
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
99
+ #table_exists? currently checks both tables and views.
100
+ This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
101
+ Use #data_source_exists? instead.
102
+ MSG
103
+
104
+ data_source_exists?(name)
105
+ end
106
+
107
+ def data_source_exists?(name)
112
108
  name = Utils.extract_schema_qualified_name(name.to_s)
113
109
  return false unless name.identifier
114
110
 
115
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
111
+ select_value(<<-SQL, 'SCHEMA').to_i > 0
116
112
  SELECT COUNT(*)
117
113
  FROM pg_class c
118
114
  LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
@@ -121,126 +117,176 @@ module ActiveRecord
121
117
  AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
122
118
  SQL
123
119
  end
124
- alias data_source_exists? table_exists?
125
120
 
126
- def drop_table(table_name, options = {})
127
- execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
121
+ def views # :nodoc:
122
+ select_values(<<-SQL, 'SCHEMA')
123
+ SELECT c.relname
124
+ FROM pg_class c
125
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
126
+ WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
127
+ AND n.nspname = ANY (current_schemas(false))
128
+ SQL
129
+ end
130
+
131
+ def view_exists?(view_name) # :nodoc:
132
+ name = Utils.extract_schema_qualified_name(view_name.to_s)
133
+ return false unless name.identifier
134
+
135
+ select_values(<<-SQL, 'SCHEMA').any?
136
+ SELECT c.relname
137
+ FROM pg_class c
138
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
139
+ WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
140
+ AND c.relname = '#{name.identifier}'
141
+ AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
142
+ SQL
143
+ end
144
+
145
+ def drop_table(table_name, options = {}) # :nodoc:
146
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
128
147
  end
129
148
 
130
149
  # Returns true if schema exists.
131
150
  def schema_exists?(name)
132
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
133
- SELECT COUNT(*)
134
- FROM pg_namespace
135
- WHERE nspname = '#{name}'
136
- SQL
151
+ select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}'", 'SCHEMA').to_i > 0
137
152
  end
138
153
 
154
+ # Verifies existence of an index with a given name.
139
155
  def index_name_exists?(table_name, index_name, default)
140
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
156
+ table = Utils.extract_schema_qualified_name(table_name.to_s)
157
+ index = Utils.extract_schema_qualified_name(index_name.to_s)
158
+
159
+ select_value(<<-SQL, 'SCHEMA').to_i > 0
141
160
  SELECT COUNT(*)
142
161
  FROM pg_class t
143
162
  INNER JOIN pg_index d ON t.oid = d.indrelid
144
163
  INNER JOIN pg_class i ON d.indexrelid = i.oid
164
+ LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
145
165
  WHERE i.relkind = 'i'
146
- AND i.relname = '#{index_name}'
147
- AND t.relname = '#{table_name}'
148
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
166
+ AND i.relname = '#{index.identifier}'
167
+ AND t.relname = '#{table.identifier}'
168
+ AND n.nspname = #{index.schema ? "'#{index.schema}'" : 'ANY (current_schemas(false))'}
149
169
  SQL
150
170
  end
151
171
 
152
172
  # Returns an array of indexes for the given table.
153
173
  def indexes(table_name, name = nil)
154
- result = query(<<-SQL, 'SCHEMA')
155
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
156
- FROM pg_class t
157
- INNER JOIN pg_index d ON t.oid = d.indrelid
158
- INNER JOIN pg_class i ON d.indexrelid = i.oid
159
- WHERE i.relkind = 'i'
160
- AND d.indisprimary = 'f'
161
- AND t.relname = '#{table_name}'
162
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
174
+ table = Utils.extract_schema_qualified_name(table_name.to_s)
175
+
176
+ result = query(<<-SQL, 'SCHEMA')
177
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
178
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment,
179
+ (SELECT COUNT(*) FROM pg_opclass o
180
+ JOIN (SELECT unnest(string_to_array(d.indclass::text, ' '))::int oid) c
181
+ ON o.oid = c.oid WHERE o.opcdefault = 'f')
182
+ FROM pg_class t
183
+ INNER JOIN pg_index d ON t.oid = d.indrelid
184
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
185
+ LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
186
+ WHERE i.relkind = 'i'
187
+ AND d.indisprimary = 'f'
188
+ AND t.relname = '#{table.identifier}'
189
+ AND n.nspname = #{table.schema ? "'#{table.schema}'" : 'ANY (current_schemas(false))'}
163
190
  ORDER BY i.relname
164
191
  SQL
165
192
 
166
193
  result.map do |row|
167
194
  index_name = row[0]
168
- unique = row[1] == 't'
169
- indkey = row[2].split(" ")
195
+ unique = row[1]
196
+ indkey = row[2].split(" ").map(&:to_i)
170
197
  inddef = row[3]
171
198
  oid = row[4]
199
+ comment = row[5]
200
+ opclass = row[6]
172
201
 
173
- columns = Hash[query(<<-SQL, "SCHEMA")]
174
- SELECT a.attnum, a.attname
175
- FROM pg_attribute a
176
- WHERE a.attrelid = #{oid}
177
- AND a.attnum IN (#{indkey.join(",")})
178
- SQL
202
+ using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten
179
203
 
180
- column_names = columns.values_at(*indkey).compact
204
+ if indkey.include?(0) || opclass > 0
205
+ columns = expressions
206
+ else
207
+ columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
208
+ SELECT a.attnum, a.attname
209
+ FROM pg_attribute a
210
+ WHERE a.attrelid = #{oid}
211
+ AND a.attnum IN (#{indkey.join(",")})
212
+ SQL
181
213
 
182
- unless column_names.empty?
183
214
  # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
184
- desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
185
- orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
186
- where = inddef.scan(/WHERE (.+)$/).flatten[0]
187
- using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
188
-
189
- IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
215
+ orders = Hash[
216
+ expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] }
217
+ ]
190
218
  end
219
+
220
+ IndexDefinition.new(table_name, index_name, unique, columns, [], orders, where, nil, using.to_sym, comment.presence)
191
221
  end.compact
192
222
  end
193
223
 
194
224
  # Returns the list of all column definitions for a table.
195
- def columns(table_name)
196
- # Limit, precision, and scale are all handled by the superclass.
197
- column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
198
- oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type)
199
- default_value = extract_value_from_default(oid, default)
225
+ def columns(table_name) # :nodoc:
226
+ table_name = table_name.to_s
227
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation, comment|
228
+ oid = oid.to_i
229
+ fmod = fmod.to_i
230
+ type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
231
+ default_value = extract_value_from_default(default)
200
232
  default_function = extract_default_function(default_value, default)
201
- new_column(column_name, default_value, oid, type, notnull == 'f', default_function)
233
+ new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function, collation, comment: comment.presence, max_identifier_length: max_identifier_length)
202
234
  end
203
235
  end
204
236
 
205
- def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil) # :nodoc:
206
- PostgreSQLColumn.new(name, default, cast_type, sql_type, null, default_function)
237
+ def new_column(*args) # :nodoc:
238
+ PostgreSQLColumn.new(*args)
239
+ end
240
+
241
+ def table_options(table_name) # :nodoc:
242
+ if comment = table_comment(table_name)
243
+ { comment: comment }
244
+ end
245
+ end
246
+
247
+ # Returns a comment stored in database for given table
248
+ def table_comment(table_name) # :nodoc:
249
+ name = Utils.extract_schema_qualified_name(table_name.to_s)
250
+ if name.identifier
251
+ select_value(<<-SQL.strip_heredoc, 'SCHEMA')
252
+ SELECT pg_catalog.obj_description(c.oid, 'pg_class')
253
+ FROM pg_catalog.pg_class c
254
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
255
+ WHERE c.relname = #{quote(name.identifier)}
256
+ AND c.relkind IN ('r') -- (r)elation/table
257
+ AND n.nspname = #{name.schema ? quote(name.schema) : 'ANY (current_schemas(false))'}
258
+ SQL
259
+ end
207
260
  end
208
261
 
209
262
  # Returns the current database name.
210
263
  def current_database
211
- query('select current_database()', 'SCHEMA')[0][0]
264
+ select_value('select current_database()', 'SCHEMA')
212
265
  end
213
266
 
214
267
  # Returns the current schema name.
215
268
  def current_schema
216
- query('SELECT current_schema', 'SCHEMA')[0][0]
269
+ select_value('SELECT current_schema', 'SCHEMA')
217
270
  end
218
271
 
219
272
  # Returns the current database encoding format.
220
273
  def encoding
221
- query(<<-end_sql, 'SCHEMA')[0][0]
222
- SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
223
- WHERE pg_database.datname LIKE '#{current_database}'
224
- end_sql
274
+ select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
225
275
  end
226
276
 
227
277
  # Returns the current database collation.
228
278
  def collation
229
- query(<<-end_sql, 'SCHEMA')[0][0]
230
- SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
231
- end_sql
279
+ select_value("SELECT datcollate FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
232
280
  end
233
281
 
234
282
  # Returns the current database ctype.
235
283
  def ctype
236
- query(<<-end_sql, 'SCHEMA')[0][0]
237
- SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
238
- end_sql
284
+ select_value("SELECT datctype FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
239
285
  end
240
286
 
241
287
  # Returns an array of schema names.
242
288
  def schema_names
243
- query(<<-SQL, 'SCHEMA').flatten
289
+ select_values(<<-SQL, 'SCHEMA')
244
290
  SELECT nspname
245
291
  FROM pg_namespace
246
292
  WHERE nspname !~ '^pg_.*'
@@ -251,12 +297,12 @@ module ActiveRecord
251
297
 
252
298
  # Creates a schema for the given schema name.
253
299
  def create_schema schema_name
254
- execute "CREATE SCHEMA #{schema_name}"
300
+ execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
255
301
  end
256
302
 
257
303
  # Drops the schema for the given schema name.
258
- def drop_schema schema_name
259
- execute "DROP SCHEMA #{schema_name} CASCADE"
304
+ def drop_schema(schema_name, options = {})
305
+ execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
260
306
  end
261
307
 
262
308
  # Sets the schema search path to a string of comma-separated schema names.
@@ -273,12 +319,12 @@ module ActiveRecord
273
319
 
274
320
  # Returns the active schema search path.
275
321
  def schema_search_path
276
- @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
322
+ @schema_search_path ||= select_value('SHOW search_path', 'SCHEMA')
277
323
  end
278
324
 
279
325
  # Returns the current client message level.
280
326
  def client_min_messages
281
- query('SHOW client_min_messages', 'SCHEMA')[0][0]
327
+ select_value('SHOW client_min_messages', 'SCHEMA')
282
328
  end
283
329
 
284
330
  # Set the client message level.
@@ -296,10 +342,7 @@ module ActiveRecord
296
342
  end
297
343
 
298
344
  def serial_sequence(table, column)
299
- result = exec_query(<<-eosql, 'SCHEMA')
300
- SELECT pg_get_serial_sequence('#{table}', '#{column}')
301
- eosql
302
- result.rows.first.first
345
+ select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", 'SCHEMA')
303
346
  end
304
347
 
305
348
  # Sets the sequence of a table's primary key to the specified value.
@@ -310,11 +353,9 @@ module ActiveRecord
310
353
  if sequence
311
354
  quoted_sequence = quote_table_name(sequence)
312
355
 
313
- select_value <<-end_sql, 'SCHEMA'
314
- SELECT setval('#{quoted_sequence}', #{value})
315
- end_sql
356
+ select_value("SELECT setval('#{quoted_sequence}', #{value})", 'SCHEMA')
316
357
  else
317
- @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
358
+ @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
318
359
  end
319
360
  end
320
361
  end
@@ -329,14 +370,22 @@ module ActiveRecord
329
370
  end
330
371
 
331
372
  if @logger && pk && !sequence
332
- @logger.warn "#{table} has primary key #{pk} with no default sequence"
373
+ @logger.warn "#{table} has primary key #{pk} with no default sequence."
333
374
  end
334
375
 
335
376
  if pk && sequence
336
377
  quoted_sequence = quote_table_name(sequence)
378
+ max_pk = select_value("select MAX(#{quote_column_name pk}) from #{quote_table_name(table)}")
379
+ if max_pk.nil?
380
+ if postgresql_version >= 100000
381
+ minvalue = select_value("SELECT seqmin from pg_sequence where seqrelid = '#{quoted_sequence}'::regclass")
382
+ else
383
+ minvalue = select_value("SELECT min_value FROM #{quoted_sequence}")
384
+ end
385
+ end
337
386
 
338
- select_value <<-end_sql, 'SCHEMA'
339
- SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
387
+ select_value(<<-end_sql, "SCHEMA")
388
+ SELECT setval('#{quoted_sequence}', #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})
340
389
  end_sql
341
390
  end
342
391
  end
@@ -395,17 +444,19 @@ module ActiveRecord
395
444
  nil
396
445
  end
397
446
 
398
- # Returns just a table's primary key
399
- def primary_key(table)
400
- pks = exec_query(<<-end_sql, 'SCHEMA').rows
401
- SELECT attr.attname
402
- FROM pg_attribute attr
403
- INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
404
- WHERE cons.contype = 'p'
405
- AND cons.conrelid = '#{quote_table_name(table)}'::regclass
406
- end_sql
407
- return nil unless pks.count == 1
408
- pks[0][0]
447
+ def primary_keys(table_name) # :nodoc:
448
+ select_values(<<-SQL.strip_heredoc, 'SCHEMA')
449
+ WITH pk_constraint AS (
450
+ SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint
451
+ WHERE contype = 'p'
452
+ AND conrelid = '#{quote_table_name(table_name)}'::regclass
453
+ ), cons AS (
454
+ SELECT conrelid, connum, row_number() OVER() AS rownum FROM pk_constraint
455
+ )
456
+ SELECT attr.attname FROM pg_attribute attr
457
+ INNER JOIN cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.connum
458
+ ORDER BY cons.rownum
459
+ SQL
409
460
  end
410
461
 
411
462
  # Renames a table.
@@ -422,7 +473,7 @@ module ActiveRecord
422
473
  new_seq = "#{new_name}_#{pk}_seq"
423
474
  idx = "#{table_name}_pkey"
424
475
  new_idx = "#{new_name}_pkey"
425
- execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
476
+ execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
426
477
  execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
427
478
  end
428
479
 
@@ -432,50 +483,69 @@ module ActiveRecord
432
483
  def add_column(table_name, column_name, type, options = {}) #:nodoc:
433
484
  clear_cache!
434
485
  super
486
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
435
487
  end
436
488
 
437
- # Changes the column of a table.
438
- def change_column(table_name, column_name, type, options = {})
489
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
439
490
  clear_cache!
440
491
  quoted_table_name = quote_table_name(table_name)
441
- sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
442
- sql_type << "[]" if options[:array]
443
- sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}"
444
- sql << " USING #{options[:using]}" if options[:using]
445
- if options[:cast_as]
446
- sql << " USING CAST(#{quote_column_name(column_name)} AS #{type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale])})"
492
+ quoted_column_name = quote_column_name(column_name)
493
+ sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale], options[:array])
494
+ sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
495
+ if options[:collation]
496
+ sql << " COLLATE \"#{options[:collation]}\""
497
+ end
498
+ if options[:using]
499
+ sql << " USING #{options[:using]}"
500
+ elsif options[:cast_as]
501
+ cast_as_type = type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale], options[:array])
502
+ sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
447
503
  end
448
504
  execute sql
449
505
 
450
506
  change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
451
507
  change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
508
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
452
509
  end
453
510
 
454
511
  # Changes the default value of a table column.
455
- def change_column_default(table_name, column_name, default)
512
+ def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
456
513
  clear_cache!
457
514
  column = column_for(table_name, column_name)
458
515
  return unless column
459
516
 
517
+ default = extract_new_default_value(default_or_changes)
460
518
  alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
461
519
  if default.nil?
462
520
  # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
463
521
  # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
464
522
  execute alter_column_query % "DROP DEFAULT"
465
523
  else
466
- execute alter_column_query % "SET DEFAULT #{quote_default_value(default, column)}"
524
+ execute alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
467
525
  end
468
526
  end
469
527
 
470
- def change_column_null(table_name, column_name, null, default = nil)
528
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
471
529
  clear_cache!
472
530
  unless null || default.nil?
473
531
  column = column_for(table_name, column_name)
474
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
532
+ 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
475
533
  end
476
534
  execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
477
535
  end
478
536
 
537
+ # Adds comment for given table column or drops it if +comment+ is a +nil+
538
+ def change_column_comment(table_name, column_name, comment) # :nodoc:
539
+ clear_cache!
540
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}"
541
+ end
542
+
543
+ # Adds comment for given table or drops it if +comment+ is a +nil+
544
+ def change_table_comment(table_name, comment) # :nodoc:
545
+ clear_cache!
546
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
547
+ end
548
+
479
549
  # Renames a column in a table.
480
550
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
481
551
  clear_cache!
@@ -484,14 +554,38 @@ module ActiveRecord
484
554
  end
485
555
 
486
556
  def add_index(table_name, column_name, options = {}) #:nodoc:
487
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
488
- execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
557
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
558
+ 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
559
+ execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
560
+ end
489
561
  end
490
562
 
491
- def remove_index!(table_name, index_name) #:nodoc:
492
- execute "DROP INDEX #{quote_table_name(index_name)}"
563
+ def remove_index(table_name, options = {}) #:nodoc:
564
+ table = Utils.extract_schema_qualified_name(table_name.to_s)
565
+
566
+ if options.is_a?(Hash) && options.key?(:name)
567
+ provided_index = Utils.extract_schema_qualified_name(options[:name].to_s)
568
+
569
+ options[:name] = provided_index.identifier
570
+ table = PostgreSQL::Name.new(provided_index.schema, table.identifier) unless table.schema.present?
571
+
572
+ if provided_index.schema.present? && table.schema != provided_index.schema
573
+ raise ArgumentError.new("Index schema '#{provided_index.schema}' does not match table schema '#{table.schema}'")
574
+ end
575
+ end
576
+
577
+ index_to_remove = PostgreSQL::Name.new(table.schema, index_name_for_remove(table.to_s, options))
578
+ algorithm =
579
+ if options.is_a?(Hash) && options.key?(:algorithm)
580
+ index_algorithms.fetch(options[:algorithm]) do
581
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
582
+ end
583
+ end
584
+ execute "DROP INDEX #{algorithm} #{quote_table_name(index_to_remove)}"
493
585
  end
494
586
 
587
+ # Renames an index of a table. Raises error if length of new
588
+ # index name is greater than allowed limit.
495
589
  def rename_index(table_name, old_name, new_name)
496
590
  validate_index_length!(table_name, new_name)
497
591
 
@@ -499,7 +593,7 @@ module ActiveRecord
499
593
  end
500
594
 
501
595
  def foreign_keys(table_name)
502
- fk_info = select_all <<-SQL.strip_heredoc
596
+ fk_info = select_all(<<-SQL.strip_heredoc, 'SCHEMA')
503
597
  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
504
598
  FROM pg_constraint c
505
599
  JOIN pg_class t1 ON c.conrelid = t1.oid
@@ -535,46 +629,36 @@ module ActiveRecord
535
629
  end
536
630
  end
537
631
 
538
- def index_name_length
539
- 63
540
- end
541
-
542
632
  # Maps logical Rails types to PostgreSQL-specific data types.
543
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
544
- case type.to_s
633
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil, array = nil)
634
+ sql = case type.to_s
545
635
  when 'binary'
546
636
  # PostgreSQL doesn't support limits on binary (bytea) columns.
547
- # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
637
+ # The hard limit is 1GB, because of a 32-bit size field, and TOAST.
548
638
  case limit
549
639
  when nil, 0..0x3fffffff; super(type)
550
640
  else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
551
641
  end
552
642
  when 'text'
553
643
  # PostgreSQL doesn't support limits on text columns.
554
- # The hard limit is 1Gb, according to section 8.3 in the manual.
644
+ # The hard limit is 1GB, according to section 8.3 in the manual.
555
645
  case limit
556
646
  when nil, 0..0x3fffffff; super(type)
557
647
  else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
558
648
  end
559
649
  when 'integer'
560
- return 'integer' unless limit
561
-
562
650
  case limit
563
- when 1, 2; 'smallint'
564
- when 3, 4; 'integer'
565
- when 5..8; 'bigint'
566
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.")
567
- end
568
- when 'datetime'
569
- return super unless precision
570
-
571
- case precision
572
- when 0..6; "timestamp(#{precision})"
573
- else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6")
651
+ when 1, 2; 'smallint'
652
+ when nil, 3, 4; 'integer'
653
+ when 5..8; 'bigint'
654
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.")
574
655
  end
575
656
  else
576
- super
657
+ super(type, limit, precision, scale)
577
658
  end
659
+
660
+ sql << '[]' if array && type != :primary_key
661
+ sql
578
662
  end
579
663
 
580
664
  # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
@@ -590,6 +674,18 @@ module ActiveRecord
590
674
 
591
675
  [super, *order_columns].join(', ')
592
676
  end
677
+
678
+ def fetch_type_metadata(column_name, sql_type, oid, fmod)
679
+ cast_type = get_oid_type(oid, fmod, column_name, sql_type)
680
+ simple_type = SqlTypeMetadata.new(
681
+ sql_type: sql_type,
682
+ type: cast_type.type,
683
+ limit: cast_type.limit,
684
+ precision: cast_type.precision,
685
+ scale: cast_type.scale,
686
+ )
687
+ PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
688
+ end
593
689
  end
594
690
  end
595
691
  end