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.

Files changed (66) hide show
  1. data/CHANGELOG +92 -0
  2. data/README +9 -9
  3. data/lib/active_record/acts/list.rb +1 -1
  4. data/lib/active_record/acts/nested_set.rb +13 -13
  5. data/lib/active_record/acts/tree.rb +7 -6
  6. data/lib/active_record/aggregations.rb +4 -4
  7. data/lib/active_record/associations.rb +82 -21
  8. data/lib/active_record/associations/association_collection.rb +0 -8
  9. data/lib/active_record/associations/association_proxy.rb +5 -2
  10. data/lib/active_record/associations/belongs_to_association.rb +6 -2
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +11 -1
  12. data/lib/active_record/associations/has_many_association.rb +34 -5
  13. data/lib/active_record/associations/has_one_association.rb +1 -1
  14. data/lib/active_record/base.rb +144 -59
  15. data/lib/active_record/callbacks.rb +6 -6
  16. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -2
  17. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -4
  18. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +10 -8
  19. data/lib/active_record/connection_adapters/abstract_adapter.rb +1 -1
  20. data/lib/active_record/connection_adapters/db2_adapter.rb +17 -1
  21. data/lib/active_record/connection_adapters/oci_adapter.rb +322 -185
  22. data/lib/active_record/connection_adapters/sqlite_adapter.rb +9 -8
  23. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +107 -14
  24. data/lib/active_record/fixtures.rb +10 -8
  25. data/lib/active_record/migration.rb +20 -6
  26. data/lib/active_record/observer.rb +4 -3
  27. data/lib/active_record/reflection.rb +5 -5
  28. data/lib/active_record/transactions.rb +2 -2
  29. data/lib/active_record/validations.rb +70 -40
  30. data/lib/active_record/vendor/mysql411.rb +9 -13
  31. data/lib/active_record/version.rb +2 -2
  32. data/rakefile +1 -1
  33. data/test/abstract_unit.rb +5 -0
  34. data/test/ar_schema_test.rb +1 -1
  35. data/test/associations_extensions_test.rb +37 -0
  36. data/test/associations_go_eager_test.rb +25 -0
  37. data/test/associations_test.rb +14 -6
  38. data/test/base_test.rb +63 -45
  39. data/test/connections/native_sqlite/connection.rb +2 -2
  40. data/test/connections/native_sqlite3/connection.rb +2 -2
  41. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  42. data/test/debug.log +2857 -0
  43. data/test/deprecated_finder_test.rb +3 -9
  44. data/test/finder_test.rb +27 -13
  45. data/test/fixtures/author.rb +4 -0
  46. data/test/fixtures/comment.rb +4 -8
  47. data/test/fixtures/db_definitions/create_oracle_db.bat +0 -5
  48. data/test/fixtures/db_definitions/create_oracle_db.sh +0 -5
  49. data/test/fixtures/db_definitions/db2.drop.sql +0 -1
  50. data/test/fixtures/db_definitions/oci.sql +2 -0
  51. data/test/fixtures/db_definitions/sqlserver.drop.sql +1 -1
  52. data/test/fixtures/developer.rb +18 -1
  53. data/test/fixtures/fixture_database.sqlite +0 -0
  54. data/test/fixtures/fixture_database_2.sqlite +0 -0
  55. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  56. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  57. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  58. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  59. data/test/fixtures/post.rb +18 -4
  60. data/test/fixtures/reply.rb +3 -1
  61. data/test/inheritance_test.rb +3 -2
  62. data/test/{conditions_scoping_test.rb → method_scoping_test.rb} +55 -10
  63. data/test/migration_test.rb +68 -31
  64. data/test/readonly_test.rb +65 -5
  65. data/test/validations_test.rb +10 -2
  66. 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 is some times appropriate (such as for creating mix-ins), and inline
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 is called for each object instantiated found by a finder, such as Base.find(:all), we've had
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 has a record.
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 has a record.
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 has a record.
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 has a record.
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
- # :dbfile => "path/to/dbfile"
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
- # "dbfile" => "path/to/dbfile"
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
- index_name = "#{table_name}_#{options[:column]}_index"
195
+ "#{table_name}_#{options[:column]}_index"
191
196
  elsif options[:name]
