activerecord 4.1.8 → 4.2.11.3

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

Potentially problematic release.


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

Files changed (186) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1165 -1591
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +15 -8
  5. data/lib/active_record/association_relation.rb +13 -0
  6. data/lib/active_record/associations/alias_tracker.rb +3 -12
  7. data/lib/active_record/associations/association.rb +16 -4
  8. data/lib/active_record/associations/association_scope.rb +84 -43
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -10
  10. data/lib/active_record/associations/builder/association.rb +16 -5
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -14
  14. data/lib/active_record/associations/builder/has_many.rb +1 -1
  15. data/lib/active_record/associations/builder/has_one.rb +2 -2
  16. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  17. data/lib/active_record/associations/collection_association.rb +87 -30
  18. data/lib/active_record/associations/collection_proxy.rb +33 -35
  19. data/lib/active_record/associations/foreign_association.rb +11 -0
  20. data/lib/active_record/associations/has_many_association.rb +83 -22
  21. data/lib/active_record/associations/has_many_through_association.rb +49 -26
  22. data/lib/active_record/associations/has_one_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
  24. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  25. data/lib/active_record/associations/join_dependency.rb +26 -12
  26. data/lib/active_record/associations/preloader/association.rb +14 -10
  27. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  28. data/lib/active_record/associations/preloader.rb +37 -26
  29. data/lib/active_record/associations/singular_association.rb +17 -2
  30. data/lib/active_record/associations/through_association.rb +16 -12
  31. data/lib/active_record/associations.rb +158 -49
  32. data/lib/active_record/attribute.rb +163 -0
  33. data/lib/active_record/attribute_assignment.rb +20 -12
  34. data/lib/active_record/attribute_decorators.rb +66 -0
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  36. data/lib/active_record/attribute_methods/dirty.rb +107 -43
  37. data/lib/active_record/attribute_methods/primary_key.rb +7 -8
  38. data/lib/active_record/attribute_methods/query.rb +1 -1
  39. data/lib/active_record/attribute_methods/read.rb +22 -59
  40. data/lib/active_record/attribute_methods/serialization.rb +16 -150
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -28
  42. data/lib/active_record/attribute_methods/write.rb +9 -24
  43. data/lib/active_record/attribute_methods.rb +57 -95
  44. data/lib/active_record/attribute_set/builder.rb +106 -0
  45. data/lib/active_record/attribute_set.rb +81 -0
  46. data/lib/active_record/attributes.rb +147 -0
  47. data/lib/active_record/autosave_association.rb +30 -12
  48. data/lib/active_record/base.rb +13 -24
  49. data/lib/active_record/callbacks.rb +6 -6
  50. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +85 -53
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
  54. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +139 -57
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +271 -74
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -60
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +295 -141
  62. data/lib/active_record/connection_adapters/column.rb +29 -240
  63. data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
  64. data/lib/active_record/connection_adapters/mysql2_adapter.rb +17 -33
  65. data/lib/active_record/connection_adapters/mysql_adapter.rb +68 -145
  66. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  67. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
  69. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -385
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  97. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  98. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +134 -43
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
  101. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
  103. data/lib/active_record/connection_handling.rb +1 -1
  104. data/lib/active_record/core.rb +163 -40
  105. data/lib/active_record/counter_cache.rb +60 -6
  106. data/lib/active_record/enum.rb +10 -12
  107. data/lib/active_record/errors.rb +53 -30
  108. data/lib/active_record/explain.rb +1 -1
  109. data/lib/active_record/explain_subscriber.rb +1 -1
  110. data/lib/active_record/fixtures.rb +62 -74
  111. data/lib/active_record/gem_version.rb +4 -4
  112. data/lib/active_record/inheritance.rb +35 -10
  113. data/lib/active_record/integration.rb +4 -4
  114. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  115. data/lib/active_record/locking/optimistic.rb +46 -26
  116. data/lib/active_record/migration/command_recorder.rb +19 -2
  117. data/lib/active_record/migration/join_table.rb +1 -1
  118. data/lib/active_record/migration.rb +79 -47
  119. data/lib/active_record/model_schema.rb +52 -58
  120. data/lib/active_record/nested_attributes.rb +18 -8
  121. data/lib/active_record/no_touching.rb +1 -1
  122. data/lib/active_record/persistence.rb +48 -27
  123. data/lib/active_record/query_cache.rb +3 -3
  124. data/lib/active_record/querying.rb +10 -7
  125. data/lib/active_record/railtie.rb +19 -14
  126. data/lib/active_record/railties/databases.rake +55 -56
  127. data/lib/active_record/readonly_attributes.rb +0 -1
  128. data/lib/active_record/reflection.rb +281 -117
  129. data/lib/active_record/relation/batches.rb +0 -1
  130. data/lib/active_record/relation/calculations.rb +41 -37
  131. data/lib/active_record/relation/delegation.rb +1 -1
  132. data/lib/active_record/relation/finder_methods.rb +71 -48
  133. data/lib/active_record/relation/merger.rb +39 -29
  134. data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
  135. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  136. data/lib/active_record/relation/predicate_builder.rb +42 -12
  137. data/lib/active_record/relation/query_methods.rb +130 -73
  138. data/lib/active_record/relation/spawn_methods.rb +10 -3
  139. data/lib/active_record/relation.rb +57 -25
  140. data/lib/active_record/result.rb +18 -7
  141. data/lib/active_record/sanitization.rb +12 -2
  142. data/lib/active_record/schema.rb +0 -1
  143. data/lib/active_record/schema_dumper.rb +59 -28
  144. data/lib/active_record/schema_migration.rb +5 -4
  145. data/lib/active_record/scoping/default.rb +6 -4
  146. data/lib/active_record/scoping/named.rb +4 -0
  147. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  148. data/lib/active_record/statement_cache.rb +95 -10
  149. data/lib/active_record/store.rb +5 -5
  150. data/lib/active_record/tasks/database_tasks.rb +61 -8
  151. data/lib/active_record/tasks/mysql_database_tasks.rb +32 -17
  152. data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
  153. data/lib/active_record/timestamp.rb +9 -7
  154. data/lib/active_record/transactions.rb +54 -28
  155. data/lib/active_record/type/big_integer.rb +13 -0
  156. data/lib/active_record/type/binary.rb +50 -0
  157. data/lib/active_record/type/boolean.rb +31 -0
  158. data/lib/active_record/type/date.rb +50 -0
  159. data/lib/active_record/type/date_time.rb +54 -0
  160. data/lib/active_record/type/decimal.rb +64 -0
  161. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  162. data/lib/active_record/type/decorator.rb +14 -0
  163. data/lib/active_record/type/float.rb +19 -0
  164. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  165. data/lib/active_record/type/integer.rb +59 -0
  166. data/lib/active_record/type/mutable.rb +16 -0
  167. data/lib/active_record/type/numeric.rb +36 -0
  168. data/lib/active_record/type/serialized.rb +62 -0
  169. data/lib/active_record/type/string.rb +40 -0
  170. data/lib/active_record/type/text.rb +11 -0
  171. data/lib/active_record/type/time.rb +26 -0
  172. data/lib/active_record/type/time_value.rb +38 -0
  173. data/lib/active_record/type/type_map.rb +64 -0
  174. data/lib/active_record/type/unsigned_integer.rb +15 -0
  175. data/lib/active_record/type/value.rb +110 -0
  176. data/lib/active_record/type.rb +23 -0
  177. data/lib/active_record/validations/associated.rb +5 -3
  178. data/lib/active_record/validations/presence.rb +5 -3
  179. data/lib/active_record/validations/uniqueness.rb +24 -20
  180. data/lib/active_record/validations.rb +25 -19
  181. data/lib/active_record.rb +5 -0
  182. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  183. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  184. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  185. metadata +66 -11
  186. 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,15 +1,9 @@
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
9
  if o.primary_key? && o.type != :primary_key
