activerecord 4.1.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +776 -1330
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +12 -8
  5. data/lib/active_record/association_relation.rb +4 -0
  6. data/lib/active_record/associations/alias_tracker.rb +14 -13
  7. data/lib/active_record/associations/association.rb +2 -2
  8. data/lib/active_record/associations/association_scope.rb +83 -43
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  10. data/lib/active_record/associations/builder/association.rb +15 -4
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -6
  13. data/lib/active_record/associations/builder/has_many.rb +1 -1
  14. data/lib/active_record/associations/builder/has_one.rb +2 -2
  15. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  16. data/lib/active_record/associations/collection_association.rb +66 -29
  17. data/lib/active_record/associations/collection_proxy.rb +22 -26
  18. data/lib/active_record/associations/has_many_association.rb +65 -18
  19. data/lib/active_record/associations/has_many_through_association.rb +55 -27
  20. data/lib/active_record/associations/has_one_association.rb +0 -1
  21. data/lib/active_record/associations/join_dependency/join_association.rb +19 -15
  22. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  23. data/lib/active_record/associations/join_dependency.rb +20 -12
  24. data/lib/active_record/associations/preloader/association.rb +34 -11
  25. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  26. data/lib/active_record/associations/preloader.rb +49 -59
  27. data/lib/active_record/associations/singular_association.rb +25 -4
  28. data/lib/active_record/associations/through_association.rb +23 -14
  29. data/lib/active_record/associations.rb +171 -42
  30. data/lib/active_record/attribute.rb +149 -0
  31. data/lib/active_record/attribute_assignment.rb +18 -10
  32. data/lib/active_record/attribute_decorators.rb +66 -0
  33. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  34. data/lib/active_record/attribute_methods/dirty.rb +98 -44
  35. data/lib/active_record/attribute_methods/primary_key.rb +14 -8
  36. data/lib/active_record/attribute_methods/query.rb +1 -1
  37. data/lib/active_record/attribute_methods/read.rb +22 -59
  38. data/lib/active_record/attribute_methods/serialization.rb +37 -147
  39. data/lib/active_record/attribute_methods/time_zone_conversion.rb +34 -28
  40. data/lib/active_record/attribute_methods/write.rb +14 -21
  41. data/lib/active_record/attribute_methods.rb +67 -94
  42. data/lib/active_record/attribute_set/builder.rb +86 -0
  43. data/lib/active_record/attribute_set.rb +77 -0
  44. data/lib/active_record/attributes.rb +139 -0
  45. data/lib/active_record/autosave_association.rb +45 -38
  46. data/lib/active_record/base.rb +10 -20
  47. data/lib/active_record/callbacks.rb +7 -7
  48. data/lib/active_record/coders/json.rb +13 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +78 -52
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +38 -59
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -55
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -5
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +126 -54
  55. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +198 -64
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +126 -114
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -55
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +240 -135
  60. data/lib/active_record/connection_adapters/column.rb +28 -239
  61. data/lib/active_record/connection_adapters/connection_specification.rb +16 -25
  62. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -22
  63. data/lib/active_record/connection_adapters/mysql_adapter.rb +65 -149
  64. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  65. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  66. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -27
  67. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -374
  93. data/lib/active_record/connection_adapters/postgresql/quoting.rb +55 -135
  94. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  96. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +127 -38
  97. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  98. data/lib/active_record/connection_adapters/postgresql_adapter.rb +220 -466
  99. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  100. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -61
  101. data/lib/active_record/connection_handling.rb +3 -3
  102. data/lib/active_record/core.rb +143 -32
  103. data/lib/active_record/counter_cache.rb +60 -7
  104. data/lib/active_record/enum.rb +10 -11
  105. data/lib/active_record/errors.rb +49 -27
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/fixtures.rb +56 -70
  108. data/lib/active_record/gem_version.rb +2 -2
  109. data/lib/active_record/inheritance.rb +35 -10
  110. data/lib/active_record/integration.rb +4 -4
  111. data/lib/active_record/locking/optimistic.rb +35 -17
  112. data/lib/active_record/log_subscriber.rb +1 -1
  113. data/lib/active_record/migration/command_recorder.rb +19 -2
  114. data/lib/active_record/migration/join_table.rb +1 -1
  115. data/lib/active_record/migration.rb +52 -49
  116. data/lib/active_record/model_schema.rb +49 -57
  117. data/lib/active_record/nested_attributes.rb +7 -7
  118. data/lib/active_record/null_relation.rb +19 -5
  119. data/lib/active_record/persistence.rb +50 -31
  120. data/lib/active_record/query_cache.rb +3 -3
  121. data/lib/active_record/querying.rb +10 -7
  122. data/lib/active_record/railtie.rb +14 -11
  123. data/lib/active_record/railties/databases.rake +56 -54
  124. data/lib/active_record/readonly_attributes.rb +0 -1
  125. data/lib/active_record/reflection.rb +286 -102
  126. data/lib/active_record/relation/batches.rb +0 -1
  127. data/lib/active_record/relation/calculations.rb +39 -31
  128. data/lib/active_record/relation/delegation.rb +2 -2
  129. data/lib/active_record/relation/finder_methods.rb +80 -36
  130. data/lib/active_record/relation/merger.rb +25 -30
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +31 -13
  132. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  133. data/lib/active_record/relation/predicate_builder.rb +11 -10
  134. data/lib/active_record/relation/query_methods.rb +141 -55
  135. data/lib/active_record/relation/spawn_methods.rb +3 -0
  136. data/lib/active_record/relation.rb +69 -30
  137. data/lib/active_record/result.rb +18 -7
  138. data/lib/active_record/sanitization.rb +12 -2
  139. data/lib/active_record/schema.rb +0 -1
  140. data/lib/active_record/schema_dumper.rb +58 -26
  141. data/lib/active_record/schema_migration.rb +11 -0
  142. data/lib/active_record/scoping/default.rb +8 -7
  143. data/lib/active_record/scoping/named.rb +4 -0
  144. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  145. data/lib/active_record/statement_cache.rb +95 -10
  146. data/lib/active_record/store.rb +19 -10
  147. data/lib/active_record/tasks/database_tasks.rb +73 -7
  148. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -2
  149. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  151. data/lib/active_record/timestamp.rb +11 -9
  152. data/lib/active_record/transactions.rb +37 -21
  153. data/lib/active_record/type/big_integer.rb +13 -0
  154. data/lib/active_record/type/binary.rb +50 -0
  155. data/lib/active_record/type/boolean.rb +30 -0
  156. data/lib/active_record/type/date.rb +46 -0
  157. data/lib/active_record/type/date_time.rb +43 -0
  158. data/lib/active_record/type/decimal.rb +40 -0
  159. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  160. data/lib/active_record/type/decorator.rb +14 -0
  161. data/lib/active_record/type/float.rb +19 -0
  162. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  163. data/lib/active_record/type/integer.rb +55 -0
  164. data/lib/active_record/type/mutable.rb +16 -0
  165. data/lib/active_record/type/numeric.rb +36 -0
  166. data/lib/active_record/type/serialized.rb +56 -0
  167. data/lib/active_record/type/string.rb +36 -0
  168. data/lib/active_record/type/text.rb +11 -0
  169. data/lib/active_record/type/time.rb +26 -0
  170. data/lib/active_record/type/time_value.rb +38 -0
  171. data/lib/active_record/type/type_map.rb +64 -0
  172. data/lib/active_record/type/unsigned_integer.rb +15 -0
  173. data/lib/active_record/type/value.rb +101 -0
  174. data/lib/active_record/type.rb +23 -0
  175. data/lib/active_record/validations/associated.rb +5 -3
  176. data/lib/active_record/validations/presence.rb +6 -4
  177. data/lib/active_record/validations/uniqueness.rb +11 -17
  178. data/lib/active_record/validations.rb +25 -19
  179. data/lib/active_record.rb +3 -0
  180. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  181. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +4 -1
  182. data/lib/rails/generators/active_record/migration/templates/migration.rb +6 -0
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +65 -10
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -0,0 +1,152 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module PostgreSQL
4
+ module ColumnMethods
5
+ def xml(*args)
6
+ options = args.extract_options!
7
+ column(args[0], :xml, options)
8
+ end
9
+
10
+ def tsvector(*args)
11
+ options = args.extract_options!
12
+ column(args[0], :tsvector, options)
13
+ end
14
+
15
+ def int4range(name, options = {})
16
+ column(name, :int4range, options)
17
+ end
18
+
19
+ def int8range(name, options = {})
20
+ column(name, :int8range, options)
21
+ end
22
+
23
+ def tsrange(name, options = {})
24
+ column(name, :tsrange, options)
25
+ end
26
+
27
+ def tstzrange(name, options = {})
28
+ column(name, :tstzrange, options)
29
+ end
30
+
31
+ def numrange(name, options = {})
32
+ column(name, :numrange, options)
33
+ end
34
+
35
+ def daterange(name, options = {})
36
+ column(name, :daterange, options)
37
+ end
38
+
39
+ def hstore(name, options = {})
40
+ column(name, :hstore, options)
41
+ end
42
+
43
+ def ltree(name, options = {})
44
+ column(name, :ltree, options)
45
+ end
46
+
47
+ def inet(name, options = {})
48
+ column(name, :inet, options)
49
+ end
50
+
51
+ def cidr(name, options = {})
52
+ column(name, :cidr, options)
53
+ end
54
+
55
+ def macaddr(name, options = {})
56
+ column(name, :macaddr, options)
57
+ end
58
+
59
+ def uuid(name, options = {})
60
+ column(name, :uuid, options)
61
+ end
62
+
63
+ def json(name, options = {})
64
+ column(name, :json, options)
65
+ end
66
+
67
+ def jsonb(name, options = {})
68
+ column(name, :jsonb, options)
69
+ end
70
+
71
+ def citext(name, options = {})
72
+ column(name, :citext, options)
73
+ end
74
+
75
+ def point(name, options = {})
76
+ column(name, :point, options)
77
+ end
78
+
79
+ def bit(name, options)
80
+ column(name, :bit, options)
81
+ end
82
+
83
+ def bit_varying(name, options)
84
+ column(name, :bit_varying, options)
85
+ end
86
+
87
+ def money(name, options)
88
+ column(name, :money, options)
89
+ end
90
+ end
91
+
92
+ class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
93
+ attr_accessor :array
94
+ end
95
+
96
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
97
+ include ColumnMethods
98
+
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
+ def new_column_definition(name, type, options) # :nodoc:
135
+ column = super
136
+ column.array = options[:array]
137
+ column
138
+ end
139
+
140
+ private
141
+
142
+ def create_column_definition(name, type)
143
+ PostgreSQL::ColumnDefinition.new name, type
144
+ end
145
+ end
146
+
147
+ class Table < ActiveRecord::ConnectionAdapters::Table
148
+ include ColumnMethods
149
+ end
150
+ end
151
+ end
152
+ end
@@ -1,18 +1,12 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
- class PostgreSQLAdapter < AbstractAdapter
3
+ module PostgreSQL
4
4
  class SchemaCreation < AbstractAdapter::SchemaCreation
