activerecord 4.2.11.3 → 5.0.0.beta1

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 (229) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1029 -1349
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -7
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record.rb +7 -3
  7. data/lib/active_record/aggregations.rb +35 -25
  8. data/lib/active_record/association_relation.rb +2 -2
  9. data/lib/active_record/associations.rb +305 -204
  10. data/lib/active_record/associations/alias_tracker.rb +19 -16
  11. data/lib/active_record/associations/association.rb +10 -8
  12. data/lib/active_record/associations/association_scope.rb +73 -102
  13. data/lib/active_record/associations/belongs_to_association.rb +20 -32
  14. data/lib/active_record/associations/builder/association.rb +28 -34
  15. data/lib/active_record/associations/builder/belongs_to.rb +41 -18
  16. data/lib/active_record/associations/builder/collection_association.rb +8 -24
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +11 -11
  18. data/lib/active_record/associations/builder/has_many.rb +4 -4
  19. data/lib/active_record/associations/builder/has_one.rb +10 -5
  20. data/lib/active_record/associations/builder/singular_association.rb +2 -9
  21. data/lib/active_record/associations/collection_association.rb +40 -43
  22. data/lib/active_record/associations/collection_proxy.rb +55 -29
  23. data/lib/active_record/associations/foreign_association.rb +1 -1
  24. data/lib/active_record/associations/has_many_association.rb +20 -71
  25. data/lib/active_record/associations/has_many_through_association.rb +8 -52
  26. data/lib/active_record/associations/has_one_association.rb +12 -5
  27. data/lib/active_record/associations/join_dependency.rb +28 -18
  28. data/lib/active_record/associations/join_dependency/join_association.rb +13 -12
  29. data/lib/active_record/associations/preloader.rb +13 -4
  30. data/lib/active_record/associations/preloader/association.rb +45 -51
  31. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  32. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  33. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  34. data/lib/active_record/associations/preloader/through_association.rb +5 -4
  35. data/lib/active_record/associations/singular_association.rb +6 -0
  36. data/lib/active_record/associations/through_association.rb +11 -3
  37. data/lib/active_record/attribute.rb +61 -17
  38. data/lib/active_record/attribute/user_provided_default.rb +23 -0
  39. data/lib/active_record/attribute_assignment.rb +27 -140
  40. data/lib/active_record/attribute_decorators.rb +6 -5
  41. data/lib/active_record/attribute_methods.rb +79 -26
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  43. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  44. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  45. data/lib/active_record/attribute_methods/query.rb +2 -2
  46. data/lib/active_record/attribute_methods/read.rb +26 -42
  47. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +42 -9
  49. data/lib/active_record/attribute_methods/write.rb +13 -24
  50. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  51. data/lib/active_record/attribute_set.rb +30 -3
  52. data/lib/active_record/attribute_set/builder.rb +6 -4
  53. data/lib/active_record/attributes.rb +194 -81
  54. data/lib/active_record/autosave_association.rb +33 -15
  55. data/lib/active_record/base.rb +30 -18
  56. data/lib/active_record/callbacks.rb +36 -40
  57. data/lib/active_record/coders/yaml_column.rb +20 -8
  58. data/lib/active_record/collection_cache_key.rb +31 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +431 -122
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +40 -22
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +62 -8
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -38
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +229 -185
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +52 -13
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +275 -115
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +32 -33
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +83 -32
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +384 -221
  70. data/lib/active_record/connection_adapters/column.rb +27 -41
  71. data/lib/active_record/connection_adapters/connection_specification.rb +2 -21
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +57 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +69 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +59 -0
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +22 -101
  77. data/lib/active_record/connection_adapters/postgresql/column.rb +6 -10
  78. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +3 -3
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  80. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +23 -57
  81. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  85. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  86. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  87. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +23 -16
  92. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  93. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  96. data/lib/active_record/connection_adapters/postgresql/quoting.rb +18 -11
  97. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -0
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +174 -128
  101. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  102. data/lib/active_record/connection_adapters/postgresql_adapter.rb +184 -112
  103. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  104. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +15 -0
  106. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +134 -110
  107. data/lib/active_record/connection_adapters/statement_pool.rb +28 -11
  108. data/lib/active_record/connection_handling.rb +5 -5
  109. data/lib/active_record/core.rb +72 -104
  110. data/lib/active_record/counter_cache.rb +9 -20
  111. data/lib/active_record/dynamic_matchers.rb +1 -20
  112. data/lib/active_record/enum.rb +110 -76
  113. data/lib/active_record/errors.rb +72 -47
  114. data/lib/active_record/explain_registry.rb +1 -1
  115. data/lib/active_record/explain_subscriber.rb +1 -1
  116. data/lib/active_record/fixture_set/file.rb +19 -4
  117. data/lib/active_record/fixtures.rb +76 -40
  118. data/lib/active_record/gem_version.rb +4 -4
  119. data/lib/active_record/inheritance.rb +27 -40
  120. data/lib/active_record/integration.rb +4 -4
  121. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  122. data/lib/active_record/locale/en.yml +3 -2
  123. data/lib/active_record/locking/optimistic.rb +10 -14
  124. data/lib/active_record/locking/pessimistic.rb +1 -1
  125. data/lib/active_record/log_subscriber.rb +40 -22
  126. data/lib/active_record/migration.rb +304 -133
  127. data/lib/active_record/migration/command_recorder.rb +59 -18
  128. data/lib/active_record/migration/compatibility.rb +90 -0
  129. data/lib/active_record/model_schema.rb +92 -40
  130. data/lib/active_record/nested_attributes.rb +45 -34
  131. data/lib/active_record/null_relation.rb +15 -7
  132. data/lib/active_record/persistence.rb +112 -72
  133. data/lib/active_record/querying.rb +6 -5
  134. data/lib/active_record/railtie.rb +20 -13
  135. data/lib/active_record/railties/controller_runtime.rb +1 -1
  136. data/lib/active_record/railties/databases.rake +47 -38
  137. data/lib/active_record/readonly_attributes.rb +1 -1
  138. data/lib/active_record/reflection.rb +182 -57
  139. data/lib/active_record/relation.rb +152 -100
  140. data/lib/active_record/relation/batches.rb +133 -33
  141. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  142. data/lib/active_record/relation/calculations.rb +80 -101
  143. data/lib/active_record/relation/delegation.rb +6 -19
  144. data/lib/active_record/relation/finder_methods.rb +58 -46
  145. data/lib/active_record/relation/from_clause.rb +32 -0
  146. data/lib/active_record/relation/merger.rb +13 -42
  147. data/lib/active_record/relation/predicate_builder.rb +99 -105
  148. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  149. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +78 -0
  150. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  151. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  152. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  153. data/lib/active_record/relation/predicate_builder/range_handler.rb +17 -0
  154. data/lib/active_record/relation/query_attribute.rb +19 -0
  155. data/lib/active_record/relation/query_methods.rb +274 -238
  156. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  157. data/lib/active_record/relation/spawn_methods.rb +3 -6
  158. data/lib/active_record/relation/where_clause.rb +173 -0
  159. data/lib/active_record/relation/where_clause_factory.rb +37 -0
  160. data/lib/active_record/result.rb +4 -3
  161. data/lib/active_record/runtime_registry.rb +1 -1
  162. data/lib/active_record/sanitization.rb +94 -65
  163. data/lib/active_record/schema.rb +23 -22
  164. data/lib/active_record/schema_dumper.rb +33 -22
  165. data/lib/active_record/schema_migration.rb +10 -4
  166. data/lib/active_record/scoping.rb +17 -6
  167. data/lib/active_record/scoping/default.rb +19 -6
  168. data/lib/active_record/scoping/named.rb +39 -28
  169. data/lib/active_record/secure_token.rb +38 -0
  170. data/lib/active_record/serialization.rb +2 -4
  171. data/lib/active_record/statement_cache.rb +15 -13
  172. data/lib/active_record/store.rb +8 -3
  173. data/lib/active_record/suppressor.rb +54 -0
  174. data/lib/active_record/table_metadata.rb +64 -0
  175. data/lib/active_record/tasks/database_tasks.rb +30 -40
  176. data/lib/active_record/tasks/mysql_database_tasks.rb +7 -15
  177. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
  178. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  179. data/lib/active_record/timestamp.rb +16 -9
  180. data/lib/active_record/touch_later.rb +58 -0
  181. data/lib/active_record/transactions.rb +138 -56
  182. data/lib/active_record/type.rb +66 -17
  183. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  184. data/lib/active_record/type/date.rb +2 -45
  185. data/lib/active_record/type/date_time.rb +2 -49
  186. data/lib/active_record/type/internal/abstract_json.rb +33 -0
  187. data/lib/active_record/type/internal/timezone.rb +15 -0
  188. data/lib/active_record/type/serialized.rb +9 -14
  189. data/lib/active_record/type/time.rb +3 -21
  190. data/lib/active_record/type/type_map.rb +4 -4
  191. data/lib/active_record/type_caster.rb +7 -0
  192. data/lib/active_record/type_caster/connection.rb +29 -0
  193. data/lib/active_record/type_caster/map.rb +19 -0
  194. data/lib/active_record/validations.rb +33 -32
  195. data/lib/active_record/validations/absence.rb +24 -0
  196. data/lib/active_record/validations/associated.rb +10 -3
  197. data/lib/active_record/validations/length.rb +36 -0
  198. data/lib/active_record/validations/presence.rb +12 -12
  199. data/lib/active_record/validations/uniqueness.rb +24 -21
  200. data/lib/rails/generators/active_record/migration.rb +7 -0
  201. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  202. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  203. data/lib/rails/generators/active_record/migration/templates/migration.rb +4 -1
  204. data/lib/rails/generators/active_record/model/model_generator.rb +21 -15
  205. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  206. metadata +50 -35
  207. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  208. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  209. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  210. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  211. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  212. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  213. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  214. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  215. data/lib/active_record/type/big_integer.rb +0 -13
  216. data/lib/active_record/type/binary.rb +0 -50
  217. data/lib/active_record/type/boolean.rb +0 -31
  218. data/lib/active_record/type/decimal.rb +0 -64
  219. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  220. data/lib/active_record/type/decorator.rb +0 -14
  221. data/lib/active_record/type/float.rb +0 -19
  222. data/lib/active_record/type/integer.rb +0 -59
  223. data/lib/active_record/type/mutable.rb +0 -16
  224. data/lib/active_record/type/numeric.rb +0 -36
  225. data/lib/active_record/type/string.rb +0 -40
  226. data/lib/active_record/type/text.rb +0 -11
  227. data/lib/active_record/type/time_value.rb +0 -38
  228. data/lib/active_record/type/unsigned_integer.rb +0 -15
  229. data/lib/active_record/type/value.rb +0 -110
