activerecord 1.13.0 → 1.13.1

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 (68) hide show
  1. data/CHANGELOG +91 -0
  2. data/lib/active_record.rb +2 -2
  3. data/lib/active_record/acts/list.rb +16 -12
  4. data/lib/active_record/acts/tree.rb +2 -2
  5. data/lib/active_record/aggregations.rb +6 -0
  6. data/lib/active_record/associations.rb +38 -16
  7. data/lib/active_record/associations/has_many_association.rb +2 -1
  8. data/lib/active_record/associations/has_one_association.rb +1 -1
  9. data/lib/active_record/base.rb +46 -33
  10. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +33 -9
  11. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +11 -2
  12. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -0
  13. data/lib/active_record/connection_adapters/abstract_adapter.rb +41 -21
  14. data/lib/active_record/connection_adapters/firebird_adapter.rb +414 -0
  15. data/lib/active_record/connection_adapters/mysql_adapter.rb +68 -29
  16. data/lib/active_record/connection_adapters/oci_adapter.rb +141 -21
  17. data/lib/active_record/connection_adapters/postgresql_adapter.rb +82 -21
  18. data/lib/active_record/connection_adapters/sqlite_adapter.rb +3 -3
  19. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +39 -6
  20. data/lib/active_record/fixtures.rb +1 -0
  21. data/lib/active_record/migration.rb +30 -13
  22. data/lib/active_record/validations.rb +18 -7
  23. data/lib/active_record/vendor/mysql.rb +89 -12
  24. data/lib/active_record/version.rb +2 -2
  25. data/rakefile +38 -3
  26. data/test/abstract_unit.rb +5 -0
  27. data/test/aggregations_test.rb +19 -0
  28. data/test/associations_go_eager_test.rb +26 -2
  29. data/test/associations_test.rb +29 -10
  30. data/test/base_test.rb +57 -6
  31. data/test/binary_test.rb +3 -3
  32. data/test/connections/native_db2/connection.rb +1 -1
  33. data/test/connections/native_firebird/connection.rb +24 -0
  34. data/test/connections/native_mysql/connection.rb +1 -1
  35. data/test/connections/native_oci/connection.rb +1 -1
  36. data/test/connections/native_postgresql/connection.rb +6 -6
  37. data/test/connections/native_sqlite/connection.rb +1 -1
  38. data/test/connections/native_sqlite3/connection.rb +1 -1
  39. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  40. data/test/connections/native_sqlserver/connection.rb +1 -1
  41. data/test/connections/native_sqlserver_odbc/connection.rb +1 -1
  42. data/test/default_test_firebird.rb +16 -0
  43. data/test/deprecated_associations_test.rb +1 -1
  44. data/test/finder_test.rb +11 -1
  45. data/test/fixtures/author.rb +30 -30
  46. data/test/fixtures/comment.rb +1 -1
  47. data/test/fixtures/company.rb +3 -1
  48. data/test/fixtures/customer.rb +4 -0
  49. data/test/fixtures/db_definitions/firebird.drop.sql +54 -0
  50. data/test/fixtures/db_definitions/firebird.sql +259 -0
  51. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  52. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  53. data/test/fixtures/db_definitions/oci.sql +8 -0
  54. data/test/fixtures/db_definitions/postgresql.sql +3 -2
  55. data/test/fixtures/developer.rb +10 -0
  56. data/test/fixtures/fixture_database.sqlite +0 -0
  57. data/test/fixtures/fixture_database_2.sqlite +0 -0
  58. data/test/fixtures/mixin.rb +11 -1
  59. data/test/fixtures/mixins.yml +20 -1
  60. data/test/fixtures_test.rb +65 -45
  61. data/test/inheritance_test.rb +1 -1
  62. data/test/migration_test.rb +7 -1
  63. data/test/mixin_test.rb +267 -98
  64. data/test/multiple_db_test.rb +13 -1
  65. data/test/pk_test.rb +1 -0
  66. metadata +11 -5
  67. data/lib/active_record/vendor/mysql411.rb +0 -311
  68. data/test/debug.log +0 -2857
@@ -5,24 +5,19 @@ module ActiveRecord
5
5
  # Establishes a connection to the database that's used by all Active Record objects.
6
6
  def self.mysql_connection(config) # :nodoc:
7
7
  # Only include the MySQL driver if one hasn't already been loaded
