activerecord 1.10.1 → 1.11.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 (84) hide show
  1. data/CHANGELOG +187 -19
  2. data/RUNNING_UNIT_TESTS +11 -0
  3. data/lib/active_record.rb +3 -1
  4. data/lib/active_record/acts/list.rb +25 -14
  5. data/lib/active_record/acts/nested_set.rb +4 -4
  6. data/lib/active_record/acts/tree.rb +18 -1
  7. data/lib/active_record/associations.rb +90 -17
  8. data/lib/active_record/associations/association_collection.rb +44 -5
  9. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +17 -4
  10. data/lib/active_record/associations/has_many_association.rb +13 -3
  11. data/lib/active_record/associations/has_one_association.rb +19 -0
  12. data/lib/active_record/base.rb +292 -268
  13. data/lib/active_record/callbacks.rb +14 -14
  14. data/lib/active_record/connection_adapters/abstract_adapter.rb +137 -75
  15. data/lib/active_record/connection_adapters/db2_adapter.rb +10 -8
  16. data/lib/active_record/connection_adapters/mysql_adapter.rb +91 -64
  17. data/lib/active_record/connection_adapters/oci_adapter.rb +6 -6
  18. data/lib/active_record/connection_adapters/postgresql_adapter.rb +113 -60
  19. data/lib/active_record/connection_adapters/sqlite_adapter.rb +15 -12
  20. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +159 -132
  21. data/lib/active_record/fixtures.rb +59 -12
  22. data/lib/active_record/locking.rb +10 -9
  23. data/lib/active_record/migration.rb +112 -5
  24. data/lib/active_record/query_cache.rb +64 -0
  25. data/lib/active_record/timestamp.rb +10 -8
  26. data/lib/active_record/validations.rb +121 -26
  27. data/rakefile +16 -10
  28. data/test/aaa_create_tables_test.rb +26 -48
  29. data/test/abstract_unit.rb +3 -0
  30. data/test/aggregations_test.rb +19 -19
  31. data/test/association_callbacks_test.rb +110 -0
  32. data/test/associations_go_eager_test.rb +48 -14
  33. data/test/associations_test.rb +344 -142
  34. data/test/base_test.rb +150 -31
  35. data/test/binary_test.rb +7 -0
  36. data/test/callbacks_test.rb +24 -5
  37. data/test/column_alias_test.rb +2 -2
  38. data/test/connections/native_sqlserver_odbc/connection.rb +26 -0
  39. data/test/deprecated_associations_test.rb +27 -28
  40. data/test/deprecated_finder_test.rb +8 -9
  41. data/test/finder_test.rb +52 -17
  42. data/test/fixtures/author.rb +39 -0
  43. data/test/fixtures/categories.yml +7 -0
  44. data/test/fixtures/categories_posts.yml +8 -0
  45. data/test/fixtures/category.rb +2 -0
  46. data/test/fixtures/comment.rb +3 -1
  47. data/test/fixtures/comments.yml +43 -1
  48. data/test/fixtures/companies.yml +14 -0
  49. data/test/fixtures/company.rb +1 -1
  50. data/test/fixtures/computers.yml +2 -1
  51. data/test/fixtures/db_definitions/db2.sql +7 -2
  52. data/test/fixtures/db_definitions/mysql.drop.sql +2 -0
  53. data/test/fixtures/db_definitions/mysql.sql +11 -6
  54. data/test/fixtures/db_definitions/oci.sql +7 -2
  55. data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
  56. data/test/fixtures/db_definitions/postgresql.sql +8 -5
  57. data/test/fixtures/db_definitions/sqlite.drop.sql +2 -0
  58. data/test/fixtures/db_definitions/sqlite.sql +9 -4
  59. data/test/fixtures/db_definitions/sqlserver.drop.sql +2 -0
  60. data/test/fixtures/db_definitions/sqlserver.sql +12 -7
  61. data/test/fixtures/developer.rb +8 -1
  62. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  63. data/test/fixtures/post.rb +8 -2
  64. data/test/fixtures/posts.yml +21 -0
  65. data/test/fixtures/project.rb +14 -1
  66. data/test/fixtures/subscriber.rb +3 -0
  67. data/test/fixtures_test.rb +14 -0
  68. data/test/inheritance_test.rb +30 -22
  69. data/test/lifecycle_test.rb +3 -4
  70. data/test/locking_test.rb +2 -4
  71. data/test/migration_test.rb +186 -0
  72. data/test/mixin_nested_set_test.rb +19 -19
  73. data/test/mixin_test.rb +88 -88
  74. data/test/modules_test.rb +5 -10
  75. data/test/multiple_db_test.rb +2 -0
  76. data/test/pk_test.rb +8 -12
  77. data/test/reflection_test.rb +8 -4
  78. data/test/schema_test_postgresql.rb +63 -0
  79. data/test/thread_safety_test.rb +4 -1
  80. data/test/transactions_test.rb +9 -2
  81. data/test/unconnected_test.rb +1 -0
  82. data/test/validations_test.rb +151 -8
  83. metadata +11 -5
  84. data/test/migration_mysql.rb +0 -104