@@ -8,10 +8,6 @@ module ActiveRecord
8
8
  def initialize(type)
9
9
  @type = type
10
10
  end
11
-
12
- def text?
13
- false
14
- end
15
11
  end
16
12
  end
17
13
  end
@@ -5,13 +5,13 @@ module ActiveRecord
5
5
  class Uuid < Type::Value # :nodoc:
6
6
  ACCEPTABLE_UUID = %r{\A\{?([a-fA-F0-9]{4}-?){8}\}?\z}x
7
7
 
8
- alias_method :type_cast_for_database, :type_cast_from_database
8
+ alias_method :serialize, :deserialize
9
9
 
10
10
  def type
11
11
  :uuid
12
12
  end
13
13
 
14
- def type_cast(value)
14
+ def cast(value)
15
15
  value.to_s[ACCEPTABLE_UUID, 0]
16
16
  end
17
17
  end
@@ -16,7 +16,7 @@ module ActiveRecord
16
16
  # FIXME: this should probably split on +delim+ and use +subtype+
17
17
  # to cast the values. Unfortunately, the current Rails behavior
18
18
  # is to just return the string.
19
- def type_cast(value)
19
+ def cast(value)
20
20
  value
21
21
  end
22
22
  end
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  :xml
8
8
  end
9
9
 