5
5
  private
6
6
 
7
- def visit_AddColumn(o)
8
- sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
9
- sql = "ADD COLUMN #{quote_column_name(o.name)} #{sql_type}"
10
- add_column_options!(sql, column_options(o))
11
- end
12
-
13
7
  def visit_ColumnDefinition(o)
14
8
  sql = super
15
- if o.primary_key? && o.type == :uuid
9
+ if o.primary_key? && o.type != :primary_key
16
10
  sql << " PRIMARY KEY "
17
11
  add_column_options!(sql, column_options(o))
18
12
  end
@@ -31,10 +25,14 @@ module ActiveRecord
31
25
  super
32
26
  end
33
27
  end
34
- end
35
28
 
36
- def schema_creation
37
- SchemaCreation.new self
29
+ def type_for_column(column)
30
+ if column.array
31
+ @conn.lookup_cast_type("#{column.sql_type}[]")
32
+ else
33
+ super
34
+ end
35
+ end
38
36
  end
39
37
 
40
38
  module SchemaStatements
@@ -56,8 +54,8 @@ module ActiveRecord
56
54
  def create_database(name, options = {})
57
55
  options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
58
56
 
59
- option_string = options.sum do |key, value|
60
- case key
57
+ option_string = options.inject("") do |memo, (key, value)|
58
+ memo += case key
61
59
  when :owner