8
- unless self.class.const_defined?(:Mysql)
8
+ unless defined? Mysql
9
9
  begin
10
10
  require_library_or_gem 'mysql'
11
- # The C version of mysql returns null fields in each_hash if Mysql::VERSION is defined
12
- ConnectionAdapters::MysqlAdapter.null_values_in_each_hash = Mysql.const_defined?(:VERSION)
13
11
  rescue LoadError => cannot_require_mysql
14
12
  # Only use the supplied backup Ruby/MySQL driver if no driver is already in place
15
13
  begin
16
14
  require 'active_record/vendor/mysql'
17
- require 'active_record/vendor/mysql411'
18
- # The ruby version of mysql returns null fields in each_hash
19
- ConnectionAdapters::MysqlAdapter.null_values_in_each_hash = true
20
15
  rescue LoadError
21
16
  raise cannot_require_mysql
22
17
  end
23
18
  end
24
19
  end
25
-
20
+
26
21
 
27
22
  config = config.symbolize_keys
28
23
  host = config[:host]
@@ -39,7 +34,17 @@ module ActiveRecord
39
34
 
40
35
  mysql = Mysql.init
41
36
  mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
42
- ConnectionAdapters::MysqlAdapter.new(mysql.real_connect(host, username, password, database, port, socket), logger, [host, username, password, database, port, socket])
37
+ if config[:encoding]
38
+ begin
39
+ mysql.options(Mysql::SET_CHARSET_NAME, config[:encoding])
40
+ rescue
41
+ raise ActiveRecord::ConnectionFailed, 'The :encoding option is only available for MySQL 4.1 and later with the mysql-ruby driver. Again, this does not work with the ruby-mysql driver or MySQL < 4.1.'
42
+ end
43
+ end
44
+
45
+ conn = mysql.real_connect(host, username, password, database, port, socket)
46
+ conn.query("SET NAMES '#{config[:encoding]}'") if config[:encoding]
47
+ ConnectionAdapters::MysqlAdapter.new(conn, logger, [host, username, password, database, port, socket], mysql)
43
48
  end
44
49
  end
45
50
 
@@ -78,9 +83,6 @@ module ActiveRecord
78
83
  @@emulate_booleans = true
79
84
  cattr_accessor :emulate_booleans
80
85
 
81
- cattr_accessor :null_values_in_each_hash
82
- @@null_values_in_each_hash = false
83
-
84
86
  LOST_CONNECTION_ERROR_MESSAGES = [
85
87
  "Server shutdown in progress",
86
88
  "Broken pipe",
@@ -88,9 +90,11 @@ module ActiveRecord
88
90
  "MySQL server has gone away"
89
91
  ]
90
92
 
91
- def initialize(connection, logger, connection_options=nil)
93
+ def initialize(connection, logger, connection_options=nil, mysql=Mysql)
92
94
  super(connection, logger)
93
95
  @connection_options = connection_options
96
+ @null_values_in_each_hash = Mysql.const_defined?(:VERSION)
97
+ @mysql = mysql
94
98
  end
95
99
 
96
100
  def adapter_name #:nodoc:
@@ -120,12 +124,21 @@ module ActiveRecord
120
124
 
121
125
  # QUOTING ==================================================
122
126
 
127
+ def quote(value, column = nil)
128
+ if value.kind_of?(String) && column && column.type == :binary
129
+ s = column.class.string_to_binary(value).unpack("H*")[0]
130
+ "x'#{s}'"
131
+ else
132
+ super
133
+ end
134
+ end
135
+
123
136
  def quote_column_name(name) #:nodoc:
124
137
  "`#{name}`"
125
138
  end
126
139
 
127
140
  def quote_string(string) #:nodoc:
128
- Mysql::quote(string)
141
+ @mysql.quote(string)
129
142
  end
130
143
 
131
144
  def quoted_true
@@ -137,6 +150,31 @@ module ActiveRecord
137
150
  end
138
151
 
139
152
 
153
+ # CONNECTION MANAGEMENT ====================================
154
+
155
+ def active?
156
+ if @connection.respond_to?(:stat)
157
+ @connection.stat
158
+ else
159
+ @connection.query 'select 1'
160
+ end
161
+
162
+ # mysql-ruby doesn't raise an exception when stat fails.
163
+ if @connection.respond_to?(:errno)
164
+ @connection.errno.zero?
165
+ else
166
+ true
167
+ end
168
+ rescue Mysql::Error
169
+ false
170
+ end
171
+
172
+ def reconnect!
173
+ @connection.close rescue nil
174
+ connect
175
+ end
176
+
177
+
140
178
  # DATABASE STATEMENTS ======================================