10
- def type_cast_for_database(value)
10
+ def serialize(value)
11
11
  return unless value
12
12
  Data.new(super)
13
13
  end
@@ -31,6 +31,11 @@ module ActiveRecord
31
31
  Utils.extract_schema_qualified_name(name.to_s).quoted
32
32
  end
33
33
 
34
+ # Quotes schema names for use in SQL queries.
35
+ def quote_schema_name(name)
36
+ PGconn.quote_ident(name)
37
+ end
38
+
34
39
  def quote_table_name_for_assignment(table, attr)
35
40
  quote_column_name(attr)
36
41
  end
@@ -40,30 +45,32 @@ module ActiveRecord
40
45
  PGconn.quote_ident(name.to_s)
41
46
  end
42
47
 
43
- # Quote date/time values for use in SQL input. Includes microseconds
44
- # if the value is a Time responding to usec.
48
+ # Quote date/time values for use in SQL input.
45
49
  def quoted_date(value) #:nodoc:
46
- result = super
47
- if value.acts_like?(:time) && value.respond_to?(:usec)
48
- result = "#{result}.#{sprintf("%06d", value.usec)}"
49
- end
50
-
51
50
  if value.year <= 0
52
51
  bce_year = format("%04d", -value.year + 1)
53
- result = result.sub(/^-?\d+/, bce_year) + " BC"
52
+ super.sub(/^-?\d+/, bce_year) + " BC"
53
+ else
54
+ super
54
55
  end