62
60
  " OWNER = \"#{value}\""
63
61
  when :template
@@ -101,22 +99,23 @@ module ActiveRecord
101
99
  # If the schema is not specified as part of +name+ then it will only find tables within
102
100
  # the current schema search path (regardless of permissions to access tables in other schemas)
103
101
  def table_exists?(name)
104
- schema, table = Utils.extract_schema_and_table(name.to_s)
105
- return false unless table
106
-
107
- binds = [[nil, table]]
108
- binds << [nil, schema] if schema
102
+ name = Utils.extract_schema_qualified_name(name.to_s)
103
+ return false unless name.identifier
109
104
 
110
105
  exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
111
106
  SELECT COUNT(*)
112
107
  FROM pg_class c
113
108
  LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
114
- WHERE c.relkind in ('v','r')
115
- AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
116
- AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
109
+ WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
110
+ AND c.relname = '#{name.identifier}'
111
+ AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
117
112
  SQL
118
113
  end
119
114
 
115
+ def drop_table(table_name, options = {})
116
+ execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
117
+ end
118
+
120
119
  # Returns true if schema exists.
121
120
  def schema_exists?(name)
122
121
  exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
@@ -185,13 +184,17 @@ module ActiveRecord
185
184
  def columns(table_name)
186
185
  # Limit, precision, and scale are all handled by the superclass.