141
179
 
142
180
  def select_all(sql, name = nil) #:nodoc:
@@ -149,22 +187,10 @@ module ActiveRecord
149
187
  end
150
188
 
151
189
  def execute(sql, name = nil, retries = 2) #:nodoc:
152
- unless @logger
153
- @connection.query(sql)
154
- else
155
- log(sql, name) { @connection.query(sql) }
156
- end
190
+ log(sql, name) { @connection.query(sql) }
157
191
  rescue ActiveRecord::StatementInvalid => exception
158
- if LOST_CONNECTION_ERROR_MESSAGES.any? { |msg| exception.message.split(":").first =~ /^#{msg}/ }
159
- @connection.real_connect(*@connection_options)
160
- unless @logger
161
- @connection.query(sql)
162
- else
163
- @logger.info "Retrying invalid statement with reopened connection"
164
- log(sql, name) { @connection.query(sql) }
165
- end
166
- elsif exception.message.split(":").first =~ /Packets out of order/
167
- raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem update mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information."
192
+ if exception.message.split(":").first =~ /Packets out of order/
193
+ raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
168
194
  else
169
195
  raise
170
196
  end
@@ -292,11 +318,24 @@ module ActiveRecord
292
318
 
293
319
 
294
320
  private
321
+ def connect
322
+ encoding = @config[:encoding]
323
+ if encoding
324
+ begin
325
+ @connection.options(Mysql::SET_CHARSET_NAME, encoding)
326
+ rescue
327
+ raise ActiveRecord::ConnectionFailed, 'The :encoding option is only available for MySQL 4.1 and later with the mysql-ruby driver. Again, this does not work with the ruby-mysql driver or MySQL < 4.1.'
328
+ end
329
+ end
330
+ @connection.real_connect(*@connection_options)
331
+ execute("SET NAMES '#{encoding}'") if encoding
332
+ end
333
+
295
334
  def select(sql, name = nil)
296
335
  @connection.query_with_result = true
297
336
  result = execute(sql, name)
298
337
  rows = []
299
- if @@null_values_in_each_hash
338
+ if @null_values_in_each_hash
300
339
  result.each_hash { |row| rows << row }
301
340
  else
302
341
  all_fields = result.fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
@@ -23,6 +23,7 @@
23
23
  # portions Copyright 2005 Graham Jenkins
24
24
 
25
25
  require 'active_record/connection_adapters/abstract_adapter'
26
+ require 'delegate'
26
27
 
27
28
  begin
28
29
  require_library_or_gem 'oci8' unless self.class.const_defined? :OCI8
@@ -30,11 +31,8 @@ begin
30
31
  module ActiveRecord
31
32
  class Base
32
33
  def self.oci_connection(config) #:nodoc:
33
- conn = OCI8.new config[:username], config[:password], config[:host]
34
- conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
35
- conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'}
36
- conn.autocommit = true
37
- ConnectionAdapters::OCIAdapter.new conn, logger
34
+ # Use OCI8AutoRecover instead of normal OCI8 driver.
35
+ ConnectionAdapters::OCIAdapter.new OCI8AutoRecover.new(config), logger
38
36
  end
39
37
 
40
38
  # Enable the id column to be bound into the sql later, by the adapter's insert method.
@@ -110,6 +108,7 @@ begin
110
108
 
111
109
  def cast_to_date_or_time(value)
112
110
  return value if value.is_a? Date
111
+ return nil if value.blank?
113
112
  guess_date_or_time (value.is_a? Time) ? value : cast_to_time(value)
114
113
  end
115
114
 
@@ -117,7 +116,7 @@ begin
117
116
  return value if value.is_a? Time
118
117
  time_array = ParseDate.parsedate value
119
118
  time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1;
120
- Time.send Base.default_timezone, *time_array
119
+ Time.send(Base.default_timezone, *time_array) rescue nil
121
120
  end
122
121
 
123
122
  def guess_date_or_time(value)
@@ -212,6 +211,27 @@ begin
212
211
  end
213
212
 
214
213
 
214
+ # CONNECTION MANAGEMENT ====================================#
215
+
216
+ # Returns true if the connection is active.
217
+ def active?
218
+ # Pings the connection to check if it's still good. Note that an
219
+ # #active? method is also available, but that simply returns the
220
+ # last known state, which isn't good enough if the connection has
221
+ # gone stale since the last use.
222
+ @connection.ping
223
+ rescue OCIError
224
+ false
225
+ end
226
+
227
+ # Reconnects to the database.
228
+ def reconnect!
229
+ @connection.reset!
230
+ rescue OCIError => e
231
+ @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}"
232
+ end
233
+
234
+
215
235
  # DATABASE STATEMENTS ======================================