55
- result
56
56
  end
57
57
 
58
58
  # Does not quote function default values for UUID columns
59
- def quote_default_value(value, column) #:nodoc:
59
+ def quote_default_expression(value, column) #:nodoc:
60
60
  if column.type == :uuid && value =~ /\(\)/
61
61
  value
62
+ elsif column.respond_to?(:array?)
63
+ value = type_cast_from_column(column, value)
64
+ quote(value)
62
65
  else
63
- quote(value, column)
66
+ super
64
67
  end
65
68
  end
66
69
 
70
+ def lookup_cast_type_from_column(column) # :nodoc:
71
+ type_map.lookup(column.oid, column.fmod, column.sql_type)
72
+ end
73
+
67
74
  private
68
75
 
69
76
  def _quote(value)
@@ -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,54 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module PostgreSQL
4
+ module ColumnDumper
5
+ def column_spec_for_primary_key(column)
6
+ spec = {}
7
+ if column.serial?
8
+ return unless column.bigint?
9
+ spec[:id] = ':bigserial'
10
+ elsif column.type == :uuid
11
+ spec[:id] = ':uuid'
12
+ spec[:default] = column.default_function.inspect
13
+ else
14
+ spec[:id] = column.type.inspect
15
+ spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
16
+ end
17
+ spec
18
+ end
19
+
20
+ # Adds +:array+ option to the default set
21
+ def prepare_column_options(column)
22
+ spec = super
23
+ spec[:array] = 'true' if column.array?
24
+ spec
25
+ end
26
+
27
+ # Adds +:array+ as a valid migration key
28
+ def migration_keys
29
+ super + [:array]
30
+ end
31
+
32
+ private
33
+
34
+ def schema_type(column)
35
+ return super unless column.serial?
36
+
37
+ if column.bigint?
38
+ 'bigserial'
39
+ else
40
+ 'serial'
41
+ end
42
+ end
43
+
44
+ def schema_default(column)
45
+ if column.default_function
46
+ column.default_function.inspect unless column.serial?
47
+ else
48
+ super
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -5,33 +5,15 @@ module ActiveRecord
5
5
  private