187
186
  column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
188
- oid = type_map.fetch(oid.to_i, fmod.to_i) {
189
- OID::Identity.new
190
- }
191
- PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f')
187
+ oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type)
188
+ default_value = extract_value_from_default(oid, default)
189
+ default_function = extract_default_function(default_value, default)
190
+ new_column(column_name, default_value, oid, type, notnull == 'f', default_function)
192
191
  end
193
192
  end
194
193
 
194
+ def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil) # :nodoc:
195
+ PostgreSQLColumn.new(name, default, cast_type, sql_type, null, default_function)
196
+ end
197
+
195
198
  # Returns the current database name.
196
199
  def current_database
197
200
  query('select current_database()', 'SCHEMA')[0][0]
@@ -276,9 +279,9 @@ module ActiveRecord
276
279
  def default_sequence_name(table_name, pk = nil) #:nodoc:
277
280
  result = serial_sequence(table_name, pk || 'id')
278
281
  return nil unless result
279
- result.split('.').last
282
+ Utils.extract_schema_qualified_name(result).to_s
280
283
  rescue ActiveRecord::StatementInvalid
281
- "#{table_name}_#{pk || 'id'}_seq"
284
+ PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s
282
285
  end
283
286
 
284
287
  def serial_sequence(table, column)
@@ -288,6 +291,23 @@ module ActiveRecord
288
291
  result.rows.first.first
289
292
  end
290
293
 
