activerecord 1.12.2 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +92 -0
- data/README +9 -9
- data/lib/active_record/acts/list.rb +1 -1
- data/lib/active_record/acts/nested_set.rb +13 -13
- data/lib/active_record/acts/tree.rb +7 -6
- data/lib/active_record/aggregations.rb +4 -4
- data/lib/active_record/associations.rb +82 -21
- data/lib/active_record/associations/association_collection.rb +0 -8
- data/lib/active_record/associations/association_proxy.rb +5 -2
- data/lib/active_record/associations/belongs_to_association.rb +6 -2
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +11 -1
- data/lib/active_record/associations/has_many_association.rb +34 -5
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/base.rb +144 -59
- data/lib/active_record/callbacks.rb +6 -6
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +10 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/db2_adapter.rb +17 -1
- data/lib/active_record/connection_adapters/oci_adapter.rb +322 -185
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +9 -8
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +107 -14
- data/lib/active_record/fixtures.rb +10 -8
- data/lib/active_record/migration.rb +20 -6
- data/lib/active_record/observer.rb +4 -3
- data/lib/active_record/reflection.rb +5 -5
- data/lib/active_record/transactions.rb +2 -2
- data/lib/active_record/validations.rb +70 -40
- data/lib/active_record/vendor/mysql411.rb +9 -13
- data/lib/active_record/version.rb +2 -2
- data/rakefile +1 -1
- data/test/abstract_unit.rb +5 -0
- data/test/ar_schema_test.rb +1 -1
- data/test/associations_extensions_test.rb +37 -0
- data/test/associations_go_eager_test.rb +25 -0
- data/test/associations_test.rb +14 -6
- data/test/base_test.rb +63 -45
- data/test/connections/native_sqlite/connection.rb +2 -2
- data/test/connections/native_sqlite3/connection.rb +2 -2
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
- data/test/debug.log +2857 -0
- data/test/deprecated_finder_test.rb +3 -9
- data/test/finder_test.rb +27 -13
- data/test/fixtures/author.rb +4 -0
- data/test/fixtures/comment.rb +4 -8
- data/test/fixtures/db_definitions/create_oracle_db.bat +0 -5
- data/test/fixtures/db_definitions/create_oracle_db.sh +0 -5
- data/test/fixtures/db_definitions/db2.drop.sql +0 -1
- data/test/fixtures/db_definitions/oci.sql +2 -0
- data/test/fixtures/db_definitions/sqlserver.drop.sql +1 -1
- data/test/fixtures/developer.rb +18 -1
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
- data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
- data/test/fixtures/post.rb +18 -4
- data/test/fixtures/reply.rb +3 -1
- data/test/inheritance_test.rb +3 -2
- data/test/{conditions_scoping_test.rb → method_scoping_test.rb} +55 -10
- data/test/migration_test.rb +68 -31
- data/test/readonly_test.rb +65 -5
- data/test/validations_test.rb +10 -2
- metadata +13 -4
@@ -86,7 +86,7 @@ module ActiveRecord
|
|
86
86
|
#
|
87
87
|
# There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
|
88
88
|
# inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects are the
|
89
|
-
# recommended approaches, inline methods using a proc
|
89
|
+
# recommended approaches, inline methods using a proc are sometimes appropriate (such as for creating mix-ins), and inline
|
90
90
|
# eval methods are deprecated.
|
91
91
|
#
|
92
92
|
# The method reference callbacks work by specifying a protected or private method available in the object, like this:
|
@@ -153,7 +153,7 @@ module ActiveRecord
|
|
153
153
|
#
|
154
154
|
# == The after_find and after_initialize exceptions
|
155
155
|
#
|
156
|
-
# Because after_find and after_initialize
|
156
|
+
# Because after_find and after_initialize are called for each object found and instantiated by a finder, such as Base.find(:all), we've had
|
157
157
|
# to implement a simple performance constraint (50% more speed on a simple test case). Unlike all the other callbacks, after_find and
|
158
158
|
# after_initialize will only be run if an explicit implementation is defined (<tt>def after_find</tt>). In that case, all of the
|
159
159
|
# callback types will be called.
|
@@ -263,10 +263,10 @@ module ActiveRecord
|
|
263
263
|
result
|
264
264
|
end
|
265
265
|
|
266
|
-
# Is called _before_ Base.save on existing objects that
|
266
|
+
# Is called _before_ Base.save on existing objects that have a record.
|
267
267
|
def before_update() end
|
268
268
|
|
269
|
-
# Is called _after_ Base.save on existing objects that
|
269
|
+
# Is called _after_ Base.save on existing objects that have a record.
|
270
270
|
def after_update() end
|
271
271
|
|
272
272
|
def update_with_callbacks #:nodoc:
|
@@ -291,11 +291,11 @@ module ActiveRecord
|
|
291
291
|
def after_validation_on_create() end
|
292
292
|
|
293
293
|
# Is called _before_ Validations.validate (which is part of the Base.save call) on
|
294
|
-
# existing objects that
|
294
|
+
# existing objects that have a record.
|
295
295
|
def before_validation_on_update() end
|
296
296
|
|
297
297
|
# Is called _after_ Validations.validate (which is part of the Base.save call) on
|
298
|
-
# existing objects that
|
298
|
+
# existing objects that have a record.
|
299
299
|
def after_validation_on_update() end
|
300
300
|
|
301
301
|
def valid_with_callbacks #:nodoc:
|
@@ -27,13 +27,13 @@ module ActiveRecord
|
|
27
27
|
#
|
28
28
|
# ActiveRecord::Base.establish_connection(
|
29
29
|
# :adapter => "sqlite",
|
30
|
-
# :
|
30
|
+
# :database => "path/to/dbfile"
|
31
31
|
# )
|
32
32
|
#
|
33
33
|
# Also accepts keys as strings (for parsing from yaml for example):
|
34
34
|
# ActiveRecord::Base.establish_connection(
|
35
35
|
# "adapter" => "sqlite",
|
36
|
-
# "
|
36
|
+
# "database" => "path/to/dbfile"
|
37
37
|
# )
|
38
38
|
#
|
39
39
|
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
|
@@ -109,6 +109,7 @@ module ActiveRecord
|
|
109
109
|
conn = @@defined_connections[klass]
|
110
110
|
@@defined_connections.delete(klass)
|
111
111
|
active_connections[klass] = nil
|
112
|
+
@connection = nil
|
112
113
|
conn.config if conn
|
113
114
|
end
|
114
115
|
|
@@ -156,12 +156,12 @@ module ActiveRecord
|
|
156
156
|
|
157
157
|
class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :default, :null) #:nodoc:
|
158
158
|
def to_sql
|
159
|
-
column_sql = "#{name} #{type_to_sql(type.to_sym, limit)}"
|
159
|
+
column_sql = "#{base.quote_column_name(name)} #{type_to_sql(type.to_sym, limit)}"
|
160
160
|
add_column_options!(column_sql, :null => null, :default => default)
|
161
161
|
column_sql
|
162
162
|
end
|
163
163
|
alias to_s :to_sql
|
164
|
-
|
164
|
+
|
165
165
|
private
|
166
166
|
def type_to_sql(name, limit)
|
167
167
|
base.type_to_sql(name, limit) rescue name
|
@@ -232,14 +232,14 @@ module ActiveRecord
|
|
232
232
|
@columns << column unless @columns.include? column
|
233
233
|
self
|
234
234
|
end
|
235
|
-
|
235
|
+
|
236
236
|
# Returns a String whose contents are the column definitions
|
237
237
|
# concatenated together. This string can then be pre and appended to
|
238
238
|
# to generate the final SQL to create the table.
|
239
239
|
def to_sql
|
240
240
|
@columns * ', '
|
241
241
|
end
|
242
|
-
|
242
|
+
|
243
243
|
private
|
244
244
|
def native
|
245
245
|
@base.native_database_types
|
@@ -184,23 +184,25 @@ module ActiveRecord
|
|
184
184
|
# remove_index :accounts, :column => :branch_id
|
185
185
|
# Remove the index named by_branch_party in the accounts table.
|
186
186
|
# remove_index :accounts, :name => :by_branch_party
|
187
|
+
|
187
188
|
def remove_index(table_name, options = {})
|
189
|
+
execute "DROP INDEX #{index_name(table_name, options)} ON #{table_name}"
|
190
|
+
end
|
191
|
+
|
192
|
+
def index_name(table_name, options) #:nodoc:
|
188
193
|
if Hash === options # legacy support
|
189
194
|
if options[:column]
|
190
|
-
|
195
|
+
"#{table_name}_#{options[:column]}_index"
|
191
196
|
elsif options[:name]
|
192
|
-
|
197
|
+
options[:name]
|
193
198
|
else
|
194
199
|
raise ArgumentError, "You must specify the index name"
|
195
200
|
end
|
196
201
|
else
|
197
|
-
|
202
|
+
"#{table_name}_#{options}_index"
|
198
203
|
end
|
199
|
-
|
200
|
-
execute "DROP INDEX #{index_name} ON #{table_name}"
|
201
204
|
end
|
202
205
|
|
203
|
-
|
204
206
|
# Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
|
205
207
|
# entire structure of the database.
|
206
208
|
def structure_dump
|
@@ -220,7 +222,7 @@ module ActiveRecord
|
|
220
222
|
def dump_schema_information #:nodoc:
|
221
223
|
begin
|
222
224
|
if (current_schema = ActiveRecord::Migrator.current_version) > 0
|
223
|
-
return "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES (#{current_schema})
|
225
|
+
return "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES (#{current_schema})"
|
224
226
|
end
|
225
227
|
rescue ActiveRecord::StatementInvalid
|
226
228
|
# No Schema Info
|
@@ -237,8 +239,8 @@ module ActiveRecord
|
|
237
239
|
end
|
238
240
|
|
239
241
|
def add_column_options!(sql, options) #:nodoc:
|
240
|
-
sql << " NOT NULL" if options[:null] == false
|
241
242
|
sql << " DEFAULT #{quote(options[:default], options[:column])}" unless options[:default].nil?
|
243
|
+
sql << " NOT NULL" if options[:null] == false
|
242
244
|
end
|
243
245
|
end
|
244
246
|
end
|
@@ -33,7 +33,7 @@ module ActiveRecord
|
|
33
33
|
'Abstract'
|
34
34
|
end
|
35
35
|
|
36
|
-
# Does this adapter support migrations
|
36
|
+
# Does this adapter support migrations? Backend specific, as the
|
37
37
|
# abstract adapter always returns +false+.
|
38
38
|
def supports_migrations?
|
39
39
|
false
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Author: Maik Schmidt <contact@maik-schmidt.de>
|
1
|
+
# Author/Maintainer: Maik Schmidt <contact@maik-schmidt.de>
|
2
2
|
|
3
3
|
require 'active_record/connection_adapters/abstract_adapter'
|
4
4
|
|
@@ -113,6 +113,14 @@ begin
|
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
116
|
+
def tables(name = nil)
|
117
|
+
stmt = DB2::Statement.new(@connection)
|
118
|
+
result = []
|
119
|
+
stmt.tables.each { |t| result << t[2].downcase }
|
120
|
+
stmt.free
|
121
|
+
result
|
122
|
+
end
|
123
|
+
|
116
124
|
def columns(table_name, name = nil)
|
117
125
|
stmt = DB2::Statement.new(@connection)
|
118
126
|
result = []
|
@@ -145,6 +153,14 @@ begin
|
|
145
153
|
}
|
146
154
|
end
|
147
155
|
|
156
|
+
def quoted_true
|
157
|
+
'1'
|
158
|
+
end
|
159
|
+
|
160
|
+
def quoted_false
|
161
|
+
'0'
|
162
|
+
end
|
163
|
+
|
148
164
|
private
|
149
165
|
|
150
166
|
def last_insert_id
|
@@ -1,20 +1,26 @@
|
|
1
|
+
# oci_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g
|
2
|
+
#
|
3
|
+
# Original author: Graham Jenkins
|
4
|
+
#
|
5
|
+
# Current maintainer: Michael Schoen <schoenm@earthlink.net>
|
6
|
+
#
|
7
|
+
#########################################################################
|
8
|
+
#
|
1
9
|
# Implementation notes:
|
2
|
-
# 1.
|
3
|
-
#
|
4
|
-
# 2.
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# the middle select to limit downwards as much as possible, before the outermost select
|
12
|
-
# limits upwards. The extra rownum column is stripped from the results.
|
13
|
-
# See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
|
10
|
+
# 1. Redefines (safely) a method in ActiveRecord to make it possible to
|
11
|
+
# implement an autonumbering solution for Oracle.
|
12
|
+
# 2. The OCI8 driver is patched to properly handle values for LONG and
|
13
|
+
# TIMESTAMP columns. The driver-author has indicated that a future
|
14
|
+
# release of the driver will obviate this patch.
|
15
|
+
# 3. LOB support is implemented through an after_save callback.
|
16
|
+
# 4. Oracle does not offer native LIMIT and OFFSET options; this
|
17
|
+
# functionality is mimiced through the use of nested selects.
|
18
|
+
# See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
|
14
19
|
#
|
15
|
-
# Do what you want with this code, at your own peril, but if any
|
16
|
-
# remains then please acknowledge my
|
17
|
-
#
|
20
|
+
# Do what you want with this code, at your own peril, but if any
|
21
|
+
# significant portion of my code remains then please acknowledge my
|
22
|
+
# contribution.
|
23
|
+
# portions Copyright 2005 Graham Jenkins
|
18
24
|
|
19
25
|
require 'active_record/connection_adapters/abstract_adapter'
|
20
26
|
|
@@ -22,23 +28,62 @@ begin
|
|
22
28
|
require_library_or_gem 'oci8' unless self.class.const_defined? :OCI8
|
23
29
|
|
24
30
|
module ActiveRecord
|
31
|
+
class Base
|
32
|
+
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
|
38
|
+
end
|
39
|
+
|
40
|
+
# Enable the id column to be bound into the sql later, by the adapter's insert method.
|
41
|
+
# This is preferable to inserting the hard-coded value here, because the insert method
|
42
|
+
# needs to know the id value explicitly.
|
43
|
+
alias :attributes_with_quotes_pre_oci :attributes_with_quotes #:nodoc:
|
44
|
+
def attributes_with_quotes(creating = true) #:nodoc:
|
45
|
+
aq = attributes_with_quotes_pre_oci creating
|
46
|
+
if connection.class == ConnectionAdapters::OCIAdapter
|
47
|
+
aq[self.class.primary_key] = ":id" if creating && aq[self.class.primary_key].nil?
|
48
|
+
end
|
49
|
+
aq
|
50
|
+
end
|
51
|
+
|
52
|
+
# After setting large objects to empty, select the OCI8::LOB
|
53
|
+
# and write back the data.
|
54
|
+
after_save :write_lobs
|
55
|
+
def write_lobs() #:nodoc:
|
56
|
+
if connection.is_a?(ConnectionAdapters::OCIAdapter)
|
57
|
+
self.class.columns.select { |c| c.type == :binary }.each { |c|
|
58
|
+
value = self[c.name]
|
59
|
+
next if value.nil? || (value == '')
|
60
|
+
lob = connection.select_one(
|
61
|
+
"select #{ c.name} from #{ self.class.table_name } WHERE #{ self.class.primary_key} = #{quote(id)}",
|
62
|
+
'Writable Large Object')[c.name]
|
63
|
+
lob.write value
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private :write_lobs
|
69
|
+
end
|
70
|
+
|
71
|
+
|
25
72
|
module ConnectionAdapters #:nodoc:
|
26
73
|
class OCIColumn < Column #:nodoc:
|
27
74
|
attr_reader :sql_type
|
28
75
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
@
|
33
|
-
end
|
76
|
+
# overridden to add the concept of scale, required to differentiate
|
77
|
+
# between integer and float fields
|
78
|
+
def initialize(name, default, sql_type, limit, scale, null)
|
79
|
+
@name, @limit, @sql_type, @scale, @null = name, limit, sql_type, scale, null
|
34
80
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
81
|
+
@type = simplified_type(sql_type)
|
82
|
+
@default = type_cast(default)
|
83
|
+
|
84
|
+
@primary = nil
|
85
|
+
@text = [:string, :text].include? @type
|
86
|
+
@number = [:float, :integer].include? @type
|
42
87
|
end
|
43
88
|
|
44
89
|
def type_cast(value)
|
@@ -53,6 +98,16 @@ begin
|
|
53
98
|
end
|
54
99
|
end
|
55
100
|
|
101
|
+
private
|
102
|
+
def simplified_type(field_type)
|
103
|
+
case field_type
|
104
|
+
when /char/i : :string
|
105
|
+
when /num|float|double|dec|real|int/i : @scale == 0 ? :integer : :float
|
106
|
+
when /date|time/i : @name =~ /_at$/ ? :time : :datetime
|
107
|
+
when /lob/i : :binary
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
56
111
|
def cast_to_date_or_time(value)
|
57
112
|
return value if value.is_a? Date
|
58
113
|
guess_date_or_time (value.is_a? Time) ? value : cast_to_time(value)
|
@@ -71,27 +126,30 @@ begin
|
|
71
126
|
end
|
72
127
|
end
|
73
128
|
|
74
|
-
|
75
|
-
#
|
76
|
-
# It
|
77
|
-
#
|
129
|
+
|
130
|
+
# This is an Oracle/OCI adapter for the ActiveRecord persistence
|
131
|
+
# framework. It relies upon the OCI8 driver, which works with Oracle 8i
|
132
|
+
# and above. Most recent development has been on Debian Linux against
|
133
|
+
# a 10g database, ActiveRecord 1.12.1 and OCI8 0.1.13.
|
134
|
+
# See: http://rubyforge.org/projects/ruby-oci8/
|
78
135
|
#
|
79
136
|
# Usage notes:
|
80
|
-
# * Key generation assumes a "${table_name}_seq" sequence is available
|
81
|
-
# sequence name can be changed using
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
137
|
+
# * Key generation assumes a "${table_name}_seq" sequence is available
|
138
|
+
# for all tables; the sequence name can be changed using
|
139
|
+
# ActiveRecord::Base.set_sequence_name. When using Migrations, these
|
140
|
+
# sequences are created automatically.
|
141
|
+
# * Oracle uses DATE or TIMESTAMP datatypes for both dates and times.
|
142
|
+
# Consequently some hacks are employed to map data back to Date or Time
|
143
|
+
# in Ruby. If the column_name ends in _time it's created as a Ruby Time.
|
144
|
+
# Else if the hours/minutes/seconds are 0, I make it a Ruby Date. Else
|
145
|
+
# it's a Ruby Time. This is a bit nasty - but if you use Duck Typing
|
146
|
+
# you'll probably not care very much. In 9i and up it's tempting to
|
147
|
+
# map DATE to Date and TIMESTAMP to Time, but too many databases use
|
148
|
+
# DATE for both. Timezones and sub-second precision on timestamps are
|
149
|
+
# not supported.
|
150
|
+
# * Default values that are functions (such as "SYSDATE") are not
|
151
|
+
# supported. This is a restriction of the way ActiveRecord supports
|
152
|
+
# default values.
|
95
153
|
#
|
96
154
|
# Options:
|
97
155
|
#
|
@@ -99,15 +157,47 @@ begin
|
|
99
157
|
# * <tt>:password</tt> -- Defaults to nothing
|
100
158
|
# * <tt>:host</tt> -- Defaults to localhost
|
101
159
|
class OCIAdapter < AbstractAdapter
|
102
|
-
|
103
|
-
|
160
|
+
|
161
|
+
def adapter_name #:nodoc:
|
162
|
+
'OCI'
|
163
|
+
end
|
164
|
+
|
165
|
+
def supports_migrations? #:nodoc:
|
166
|
+
true
|
167
|
+
end
|
168
|
+
|
169
|
+
def native_database_types #:nodoc
|
170
|
+
{
|
171
|
+
:primary_key => "NUMBER(38) NOT NULL",
|
172
|
+
:string => { :name => "VARCHAR2", :limit => 255 },
|
173
|
+
:text => { :name => "LONG" },
|
174
|
+
:integer => { :name => "NUMBER", :limit => 38 },
|
175
|
+
:float => { :name => "NUMBER" },
|
176
|
+
:datetime => { :name => "DATE" },
|
177
|
+
:timestamp => { :name => "DATE" },
|
178
|
+
:time => { :name => "DATE" },
|
179
|
+
:date => { :name => "DATE" },
|
180
|
+
:binary => { :name => "BLOB" },
|
181
|
+
:boolean => { :name => "NUMBER", :limit => 1 }
|
182
|
+
}
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
# QUOTING ==================================================
|
187
|
+
#
|
188
|
+
# see: abstract/quoting.rb
|
189
|
+
|
190
|
+
# camelCase column names need to be quoted; not that anyone using Oracle
|
191
|
+
# would really do this, but handling this case means we pass the test...
|
192
|
+
def quote_column_name(name) #:nodoc:
|
193
|
+
name =~ /[A-Z]/ ? "\"#{name}\"" : name
|
104
194
|
end
|
105
195
|
|
106
|
-
def quote_string(string)
|
196
|
+
def quote_string(string) #:nodoc:
|
107
197
|
string.gsub(/'/, "''")
|
108
198
|
end
|
109
199
|
|
110
|
-
def quote(value, column = nil)
|
200
|
+
def quote(value, column = nil) #:nodoc:
|
111
201
|
if column and column.type == :binary then %Q{empty_#{ column.sql_type }()}
|
112
202
|
else case value
|
113
203
|
when String then %Q{'#{quote_string(value)}'}
|
@@ -121,13 +211,170 @@ begin
|
|
121
211
|
end
|
122
212
|
end
|
123
213
|
|
124
|
-
|
125
|
-
#
|
126
|
-
|
127
|
-
|
214
|
+
|
215
|
+
# DATABASE STATEMENTS ======================================
|
216
|
+
#
|
217
|
+
# see: abstract/database_statements.rb
|
218
|
+
|
219
|
+
def select_all(sql, name = nil) #:nodoc:
|
220
|
+
select(sql, name)
|
221
|
+
end
|
222
|
+
|
223
|
+
def select_one(sql, name = nil) #:nodoc:
|
224
|
+
result = select_all(sql, name)
|
225
|
+
result.size > 0 ? result.first : nil
|
226
|
+
end
|
227
|
+
|
228
|
+
def execute(sql, name = nil) #:nodoc:
|
229
|
+
log(sql, name) { @connection.exec sql }
|
230
|
+
end
|
231
|
+
|
232
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
233
|
+
if pk.nil? # Who called us? What does the sql look like? No idea!
|
234
|
+
execute sql, name
|
235
|
+
elsif id_value # Pre-assigned id
|
236
|
+
log(sql, name) { @connection.exec sql }
|
237
|
+
else # Assume the sql contains a bind-variable for the id
|
238
|
+
id_value = select_one("select #{sequence_name}.nextval id from dual")['id']
|
239
|
+
log(sql, name) { @connection.exec sql, id_value }
|
240
|
+
end
|
241
|
+
|
242
|
+
id_value
|
243
|
+
end
|
244
|
+
|
245
|
+
alias :update :execute #:nodoc:
|
246
|
+
alias :delete :execute #:nodoc:
|
247
|
+
|
248
|
+
def begin_db_transaction #:nodoc:
|
249
|
+
@connection.autocommit = false
|
250
|
+
end
|
251
|
+
|
252
|
+
def commit_db_transaction #:nodoc:
|
253
|
+
@connection.commit
|
254
|
+
ensure
|
255
|
+
@connection.autocommit = true
|
256
|
+
end
|
257
|
+
|
258
|
+
def rollback_db_transaction #:nodoc:
|
259
|
+
@connection.rollback
|
260
|
+
ensure
|
261
|
+
@connection.autocommit = true
|
262
|
+
end
|
263
|
+
|
264
|
+
def add_limit_offset!(sql, options) #:nodoc:
|
265
|
+
offset = options[:offset] || 0
|
266
|
+
|
267
|
+
if limit = options[:limit]
|
268
|
+
sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
|
269
|
+
elsif offset > 0
|
270
|
+
sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def default_sequence_name(table, column) #:nodoc:
|
275
|
+
"#{table}_seq"
|
276
|
+
end
|
277
|
+
|
278
|
+
|
279
|
+
# SCHEMA STATEMENTS ========================================
|
280
|
+
#
|
281
|
+
# see: abstract/schema_statements.rb
|
282
|
+
|
283
|
+
def tables(name = nil) #:nodoc:
|
284
|
+
select_all("select lower(table_name) from user_tables").inject([]) do | tabs, t |
|
285
|
+
tabs << t.to_a.first.last
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def indexes(table_name, name = nil) #:nodoc:
|
290
|
+
result = select_all(<<-SQL, name)
|
291
|
+
SELECT lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name
|
292
|
+
FROM user_indexes i, user_ind_columns c
|
293
|
+
WHERE i.table_name = '#{table_name.to_s.upcase}'
|
294
|
+
AND c.index_name = i.index_name
|
295
|
+
AND i.index_name NOT IN (SELECT index_name FROM user_constraints WHERE constraint_type = 'P')
|
296
|
+
ORDER BY i.index_name, c.column_position
|
297
|
+
SQL
|
298
|
+
|
299
|
+
current_index = nil
|
300
|
+
indexes = []
|
301
|
+
|
302
|
+
result.each do |row|
|
303
|
+
if current_index != row['index_name']
|
304
|
+
indexes << IndexDefinition.new(table_name, row['index_name'], row['uniqueness'] == "UNIQUE", [])
|
305
|
+
current_index = row['index_name']
|
306
|
+
end
|
307
|
+
|
308
|
+
indexes.last.columns << row['column_name']
|
309
|
+
end
|
310
|
+
|
311
|
+
indexes
|
312
|
+
end
|
313
|
+
|
314
|
+
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']
|
329
|
+
OCIColumn.new(
|
330
|
+
oci_downcase(row['column_name']),
|
331
|
+
row['data_default'],
|
332
|
+
row['data_type'],
|
333
|
+
row['length'],
|
334
|
+
row['scale'],
|
335
|
+
row['nullable'] == 'Y'
|
336
|
+
)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def create_table(name, options = {}) #:nodoc:
|
341
|
+
super(name, options)
|
342
|
+
execute "CREATE SEQUENCE #{name}_seq"
|
343
|
+
end
|
344
|
+
|
345
|
+
def rename_table(name, new_name) #:nodoc:
|
346
|
+
execute "RENAME #{name} TO #{new_name}"
|
347
|
+
execute "RENAME #{name}_seq TO #{new_name}_seq"
|
348
|
+
end
|
349
|
+
|
350
|
+
def drop_table(name) #:nodoc:
|
351
|
+
super(name)
|
352
|
+
execute "DROP SEQUENCE #{name}_seq"
|
353
|
+
end
|
354
|
+
|
355
|
+
def remove_index(table_name, options = {}) #:nodoc:
|
356
|
+
execute "DROP INDEX #{index_name(table_name, options)}"
|
357
|
+
end
|
358
|
+
|
359
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
360
|
+
execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}"
|
361
|
+
end
|
362
|
+
|
363
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
364
|
+
change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"
|
365
|
+
add_column_options!(change_column_sql, options)
|
366
|
+
execute(change_column_sql)
|
367
|
+
end
|
368
|
+
|
369
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
370
|
+
execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} to #{new_column_name}"
|
128
371
|
end
|
129
372
|
|
130
|
-
def
|
373
|
+
def remove_column(table_name, column_name) #:nodoc:
|
374
|
+
execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
|
375
|
+
end
|
376
|
+
|
377
|
+
def structure_dump #:nodoc:
|
131
378
|
s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
|
132
379
|
structure << "create sequence #{seq.to_a.first.last};\n\n"
|
133
380
|
end
|
@@ -158,7 +405,7 @@ begin
|
|
158
405
|
end
|
159
406
|
end
|
160
407
|
|
161
|
-
def structure_drop
|
408
|
+
def structure_drop #:nodoc:
|
162
409
|
s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq|
|
163
410
|
drop << "drop sequence #{seq.to_a.first.last};\n\n"
|
164
411
|
end
|
@@ -168,30 +415,25 @@ begin
|
|
168
415
|
end
|
169
416
|
end
|
170
417
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
if limit
|
176
|
-
sql = "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
|
177
|
-
elsif offset > 0
|
178
|
-
sql = "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
|
179
|
-
end
|
180
|
-
|
418
|
+
|
419
|
+
private
|
420
|
+
|
421
|
+
def select(sql, name = nil)
|
181
422
|
cursor = log(sql, name) { @connection.exec sql }
|
182
423
|
cols = cursor.get_col_names.map { |x| oci_downcase(x) }
|
183
424
|
rows = []
|
184
|
-
|
425
|
+
|
185
426
|
while row = cursor.fetch
|
186
427
|
hash = Hash.new
|
187
428
|
|
188
429
|
cols.each_with_index do |col, i|
|
189
|
-
hash[col] =
|
430
|
+
hash[col] =
|
431
|
+
case row[i]
|
190
432
|
when OCI8::LOB
|
191
433
|
name == 'Writable Large Object' ? row[i]: row[i].read
|
192
434
|
when OraDate
|
193
435
|
(row[i].hour == 0 and row[i].minute == 0 and row[i].second == 0) ?
|
194
|
-
|
436
|
+
row[i].to_date : row[i].to_time
|
195
437
|
else row[i]
|
196
438
|
end unless col == 'raw_rnum_'
|
197
439
|
end
|
@@ -204,129 +446,23 @@ begin
|
|
204
446
|
cursor.close if cursor
|
205
447
|
end
|
206
448
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
from user_catalog cat, user_synonyms syn, all_tab_columns col
|
216
|
-
where cat.table_name = '#{table_name.upcase}'
|
217
|
-
and syn.synonym_name (+)= cat.table_name
|
218
|
-
and col.owner = nvl(syn.table_owner, user)
|
219
|
-
and col.table_name = nvl(syn.table_name, cat.table_name)}
|
220
|
-
).map do |row|
|
221
|
-
OCIColumn.new(
|
222
|
-
oci_downcase(row['column_name']),
|
223
|
-
row['data_default'],
|
224
|
-
row['data_length'],
|
225
|
-
row['data_type'],
|
226
|
-
row['data_scale']
|
227
|
-
)
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
232
|
-
if pk.nil? # Who called us? What does the sql look like? No idea!
|
233
|
-
execute sql, name
|
234
|
-
elsif id_value # Pre-assigned id
|
235
|
-
log(sql, name) { @connection.exec sql }
|
236
|
-
else # Assume the sql contains a bind-variable for the id
|
237
|
-
id_value = select_one("select #{sequence_name}.nextval id from dual")['id']
|
238
|
-
log(sql, name) { @connection.exec sql, id_value }
|
239
|
-
end
|
240
|
-
|
241
|
-
id_value
|
242
|
-
end
|
243
|
-
|
244
|
-
def execute(sql, name = nil)
|
245
|
-
log(sql, name) { @connection.exec sql }
|
246
|
-
end
|
247
|
-
|
248
|
-
alias :update :execute
|
249
|
-
alias :delete :execute
|
250
|
-
|
251
|
-
def begin_db_transaction()
|
252
|
-
@connection.autocommit = false
|
449
|
+
# Oracle column names by default are case-insensitive, but treated as upcase;
|
450
|
+
# for neatness, we'll downcase within Rails. EXCEPT that folks CAN quote
|
451
|
+
# their column names when creating Oracle tables, which makes then case-sensitive.
|
452
|
+
# I don't know anybody who does this, but we'll handle the theoretical case of a
|
453
|
+
# camelCase column name. I imagine other dbs handle this different, since there's a
|
454
|
+
# unit test that's currently failing test_oci.
|
455
|
+
def oci_downcase(column_name)
|
456
|
+
column_name =~ /[a-z]/ ? column_name : column_name.downcase
|
253
457
|
end
|
254
458
|
|
255
|
-
def commit_db_transaction()
|
256
|
-
@connection.commit
|
257
|
-
ensure
|
258
|
-
@connection.autocommit = true
|
259
|
-
end
|
260
|
-
|
261
|
-
def rollback_db_transaction()
|
262
|
-
@connection.rollback
|
263
|
-
ensure
|
264
|
-
@connection.autocommit = true
|
265
|
-
end
|
266
|
-
|
267
|
-
def adapter_name()
|
268
|
-
'OCI'
|
269
|
-
end
|
270
|
-
|
271
|
-
private
|
272
|
-
# Oracle column names by default are case-insensitive, but treated as upcase;
|
273
|
-
# for neatness, we'll downcase within Rails. EXCEPT that folks CAN quote
|
274
|
-
# their column names when creating Oracle tables, which makes then case-sensitive.
|
275
|
-
# I don't know anybody who does this, but we'll handle the theoretical case of a
|
276
|
-
# camelCase column name. I imagine other dbs handle this different, since there's a
|
277
|
-
# unit test that's currently failing test_oci.
|
278
|
-
def oci_downcase(column_name)
|
279
|
-
column_name =~ /[a-z]/ ? column_name : column_name.downcase
|
280
|
-
end
|
281
459
|
end
|
282
460
|
end
|
283
461
|
end
|
284
462
|
|
285
|
-
module ActiveRecord
|
286
|
-
class Base
|
287
|
-
class << self
|
288
|
-
def oci_connection(config) #:nodoc:
|
289
|
-
conn = OCI8.new config[:username], config[:password], config[:host]
|
290
|
-
conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
|
291
|
-
conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'}
|
292
|
-
conn.autocommit = true
|
293
|
-
ConnectionAdapters::OCIAdapter.new conn, logger
|
294
|
-
end
|
295
|
-
end
|
296
|
-
|
297
|
-
alias :attributes_with_quotes_pre_oci :attributes_with_quotes #:nodoc:
|
298
|
-
# Enable the id column to be bound into the sql later, by the adapter's insert method.
|
299
|
-
# This is preferable to inserting the hard-coded value here, because the insert method
|
300
|
-
# needs to know the id value explicitly.
|
301
|
-
def attributes_with_quotes(creating = true) #:nodoc:
|
302
|
-
aq = attributes_with_quotes_pre_oci creating
|
303
|
-
if connection.class == ConnectionAdapters::OCIAdapter
|
304
|
-
aq[self.class.primary_key] = ":id" if creating && aq[self.class.primary_key].nil?
|
305
|
-
end
|
306
|
-
aq
|
307
|
-
end
|
308
|
-
|
309
|
-
after_save :write_lobs
|
310
|
-
|
311
|
-
# After setting large objects to empty, select the OCI8::LOB and write back the data
|
312
|
-
def write_lobs() #:nodoc:
|
313
|
-
if connection.is_a?(ConnectionAdapters::OCIAdapter)
|
314
|
-
self.class.columns.select { |c| c.type == :binary }.each { |c|
|
315
|
-
value = self[c.name]
|
316
|
-
next if value.nil? || (value == '')
|
317
|
-
lob = connection.select_one(
|
318
|
-
"select #{ c.name} from #{ self.class.table_name } WHERE #{ self.class.primary_key} = #{quote(id)}",
|
319
|
-
'Writable Large Object'
|
320
|
-
)[c.name]
|
321
|
-
lob.write value
|
322
|
-
}
|
323
|
-
end
|
324
|
-
end
|
325
|
-
|
326
|
-
private :write_lobs
|
327
|
-
end
|
328
|
-
end
|
329
463
|
|
464
|
+
# This OCI8 patch may not longer be required with the upcoming
|
465
|
+
# release of version 0.2.
|
330
466
|
class OCI8 #:nodoc:
|
331
467
|
class Cursor #:nodoc:
|
332
468
|
alias :define_a_column_pre_ar :define_a_column
|
@@ -339,6 +475,7 @@ begin
|
|
339
475
|
end
|
340
476
|
end
|
341
477
|
end
|
478
|
+
|
342
479
|
rescue LoadError
|
343
480
|
# OCI8 driver is unavailable.
|
344
481
|
end
|