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.
- data/CHANGELOG +91 -0
- data/lib/active_record.rb +2 -2
- data/lib/active_record/acts/list.rb +16 -12
- data/lib/active_record/acts/tree.rb +2 -2
- data/lib/active_record/aggregations.rb +6 -0
- data/lib/active_record/associations.rb +38 -16
- data/lib/active_record/associations/has_many_association.rb +2 -1
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/base.rb +46 -33
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +33 -9
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +11 -2
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +41 -21
- data/lib/active_record/connection_adapters/firebird_adapter.rb +414 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +68 -29
- data/lib/active_record/connection_adapters/oci_adapter.rb +141 -21
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +82 -21
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +39 -6
- data/lib/active_record/fixtures.rb +1 -0
- data/lib/active_record/migration.rb +30 -13
- data/lib/active_record/validations.rb +18 -7
- data/lib/active_record/vendor/mysql.rb +89 -12
- data/lib/active_record/version.rb +2 -2
- data/rakefile +38 -3
- data/test/abstract_unit.rb +5 -0
- data/test/aggregations_test.rb +19 -0
- data/test/associations_go_eager_test.rb +26 -2
- data/test/associations_test.rb +29 -10
- data/test/base_test.rb +57 -6
- data/test/binary_test.rb +3 -3
- data/test/connections/native_db2/connection.rb +1 -1
- data/test/connections/native_firebird/connection.rb +24 -0
- data/test/connections/native_mysql/connection.rb +1 -1
- data/test/connections/native_oci/connection.rb +1 -1
- data/test/connections/native_postgresql/connection.rb +6 -6
- data/test/connections/native_sqlite/connection.rb +1 -1
- data/test/connections/native_sqlite3/connection.rb +1 -1
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
- data/test/connections/native_sqlserver/connection.rb +1 -1
- data/test/connections/native_sqlserver_odbc/connection.rb +1 -1
- data/test/default_test_firebird.rb +16 -0
- data/test/deprecated_associations_test.rb +1 -1
- data/test/finder_test.rb +11 -1
- data/test/fixtures/author.rb +30 -30
- data/test/fixtures/comment.rb +1 -1
- data/test/fixtures/company.rb +3 -1
- data/test/fixtures/customer.rb +4 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +54 -0
- data/test/fixtures/db_definitions/firebird.sql +259 -0
- data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
- data/test/fixtures/db_definitions/firebird2.sql +6 -0
- data/test/fixtures/db_definitions/oci.sql +8 -0
- data/test/fixtures/db_definitions/postgresql.sql +3 -2
- data/test/fixtures/developer.rb +10 -0
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/mixin.rb +11 -1
- data/test/fixtures/mixins.yml +20 -1
- data/test/fixtures_test.rb +65 -45
- data/test/inheritance_test.rb +1 -1
- data/test/migration_test.rb +7 -1
- data/test/mixin_test.rb +267 -98
- data/test/multiple_db_test.rb +13 -1
- data/test/pk_test.rb +1 -0
- metadata +11 -5
- data/lib/active_record/vendor/mysql411.rb +0 -311
- 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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
159
|
-
|
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
|
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
|
-
|
34
|
-
|
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
|
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
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
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
|
-
|
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 =
|
203
|
-
|
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
|
-
#
|
207
|
-
def reset_pk_sequence!(table)
|
208
|
-
pk
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
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
|
-
|
219
|
-
|
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
|
284
|
-
if
|
285
|
-
|
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
|