294
+ # Sets the sequence of a table's primary key to the specified value.
295
+ def set_pk_sequence!(table, value) #:nodoc:
296
+ pk, sequence = pk_and_sequence_for(table)
297
+
298
+ if pk
299
+ if sequence
300
+ quoted_sequence = quote_table_name(sequence)
301
+
302
+ select_value <<-end_sql, 'SCHEMA'
303
+ SELECT setval('#{quoted_sequence}', #{value})
304
+ end_sql
305
+ else
306
+ @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
307
+ end
308
+ end
309
+ end
310
+
291
311
  # Resets the sequence of a table's primary key to the maximum value.
292
312
  def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
293
313
  unless pk and sequence
@@ -315,24 +335,27 @@ module ActiveRecord
315
335
  # First try looking for a sequence with a dependency on the
316
336
  # given table's primary key.
317
337
  result = query(<<-end_sql, 'SCHEMA')[0]
318
- SELECT attr.attname, seq.relname
338
+ SELECT attr.attname, nsp.nspname, seq.relname
319
339
  FROM pg_class seq,
320
340
  pg_attribute attr,
321
341
  pg_depend dep,
322
- pg_constraint cons
342
+ pg_constraint cons,
343
+ pg_namespace nsp
323
344
  WHERE seq.oid = dep.objid
324
345
  AND seq.relkind = 'S'
325
346
  AND attr.attrelid = dep.refobjid
326
347
  AND attr.attnum = dep.refobjsubid
327
348
  AND attr.attrelid = cons.conrelid
328
349
  AND attr.attnum = cons.conkey[1]
350
+ AND seq.relnamespace = nsp.oid
329
351
  AND cons.contype = 'p'
352
+ AND dep.classid = 'pg_class'::regclass
330
353
  AND dep.refobjid = '#{quote_table_name(table)}'::regclass
331
354
  end_sql
332
355
 
333
356
  if result.nil? or result.empty?
334
357
  result = query(<<-end_sql, 'SCHEMA')[0]
335
- SELECT attr.attname,
358
+ SELECT attr.attname, nsp.nspname,
336
359
  CASE
337
360
  WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
338
361
  WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
@@ -344,13 +367,19 @@ module ActiveRecord
344
367
  JOIN pg_attribute attr ON (t.oid = attrelid)
345
368
  JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
346
369
  JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
370
+ JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
347
371
  WHERE t.oid = '#{quote_table_name(table)}'::regclass
348
372
  AND cons.contype = 'p'
349
373
  AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
350
374
  end_sql
351
375
  end
352
376
 
353
- [result.first, result.last]
377
+ pk = result.shift
378
+ if result.last
379
+ [pk, PostgreSQL::Name.new(*result)]
380
+ else
381
+ [pk, nil]
382
+ end
354
383
  rescue
355
384
  nil
356
385
  end
@@ -369,8 +398,8 @@ module ActiveRecord
369
398
  end
370
399
 
371
400
  # Renames a table.
372
- # Also renames a table's primary key sequence if the sequence name matches the
373
- # Active Record default.
401
+ # Also renames a table's primary key sequence if the sequence name exists and
402
+ # matches the Active Record default.
374
403
  #
375
404
  # Example:
376
405
  # rename_table('octopuses', 'octopi')
@@ -378,9 +407,12 @@ module ActiveRecord
378
407
  clear_cache!
379
408
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
380
409
  pk, seq = pk_and_sequence_for(new_name)
381
- if seq == "#{table_name}_#{pk}_seq"
410
+ if seq && seq.identifier == "#{table_name}_#{pk}_seq"
382
411
  new_seq = "#{new_name}_#{pk}_seq"
412
+ idx = "#{table_name}_pkey"
413
+ new_idx = "#{new_name}_pkey"
383
414
  execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
415
+ execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
384
416
  end
385
417
 
386
418
  rename_table_indexes(table_name, new_name)
@@ -399,7 +431,12 @@ module ActiveRecord
399
431
  quoted_table_name = quote_table_name(table_name)
400
432
  sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
401
433
  sql_type << "[]" if options[:array]
402
- execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}"
434
+ sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}"
435
+ sql << " USING #{options[:using]}" if options[:using]
436
+ if options[:cast_as]
437
+ sql << " USING CAST(#{quote_column_name(column_name)} AS #{type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale])})"
438
+ end
439
+ execute sql
403
440
 
404
441
  change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
405
442
  change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
@@ -408,13 +445,24 @@ module ActiveRecord
408
445
  # Changes the default value of a table column.
409
446
  def change_column_default(table_name, column_name, default)
410
447
  clear_cache!
411
- execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
448
+ column = column_for(table_name, column_name)
449
+ return unless column
450
+
451
+ alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
452
+ if default.nil?
453
+ # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
454
+ # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
455
+ execute alter_column_query % "DROP DEFAULT"
456
+ else
457
+ execute alter_column_query % "SET DEFAULT #{quote_default_value(default, column)}"
458
+ end
412
459
  end
413
460
 
414
461
  def change_column_null(table_name, column_name, null, default = nil)
415
462
  clear_cache!
416
463
  unless null || default.nil?
417
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
464
+ column = column_for(table_name, column_name)
465
+ 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
418
466
  end
419
467
  execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
420
468
  end
@@ -436,9 +484,49 @@ module ActiveRecord
436
484
  end
437
485
 
438
486
  def rename_index(table_name, old_name, new_name)
487
+ if new_name.length > allowed_index_name_length
488
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
489
+ end
439
490
  execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
440
491
  end
441
492
 