192
- index_name = options[:name]
197
+ options[:name]
193
198
  else
194
199
  raise ArgumentError, "You must specify the index name"
195
200
  end
196
201
  else
197
- index_name = "#{table_name}_#{options}_index"
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 ? Backend specific, as the
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. I had to redefine a method in ActiveRecord to make it possible to implement an autonumbering
3
- # solution for oracle. It's implemented in a way that is intended to not break other adapters.
4
- # 2. Default value support needs a patch to the OCI8 driver, to enable it to read LONG columns.
5
- # The driver-author has said he will add this in a future release.
6
- # A similar patch is needed for TIMESTAMP. This should be replaced with the 0.2 version of the
7
- # driver, which will support TIMESTAMP properly.
8
- # 3. Large Object support works by an after_save callback added to the ActiveRecord. This is not
9
- # a problem - you can add other (chained) after_save callbacks.
10
- # 4. LIMIT and OFFSET now work using a select from select from select. This pattern enables
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 significant portion of my code
16
- # remains then please acknowledge my contribution.
17
- # Copyright 2005 Graham Jenkins
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
- def initialize(name, default, limit, sql_type, scale)
30
- @name, @limit, @sql_type, @scale, @sequence = name, limit, sql_type, scale
31
- @type = simplified_type sql_type
32
- @default = type_cast default
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
- def simplified_type(field_type)
36
- case field_type
37
- when /char/i : :string
38
- when /num|float|double|dec|real|int/i : @scale == 0 ? :integer : :float
39
- when /date|time/i : @name =~ /_at$/ ? :time : :datetime
40
- when /lob/i : :binary
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
- # This is an Oracle adapter for the ActiveRecord persistence framework. It relies upon the OCI8
75
- # driver (http://rubyforge.org/projects/ruby-oci8/), which works with Oracle 8i and above.
76
- # It was developed on Windows 2000 against an 8i database, using ActiveRecord 1.6.0 and OCI8 0.1.9.
77
- # It has also been tested against a 9i database.
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 for all tables; the
81
- # sequence name can be changed using ActiveRecord::Base.set_sequence_name
82
- # * Oracle uses DATE or TIMESTAMP datatypes for both dates and times. Consequently I have had to
83
- # resort to some hacks to get data converted to Date or Time in Ruby.
84
- # If the column_name ends in _time it's created as a Ruby Time. Else if the
85
- # hours/minutes/seconds are 0, I make it a Ruby Date. Else it's a Ruby Time.
86
- # This is nasty - but if you use Duck Typing you'll probably not care very much.
87
- # In 9i it's tempting to map DATE to Date and TIMESTAMP to Time but I don't think that is
88
- # valid - too many databases use DATE for both.
89
- # Timezones and sub-second precision on timestamps are not supported.
90
- # * Default values that are functions (such as "SYSDATE") are not supported. This is a
91
- # restriction of the way active record supports default values.
92
- # * Referential integrity constraints are not fully supported. Under at least
93
- # some circumstances, active record appears to delete parent and child records out of
94
- # sequence and out of transaction scope. (Or this may just be a problem of test setup.)
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
- def default_sequence_name(table, column)
103
- "#{table}_seq"
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
- # camelCase column names need to be quoted; not that anyone using Oracle
125
- # would really do this, but handling this case means we pass the test...
126
- def quote_column_name(name)
127
- name =~ /[A-Z]/ ? "\"#{name}\"" : name
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 structure_dump
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
- def select_all(sql, name = nil)
172
- offset = sql =~ /OFFSET (\d+)$/ ? $1.to_i : 0
173
- sql, limit = $1, $2.to_i if sql =~ /(.*)(?: LIMIT[= ](\d+))(\s*OFFSET \d+)?$/
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] = case row[i]
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
- row[i].to_date : row[i].to_time
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
- def select_one(sql, name = nil)
208
- result = select_all sql, name
209
- result.size > 0 ? result.first : nil
210
- end
211
-
212
- def columns(table_name, name = nil)
213
- select_all(%Q{
214
- select column_name, data_type, data_default, data_length, data_scale
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