@@ -52,8 +52,8 @@ begin
52
52
  def execute(sql, name = nil)
53
53
  rows_affected = 0
54
54
 
55
- log(sql, name, @connection) do |connection|
56
- stmt = DB2::Statement.new(connection)
55
+ log(sql, name) do
56
+ stmt = DB2::Statement.new(@connection)
57
57
  stmt.exec_direct(sql)
58
58
  rows_affected = stmt.row_count
59
59
  stmt.free
@@ -79,14 +79,16 @@ begin
79
79
  @connection.set_auto_commit_on
80
80
  end
81
81
 
82
- def quote_column_name(name) name; end
82
+ def quote_column_name(column_name)
83
+ column_name
84
+ end
83
85
 
84
86
  def adapter_name()
85
87
  'DB2'
86
88
  end
87
89
 
88
- def quote_string(s)
89
- s.gsub(/'/, "''") # ' (for ruby-mode)
90
+ def quote_string(string)
91
+ string.gsub(/'/, "''") # ' (for ruby-mode)
90
92
  end
91
93
 
92
94
  def add_limit_with_offset!(sql, limit, offset)
@@ -124,9 +126,9 @@ begin
124
126
 
125
127
  def select(sql, name = nil)
126
128
  stmt = nil
127
- log(sql, name, @connection) do |connection|
128
- stmt = DB2::Statement.new(connection)
129
- stmt.exec_direct(sql + " with ur")
129
+ log(sql, name) do
130
+ stmt = DB2::Statement.new(@connection)
131
+ stmt.exec_direct("#{sql} with ur")
130
132
  end
131
133
 
132
134
  rows = []
@@ -1,6 +1,6 @@
1
1
  require 'active_record/connection_adapters/abstract_adapter'
2
2
  require 'parsedate'
3
-
3
+
4
4
  module ActiveRecord
5
5
  class Base
6
6
  # Establishes a connection to the database that's used by all Active Record objects.
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
  require_library_or_gem 'mysql'
12
12
  rescue LoadError => cannot_require_mysql
13
13
  # Only use the supplied backup Ruby/MySQL driver if no driver is already in place
14
- begin
14
+ begin
15
15
  require 'active_record/vendor/mysql'
16
16
  require 'active_record/vendor/mysql411'
17
17
  rescue LoadError
@@ -19,27 +19,27 @@ module ActiveRecord
19
19
  end
20
20
  end
21
21
  end
22
-
22
+
23
23
  symbolize_strings_in_hash(config)
24
-
24
+
25
25
  host = config[:host]
26
26
  port = config[:port]
27
27
  socket = config[:socket]
28
28
  username = config[:username] ? config[:username].to_s : 'root'
29
29
  password = config[:password].to_s
30
-
30
+
31
31
  if config.has_key?(:database)
32
32
  database = config[:database]
33
33
  else
34
34
  raise ArgumentError, "No database specified. Missing argument: database."
35
35
  end
36
-
36
+
37
37
  mysql = Mysql.init
38
38
  mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
39
39
  ConnectionAdapters::MysqlAdapter.new(mysql.real_connect(host, username, password, database, port, socket), logger, [host, username, password, database, port, socket])
40
40
  end
41
41
  end
42
-
42
+
43
43
  module ConnectionAdapters
44
44
  # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
45
45
  # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
@@ -57,12 +57,16 @@ module ActiveRecord
57
57
  # * <tt>:sslcapath</tt> -- Necessary to use MySQL with an SSL connection
58
58
  # * <tt>:sslcipher</tt> -- Necessary to use MySQL with an SSL connection
59
59
  class MysqlAdapter < AbstractAdapter
60
- LOST_CONNECTION_ERROR_MESSAGES = [
60
+ LOST_CONNECTION_ERROR_MESSAGES = [
61
61
  "Server shutdown in progress",
62
- "Broken pipe",
63
- "Lost connection to MySQL server during query",
62
+ "Broken pipe",
63
+ "Lost connection to MySQL server during query",
64
64
  "MySQL server has gone away"
65
65
  ]
66
+
67
+ def supports_migrations?
68
+ true
69
+ end
66
70
 
67
71
  def native_database_types
68
72
  {
@@ -84,86 +88,85 @@ module ActiveRecord
84
88
  super(connection, logger)
85
89
  @connection_options = connection_options
86
90
  end
87
-
91
+
88
92
  def adapter_name
89
93
  'MySQL'
90
94
  end
91
95
 
92
-
93
96
  def select_all(sql, name = nil)
94
97
  select(sql, name)
95
98
  end
96
-
99
+
97
100
  def select_one(sql, name = nil)
98
101
  result = select(sql, name)
99
102
  result.nil? ? nil : result.first
100
103
  end
101
-
104
+
102
105
  def columns(table_name, name = nil)
103
- sql = "SHOW FIELDS FROM #{table_name}"
106
+ sql = "SHOW FIELDS FROM #{table_name}"
104
107
  columns = []
105
108
  execute(sql, name).each { |field| columns << Column.new(field[0], field[4], field[1]) }
106
109
  columns
107
110
  end
108
-
111
+
109
112
  def insert(sql, name = nil, pk = nil, id_value = nil)
110
113
  execute(sql, name = nil)
111
- return id_value || @connection.insert_id
114
+ id_value || @connection.insert_id
112
115
  end
113
-
114
- def execute(sql, name = nil)
115
- begin
116
- return log(sql, name, @connection) { |connection| connection.query(sql) }
117
- rescue ActiveRecord::StatementInvalid => exception
118
- if LOST_CONNECTION_ERROR_MESSAGES.any? { |msg| exception.message.split(":").first =~ /^#{msg}/ }
119
- @connection.real_connect(*@connection_options)
120
- @logger.info("Retrying invalid statement with reopened connection") if @logger
121
- return log(sql, name, @connection) { |connection| connection.query(sql) }
116
+
117
+ def execute(sql, name = nil, retries = 2)
118
+ unless @logger
119
+ @connection.query(sql)
120
+ else
121
+ log(sql, name) { @connection.query(sql) }
122
+ end
123
+ rescue ActiveRecord::StatementInvalid => exception
124
+ if LOST_CONNECTION_ERROR_MESSAGES.any? { |msg| exception.message.split(":").first =~ /^#{msg}/ }
125
+ @connection.real_connect(*@connection_options)
126
+ unless @logger
127
+ @connection.query(sql)
122
128
  else
123
- raise
129
+ @logger.info "Retrying invalid statement with reopened connection"
130
+ log(sql, name) { @connection.query(sql) }
124
131
  end
132
+ else
133
+ raise
125
134
  end
126
135
  end
127
-
136
+
128
137
  def update(sql, name = nil)
129
138
  execute(sql, name)
130
139
  @connection.affected_rows
131
140
  end
132
-
141
+
133
142
  alias_method :delete, :update
134
-
135
-
143
+
144
+
136
145
  def begin_db_transaction
137
- begin
138
- execute "BEGIN"
139
- rescue Exception
140
- # Transactions aren't supported
141
- end
146
+ execute "BEGIN"
147
+ rescue Exception
148
+ # Transactions aren't supported
142
149
  end
143
-
150
+
144
151
  def commit_db_transaction
145
- begin
146
- execute "COMMIT"
147
- rescue Exception
148
- # Transactions aren't supported
149
- end
152
+ execute "COMMIT"
153
+ rescue Exception
154
+ # Transactions aren't supported
150
155
  end
151
-
156
+
152
157
  def rollback_db_transaction
153
- begin
154
- execute "ROLLBACK"
155
- rescue Exception
156
- # Transactions aren't supported
157
- end
158
+ execute "ROLLBACK"
159
+ rescue Exception
160
+ # Transactions aren't supported
158
161
  end
159
162
 
160
-
163
+
161
164
  def quote_column_name(name)
162
- return "`#{name}`"
165
+ "`#{name}`"
163
166
  end
164
-
165
- def quote_string(s)
166
- Mysql::quote(s)
167
+
168
+ def quote_string(string)
169
+ Mysql::quote(string)
167
170
  end
168
171
 
169
172
 
@@ -172,39 +175,63 @@ module ActiveRecord
172
175
  structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
173
176
  end
174
177
  end
175
-
176
- def add_limit_with_offset!(sql, limit, offset)
177
- sql << " LIMIT #{offset}, #{limit}"
178
+
179
+ def add_limit_offset!(sql, options)
180
+ if options[:limit]
181
+ if options[:offset].blank?
182
+ sql << " LIMIT #{options[:limit]}"
183
+ else
184
+ sql << " LIMIT #{options[:offset]}, #{options[:limit]}"
185
+ end
186
+ end
178
187
  end
179
-
188
+
180
189
  def recreate_database(name)
181
190
  drop_database(name)
182
191
  create_database(name)
183
192
  end
184
-
193
+
185
194
  def drop_database(name)
186
195
  execute "DROP DATABASE IF EXISTS #{name}"
187
196
  end
188
-
197
+
189
198
  def create_database(name)
190
199
  execute "CREATE DATABASE #{name}"
191
200
  end
192
-
193
201
 
194
- def create_table(name)
195
- super(name, "ENGINE=InnoDB")
202
+ def change_column_default(table_name, column_name, default)
203
+ current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
204
+
205
+ change_column(table_name, column_name, current_type, { :default => default })
206
+ end
207
+
208
+ def change_column(table_name, column_name, type, options = {})
209
+ options[:default] ||= select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Default"]
210
+
211
+ change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit])}"
212
+ add_column_options!(change_column_sql, options)
213
+ execute(change_column_sql)
214
+ end
215
+
216
+ def rename_column(table_name, column_name, new_column_name)
217
+ current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
218
+ execute "ALTER TABLE #{table_name} CHANGE #{column_name} #{new_column_name} #{current_type}"
219
+ end
220
+
221
+ def create_table(name, options = {})
222
+ super(name, {:options => "ENGINE=InnoDB"}.merge(options))
196
223
  end
197
224
 
198
225
  private
199
226
  def select(sql, name = nil)
200
- result = nil
201
227
  @connection.query_with_result = true
202
228
  result = execute(sql, name)
203
229
  rows = []
204
230
  all_fields_initialized = result.fetch_fields.inject({}) { |all_fields, f| all_fields[f.name] = nil; all_fields }
205
231
  result.each_hash { |row| rows << all_fields_initialized.dup.update(row) }
232
+ result.free
206
233
  rows
207
234
  end
208
235
  end
209
236
  end
210
- end
237
+ end
@@ -99,8 +99,8 @@ begin
99
99
  # * <tt>:password</tt> -- Defaults to nothing
100
100
  # * <tt>:host</tt> -- Defaults to localhost
101
101
  class OCIAdapter < AbstractAdapter
102
- def quote_string(s)
103
- s.gsub /'/, "''"
102
+ def quote_string(string)
103
+ string.gsub(/'/, "''")
104
104
  end
105
105
 
106
106
  def quote(value, column = nil)
@@ -125,7 +125,7 @@ begin
125
125
  elsif offset > 0
126
126
  sql = "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
127
127
  end
128
- cursor = log(sql, name, @connection) { @connection.exec sql }
128
+ cursor = log(sql, name) { @connection.exec sql }
129
129
  cols = cursor.get_col_names.map { |x| x.downcase }
130
130
  rows = []
131
131
  while row = cursor.fetch
@@ -167,16 +167,16 @@ begin
167
167
  if pk.nil? # Who called us? What does the sql look like? No idea!
168
168
  execute sql, name
169
169
  elsif id_value # Pre-assigned id
170
- log(sql, name, @connection) { @connection.exec sql }
170
+ log(sql, name) { @connection.exec sql }
171
171
  else # Assume the sql contains a bind-variable for the id
172
172
  id_value = select_one("select rails_sequence.nextval id from dual")['id']
173
- log(sql, name, @connection) { @connection.exec sql, id_value }
173
+ log(sql, name) { @connection.exec sql, id_value }
174
174
  end
175
175
  id_value
176
176
  end
177
177
 
178
178
  def execute(sql, name = nil)
179
- log(sql, name, @connection) { @connection.exec sql }
179
+ log(sql, name) { @connection.exec sql }
180
180
  end
181
181
 
182
182
  alias :update :execute
@@ -24,7 +24,8 @@ module ActiveRecord
24
24
  username = config[:username].to_s
25
25
  password = config[:password].to_s
26
26
 
27
- schema_order = config[:schema_order]
27
+ encoding = config[:encoding]
28
+ min_messages = config[:min_messages]
28
29
 
29
30
  if config.has_key?(:database)
30
31
  database = config[:database]
@@ -36,7 +37,9 @@ module ActiveRecord
36
37
  PGconn.connect(host, port, "", "", database, username, password), logger
37
38
  )
38
39
 
39
- pga.execute("SET search_path TO #{schema_order}") if schema_order
40
+ pga.schema_search_path = config[:schema_search_path] || config[:schema_order]
41
+ pga.execute("SET client_encoding TO '#{encoding}'") if encoding
42
+ pga.execute("SET client_min_messages TO '#{min_messages}'") if min_messages
40
43
 
41
44
  pga
42
45
  end
@@ -53,8 +56,30 @@ module ActiveRecord
53
56
  # * <tt>:username</tt> -- Defaults to nothing
54
57
  # * <tt>:password</tt> -- Defaults to nothing
55
58
  # * <tt>:database</tt> -- The name of the database. No default, must be provided.
56
- # * <tt>:schema_order</tt> -- An optional schema order string that is using in a SET search_path TO <schema_order> call on connection.
59
+ # * <tt>:schema_search_path</tt> -- An optional schema search path for the connection given as a string of comma-separated schema names. This is backward-compatible with the :schema_order option.
60
+ # * <tt>:encoding</tt> -- An optional client encoding that is using in a SET client_encoding TO <encoding> call on connection.
61
+ # * <tt>:min_messages</tt> -- An optional client min messages that is using in a SET client_min_messages TO <min_messages> call on connection.
57
62
  class PostgreSQLAdapter < AbstractAdapter
63
+ def native_database_types
64
+ {
65
+ :primary_key => "serial primary key",
66
+ :string => { :name => "character varying", :limit => 255 },
67
+ :text => { :name => "text" },
68
+ :integer => { :name => "integer" },
69
+ :float => { :name => "float" },
70
+ :datetime => { :name => "timestamp" },
71
+ :timestamp => { :name => "timestamp" },
72
+ :time => { :name => "timestamp" },
73
+ :date => { :name => "date" },
74
+ :binary => { :name => "bytea" },
75
+ :boolean => { :name => "boolean"}
76
+ }
77
+ end
78
+
79
+ def supports_migrations?
80
+ true
81
+ end
82
+
58
83
  def select_all(sql, name = nil)
59
84
  select(sql, name)
60
85
  end
@@ -65,26 +90,27 @@ module ActiveRecord
65
90
  end
66
91
 
67
92
  def columns(table_name, name = nil)
68
- table_structure(table_name).inject([]) do |columns, field|
69
- columns << Column.new(field[0], field[2], field[1])
70
- columns
93
+ column_definitions(table_name).collect do |name, type, default|
94
+ Column.new(name, default_value(default), translate_field_type(type))
71
95
  end
72
96
  end
73
97
 
74
98
  def insert(sql, name = nil, pk = nil, id_value = nil)
75
- execute(sql, name = nil)
99
+ execute(sql, name)
76
100
  table = sql.split(" ", 4)[2]
77
101
  return id_value || last_insert_id(table, pk)
78
102
  end
79
103
 
104
+ def query(sql, name = nil)
105
+ log(sql, name) { @connection.query(sql) }
106
+ end
107
+
80
108
  def execute(sql, name = nil)
81
- log(sql, name, @connection) { |connection| connection.query(sql) }
109
+ log(sql, name) { @connection.exec(sql) }
82
110
  end
83
111
 
84
112
  def update(sql, name = nil)
85
- result = nil
86
- log(sql, name, @connection) { |connection| result = connection.exec(sql) }
87
- result.cmdtuples
113
+ execute(sql, name).cmdtuples
88
114
  end
89
115
 
90
116
  alias_method :delete, :update
@@ -102,23 +128,55 @@ module ActiveRecord
102
128
  end
103
129
 
104
130
  def quote_column_name(name)
105
- return "\"#{name}\""
131
+ %("#{name}")
106
132
  end
107
133
 
108
- def adapter_name()
134
+ def adapter_name
109
135
  'PostgreSQL'
110
136
  end
111
137
 
138
+
139
+ # Set the schema search path to a string of comma-separated schema names.
140
+ # Names beginning with $ are quoted (e.g. $user => '$user')
141
+ # See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html
142
+ def schema_search_path=(schema_csv)
143
+ if schema_csv
144
+ execute "SET search_path TO #{schema_csv}"
145
+ @schema_search_path = nil
146
+ end
147
+ end
148
+
149
+ def schema_search_path
150
+ @schema_search_path ||= query('SHOW search_path')[0][0]
151
+ end
152
+
153
+ def change_column(table_name, column_name, type, options = {})
154
+ execute = "ALTER TABLE #{table_name} ALTER #{column_name} TYPE #{type}"
155
+ change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
156
+ end
157
+
158
+ def change_column_default(table_name, column_name, default)
159
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT '#{default}'"
160
+ end
161
+
162
+ def rename_column(table_name, column_name, new_column_name)
163
+ execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} TO #{new_column_name}"
164
+ end
165
+
166
+ def remove_index(table_name, column_name)
167
+ execute "DROP INDEX #{table_name}_#{column_name}_index"
168
+ end
169
+
112
170
  private
171
+ BYTEA_COLUMN_TYPE_OID = 17
172
+
113
173
  def last_insert_id(table, column = "id")
114
174
  sequence_name = "#{table}_#{column || 'id'}_seq"
115
175
  @connection.exec("SELECT currval('#{sequence_name}')")[0][0].to_i
116
176
  end
117
177
 
118
178
  def select(sql, name = nil)
119
- res = nil
120
- log(sql, name, @connection) { |connection| res = connection.exec(sql) }
121
-
179
+ res = execute(sql, name)
122
180
  results = res.result
123
181
  rows = []
124
182
  if results.length > 0
@@ -127,7 +185,7 @@ module ActiveRecord
127
185
  hashed_row = {}
128
186
  row.each_index do |cel_index|
129
187
  column = row[cel_index]
130
- if res.type(cel_index) == 17 # type oid for bytea
188
+ if res.type(cel_index) == BYTEA_COLUMN_TYPE_OID
131
189
  column = unescape_bytea(column)
132
190
  end
133
191
  hashed_row[fields[cel_index]] = column
@@ -150,54 +208,49 @@ module ActiveRecord
150
208
  s.gsub(/\\([0-9][0-9][0-9])/) { $1.oct.chr }.gsub(/\\\\/) { '\\' } unless s.nil?
151
209
  end
152
210
 
153
- def split_table_schema(table_name)
154
- schema_split = table_name.split('.')
155
- schema_name = "public"
156
- if schema_split.length > 1
157
- schema_name = schema_split.first.strip
158
- table_name = schema_split.last.strip
159
- end
160
- return [schema_name, table_name]
211
+ # Query a table's column names, default values, and types.
212
+ #
213
+ # The underlying query is roughly:
214
+ # SELECT column.name, column.type, default.value
215
+ # FROM column LEFT JOIN default
216
+ # ON column.table_id = default.table_id
217
+ # AND column.num = default.column_num
218
+ # WHERE column.table_id = get_table_id('table_name')
219
+ # AND column.num > 0
220
+ # AND NOT column.is_dropped
221
+ # ORDER BY column.num
222
+ #
223
+ # If the table name is not prefixed with a schema, the database will
224
+ # take the first match from the schema search path.
225
+ #
226
+ # Query implementation notes:
227
+ # - format_type includes the column size constraint, e.g. varchar(50)
228
+ # - ::regclass is a function that gives the id for a table name
229
+ def column_definitions(table_name)
230
+ query <<-end_sql
231
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc
232
+ FROM pg_attribute a LEFT JOIN pg_attrdef d
233
+ ON a.attrelid = d.adrelid AND a.attnum = d.adnum
234
+ WHERE a.attrelid = '#{table_name}'::regclass
235
+ AND a.attnum > 0 AND NOT a.attisdropped
236
+ ORDER BY a.attnum
237
+ end_sql
161
238
  end
162
239
 
163
- def table_structure(table_name)
164
- database_name = @connection.db
165
- schema_name, table_name = split_table_schema(table_name)
166
-
167
- # Grab a list of all the default values for the columns.
168
- sql = "SELECT column_name, column_default, character_maximum_length, data_type "
169
- sql << " FROM information_schema.columns "
170
- sql << " WHERE table_catalog = '#{database_name}' "
171
- sql << " AND table_schema = '#{schema_name}' "
172
- sql << " AND table_name = '#{table_name}';"
173
-
174
- column_defaults = nil
175
- log(sql, nil, @connection) { |connection| column_defaults = connection.query(sql) }
176
- column_defaults.collect do |row|
177
- field = row[0]
178
- type = type_as_string(row[3], row[2])
179
- default = default_value(row[1])
180
- length = row[2]
181
-
182
- [field, type, default, length]
240
+ # Translate PostgreSQL-specific types into simplified SQL types.
241
+ # These are special cases; standard types are handled by
242
+ # ConnectionAdapters::Column#simplified_type.
243
+ def translate_field_type(field_type)
244
+ # Match the beginning of field_type since it may have a size constraint on the end.
245
+ case field_type
246
+ when /^timestamp/i then 'datetime'
247
+ when /^real|^money/i then 'float'
248
+ when /^interval/i then 'string'
249
+ when /^bytea/i then 'binary'
250
+ else field_type # Pass through standard types.
183
251
  end
184
252
  end
185
253
 
186
- def type_as_string(field_type, field_length)
187
- type = case field_type
188
- when 'numeric', 'real', 'money' then 'float'
189
- when 'character varying', 'interval' then 'string'
190
- when 'timestamp without time zone' then 'datetime'
191
- when 'timestamp with time zone' then 'datetime'
192
- when 'bytea' then 'binary'
193
- else field_type
194
- end
195
-
196
- size = field_length.nil? ? "" : "(#{field_length})"
197
-
198
- return type + size
199
- end
200
-
201
254
  def default_value(value)
202
255
  # Boolean types
203
256
  return "t" if value =~ /true/i