493
+ def foreign_keys(table_name)
494
+ fk_info = select_all <<-SQL.strip_heredoc
495
+ 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
496
+ FROM pg_constraint c
497
+ JOIN pg_class t1 ON c.conrelid = t1.oid
498
+ JOIN pg_class t2 ON c.confrelid = t2.oid
499
+ JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
500
+ JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
501
+ JOIN pg_namespace t3 ON c.connamespace = t3.oid
502
+ WHERE c.contype = 'f'
503
+ AND t1.relname = #{quote(table_name)}
504
+ AND t3.nspname = ANY (current_schemas(false))
505
+ ORDER BY c.conname
506
+ SQL
507
+
508
+ fk_info.map do |row|
509
+ options = {
510
+ column: row['column'],
511
+ name: row['name'],
512
+ primary_key: row['primary_key']
513
+ }
514
+
515
+ options[:on_delete] = extract_foreign_key_action(row['on_delete'])
516
+ options[:on_update] = extract_foreign_key_action(row['on_update'])
517
+
518
+ ForeignKeyDefinition.new(table_name, row['to_table'], options)
519
+ end
520
+ end
521
+
522
+ def extract_foreign_key_action(specifier) # :nodoc:
523
+ case specifier
524
+ when 'c'; :cascade
525
+ when 'n'; :nullify
526
+ when 'r'; :restrict
527
+ end
528
+ end
529
+
442
530
  def index_name_length
443
531
  63
444
532
  end
@@ -488,7 +576,8 @@ module ActiveRecord
488
576
  # Convert Arel node to string
489
577
  s = s.to_sql unless s.is_a?(String)
490
578
  # Remove any ASC/DESC modifiers
491
- s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
579
+ s.gsub(/\s+(?:ASC|DESC)\b/i, '')
580
+ .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '')
492
581
  }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
493
582
 
494
583
  [super, *order_columns].join(', ')
@@ -0,0 +1,77 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module PostgreSQL
4
+ # Value Object to hold a schema qualified name.
5
+ # This is usually the name of a PostgreSQL relation but it can also represent
6
+ # schema qualified type names. +schema+ and +identifier+ are unquoted to prevent
7
+ # double quoting.
8
+ class Name # :nodoc:
9
+ SEPARATOR = "."
10
+ attr_reader :schema, :identifier
11
+
12
+ def initialize(schema, identifier)
13
+ @schema, @identifier = unquote(schema), unquote(identifier)
14
+ end
15
+
16
+ def to_s
17
+ parts.join SEPARATOR
18
+ end
19
+
20
+ def quoted
21
+ if schema
22
+ PGconn.quote_ident(schema) << SEPARATOR << PGconn.quote_ident(identifier)
23
+ else
24
+ PGconn.quote_ident(identifier)
25
+ end
26
+ end
27
+
28
+ def ==(o)
29
+ o.class == self.class && o.parts == parts
30
+ end
31
+ alias_method :eql?, :==
32
+
33
+ def hash
34
+ parts.hash
35
+ end
36
+
37
+ protected
38
+ def unquote(part)
39
+ if part && part.start_with?('"')
40
+ part[1..-2]
41
+ else
42
+ part
43
+ end
44
+ end
45
+
46
+ def parts
47
+ @parts ||= [@schema, @identifier].compact
48
+ end
49
+ end
50
+
51
+ module Utils # :nodoc:
52
+ extend self
53
+
54
+ # Returns an instance of <tt>ActiveRecord::ConnectionAdapters::PostgreSQL::Name</tt>
55
+ # extracted from +string+.
56
+ # +schema+ is nil if not specified in +string+.
57
+ # +schema+ and +identifier+ exclude surrounding quotes (regardless of whether provided in +string+)
58
+ # +string+ supports the range of schema/table references understood by PostgreSQL, for example:
59
+ #
60
+ # * <tt>table_name</tt>
61
+ # * <tt>"table.name"</tt>
62
+ # * <tt>schema_name.table_name</tt>
63
+ # * <tt>schema_name."table.name"</tt>
64
+ # * <tt>"schema_name".table_name</tt>
65
+ # * <tt>"schema.name"."table name"</tt>
66
+ def extract_schema_qualified_name(string)
67
+ schema, table = string.scan(/[^".\s]+|"[^"]*"/)
68
+ if table.nil?
69
+ table = schema
70
+ schema = nil
71
+ end
72
+ PostgreSQL::Name.new(schema, table)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end