6
6
 
7
7
  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
8
+ o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array)
9
+ super
14
10
  end
15
11
 
16
12
  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
13
+ if options[:collation]
14
+ sql << " COLLATE \"#{options[:collation]}\""
34
15
  end
16
+ super
35
17
  end
36
18
  end
37
19
 
@@ -88,11 +70,13 @@ module ActiveRecord
88
70
 
89
71
  # Returns the list of all tables in the schema search path.
90
72
  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
73
+ if name
74
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
75
+ Passing arguments to #tables is deprecated without replacement.
76
+ MSG
77
+ end
78
+
79
+ select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA')
96
80
  end
97
81
 
98
82
  def data_sources # :nodoc
@@ -109,10 +93,20 @@ module ActiveRecord
109
93
  # If the schema is not specified as part of +name+ then it will only find tables within
110
94
  # the current schema search path (regardless of permissions to access tables in other schemas)
111
95
  def table_exists?(name)
96
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
97
+ #table_exists? currently checks both tables and views.
98
+ This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
99
+ Use #data_source_exists? instead.
100
+ MSG
101
+
102
+ data_source_exists?(name)
103
+ end
104
+
105
+ def data_source_exists?(name)
112
106
  name = Utils.extract_schema_qualified_name(name.to_s)
113
107
  return false unless name.identifier
114
108
 
115
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
109
+ select_value(<<-SQL, 'SCHEMA').to_i > 0
116
110
  SELECT COUNT(*)
117
111
  FROM pg_class c
118
112
  LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
@@ -121,52 +115,79 @@ module ActiveRecord
121
115
  AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
122
116
  SQL
123
117
  end
124
- alias data_source_exists? table_exists?
125
118
 
126
- def drop_table(table_name, options = {})
127
- execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
119
+ def views # :nodoc:
120
+ select_values(<<-SQL, 'SCHEMA')
121
+ SELECT c.relname
122
+ FROM pg_class c
123
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
124
+ WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
125
+ AND n.nspname = ANY (current_schemas(false))
126
+ SQL
127
+ end
128
+
129
+ def view_exists?(view_name) # :nodoc:
130
+ name = Utils.extract_schema_qualified_name(view_name.to_s)
131
+ return false unless name.identifier
132
+
133
+ select_values(<<-SQL, 'SCHEMA').any?
134
+ SELECT c.relname
135
+ FROM pg_class c
136
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
137
+ WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
138
+ AND c.relname = '#{name.identifier}'
139
+ AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
140
+ SQL
141
+ end
142
+
143
+ def drop_table(table_name, options = {}) # :nodoc:
144
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
128
145
  end
129
146
 
130
147
  # Returns true if schema exists.
131
148
  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
149
+ select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}'", 'SCHEMA').to_i > 0
137
150
  end
138
151
 
152
+ # Verifies existence of an index with a given name.
139
153
  def index_name_exists?(table_name, index_name, default)
140
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
154
+ table = Utils.extract_schema_qualified_name(table_name.to_s)
155
+ index = Utils.extract_schema_qualified_name(index_name.to_s)
156
+
157
+ select_value(<<-SQL, 'SCHEMA').to_i > 0
141
158
  SELECT COUNT(*)
142
159
  FROM pg_class t
143
160
  INNER JOIN pg_index d ON t.oid = d.indrelid
144
161
  INNER JOIN pg_class i ON d.indexrelid = i.oid
162
+ LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
145
163
  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)) )
164
+ AND i.relname = '#{index.identifier}'
165
+ AND t.relname = '#{table.identifier}'
166
+ AND n.nspname = #{index.schema ? "'#{index.schema}'" : 'ANY (current_schemas(false))'}
149
167
  SQL
150
168
  end
151
169
 
152
170
  # Returns an array of indexes for the given table.