216
236
  #
217
237
  # see: abstract/database_statements.rb
@@ -312,20 +332,31 @@ begin
312
332
  end
313
333
 
314
334
  def columns(table_name, name = nil) #:nodoc:
315
- select_all(%Q{
316
- select column_name, data_type, data_default, nullable,
317
- case when data_type = 'NUMBER' then data_precision
318
- when data_type = 'VARCHAR2' then data_length
319
- else null end as length,
320
- case when data_type = 'NUMBER' then data_scale
321
- else null end as scale
322
- from user_catalog cat, user_synonyms syn, all_tab_columns col
323
- where cat.table_name = '#{table_name.to_s.upcase}'
324
- and syn.synonym_name (+)= cat.table_name
325
- and col.owner = nvl(syn.table_owner, user)
326
- and col.table_name = nvl(syn.table_name, cat.table_name)}
327
- ).map do |row|
328
- row['data_default'].gsub!(/^'(.*)'$/, '\1') if row['data_default']
335
+ table_name = table_name.to_s.upcase
336
+ owner = table_name.include?('.') ? "'#{table_name.split('.').first}'" : "user"
337
+ table = "'#{table_name.split('.').last}'"
338
+ scope = (owner == "user" ? "user" : "all")
339
+
340
+ table_cols = %Q{
341
+ select column_name, data_type, data_default, nullable,
342
+ decode(data_type, 'NUMBER', data_precision,
343
+ 'VARCHAR2', data_length,
344
+ null) as length,
345
+ decode(data_type, 'NUMBER', data_scale, null) as scale
346
+ from #{scope}_catalog cat, #{scope}_synonyms syn, all_tab_columns col
347
+ where cat.table_name = #{table}
348
+ and syn.synonym_name (+)= cat.table_name
349
+ and col.table_name = nvl(syn.table_name, cat.table_name)
350
+ and col.owner = nvl(syn.table_owner, #{(scope == "all" ? "cat.owner" : "user")}) }
351
+
352
+ if scope == "all"
353
+ table_cols << %Q{
354
+ and cat.owner = #{owner}
355
+ and syn.owner (+)= cat.owner }
356
+ end
357
+
358
+ select_all(table_cols, name).map do |row|
359
+ row['data_default'].sub!(/^'(.*)'\s*$/, '\1') if row['data_default']
329
360
  OCIColumn.new(
330
361
  oci_downcase(row['column_name']),
331
362
  row['data_default'],
@@ -472,10 +503,99 @@ begin
472
503
  when 187 : @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
473
504
  else define_a_column_pre_ar i
474
505
  end
475
- end
506
+ end
476
507
  end
477
508
  end
478
509
 
510
+
511
+ # The OCIConnectionFactory factors out the code necessary to connect and
512
+ # configure an OCI connection.
513
+ class OCIConnectionFactory #:nodoc:
514
+ def new_connection(username, password, host)
515
+ conn = OCI8.new username, password, host
516
+ conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
517
+ conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil
518
+ conn.autocommit = true
519
+ conn
520
+ end
521
+ end
522
+
523
+
524
+ # The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
525
+ # reset functionality. If a call to #exec fails, and autocommit is turned on
526
+ # (ie., we're not in the middle of a longer transaction), it will
527
+ # automatically reconnect and try again. If autocommit is turned off,
528
+ # this would be dangerous (as the earlier part of the implied transaction
529
+ # may have failed silently if the connection died) -- so instead the
530
+ # connection is marked as dead, to be reconnected on it's next use.
531
+ class OCI8AutoRecover < DelegateClass(OCI8) #:nodoc:
532
+ attr_accessor :active
533
+ alias :active? :active
534
+
535
+ cattr_accessor :auto_retry
536
+ class << self
537
+ alias :auto_retry? :auto_retry
538
+ end
539
+ @@auto_retry = false
540
+
541
+ def initialize(config, factory = OCIConnectionFactory.new)
542
+ @active = true
543
+ @username, @password, @host = config[:username], config[:password], config[:host]
544
+ @factory = factory
545
+ @connection = @factory.new_connection @username, @password, @host
546
+ super @connection
547
+ end
548
+
549
+ # Checks connection, returns true if active. Note that ping actively
550
+ # checks the connection, while #active? simply returns the last
551
+ # known state.
552
+ def ping
553
+ @connection.commit
554
+ @active = true
555
+ rescue
556
+ @active = false
557
+ raise
558
+ end
559
+
560
+ # Resets connection, by logging off and creating a new connection.
561
+ def reset!
562
+ logoff rescue nil
563
+ begin
564
+ @connection = @factory.new_connection @username, @password, @host
565
+ __setobj__ @connection
566
+ @active = true
567
+ rescue
568
+ @active = false
569
+ raise
570
+ end
571
+ end
572
+
573
+ # ORA-00028: your session has been killed
574
+ # ORA-01012: not logged on
575
+ # ORA-03113: end-of-file on communication channel
576
+ # ORA-03114: not connected to ORACLE
577
+ LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ]
578
+
579
+ # Adds auto-recovery functionality.
580
+ #
581
+ # See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
582
+ def exec(sql, *bindvars)
583
+ should_retry = self.class.auto_retry? && autocommit?
584
+
585
+ begin
586
+ @connection.exec(sql, *bindvars)
587
+ rescue OCIError => e
588
+ raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code)
589
+ @active = false
590
+ raise unless should_retry
591
+ should_retry = false
592
+ reset! rescue nil
593
+ retry
594
+ end
595
+ end
596
+
597
+ end
598
+
479
599
  rescue LoadError
480
600
  # OCI8 driver is unavailable.
481
601
  end
@@ -12,7 +12,6 @@ module ActiveRecord
12
12
  username = config[:username].to_s
13
13
  password = config[:password].to_s
14
14
 
15
- encoding = config[:encoding]
16
15
  min_messages = config[:min_messages]
17
16
 
18
17
  if config.has_key?(:database)
@@ -22,12 +21,10 @@ module ActiveRecord
22
21
  end
23
22
 
24
23
  pga = ConnectionAdapters::PostgreSQLAdapter.new(
25
- PGconn.connect(host, port, "", "", database, username, password), logger
24
+ PGconn.connect(host, port, "", "", database, username, password), logger, config
26
25
  )
27
26
 
28
27
  pga.schema_search_path = config[:schema_search_path] || config[:schema_order]
29
- pga.execute("SET client_encoding TO '#{encoding}'") if encoding
30
- pga.execute("SET client_min_messages TO '#{min_messages}'") if min_messages
31
28
 
32
29
  pga
33
30
  end
@@ -52,6 +49,33 @@ module ActiveRecord
52
49
  'PostgreSQL'
53
50
  end
54
51
 
52
+ def initialize(connection, logger, config = {})
53
+ super(connection, logger)
54
+ @config = config
55
+ configure_connection
56
+ end
57
+
58
+ # Is this connection alive and ready for queries?
59
+ def active?
60
+ if @connection.respond_to?(:status)
61
+ @connection.status == PGconn::CONNECTION_OK
62
+ else
63
+ @connection.query 'SELECT 1'
64
+ true
65
+ end
66
+ rescue PGError
67
+ false
68
+ end
69
+
70
+ # Close then reopen the connection.
71
+ def reconnect!
72
+ # TODO: postgres-pr doesn't have PGconn#reset.
73
+ if @connection.respond_to?(:reset)
74
+ @connection.reset
75
+ configure_connection
76
+ end
77
+ end
78
+
55
79
  def native_database_types
56
80
  {
57
81
  :primary_key => "serial primary key",
@@ -102,7 +126,7 @@ module ActiveRecord
102
126
  def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
103
127
  execute(sql, name)
104
128
  table = sql.split(" ", 4)[2]
105
- id_value || last_insert_id(table, sequence_name)
129
+ id_value || last_insert_id(table, sequence_name || default_sequence_name(table, pk))
106
130
  end
107
131
 
108
132
  def query(sql, name = nil) #:nodoc:
@@ -199,24 +223,35 @@ module ActiveRecord
199
223
  @schema_search_path ||= query('SHOW search_path')[0][0]
200
224
  end
201
225
 
202
- def default_sequence_name(table_name, pk = 'id')
203
- "#{table_name}_#{pk}_seq"
226
+ def default_sequence_name(table_name, pk = nil)
227
+ default_pk, default_seq = pk_and_sequence_for(table_name)
228
+ default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
204
229
  end
205
230
 
206
- # Set the sequence to the max value of the table's pk.
207
- def reset_pk_sequence!(table)
208
- pk, sequence = pk_and_sequence_for(table)
209
- if pk and sequence
210
- select_value <<-end_sql, 'Reset sequence'
211
- SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false)
212
- end_sql
231
+ # Resets sequence to the max value of the table's pk if present.
232
+ def reset_pk_sequence!(table, pk = nil, sequence = nil)
233
+ unless pk and sequence
234
+ default_pk, default_sequence = pk_and_sequence_for(table)
235
+ pk ||= default_pk
236
+ sequence ||= default_sequence
237
+ end
238
+ if pk
239
+ if sequence
240
+ select_value <<-end_sql, 'Reset sequence'
241
+ SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false)
242
+ end_sql
243
+ else
244
+ @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
245
+ end
213
246
  end
