activerecord 1.14.4 → 1.15.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.
- data/CHANGELOG +400 -1
- data/README +2 -2
- data/RUNNING_UNIT_TESTS +21 -3
- data/Rakefile +55 -10
- data/lib/active_record.rb +10 -4
- data/lib/active_record/acts/list.rb +15 -4
- data/lib/active_record/acts/nested_set.rb +11 -12
- data/lib/active_record/acts/tree.rb +13 -14
- data/lib/active_record/aggregations.rb +46 -22
- data/lib/active_record/associations.rb +213 -162
- data/lib/active_record/associations/association_collection.rb +45 -15
- data/lib/active_record/associations/association_proxy.rb +32 -13
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +18 -18
- data/lib/active_record/associations/has_many_association.rb +37 -17
- data/lib/active_record/associations/has_many_through_association.rb +120 -30
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/attribute_methods.rb +75 -0
- data/lib/active_record/base.rb +282 -203
- data/lib/active_record/calculations.rb +95 -54
- data/lib/active_record/callbacks.rb +13 -24
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +12 -1
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb.rej +21 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -4
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -9
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +121 -37
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -23
- data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -0
- data/lib/active_record/connection_adapters/db2_adapter.rb +1 -11
- data/lib/active_record/connection_adapters/firebird_adapter.rb +364 -50
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -33
- data/lib/active_record/connection_adapters/openbase_adapter.rb +4 -3
- data/lib/active_record/connection_adapters/oracle_adapter.rb +151 -127
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +125 -48
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +38 -10
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +183 -155
- data/lib/active_record/connection_adapters/sybase_adapter.rb +190 -212
- data/lib/active_record/deprecated_associations.rb +24 -10
- data/lib/active_record/deprecated_finders.rb +4 -1
- data/lib/active_record/fixtures.rb +37 -23
- data/lib/active_record/locking/optimistic.rb +106 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/migration.rb +8 -5
- data/lib/active_record/observer.rb +73 -34
- data/lib/active_record/reflection.rb +21 -7
- data/lib/active_record/schema_dumper.rb +33 -5
- data/lib/active_record/timestamp.rb +23 -34
- data/lib/active_record/transactions.rb +37 -30
- data/lib/active_record/validations.rb +46 -30
- data/lib/active_record/vendor/mysql.rb +20 -5
- data/lib/active_record/version.rb +2 -2
- data/lib/active_record/wrappings.rb +1 -2
- data/lib/active_record/xml_serialization.rb +308 -0
- data/test/aaa_create_tables_test.rb +5 -1
- data/test/abstract_unit.rb +18 -8
- data/test/{active_schema_mysql.rb → active_schema_test_mysql.rb} +2 -2
- data/test/adapter_test.rb +9 -7
- data/test/adapter_test_sqlserver.rb +81 -0
- data/test/aggregations_test.rb +29 -0
- data/test/{association_callbacks_test.rb → associations/callbacks_test.rb} +10 -8
- data/test/{associations_cascaded_eager_loading_test.rb → associations/cascaded_eager_loading_test.rb} +35 -3
- data/test/{associations_go_eager_test.rb → associations/eager_test.rb} +36 -2
- data/test/{associations_extensions_test.rb → associations/extension_test.rb} +5 -0
- data/test/{associations_join_model_test.rb → associations/join_model_test.rb} +118 -8
- data/test/associations_test.rb +339 -45
- data/test/attribute_methods_test.rb +49 -0
- data/test/base_test.rb +321 -67
- data/test/calculations_test.rb +48 -10
- data/test/callbacks_test.rb +13 -0
- data/test/connection_test_firebird.rb +8 -0
- data/test/connections/native_db2/connection.rb +18 -17
- data/test/connections/native_firebird/connection.rb +19 -17
- data/test/connections/native_frontbase/connection.rb +27 -0
- data/test/connections/native_mysql/connection.rb +18 -15
- data/test/connections/native_openbase/connection.rb +14 -15
- data/test/connections/native_oracle/connection.rb +16 -12
- data/test/connections/native_postgresql/connection.rb +16 -17
- data/test/connections/native_sqlite/connection.rb +3 -6
- data/test/connections/native_sqlite3/connection.rb +3 -6
- data/test/connections/native_sqlserver/connection.rb +16 -17
- data/test/connections/native_sqlserver_odbc/connection.rb +18 -19
- data/test/connections/native_sybase/connection.rb +16 -17
- data/test/datatype_test_postgresql.rb +52 -0
- data/test/defaults_test.rb +52 -10
- data/test/deprecated_associations_test.rb +151 -107
- data/test/deprecated_finder_test.rb +83 -66
- data/test/empty_date_time_test.rb +25 -0
- data/test/finder_test.rb +118 -11
- data/test/fixtures/accounts.yml +6 -1
- data/test/fixtures/author.rb +27 -4
- data/test/fixtures/categorizations.yml +8 -2
- data/test/fixtures/category.rb +1 -2
- data/test/fixtures/comments.yml +0 -6
- data/test/fixtures/companies.yml +6 -1
- data/test/fixtures/company.rb +23 -1
- data/test/fixtures/company_in_module.rb +8 -10
- data/test/fixtures/customer.rb +2 -2
- data/test/fixtures/customers.yml +9 -0
- data/test/fixtures/db_definitions/db2.drop.sql +1 -0
- data/test/fixtures/db_definitions/db2.sql +9 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +3 -0
- data/test/fixtures/db_definitions/firebird.sql +13 -1
- data/test/fixtures/db_definitions/frontbase.drop.sql +31 -0
- data/test/fixtures/db_definitions/frontbase.sql +262 -0
- data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase2.sql +4 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
- data/test/fixtures/db_definitions/mysql.sql +23 -14
- data/test/fixtures/db_definitions/openbase.sql +13 -1
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +29 -2
- data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
- data/test/fixtures/db_definitions/postgresql.sql +13 -3
- data/test/fixtures/db_definitions/schema.rb +29 -1
- data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +12 -3
- data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
- data/test/fixtures/db_definitions/sqlserver.sql +35 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
- data/test/fixtures/db_definitions/sybase.sql +13 -4
- data/test/fixtures/developer.rb +12 -0
- data/test/fixtures/edge.rb +5 -0
- data/test/fixtures/edges.yml +6 -0
- data/test/fixtures/funny_jokes.yml +3 -7
- data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
- data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
- data/test/fixtures/mixin.rb +15 -0
- data/test/fixtures/mixins.yml +38 -0
- data/test/fixtures/post.rb +3 -2
- data/test/fixtures/project.rb +3 -1
- data/test/fixtures/topic.rb +6 -1
- data/test/fixtures/topics.yml +4 -4
- data/test/fixtures/vertex.rb +9 -0
- data/test/fixtures/vertices.yml +4 -0
- data/test/fixtures_test.rb +45 -0
- data/test/inheritance_test.rb +67 -6
- data/test/lifecycle_test.rb +40 -19
- data/test/locking_test.rb +170 -26
- data/test/method_scoping_test.rb +2 -2
- data/test/migration_test.rb +387 -110
- data/test/migration_test_firebird.rb +124 -0
- data/test/mixin_nested_set_test.rb +14 -2
- data/test/mixin_test.rb +56 -18
- data/test/modules_test.rb +8 -2
- data/test/multiple_db_test.rb +2 -2
- data/test/pk_test.rb +1 -0
- data/test/reflection_test.rb +8 -2
- data/test/schema_authorization_test_postgresql.rb +75 -0
- data/test/schema_dumper_test.rb +40 -4
- data/test/table_name_test_sqlserver.rb +23 -0
- data/test/threaded_connections_test.rb +19 -16
- data/test/transactions_test.rb +86 -72
- data/test/validations_test.rb +126 -56
- data/test/xml_serialization_test.rb +125 -0
- metadata +45 -11
- data/lib/active_record/locking.rb +0 -79
@@ -1,15 +1,53 @@
|
|
1
1
|
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module MysqlCompat #:nodoc:
|
5
|
+
# add all_hashes method to standard mysql-c bindings or pure ruby version
|
6
|
+
def self.define_all_hashes_method!
|
7
|
+
raise 'Mysql not loaded' unless defined?(::Mysql)
|
8
|
+
|
9
|
+
target = defined?(Mysql::Result) ? Mysql::Result : MysqlRes
|
10
|
+
return if target.instance_methods.include?('all_hashes')
|
11
|
+
|
12
|
+
# Ruby driver has a version string and returns null values in each_hash
|
13
|
+
# C driver >= 2.7 returns null values in each_hash
|
14
|
+
if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700)
|
15
|
+
target.class_eval <<-'end_eval'
|
16
|
+
def all_hashes
|
17
|
+
rows = []
|
18
|
+
each_hash { |row| rows << row }
|
19
|
+
rows
|
20
|
+
end
|
21
|
+
end_eval
|
22
|
+
|
23
|
+
# adapters before 2.7 don't have a version constant
|
24
|
+
# and don't return null values in each_hash
|
25
|
+
else
|
26
|
+
target.class_eval <<-'end_eval'
|
27
|
+
def all_hashes
|
28
|
+
rows = []
|
29
|
+
all_fields = fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
|
30
|
+
each_hash { |row| rows << all_fields.dup.update(row) }
|
31
|
+
rows
|
32
|
+
end
|
33
|
+
end_eval
|
34
|
+
end
|
35
|
+
|
36
|
+
unless target.instance_methods.include?('all_hashes')
|
37
|
+
raise "Failed to defined #{target.name}#all_hashes method. Mysql::VERSION = #{Mysql::VERSION.inspect}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
2
41
|
|
3
42
|
module ActiveRecord
|
4
43
|
class Base
|
5
|
-
|
6
|
-
|
7
|
-
# Only include the MySQL driver if one hasn't already been loaded
|
44
|
+
def self.require_mysql
|
45
|
+
# Include the MySQL driver if one hasn't already been loaded
|
8
46
|
unless defined? Mysql
|
9
47
|
begin
|
10
48
|
require_library_or_gem 'mysql'
|
11
49
|
rescue LoadError => cannot_require_mysql
|
12
|
-
#
|
50
|
+
# Use the bundled Ruby/MySQL driver if no driver is already in place
|
13
51
|
begin
|
14
52
|
require 'active_record/vendor/mysql'
|
15
53
|
rescue LoadError
|
@@ -18,6 +56,12 @@ module ActiveRecord
|
|
18
56
|
end
|
19
57
|
end
|
20
58
|
|
59
|
+
# Define Mysql::Result.all_hashes
|
60
|
+
MysqlCompat.define_all_hashes_method!
|
61
|
+
end
|
62
|
+
|
63
|
+
# Establishes a connection to the database that's used by all Active Record objects.
|
64
|
+
def self.mysql_connection(config) # :nodoc:
|
21
65
|
config = config.symbolize_keys
|
22
66
|
host = config[:host]
|
23
67
|
port = config[:port]
|
@@ -31,20 +75,41 @@ module ActiveRecord
|
|
31
75
|
raise ArgumentError, "No database specified. Missing argument: database."
|
32
76
|
end
|
33
77
|
|
78
|
+
require_mysql
|
34
79
|
mysql = Mysql.init
|
35
80
|
mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
|
81
|
+
|
36
82
|
ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
|
37
83
|
end
|
38
84
|
end
|
39
85
|
|
40
86
|
module ConnectionAdapters
|
41
87
|
class MysqlColumn < Column #:nodoc:
|
88
|
+
TYPES_ALLOWING_EMPTY_STRING_DEFAULT = Set.new([:binary, :string, :text])
|
89
|
+
|
90
|
+
def initialize(name, default, sql_type = nil, null = true)
|
91
|
+
@original_default = default
|
92
|
+
super
|
93
|
+
@default = nil if missing_default_forged_as_empty_string?
|
94
|
+
end
|
95
|
+
|
42
96
|
private
|
43
97
|
def simplified_type(field_type)
|
44
98
|
return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
|
45
99
|
return :string if field_type =~ /enum/i
|
46
100
|
super
|
47
101
|
end
|
102
|
+
|
103
|
+
# MySQL misreports NOT NULL column default when none is given.
|
104
|
+
# We can't detect this for columns which may have a legitimate ''
|
105
|
+
# default (string, text, binary) but we can for others (integer,
|
106
|
+
# datetime, boolean, and the rest).
|
107
|
+
#
|
108
|
+
# Test whether the column has default '', is not null, and is not
|
109
|
+
# a type allowing default ''.
|
110
|
+
def missing_default_forged_as_empty_string?
|
111
|
+
!null && @original_default == '' && !TYPES_ALLOWING_EMPTY_STRING_DEFAULT.include?(type)
|
112
|
+
end
|
48
113
|
end
|
49
114
|
|
50
115
|
# The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
|
@@ -83,7 +148,7 @@ module ActiveRecord
|
|
83
148
|
def initialize(connection, logger, connection_options, config)
|
84
149
|
super(connection, logger)
|
85
150
|
@connection_options, @config = connection_options, config
|
86
|
-
|
151
|
+
|
87
152
|
connect
|
88
153
|
end
|
89
154
|
|
@@ -95,13 +160,14 @@ module ActiveRecord
|
|
95
160
|
true
|
96
161
|
end
|
97
162
|
|
98
|
-
def native_database_types #:nodoc
|
163
|
+
def native_database_types #:nodoc:
|
99
164
|
{
|
100
165
|
:primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
|
101
166
|
:string => { :name => "varchar", :limit => 255 },
|
102
167
|
:text => { :name => "text" },
|
103
168
|
:integer => { :name => "int", :limit => 11 },
|
104
169
|
:float => { :name => "float" },
|
170
|
+
:decimal => { :name => "decimal" },
|
105
171
|
:datetime => { :name => "datetime" },
|
106
172
|
:timestamp => { :name => "datetime" },
|
107
173
|
:time => { :name => "time" },
|
@@ -118,6 +184,8 @@ module ActiveRecord
|
|
118
184
|
if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
119
185
|
s = column.class.string_to_binary(value).unpack("H*")[0]
|
120
186
|
"x'#{s}'"
|
187
|
+
elsif value.kind_of?(BigDecimal)
|
188
|
+
"'#{value.to_s("F")}'"
|
121
189
|
else
|
122
190
|
super
|
123
191
|
end
|
@@ -171,16 +239,7 @@ module ActiveRecord
|
|
171
239
|
|
172
240
|
# DATABASE STATEMENTS ======================================
|
173
241
|
|
174
|
-
def
|
175
|
-
select(sql, name)
|
176
|
-
end
|
177
|
-
|
178
|
-
def select_one(sql, name = nil) #:nodoc:
|
179
|
-
result = select(sql, name)
|
180
|
-
result.nil? ? nil : result.first
|
181
|
-
end
|
182
|
-
|
183
|
-
def execute(sql, name = nil, retries = 2) #:nodoc:
|
242
|
+
def execute(sql, name = nil) #:nodoc:
|
184
243
|
log(sql, name) { @connection.query(sql) }
|
185
244
|
rescue ActiveRecord::StatementInvalid => exception
|
186
245
|
if exception.message.split(":").first =~ /Packets out of order/
|
@@ -200,9 +259,6 @@ module ActiveRecord
|
|
200
259
|
@connection.affected_rows
|
201
260
|
end
|
202
261
|
|
203
|
-
alias_method :delete, :update #:nodoc:
|
204
|
-
|
205
|
-
|
206
262
|
def begin_db_transaction #:nodoc:
|
207
263
|
execute "BEGIN"
|
208
264
|
rescue Exception
|
@@ -222,7 +278,7 @@ module ActiveRecord
|
|
222
278
|
end
|
223
279
|
|
224
280
|
|
225
|
-
def add_limit_offset!(sql, options) #:nodoc
|
281
|
+
def add_limit_offset!(sql, options) #:nodoc:
|
226
282
|
if limit = options[:limit]
|
227
283
|
unless offset = options[:offset]
|
228
284
|
sql << " LIMIT #{limit}"
|
@@ -304,13 +360,15 @@ module ActiveRecord
|
|
304
360
|
def change_column_default(table_name, column_name, default) #:nodoc:
|
305
361
|
current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
|
306
362
|
|
307
|
-
|
363
|
+
execute("ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{current_type} DEFAULT #{quote(default)}")
|
308
364
|
end
|
309
365
|
|
310
366
|
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
311
|
-
|
312
|
-
|
313
|
-
|
367
|
+
unless options_include_default?(options)
|
368
|
+
options[:default] = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Default"]
|
369
|
+
end
|
370
|
+
|
371
|
+
change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
314
372
|
add_column_options!(change_column_sql, options)
|
315
373
|
execute(change_column_sql)
|
316
374
|
end
|
@@ -327,6 +385,7 @@ module ActiveRecord
|
|
327
385
|
if encoding
|
328
386
|
@connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
|
329
387
|
end
|
388
|
+
@connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
|
330
389
|
@connection.real_connect(*@connection_options)
|
331
390
|
execute("SET NAMES '#{encoding}'") if encoding
|
332
391
|
end
|
@@ -334,21 +393,15 @@ module ActiveRecord
|
|
334
393
|
def select(sql, name = nil)
|
335
394
|
@connection.query_with_result = true
|
336
395
|
result = execute(sql, name)
|
337
|
-
rows =
|
338
|
-
if @null_values_in_each_hash
|
339
|
-
result.each_hash { |row| rows << row }
|
340
|
-
else
|
341
|
-
all_fields = result.fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
|
342
|
-
result.each_hash { |row| rows << all_fields.dup.update(row) }
|
343
|
-
end
|
396
|
+
rows = result.all_hashes
|
344
397
|
result.free
|
345
398
|
rows
|
346
399
|
end
|
347
|
-
|
400
|
+
|
348
401
|
def supports_views?
|
349
402
|
version[0] >= 5
|
350
403
|
end
|
351
|
-
|
404
|
+
|
352
405
|
def version
|
353
406
|
@version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
|
354
407
|
end
|
@@ -32,7 +32,7 @@ module ActiveRecord
|
|
32
32
|
private
|
33
33
|
def simplified_type(field_type)
|
34
34
|
return :integer if field_type.downcase =~ /long/
|
35
|
-
return :
|
35
|
+
return :decimal if field_type.downcase == "money"
|
36
36
|
return :binary if field_type.downcase == "object"
|
37
37
|
super
|
38
38
|
end
|
@@ -55,7 +55,7 @@ module ActiveRecord
|
|
55
55
|
#
|
56
56
|
# Caveat: Operations involving LIMIT and OFFSET do not yet work!
|
57
57
|
#
|
58
|
-
# Maintainer:
|
58
|
+
# Maintainer: derrick.spell@gmail.com
|
59
59
|
class OpenBaseAdapter < AbstractAdapter
|
60
60
|
def adapter_name
|
61
61
|
'OpenBase'
|
@@ -68,6 +68,7 @@ module ActiveRecord
|
|
68
68
|
:text => { :name => "text" },
|
69
69
|
:integer => { :name => "integer" },
|
70
70
|
:float => { :name => "float" },
|
71
|
+
:decimal => { :name => "decimal" },
|
71
72
|
:datetime => { :name => "datetime" },
|
72
73
|
:timestamp => { :name => "timestamp" },
|
73
74
|
:time => { :name => "time" },
|
@@ -121,7 +122,7 @@ module ActiveRecord
|
|
121
122
|
|
122
123
|
# DATABASE STATEMENTS ======================================
|
123
124
|
|
124
|
-
def add_limit_offset!(sql, options) #:nodoc
|
125
|
+
def add_limit_offset!(sql, options) #:nodoc:
|
125
126
|
if limit = options[:limit]
|
126
127
|
unless offset = options[:offset]
|
127
128
|
sql << " RETURN RESULTS #{limit}"
|
@@ -41,28 +41,17 @@ begin
|
|
41
41
|
self.oracle_connection(config)
|
42
42
|
end
|
43
43
|
|
44
|
-
# Enable the id column to be bound into the sql later, by the adapter's insert method.
|
45
|
-
# This is preferable to inserting the hard-coded value here, because the insert method
|
46
|
-
# needs to know the id value explicitly.
|
47
|
-
alias :attributes_with_quotes_pre_oracle :attributes_with_quotes
|
48
|
-
def attributes_with_quotes(include_primary_key = true) #:nodoc:
|
49
|
-
aq = attributes_with_quotes_pre_oracle(include_primary_key)
|
50
|
-
if connection.class == ConnectionAdapters::OracleAdapter
|
51
|
-
aq[self.class.primary_key] = ":id" if include_primary_key && aq[self.class.primary_key].nil?
|
52
|
-
end
|
53
|
-
aq
|
54
|
-
end
|
55
|
-
|
56
44
|
# After setting large objects to empty, select the OCI8::LOB
|
57
45
|
# and write back the data.
|
58
|
-
after_save :write_lobs
|
46
|
+
after_save :write_lobs
|
59
47
|
def write_lobs() #:nodoc:
|
60
48
|
if connection.is_a?(ConnectionAdapters::OracleAdapter)
|
61
|
-
self.class.columns.select { |c| c.
|
49
|
+
self.class.columns.select { |c| c.sql_type =~ /LOB$/i }.each { |c|
|
62
50
|
value = self[c.name]
|
51
|
+
value = value.to_yaml if unserializable_attribute?(c.name, c)
|
63
52
|
next if value.nil? || (value == '')
|
64
53
|
lob = connection.select_one(
|
65
|
-
"SELECT #{
|
54
|
+
"SELECT #{c.name} FROM #{self.class.table_name} WHERE #{self.class.primary_key} = #{quote_value(id)}",
|
66
55
|
'Writable Large Object')[c.name]
|
67
56
|
lob.write value
|
68
57
|
}
|
@@ -75,57 +64,21 @@ begin
|
|
75
64
|
|
76
65
|
module ConnectionAdapters #:nodoc:
|
77
66
|
class OracleColumn < Column #:nodoc:
|
78
|
-
attr_reader :sql_type
|
79
|
-
|
80
|
-
# overridden to add the concept of scale, required to differentiate
|
81
|
-
# between integer and float fields
|
82
|
-
def initialize(name, default, sql_type, limit, scale, null)
|
83
|
-
@name, @limit, @sql_type, @scale, @null = name, limit, sql_type, scale, null
|
84
|
-
|
85
|
-
@type = simplified_type(sql_type)
|
86
|
-
@default = type_cast(default)
|
87
|
-
|
88
|
-
@primary = nil
|
89
|
-
@text = [:string, :text].include? @type
|
90
|
-
@number = [:float, :integer].include? @type
|
91
|
-
end
|
92
67
|
|
93
68
|
def type_cast(value)
|
94
|
-
return
|
95
|
-
|
96
|
-
when :string then value
|
97
|
-
when :integer then defined?(value.to_i) ? value.to_i : (value ? 1 : 0)
|
98
|
-
when :float then value.to_f
|
99
|
-
when :datetime then cast_to_date_or_time(value)
|
100
|
-
when :time then cast_to_time(value)
|
101
|
-
else value
|
102
|
-
end
|
69
|
+
return guess_date_or_time(value) if type == :datetime && OracleAdapter.emulate_dates
|
70
|
+
super
|
103
71
|
end
|
104
72
|
|
105
73
|
private
|
106
74
|
def simplified_type(field_type)
|
75
|
+
return :boolean if OracleAdapter.emulate_booleans && field_type == 'NUMBER(1)'
|
107
76
|
case field_type
|
108
|
-
|
109
|
-
|
110
|
-
when /date|time/i : @name =~ /_at$/ ? :time : :datetime
|
111
|
-
when /clob/i : :text
|
112
|
-
when /blob/i : :binary
|
77
|
+
when /date|time/i then :datetime
|
78
|
+
else super
|
113
79
|
end
|
114
80
|
end
|
115
81
|
|
116
|
-
def cast_to_date_or_time(value)
|
117
|
-
return value if value.is_a? Date
|
118
|
-
return nil if value.blank?
|
119
|
-
guess_date_or_time (value.is_a? Time) ? value : cast_to_time(value)
|
120
|
-
end
|
121
|
-
|
122
|
-
def cast_to_time(value)
|
123
|
-
return value if value.is_a? Time
|
124
|
-
time_array = ParseDate.parsedate value
|
125
|
-
time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1;
|
126
|
-
Time.send(Base.default_timezone, *time_array) rescue nil
|
127
|
-
end
|
128
|
-
|
129
82
|
def guess_date_or_time(value)
|
130
83
|
(value.hour == 0 and value.min == 0 and value.sec == 0) ?
|
131
84
|
Date.new(value.year, value.month, value.day) : value
|
@@ -167,6 +120,12 @@ begin
|
|
167
120
|
# * <tt>:database</tt>
|
168
121
|
class OracleAdapter < AbstractAdapter
|
169
122
|
|
123
|
+
@@emulate_booleans = true
|
124
|
+
cattr_accessor :emulate_booleans
|
125
|
+
|
126
|
+
@@emulate_dates = false
|
127
|
+
cattr_accessor :emulate_dates
|
128
|
+
|
170
129
|
def adapter_name #:nodoc:
|
171
130
|
'Oracle'
|
172
131
|
end
|
@@ -174,14 +133,15 @@ begin
|
|
174
133
|
def supports_migrations? #:nodoc:
|
175
134
|
true
|
176
135
|
end
|
177
|
-
|
178
|
-
def native_database_types #:nodoc
|
136
|
+
|
137
|
+
def native_database_types #:nodoc:
|
179
138
|
{
|
180
139
|
:primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
|
181
140
|
:string => { :name => "VARCHAR2", :limit => 255 },
|
182
141
|
:text => { :name => "CLOB" },
|
183
142
|
:integer => { :name => "NUMBER", :limit => 38 },
|
184
143
|
:float => { :name => "NUMBER" },
|
144
|
+
:decimal => { :name => "DECIMAL" },
|
185
145
|
:datetime => { :name => "DATE" },
|
186
146
|
:timestamp => { :name => "DATE" },
|
187
147
|
:time => { :name => "DATE" },
|
@@ -205,26 +165,26 @@ begin
|
|
205
165
|
name =~ /[A-Z]/ ? "\"#{name}\"" : name
|
206
166
|
end
|
207
167
|
|
208
|
-
def quote_string(
|
209
|
-
|
168
|
+
def quote_string(s) #:nodoc:
|
169
|
+
s.gsub(/'/, "''")
|
210
170
|
end
|
211
171
|
|
212
172
|
def quote(value, column = nil) #:nodoc:
|
213
|
-
if column && column.type
|
214
|
-
%Q{empty_#{ column.sql_type rescue 'blob' }()}
|
173
|
+
if column && [:text, :binary].include?(column.type)
|
174
|
+
%Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
|
215
175
|
else
|
216
|
-
|
217
|
-
when String : %Q{'#{quote_string(value)}'}
|
218
|
-
when NilClass : 'null'
|
219
|
-
when TrueClass : '1'
|
220
|
-
when FalseClass : '0'
|
221
|
-
when Numeric : value.to_s
|
222
|
-
when Date, Time : %Q{'#{value.strftime("%Y-%m-%d %H:%M:%S")}'}
|
223
|
-
else %Q{'#{quote_string(value.to_yaml)}'}
|
224
|
-
end
|
176
|
+
super
|
225
177
|
end
|
226
178
|
end
|
227
179
|
|
180
|
+
def quoted_true
|
181
|
+
"1"
|
182
|
+
end
|
183
|
+
|
184
|
+
def quoted_false
|
185
|
+
"0"
|
186
|
+
end
|
187
|
+
|
228
188
|
|
229
189
|
# CONNECTION MANAGEMENT ====================================
|
230
190
|
#
|
@@ -232,7 +192,7 @@ begin
|
|
232
192
|
# Returns true if the connection is active.
|
233
193
|
def active?
|
234
194
|
# Pings the connection to check if it's still good. Note that an
|
235
|
-
# #active? method is also available, but that simply returns the
|
195
|
+
# #active? method is also available, but that simply returns the
|
236
196
|
# last known state, which isn't good enough if the connection has
|
237
197
|
# gone stale since the last use.
|
238
198
|
@connection.ping
|
@@ -258,35 +218,24 @@ begin
|
|
258
218
|
#
|
259
219
|
# see: abstract/database_statements.rb
|
260
220
|
|
261
|
-
def select_all(sql, name = nil) #:nodoc:
|
262
|
-
select(sql, name)
|
263
|
-
end
|
264
|
-
|
265
|
-
def select_one(sql, name = nil) #:nodoc:
|
266
|
-
result = select_all(sql, name)
|
267
|
-
result.size > 0 ? result.first : nil
|
268
|
-
end
|
269
|
-
|
270
221
|
def execute(sql, name = nil) #:nodoc:
|
271
222
|
log(sql, name) { @connection.exec sql }
|
272
223
|
end
|
273
224
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
end
|
225
|
+
# Returns the next sequence value from a sequence generator. Not generally
|
226
|
+
# called directly; used by ActiveRecord to get the next primary key value
|
227
|
+
# when inserting a new database record (see #prefetch_primary_key?).
|
228
|
+
def next_sequence_value(sequence_name)
|
229
|
+
id = 0
|
230
|
+
@connection.exec("select #{sequence_name}.nextval id from dual") { |r| id = r[0].to_i }
|
231
|
+
id
|
232
|
+
end
|
283
233
|
|
234
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
235
|
+
execute(sql, name)
|
284
236
|
id_value
|
285
237
|
end
|
286
238
|
|
287
|
-
alias :update :execute #:nodoc:
|
288
|
-
alias :delete :execute #:nodoc:
|
289
|
-
|
290
239
|
def begin_db_transaction #:nodoc:
|
291
240
|
@connection.autocommit = false
|
292
241
|
end
|
@@ -313,6 +262,12 @@ begin
|
|
313
262
|
end
|
314
263
|
end
|
315
264
|
|
265
|
+
# Returns true for Oracle adapter (since Oracle requires primary key
|
266
|
+
# values to be pre-fetched before insert). See also #next_sequence_value.
|
267
|
+
def prefetch_primary_key?(table_name = nil)
|
268
|
+
true
|
269
|
+
end
|
270
|
+
|
316
271
|
def default_sequence_name(table, column) #:nodoc:
|
317
272
|
"#{table}_seq"
|
318
273
|
end
|
@@ -338,7 +293,7 @@ begin
|
|
338
293
|
FROM user_indexes i, user_ind_columns c
|
339
294
|
WHERE i.table_name = '#{table_name.to_s.upcase}'
|
340
295
|
AND c.index_name = i.index_name
|
341
|
-
AND i.index_name NOT IN (SELECT index_name FROM user_constraints WHERE constraint_type = 'P')
|
296
|
+
AND i.index_name NOT IN (SELECT uc.index_name FROM user_constraints uc WHERE uc.constraint_type = 'P')
|
342
297
|
ORDER BY i.index_name, c.column_position
|
343
298
|
SQL
|
344
299
|
|
@@ -360,47 +315,54 @@ begin
|
|
360
315
|
def columns(table_name, name = nil) #:nodoc:
|
361
316
|
(owner, table_name) = @connection.describe(table_name)
|
362
317
|
|
363
|
-
table_cols =
|
364
|
-
select column_name, data_type, data_default, nullable,
|
318
|
+
table_cols = <<-SQL
|
319
|
+
select column_name as name, data_type as sql_type, data_default, nullable,
|
365
320
|
decode(data_type, 'NUMBER', data_precision,
|
321
|
+
'FLOAT', data_precision,
|
366
322
|
'VARCHAR2', data_length,
|
367
|
-
null) as
|
323
|
+
null) as limit,
|
368
324
|
decode(data_type, 'NUMBER', data_scale, null) as scale
|
369
325
|
from all_tab_columns
|
370
326
|
where owner = '#{owner}'
|
371
327
|
and table_name = '#{table_name}'
|
372
328
|
order by column_id
|
373
|
-
|
329
|
+
SQL
|
374
330
|
|
375
331
|
select_all(table_cols, name).map do |row|
|
332
|
+
limit, scale = row['limit'], row['scale']
|
333
|
+
if limit || scale
|
334
|
+
row['sql_type'] << "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
|
335
|
+
end
|
336
|
+
|
337
|
+
# clean up odd default spacing from Oracle
|
376
338
|
if row['data_default']
|
377
339
|
row['data_default'].sub!(/^(.*?)\s*$/, '\1')
|
378
340
|
row['data_default'].sub!(/^'(.*)'$/, '\1')
|
341
|
+
row['data_default'] = nil if row['data_default'] =~ /^null$/i
|
379
342
|
end
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
(s = row['scale']).nil? ? nil : s.to_i,
|
386
|
-
row['nullable'] == 'Y'
|
387
|
-
)
|
343
|
+
|
344
|
+
OracleColumn.new(oracle_downcase(row['name']),
|
345
|
+
row['data_default'],
|
346
|
+
row['sql_type'],
|
347
|
+
row['nullable'] == 'Y')
|
388
348
|
end
|
389
349
|
end
|
390
350
|
|
391
351
|
def create_table(name, options = {}) #:nodoc:
|
392
352
|
super(name, options)
|
393
|
-
|
353
|
+
seq_name = options[:sequence_name] || "#{name}_seq"
|
354
|
+
execute "CREATE SEQUENCE #{seq_name} START WITH 10000" unless options[:id] == false
|
394
355
|
end
|
395
356
|
|
396
357
|
def rename_table(name, new_name) #:nodoc:
|
397
358
|
execute "RENAME #{name} TO #{new_name}"
|
398
359
|
execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
|
399
|
-
end
|
360
|
+
end
|
400
361
|
|
401
|
-
def drop_table(name) #:nodoc:
|
362
|
+
def drop_table(name, options = {}) #:nodoc:
|
402
363
|
super(name)
|
403
|
-
|
364
|
+
seq_name = options[:sequence_name] || "#{name}_seq"
|
365
|
+
execute "DROP SEQUENCE #{seq_name}" rescue nil
|
404
366
|
end
|
405
367
|
|
406
368
|
def remove_index(table_name, options = {}) #:nodoc:
|
@@ -412,7 +374,7 @@ begin
|
|
412
374
|
end
|
413
375
|
|
414
376
|
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
415
|
-
change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"
|
377
|
+
change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
416
378
|
add_column_options!(change_column_sql, options)
|
417
379
|
execute(change_column_sql)
|
418
380
|
end
|
@@ -425,26 +387,45 @@ begin
|
|
425
387
|
execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
|
426
388
|
end
|
427
389
|
|
390
|
+
# Find a table's primary key and sequence.
|
391
|
+
# *Note*: Only primary key is implemented - sequence will be nil.
|
392
|
+
def pk_and_sequence_for(table_name)
|
393
|
+
(owner, table_name) = @connection.describe(table_name)
|
394
|
+
|
395
|
+
pks = select_values(<<-SQL, 'Primary Key')
|
396
|
+
select cc.column_name
|
397
|
+
from all_constraints c, all_cons_columns cc
|
398
|
+
where c.owner = '#{owner}'
|
399
|
+
and c.table_name = '#{table_name}'
|
400
|
+
and c.constraint_type = 'P'
|
401
|
+
and cc.owner = c.owner
|
402
|
+
and cc.constraint_name = c.constraint_name
|
403
|
+
SQL
|
404
|
+
|
405
|
+
# only support single column keys
|
406
|
+
pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
|
407
|
+
end
|
408
|
+
|
428
409
|
def structure_dump #:nodoc:
|
429
410
|
s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
|
430
411
|
structure << "create sequence #{seq.to_a.first.last};\n\n"
|
431
412
|
end
|
432
413
|
|
433
414
|
select_all("select table_name from user_tables").inject(s) do |structure, table|
|
434
|
-
ddl = "create table #{table.to_a.first.last} (\n "
|
415
|
+
ddl = "create table #{table.to_a.first.last} (\n "
|
435
416
|
cols = select_all(%Q{
|
436
417
|
select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
|
437
418
|
from user_tab_columns
|
438
419
|
where table_name = '#{table.to_a.first.last}'
|
439
420
|
order by column_id
|
440
|
-
}).map do |row|
|
441
|
-
col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
|
421
|
+
}).map do |row|
|
422
|
+
col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
|
442
423
|
if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
|
443
424
|
col << "(#{row['data_precision'].to_i}"
|
444
425
|
col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
|
445
426
|
col << ')'
|
446
427
|
elsif row['data_type'].include?('CHAR')
|
447
|
-
col << "(#{row['data_length'].to_i})"
|
428
|
+
col << "(#{row['data_length'].to_i})"
|
448
429
|
end
|
449
430
|
col << " default #{row['data_default']}" if !row['data_default'].nil?
|
450
431
|
col << ' not null' if row['nullable'] == 'N'
|
@@ -466,6 +447,41 @@ begin
|
|
466
447
|
end
|
467
448
|
end
|
468
449
|
|
450
|
+
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
|
451
|
+
#
|
452
|
+
# Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
|
453
|
+
# queries. However, with those columns included in the SELECT DISTINCT list, you
|
454
|
+
# won't actually get a distinct list of the column you want (presuming the column
|
455
|
+
# has duplicates with multiple values for the ordered-by columns. So we use the
|
456
|
+
# FIRST_VALUE function to get a single (first) value for each column, effectively
|
457
|
+
# making every row the same.
|
458
|
+
#
|
459
|
+
# distinct("posts.id", "posts.created_at desc")
|
460
|
+
def distinct(columns, order_by)
|
461
|
+
return "DISTINCT #{columns}" if order_by.blank?
|
462
|
+
|
463
|
+
# construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
|
464
|
+
# FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
|
465
|
+
order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
|
466
|
+
order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
|
467
|
+
"FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
|
468
|
+
end
|
469
|
+
sql = "DISTINCT #{columns}, "
|
470
|
+
sql << order_columns * ", "
|
471
|
+
end
|
472
|
+
|
473
|
+
# ORDER BY clause for the passed order option.
|
474
|
+
#
|
475
|
+
# Uses column aliases as defined by #distinct.
|
476
|
+
def add_order_by_for_association_limiting!(sql, options)
|
477
|
+
return sql if options[:order].blank?
|
478
|
+
|
479
|
+
order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
|
480
|
+
order.map! {|s| $1 if s =~ / (.*)/}
|
481
|
+
order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
|
482
|
+
|
483
|
+
sql << "ORDER BY #{order}"
|
484
|
+
end
|
469
485
|
|
470
486
|
private
|
471
487
|
|
@@ -542,7 +558,7 @@ begin
|
|
542
558
|
def describe(name)
|
543
559
|
@desc ||= @@env.alloc(OCIDescribe)
|
544
560
|
@desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14'
|
545
|
-
@desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK)
|
561
|
+
@desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK) rescue raise %Q{"DESC #{name}" failed; does it exist?}
|
546
562
|
info = @desc.attrGet(OCI_ATTR_PARAM)
|
547
563
|
|
548
564
|
case info.attrGet(OCI_ATTR_PTYPE)
|
@@ -554,6 +570,7 @@ begin
|
|
554
570
|
schema = info.attrGet(OCI_ATTR_SCHEMA_NAME)
|
555
571
|
name = info.attrGet(OCI_ATTR_NAME)
|
556
572
|
describe(schema + '.' + name)
|
573
|
+
else raise %Q{"DESC #{name}" failed; not a table or view.}
|
557
574
|
end
|
558
575
|
end
|
559
576
|
|
@@ -563,11 +580,14 @@ begin
|
|
563
580
|
# The OracleConnectionFactory factors out the code necessary to connect and
|
564
581
|
# configure an Oracle/OCI connection.
|
565
582
|
class OracleConnectionFactory #:nodoc:
|
566
|
-
def new_connection(username, password, database)
|
583
|
+
def new_connection(username, password, database, async, prefetch_rows, cursor_sharing)
|
567
584
|
conn = OCI8.new username, password, database
|
568
585
|
conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
|
569
586
|
conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil
|
570
587
|
conn.autocommit = true
|
588
|
+
conn.non_blocking = true if async
|
589
|
+
conn.prefetch_rows = prefetch_rows
|
590
|
+
conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil
|
571
591
|
conn
|
572
592
|
end
|
573
593
|
end
|
@@ -575,10 +595,10 @@ begin
|
|
575
595
|
|
576
596
|
# The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
|
577
597
|
# reset functionality. If a call to #exec fails, and autocommit is turned on
|
578
|
-
# (ie., we're not in the middle of a longer transaction), it will
|
598
|
+
# (ie., we're not in the middle of a longer transaction), it will
|
579
599
|
# automatically reconnect and try again. If autocommit is turned off,
|
580
600
|
# this would be dangerous (as the earlier part of the implied transaction
|
581
|
-
# may have failed silently if the connection died) -- so instead the
|
601
|
+
# may have failed silently if the connection died) -- so instead the
|
582
602
|
# connection is marked as dead, to be reconnected on it's next use.
|
583
603
|
class OCI8AutoRecover < DelegateClass(OCI8) #:nodoc:
|
584
604
|
attr_accessor :active
|
@@ -592,9 +612,12 @@ begin
|
|
592
612
|
|
593
613
|
def initialize(config, factory = OracleConnectionFactory.new)
|
594
614
|
@active = true
|
595
|
-
@username, @password, @database = config[:username], config[:password], config[:database]
|
615
|
+
@username, @password, @database, = config[:username], config[:password], config[:database]
|
616
|
+
@async = config[:allow_concurrency]
|
617
|
+
@prefetch_rows = config[:prefetch_rows] || 100
|
618
|
+
@cursor_sharing = config[:cursor_sharing] || 'similar'
|
596
619
|
@factory = factory
|
597
|
-
@connection = @factory.new_connection @username, @password, @database
|
620
|
+
@connection = @factory.new_connection @username, @password, @database, @async, @prefetch_rows, @cursor_sharing
|
598
621
|
super @connection
|
599
622
|
end
|
600
623
|
|
@@ -613,7 +636,7 @@ begin
|
|
613
636
|
def reset!
|
614
637
|
logoff rescue nil
|
615
638
|
begin
|
616
|
-
@connection = @factory.new_connection @username, @password, @database
|
639
|
+
@connection = @factory.new_connection @username, @password, @database, @async, @prefetch_rows, @cursor_sharing
|
617
640
|
__setobj__ @connection
|
618
641
|
@active = true
|
619
642
|
rescue
|
@@ -623,7 +646,7 @@ begin
|
|
623
646
|
end
|
624
647
|
|
625
648
|
# ORA-00028: your session has been killed
|
626
|
-
# ORA-01012: not logged on
|
649
|
+
# ORA-01012: not logged on
|
627
650
|
# ORA-03113: end-of-file on communication channel
|
628
651
|
# ORA-03114: not connected to ORACLE
|
629
652
|
LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ]
|
@@ -631,11 +654,11 @@ begin
|
|
631
654
|
# Adds auto-recovery functionality.
|
632
655
|
#
|
633
656
|
# See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
|
634
|
-
def exec(sql, *bindvars)
|
657
|
+
def exec(sql, *bindvars, &block)
|
635
658
|
should_retry = self.class.auto_retry? && autocommit?
|
636
659
|
|
637
660
|
begin
|
638
|
-
@connection.exec(sql, *bindvars)
|
661
|
+
@connection.exec(sql, *bindvars, &block)
|
639
662
|
rescue OCIException => e
|
640
663
|
raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code)
|
641
664
|
@active = false
|
@@ -652,13 +675,14 @@ rescue LoadError
|
|
652
675
|
# OCI8 driver is unavailable.
|
653
676
|
module ActiveRecord # :nodoc:
|
654
677
|
class Base
|
678
|
+
@@oracle_error_message = "Oracle/OCI libraries could not be loaded: #{$!.to_s}"
|
655
679
|
def self.oracle_connection(config) # :nodoc:
|
656
680
|
# Set up a reasonable error message
|
657
|
-
raise LoadError,
|
681
|
+
raise LoadError, @@oracle_error_message
|
658
682
|
end
|
659
683
|
def self.oci_connection(config) # :nodoc:
|
660
684
|
# Set up a reasonable error message
|
661
|
-
raise LoadError,
|
685
|
+
raise LoadError, @@oracle_error_message
|
662
686
|
end
|
663
687
|
end
|
664
688
|
end
|