153
171
  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)) )
172
+ table = Utils.extract_schema_qualified_name(table_name.to_s)
173
+
174
+ result = query(<<-SQL, 'SCHEMA')
175
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
176
+ FROM pg_class t
177
+ INNER JOIN pg_index d ON t.oid = d.indrelid
178
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
179
+ LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
180
+ WHERE i.relkind = 'i'
181
+ AND d.indisprimary = 'f'
182
+ AND t.relname = '#{table.identifier}'
183
+ AND n.nspname = #{table.schema ? "'#{table.schema}'" : 'ANY (current_schemas(false))'}
163
184
  ORDER BY i.relname
164
185
  SQL
165
186
 
166
187
  result.map do |row|
167
188
  index_name = row[0]
168
- unique = row[1] == 't'
169
- indkey = row[2].split(" ")
189
+ unique = row[1]
190
+ indkey = row[2].split(" ").map(&:to_i)
170
191
  inddef = row[3]
171
192
  oid = row[4]
172
193
 
@@ -194,53 +215,48 @@ module ActiveRecord
194
215
  # Returns the list of all column definitions for a table.
195
216
  def columns(table_name)
196
217
  # 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)
218
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation|
219
+ oid = oid.to_i
220
+ fmod = fmod.to_i
221
+ type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
222
+ default_value = extract_value_from_default(default)
200
223
  default_function = extract_default_function(default_value, default)
201
- new_column(column_name, default_value, oid, type, notnull == 'f', default_function)
224
+ new_column(column_name, default_value, type_metadata, !notnull, default_function, collation)
202
225
  end
203
226
  end
204
227
 
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)
228
+ def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
229
+ PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function, collation)
207
230
  end
208
231
 
209
232
  # Returns the current database name.
210
233
  def current_database
211
- query('select current_database()', 'SCHEMA')[0][0]
234
+ select_value('select current_database()', 'SCHEMA')
212
235
  end
213
236
 
214
237
  # Returns the current schema name.
215
238
  def current_schema
216
- query('SELECT current_schema', 'SCHEMA')[0][0]
239
+ select_value('SELECT current_schema', 'SCHEMA')
217
240
  end
218
241
 
219
242
  # Returns the current database encoding format.
220
243
  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
244
+ select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
225
245
  end
226
246
 
227
247
  # Returns the current database collation.
228
248
  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