@@ -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
@@ -88,7 +86,7 @@ module ActiveRecord
88
86
  execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
89
87
  end
90
88
 
91
- # Returns the list of all tables in the schema search path or a specified schema.
89
+ # Returns the list of all tables in the schema search path.
92
90
  def tables(name = nil)
93
91
  query(<<-SQL, 'SCHEMA').map { |row| row[0] }
94
92
  SELECT tablename
@@ -97,25 +95,37 @@ module ActiveRecord
97
95
  SQL
98
96
  end
99
97
 
98
+ def data_sources # :nodoc
99
+ select_values(<<-SQL, 'SCHEMA')
100
+ SELECT c.relname
101
+ FROM pg_class c
102
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
103
+ WHERE c.relkind IN ('r', 'v','m') -- (r)elation/table, (v)iew, (m)aterialized view
104
+ AND n.nspname = ANY (current_schemas(false))
105
+ SQL
106
+ end
107
+
100
108
  # Returns true if table exists.
101
109
  # If the schema is not specified as part of +name+ then it will only find tables within
102
110
  # the current schema search path (regardless of permissions to access tables in other schemas)
103
111
  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
112
+ name = Utils.extract_schema_qualified_name(name.to_s)
113
+ return false unless name.identifier
109
114
 
110
115
  exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
111
116
  SELECT COUNT(*)
112
117
  FROM pg_class c
113
118
  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))'}
119
+ WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
120
+ AND c.relname = '#{name.identifier}'
121
+ AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
117
122
  SQL
118
123
  end
124
+ alias data_source_exists? table_exists?
125
+
126
+ def drop_table(table_name, options = {})
127
+ execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
128
+ end
119
129
 