214
247
  end
215
248
 
216
249
  # Find a table's primary key and sequence.
217
250
  def pk_and_sequence_for(table)
218
- execute(<<-end_sql, 'Find pk sequence')[0]
219
- SELECT attr.attname, (name.nspname || '.' || seq.relname)
251
+ # First try looking for a sequence with a dependency on the
252
+ # given table's primary key.
253
+ result = execute(<<-end_sql, 'PK and serial sequence')[0]
254
+ SELECT attr.attname, name.nspname, seq.relname
220
255
  FROM pg_class seq,
221
256
  pg_attribute attr,
222
257
  pg_depend dep,
@@ -232,11 +267,30 @@ module ActiveRecord
232
267
  AND cons.contype = 'p'
233
268
  AND dep.refobjid = '#{table}'::regclass
234
269
  end_sql
270
+
271
+ if result.nil? or result.empty?
272
+ # If that fails, try parsing the primary key's default value.
273
+ # Support the 7.x and 8.0 nextval('foo'::text) as well as
274
+ # the 8.1+ nextval('foo'::regclass).
275
+ # TODO: assumes sequence is in same schema as table.
276
+ result = execute(<<-end_sql, 'PK and custom sequence')[0]
277
+ SELECT attr.attname, name.nspname, split_part(def.adsrc, '\\\'', 2)
278
+ FROM pg_class t
279
+ JOIN pg_namespace name ON (t.relnamespace = name.oid)
280
+ JOIN pg_attribute attr ON (t.oid = attrelid)
281
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
282
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
283
+ WHERE t.oid = '#{table}'::regclass
284
+ AND cons.contype = 'p'
285
+ AND def.adsrc ~* 'nextval'
286
+ end_sql
287
+ end
288
+ # check for existence of . in sequence name as in public.foo_sequence. if it does not exist, join the current namespace
289
+ result.last['.'] ? [result.first, result.last] : [result.first, "#{result[1]}.#{result[2]}"]
235
290
  rescue
236
291
  nil
237
292
  end
238
293
 
239
-
240
294
  def rename_table(name, new_name)
241
295
  execute "ALTER TABLE #{name} RENAME TO #{new_name}"
242
296
  end
@@ -276,16 +330,23 @@ module ActiveRecord
276
330
  execute "DROP INDEX #{index_name}"
277
331
  end
278
332
 
279
-
333
+
280
334
  private
281
335
  BYTEA_COLUMN_TYPE_OID = 17
282
336
 
283
- def last_insert_id(table, sequence_name)
284
- if sequence_name
285
- @connection.exec("SELECT currval('#{sequence_name}')")[0][0].to_i
337
+ def configure_connection
338
+ if @config[:encoding]
339
+ execute("SET client_encoding TO '#{@config[:encoding]}'")
340
+ end
341
+ if @config[:min_messages]
342
+ execute("SET client_min_messages TO '#{@config[:min_messages]}'")
286
343
  end
287
344
  end
288
345
 
346
+ def last_insert_id(table, sequence_name)
347
+ Integer(select_value("SELECT currval('#{sequence_name}')"))
348
+ end
349
+
289
350
  def select(sql, name = nil)
290
351
  res = execute(sql, name)
291
352
  results = res.result