249
+ select_value("SELECT datcollate FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
232
250
  end
233
251
 
234
252
  # Returns the current database ctype.
235
253
  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
254
+ select_value("SELECT datctype FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
239
255
  end
240
256
 
241
257
  # Returns an array of schema names.
242
258
  def schema_names
243
- query(<<-SQL, 'SCHEMA').flatten
259
+ select_values(<<-SQL, 'SCHEMA')
244
260
  SELECT nspname
245
261
  FROM pg_namespace
246
262
  WHERE nspname !~ '^pg_.*'
@@ -251,12 +267,12 @@ module ActiveRecord
251
267
 
252
268
  # Creates a schema for the given schema name.
253
269
  def create_schema schema_name
254
- execute "CREATE SCHEMA #{schema_name}"
270
+ execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
255
271
  end
256
272
 
257
273
  # Drops the schema for the given schema name.
258
- def drop_schema schema_name
259
- execute "DROP SCHEMA #{schema_name} CASCADE"
274
+ def drop_schema(schema_name, options = {})
275
+ execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
260
276
  end
261
277
 
262
278
  # Sets the schema search path to a string of comma-separated schema names.
@@ -273,12 +289,12 @@ module ActiveRecord
273
289
 
274
290
  # Returns the active schema search path.
275
291
  def schema_search_path
276
- @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
292
+ @schema_search_path ||= select_value('SHOW search_path', 'SCHEMA')
277
293
  end
278
294
 
279
295
  # Returns the current client message level.
280
296
  def client_min_messages
281
- query('SHOW client_min_messages', 'SCHEMA')[0][0]
297
+ select_value('SHOW client_min_messages', 'SCHEMA')
282
298
  end
283
299
 
284
300
  # Set the client message level.
@@ -296,10 +312,7 @@ module ActiveRecord
296
312
  end
297
313
 
298
314
  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
315
+ select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", 'SCHEMA')
303
316
  end
304
317
 
305
318
  # Sets the sequence of a table's primary key to the specified value.
@@ -310,9 +323,7 @@ module ActiveRecord
310
323
  if sequence
311
324
  quoted_sequence = quote_table_name(sequence)
312
325
 
313
- select_value <<-end_sql, 'SCHEMA'
314
- SELECT setval('#{quoted_sequence}', #{value})
315
- end_sql
326
+ select_value("SELECT setval('#{quoted_sequence}', #{value})", 'SCHEMA')
316
327
  else
317
328
  @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
318
329
  end
@@ -335,7 +346,7 @@ module ActiveRecord
335
346
  if pk && sequence
336
347
  quoted_sequence = quote_table_name(sequence)
337
348
 
338
- select_value <<-end_sql, 'SCHEMA'
349
+ select_value(<<-end_sql, 'SCHEMA')
339
350
  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)
340
351
  end_sql
341
352
  end
@@ -395,17 +406,19 @@ module ActiveRecord
395
406
  nil
396
407
  end
397
408
 
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]
409
+ def primary_keys(table_name) # :nodoc:
410
+ select_values(<<-SQL.strip_heredoc, 'SCHEMA')
411
+ WITH pk_constraint AS (
412
+ SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint
413
+ WHERE contype = 'p'
414
+ AND conrelid = '#{quote_table_name(table_name)}'::regclass
415
+ ), cons AS (
416
+ SELECT conrelid, connum, row_number() OVER() AS rownum FROM pk_constraint
417
+ )
418
+ SELECT attr.attname FROM pg_attribute attr
419
+ INNER JOIN cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.connum
420
+ ORDER BY cons.rownum
421
+ SQL
409
422
  end
410
423
 
411
424
  # Renames a table.
@@ -422,7 +435,7 @@ module ActiveRecord
422
435
  new_seq = "#{new_name}_#{pk}_seq"
423
436
  idx = "#{table_name}_pkey"
424
437
  new_idx = "#{new_name}_pkey"
425
- execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
438
+ execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
426
439
  execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
427
440
  end
428
441
 
@@ -434,16 +447,20 @@ module ActiveRecord
434
447
  super
435
448
  end
436
449
 
437
- # Changes the column of a table.
438
- def change_column(table_name, column_name, type, options = {})
450
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
439
451
  clear_cache!
440
452
  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])})"
453
+ quoted_column_name = quote_column_name(column_name)
454
+ sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale], options[:array])
455
+ sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
456
+ if options[:collation]
457
+ sql << " COLLATE \"#{options[:collation]}\""
458
+ end
459
+ if options[:using]
460
+ sql << " USING #{options[:using]}"
461
+ elsif options[:cast_as]
462
+ cast_as_type = type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale], options[:array])
463
+ sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
447
464
  end
448
465
  execute sql
449
466
 
@@ -452,26 +469,27 @@ module ActiveRecord
452
469
  end
453
470
 
454
471
  # Changes the default value of a table column.
455
- def change_column_default(table_name, column_name, default)
472
+ def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
456
473
  clear_cache!
457
474
  column = column_for(table_name, column_name)
458
475
  return unless column
459
476
 
477
+ default = extract_new_default_value(default_or_changes)
460
478
  alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
461
479
  if default.nil?
462
480
  # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
463
481
  # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
464
482
  execute alter_column_query % "DROP DEFAULT"
465
483
  else
466
- execute alter_column_query % "SET DEFAULT #{quote_default_value(default, column)}"
484
+ execute alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
467
485
  end
468
486
  end
469
487
 