120
130
  # Returns true if schema exists.
121
131
  def schema_exists?(name)
@@ -185,13 +195,15 @@ module ActiveRecord
185
195
  def columns(table_name)
186
196
  # Limit, precision, and scale are all handled by the superclass.
187
197
  column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
188
- oid = get_oid_type(oid.to_i, fmod.to_i, column_name)
189
- PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f')
198
+ oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type)
199
+ default_value = extract_value_from_default(oid, default)
200
+ default_function = extract_default_function(default_value, default)
201
+ new_column(column_name, default_value, oid, type, notnull == 'f', default_function)
190
202
  end
191
203
  end
192
204
 
193
- def column_for(table_name, column_name) #:nodoc:
194
- columns(table_name).detect { |c| c.name == column_name.to_s }
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)
195
207
  end
196
208
 
197
209
  # Returns the current database name.
@@ -278,9 +290,9 @@ module ActiveRecord
278
290
  def default_sequence_name(table_name, pk = nil) #:nodoc:
279
291
  result = serial_sequence(table_name, pk || 'id')
280
292
  return nil unless result
281
- result.split('.').last
293
+ Utils.extract_schema_qualified_name(result).to_s
282
294
  rescue ActiveRecord::StatementInvalid
283
- "#{table_name}_#{pk || 'id'}_seq"
295
+ PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s
284
296
  end
285
297
 
286
298
  def serial_sequence(table, column)
@@ -290,6 +302,23 @@ module ActiveRecord
290
302
  result.rows.first.first
291
303
  end
292
304
 
305
+ # Sets the sequence of a table's primary key to the specified value.
306
+ def set_pk_sequence!(table, value) #:nodoc:
307
+ pk, sequence = pk_and_sequence_for(table)
308
+
309
+ if pk
310
+ if sequence
311
+ quoted_sequence = quote_table_name(sequence)
312
+
313
+ select_value <<-end_sql, 'SCHEMA'
314
+ SELECT setval('#{quoted_sequence}', #{value})
315
+ end_sql
316
+ else
317
+ @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
318
+ end
319
+ end
320
+ end
321
+
293
322
  # Resets the sequence of a table's primary key to the maximum value.
294
323
  def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
295
324
  unless pk and sequence
@@ -317,24 +346,27 @@ module ActiveRecord
317
346
  # First try looking for a sequence with a dependency on the
318
347
  # given table's primary key.
319
348
  result = query(<<-end_sql, 'SCHEMA')[0]
320
- SELECT attr.attname, seq.relname
349
+ SELECT attr.attname, nsp.nspname, seq.relname
321
350
  FROM pg_class seq,
322
351
  pg_attribute attr,
323
352
  pg_depend dep,
324
- pg_constraint cons
353
+ pg_constraint cons,
354
+ pg_namespace nsp
325
355
  WHERE seq.oid = dep.objid
326
356
  AND seq.relkind = 'S'
327
357
  AND attr.attrelid = dep.refobjid
328
358
  AND attr.attnum = dep.refobjsubid
329
359
  AND attr.attrelid = cons.conrelid
330
360
  AND attr.attnum = cons.conkey[1]
361
+ AND seq.relnamespace = nsp.oid
331
362
  AND cons.contype = 'p'
363
+ AND dep.classid = 'pg_class'::regclass
332
364
  AND dep.refobjid = '#{quote_table_name(table)}'::regclass
333
365
  end_sql
334
366
 
335
367
  if result.nil? or result.empty?
336
368
  result = query(<<-end_sql, 'SCHEMA')[0]
337
- SELECT attr.attname,
369
+ SELECT attr.attname, nsp.nspname,
338
370
  CASE
339
371
  WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
340
372
  WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
@@ -346,33 +378,39 @@ module ActiveRecord
346
378
  JOIN pg_attribute attr ON (t.oid = attrelid)
347
379
  JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
348
380
  JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
381
+ JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
349
382
  WHERE t.oid = '#{quote_table_name(table)}'::regclass
350
383
  AND cons.contype = 'p'
351
384
  AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
352
385
  end_sql
353
386
  end
354
387
 
355
- [result.first, result.last]
388
+ pk = result.shift
389
+ if result.last
390
+ [pk, PostgreSQL::Name.new(*result)]
391
+ else
392
+ [pk, nil]
393
+ end
356
394
  rescue
357
395
  nil
358
396
  end
359
397
 
360
398
  # Returns just a table's primary key
361
399
  def primary_key(table)
362
- row = exec_query(<<-end_sql, 'SCHEMA').rows.first
400
+ pks = exec_query(<<-end_sql, 'SCHEMA').rows
363
401
  SELECT attr.attname
364
402
  FROM pg_attribute attr
365
- INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
403
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
366
404
  WHERE cons.contype = 'p'
367
405
  AND cons.conrelid = '#{quote_table_name(table)}'::regclass
368
406
  end_sql
369
-
370
- row && row.first
407
+ return nil unless pks.count == 1
408
+ pks[0][0]
371
409
  end
372
410
 
373
411
  # Renames a table.
374
- # Also renames a table's primary key sequence if the sequence name matches the
375
- # Active Record default.
412
+ # Also renames a table's primary key sequence if the sequence name exists and
413
+ # matches the Active Record default.
376
414
  #
377
415
  # Example:
378
416
  # rename_table('octopuses', 'octopi')
@@ -380,17 +418,18 @@ module ActiveRecord
380
418
  clear_cache!
381
419
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
382
420
  pk, seq = pk_and_sequence_for(new_name)
383
- if seq == "#{table_name}_#{pk}_seq"
421
+ if seq && seq.identifier == "#{table_name}_#{pk}_seq"
384
422
  new_seq = "#{new_name}_#{pk}_seq"
423
+ idx = "#{table_name}_pkey"
424
+ new_idx = "#{new_name}_pkey"
385
425
  execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
426
+ execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
386
427
  end
387
428
 
388
429
  rename_table_indexes(table_name, new_name)
389
430
  end
390
431
 
391
- # Adds a new column to the named table.
392
- # See TableDefinition#column for details of the options you can use.
393
- def add_column(table_name, column_name, type, options = {})
432
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
394
433
  clear_cache!
395
434
  super
396
435
  end
@@ -401,7 +440,12 @@ module ActiveRecord
401
440
  quoted_table_name = quote_table_name(table_name)
402
441
  sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
403
442
  sql_type << "[]" if options[:array]
404
- execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}"
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])})"
447
+ end
448
+ execute sql
405
449
 
406
450
  change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
407
451
  change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
@@ -411,8 +455,16 @@ module ActiveRecord
411
455
  def change_column_default(table_name, column_name, default)
412
456
  clear_cache!
413
457
  column = column_for(table_name, column_name)
458
+ return unless column
414
459
 
415
- execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote_default_value(default, column)}" if column
460
+ alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
461
+ if default.nil?
462
+ # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
463
+ # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
464
+ execute alter_column_query % "DROP DEFAULT"
465
+ else
466
+ execute alter_column_query % "SET DEFAULT #{quote_default_value(default, column)}"
467
+ end
416
468
  end
417
469
 
418
470
  def change_column_null(table_name, column_name, null, default = nil)
@@ -425,7 +477,7 @@ module ActiveRecord
425
477
  end
426
478
 
427
479
  # Renames a column in a table.
428
- def rename_column(table_name, column_name, new_column_name)
480
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
429
481
  clear_cache!
430
482
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
431
483
  rename_column_indexes(table_name, column_name, new_column_name)
@@ -441,9 +493,48 @@ module ActiveRecord
441
493
  end
442
494
 
443
495
  def rename_index(table_name, old_name, new_name)
496
+ validate_index_length!(table_name, new_name)
497
+
444
498
  execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
445
499
  end
446
500
 
501
+ def foreign_keys(table_name)
502
+ fk_info = select_all <<-SQL.strip_heredoc
503
+ 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
+ FROM pg_constraint c
505
+ JOIN pg_class t1 ON c.conrelid = t1.oid
506
+ JOIN pg_class t2 ON c.confrelid = t2.oid
507
+ JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
508
+ JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
509
+ JOIN pg_namespace t3 ON c.connamespace = t3.oid
510
+ WHERE c.contype = 'f'
511
+ AND t1.relname = #{quote(table_name)}
512
+ AND t3.nspname = ANY (current_schemas(false))
513
+ ORDER BY c.conname
514
+ SQL
515
+
516
+ fk_info.map do |row|
517
+ options = {
518
+ column: row['column'],
519
+ name: row['name'],
520
+ primary_key: row['primary_key']
521
+ }
522
+
523
+ options[:on_delete] = extract_foreign_key_action(row['on_delete'])
524
+ options[:on_update] = extract_foreign_key_action(row['on_update'])
525
+
526
+ ForeignKeyDefinition.new(table_name, row['to_table'], options)
527
+ end
528
+ end
529
+
530
+ def extract_foreign_key_action(specifier) # :nodoc:
531
+ case specifier
532
+ when 'c'; :cascade
533
+ when 'n'; :nullify
534
+ when 'r'; :restrict
535
+ end
536
+ end
537
+
447
538
  def index_name_length
448
539
  63
449
540
  end
@@ -472,7 +563,7 @@ module ActiveRecord
472
563
  when 1, 2; 'smallint'
473
564
  when 3, 4; 'integer'
474
565
  when 5..8; 'bigint'
475
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
566
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.")
476
567
  end
477
568
  when 'datetime'
478
569
  return super unless precision
@@ -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