470
- def change_column_null(table_name, column_name, null, default = nil)
488
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
471
489
  clear_cache!
472
490
  unless null || default.nil?
473
491
  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
492
+ 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
493
  end
476
494
  execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
477
495
  end
@@ -488,10 +506,32 @@ module ActiveRecord
488
506
  execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
489
507
  end
490
508
 
491
- def remove_index!(table_name, index_name) #:nodoc:
492
- execute "DROP INDEX #{quote_table_name(index_name)}"
509
+ def remove_index(table_name, options = {}) #:nodoc:
510
+ table = Utils.extract_schema_qualified_name(table_name.to_s)
511
+
512
+ if options.is_a?(Hash) && options.key?(:name)
513
+ provided_index = Utils.extract_schema_qualified_name(options[:name].to_s)
514
+
515
+ options[:name] = provided_index.identifier
516
+ table = PostgreSQL::Name.new(provided_index.schema, table.identifier) unless table.schema.present?
517
+
518
+ if provided_index.schema.present? && table.schema != provided_index.schema
519
+ raise ArgumentError.new("Index schema '#{provided_index.schema}' does not match table schema '#{table.schema}'")
520
+ end
521
+ end
522
+
523
+ index_to_remove = PostgreSQL::Name.new(table.schema, index_name_for_remove(table.to_s, options))
524
+ algorithm =
525
+ if options.is_a?(Hash) && options.key?(:algorithm)
526
+ index_algorithms.fetch(options[:algorithm]) do
527
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
528
+ end
529
+ end
530
+ execute "DROP INDEX #{algorithm} #{quote_table_name(index_to_remove)}"
493
531
  end
494
532
 
533
+ # Renames an index of a table. Raises error if length of new
534
+ # index name is greater than allowed limit.
495
535
  def rename_index(table_name, old_name, new_name)
496
536
  validate_index_length!(table_name, new_name)
497
537
 
@@ -540,41 +580,35 @@ module ActiveRecord
540
580
  end
541
581
 
542
582
  # 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
583
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil, array = nil)
584
+ sql = case type.to_s
545
585
  when 'binary'
546
586
  # PostgreSQL doesn't support limits on binary (bytea) columns.
547
- # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
587
+ # The hard limit is 1GB, because of a 32-bit size field, and TOAST.
548
588
  case limit
549
589
  when nil, 0..0x3fffffff; super(type)
550
590
  else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
551
591
  end
552
592
  when 'text'
553
593
  # PostgreSQL doesn't support limits on text columns.
554
- # The hard limit is 1Gb, according to section 8.3 in the manual.
594
+ # The hard limit is 1GB, according to section 8.3 in the manual.
555
595
  case limit
556
596
  when nil, 0..0x3fffffff; super(type)
557
597
  else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
558
598
  end
559
599
  when 'integer'
560
- return 'integer' unless limit
561
-
562
600
  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")
601
+ when 1, 2; 'smallint'
602
+ when nil, 3, 4; 'integer'
603
+ when 5..8; 'bigint'
604
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
574
605
  end
575
606
  else
576
- super
607
+ super(type, limit, precision, scale)
577
608
  end
609
+
610
+ sql << '[]' if array && type != :primary_key
611
+ sql
578
612
  end
579
613
 
580
614
  # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
@@ -590,6 +624,18 @@ module ActiveRecord
590
624
 
591
625
  [super, *order_columns].join(', ')
592
626
  end
627
+
628
+ def fetch_type_metadata(column_name, sql_type, oid, fmod)
629
+ cast_type = get_oid_type(oid, fmod, column_name, sql_type)
630
+ simple_type = SqlTypeMetadata.new(
631
+ sql_type: sql_type,
632
+ type: cast_type.type,
633
+ limit: cast_type.limit,
634
+ precision: cast_type.precision,
635
+ scale: cast_type.scale,
636
+ )
637
+ PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
638
+ end
593
639
  end
594
640
  end
595
641
  end