activerecord 1.13.2 → 1.14.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.

Files changed (144) hide show
  1. data/CHANGELOG +452 -10
  2. data/RUNNING_UNIT_TESTS +1 -1
  3. data/lib/active_record.rb +5 -2
  4. data/lib/active_record/acts/list.rb +1 -1
  5. data/lib/active_record/acts/tree.rb +29 -25
  6. data/lib/active_record/aggregations.rb +3 -2
  7. data/lib/active_record/associations.rb +783 -337
  8. data/lib/active_record/associations/association_collection.rb +7 -12
  9. data/lib/active_record/associations/association_proxy.rb +62 -24
  10. data/lib/active_record/associations/belongs_to_association.rb +27 -46
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  12. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +38 -38
  13. data/lib/active_record/associations/has_many_association.rb +61 -56
  14. data/lib/active_record/associations/has_many_through_association.rb +144 -0
  15. data/lib/active_record/associations/has_one_association.rb +22 -16
  16. data/lib/active_record/base.rb +482 -182
  17. data/lib/active_record/calculations.rb +225 -0
  18. data/lib/active_record/callbacks.rb +7 -7
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +162 -47
  20. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  21. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +2 -1
  22. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +21 -1
  23. data/lib/active_record/connection_adapters/abstract_adapter.rb +34 -2
  24. data/lib/active_record/connection_adapters/db2_adapter.rb +107 -61
  25. data/lib/active_record/connection_adapters/mysql_adapter.rb +29 -6
  26. data/lib/active_record/connection_adapters/openbase_adapter.rb +349 -0
  27. data/lib/active_record/connection_adapters/{oci_adapter.rb → oracle_adapter.rb} +125 -59
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +24 -21
  29. data/lib/active_record/connection_adapters/sqlite_adapter.rb +47 -8
  30. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +36 -16
  31. data/lib/active_record/connection_adapters/sybase_adapter.rb +684 -0
  32. data/lib/active_record/fixtures.rb +42 -17
  33. data/lib/active_record/locking.rb +36 -15
  34. data/lib/active_record/migration.rb +111 -8
  35. data/lib/active_record/observer.rb +25 -1
  36. data/lib/active_record/reflection.rb +103 -41
  37. data/lib/active_record/schema.rb +2 -2
  38. data/lib/active_record/schema_dumper.rb +55 -18
  39. data/lib/active_record/timestamp.rb +6 -6
  40. data/lib/active_record/validations.rb +65 -40
  41. data/lib/active_record/vendor/db2.rb +10 -5
  42. data/lib/active_record/vendor/simple.rb +693 -702
  43. data/lib/active_record/version.rb +2 -2
  44. data/rakefile +4 -4
  45. data/test/aaa_create_tables_test.rb +25 -6
  46. data/test/abstract_unit.rb +39 -1
  47. data/test/adapter_test.rb +31 -4
  48. data/test/associations_cascaded_eager_loading_test.rb +106 -0
  49. data/test/associations_go_eager_test.rb +85 -16
  50. data/test/associations_join_model_test.rb +338 -0
  51. data/test/associations_test.rb +129 -50
  52. data/test/base_test.rb +204 -49
  53. data/test/binary_test.rb +1 -1
  54. data/test/calculations_test.rb +169 -0
  55. data/test/callbacks_test.rb +5 -23
  56. data/test/class_inheritable_attributes_test.rb +1 -1
  57. data/test/column_alias_test.rb +1 -1
  58. data/test/connections/native_mysql/connection.rb +1 -0
  59. data/test/connections/native_openbase/connection.rb +22 -0
  60. data/test/connections/{native_oci → native_oracle}/connection.rb +7 -9
  61. data/test/connections/native_sqlite/connection.rb +1 -1
  62. data/test/connections/native_sqlite3/connection.rb +1 -0
  63. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -0
  64. data/test/connections/native_sybase/connection.rb +24 -0
  65. data/test/defaults_test.rb +18 -0
  66. data/test/deprecated_associations_test.rb +2 -2
  67. data/test/deprecated_finder_test.rb +0 -6
  68. data/test/finder_test.rb +26 -23
  69. data/test/fixtures/accounts.yml +10 -0
  70. data/test/fixtures/author.rb +31 -6
  71. data/test/fixtures/author_favorites.yml +4 -0
  72. data/test/fixtures/categories/special_categories.yml +9 -0
  73. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  74. data/test/fixtures/categories_posts.yml +4 -0
  75. data/test/fixtures/categorization.rb +5 -0
  76. data/test/fixtures/categorizations.yml +11 -0
  77. data/test/fixtures/category.rb +6 -0
  78. data/test/fixtures/company.rb +17 -5
  79. data/test/fixtures/company_in_module.rb +19 -5
  80. data/test/fixtures/db_definitions/db2.drop.sql +3 -0
  81. data/test/fixtures/db_definitions/db2.sql +121 -100
  82. data/test/fixtures/db_definitions/db22.sql +2 -2
  83. data/test/fixtures/db_definitions/firebird.drop.sql +4 -0
  84. data/test/fixtures/db_definitions/firebird.sql +26 -0
  85. data/test/fixtures/db_definitions/mysql.drop.sql +3 -0
  86. data/test/fixtures/db_definitions/mysql.sql +21 -1
  87. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  88. data/test/fixtures/db_definitions/openbase.sql +282 -0
  89. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  90. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  91. data/test/fixtures/db_definitions/{oci.drop.sql → oracle.drop.sql} +6 -0
  92. data/test/fixtures/db_definitions/{oci.sql → oracle.sql} +25 -4
  93. data/test/fixtures/db_definitions/{oci2.drop.sql → oracle2.drop.sql} +0 -0
  94. data/test/fixtures/db_definitions/{oci2.sql → oracle2.sql} +0 -0
  95. data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
  96. data/test/fixtures/db_definitions/postgresql.sql +22 -1
  97. data/test/fixtures/db_definitions/schema.rb +32 -0
  98. data/test/fixtures/db_definitions/sqlite.drop.sql +3 -0
  99. data/test/fixtures/db_definitions/sqlite.sql +18 -0
  100. data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
  101. data/test/fixtures/db_definitions/sqlserver.sql +23 -3
  102. data/test/fixtures/db_definitions/sybase.drop.sql +31 -0
  103. data/test/fixtures/db_definitions/sybase.sql +204 -0
  104. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  105. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  106. data/test/fixtures/developers.yml +6 -1
  107. data/test/fixtures/developers_projects.yml +4 -0
  108. data/test/fixtures/funny_jokes.yml +14 -0
  109. data/test/fixtures/joke.rb +6 -0
  110. data/test/fixtures/legacy_thing.rb +3 -0
  111. data/test/fixtures/legacy_things.yml +3 -0
  112. data/test/fixtures/mixin.rb +1 -1
  113. data/test/fixtures/person.rb +4 -1
  114. data/test/fixtures/post.rb +26 -1
  115. data/test/fixtures/project.rb +1 -0
  116. data/test/fixtures/reader.rb +4 -0
  117. data/test/fixtures/readers.yml +4 -0
  118. data/test/fixtures/reply.rb +2 -1
  119. data/test/fixtures/tag.rb +5 -0
  120. data/test/fixtures/tagging.rb +6 -0
  121. data/test/fixtures/taggings.yml +18 -0
  122. data/test/fixtures/tags.yml +7 -0
  123. data/test/fixtures/tasks.yml +2 -2
  124. data/test/fixtures/topic.rb +2 -2
  125. data/test/fixtures/topics.yml +1 -0
  126. data/test/fixtures_test.rb +47 -13
  127. data/test/inheritance_test.rb +2 -2
  128. data/test/locking_test.rb +15 -1
  129. data/test/method_scoping_test.rb +248 -13
  130. data/test/migration_test.rb +68 -11
  131. data/test/mixin_nested_set_test.rb +1 -1
  132. data/test/modules_test.rb +6 -1
  133. data/test/readonly_test.rb +1 -1
  134. data/test/reflection_test.rb +63 -9
  135. data/test/schema_dumper_test.rb +41 -0
  136. data/test/{synonym_test_oci.rb → synonym_test_oracle.rb} +1 -1
  137. data/test/threaded_connections_test.rb +10 -0
  138. data/test/unconnected_test.rb +12 -5
  139. data/test/validations_test.rb +197 -10
  140. metadata +295 -260
  141. data/test/fixtures/db_definitions/create_oracle_db.bat +0 -0
  142. data/test/fixtures/db_definitions/create_oracle_db.sh +0 -0
  143. data/test/fixtures/fixture_database.sqlite +0 -0
  144. data/test/fixtures/fixture_database_2.sqlite +0 -0
@@ -0,0 +1,349 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+
3
+ module ActiveRecord
4
+ class Base
5
+ # Establishes a connection to the database that's used by all Active Record objects
6
+ def self.openbase_connection(config) # :nodoc:
7
+ require_library_or_gem 'openbase' unless self.class.const_defined?(:OpenBase)
8
+
9
+ config = config.symbolize_keys
10
+ host = config[:host]
11
+ username = config[:username].to_s
12
+ password = config[:password].to_s
13
+
14
+
15
+ if config.has_key?(:database)
16
+ database = config[:database]
17
+ else
18
+ raise ArgumentError, "No database specified. Missing argument: database."
19
+ end
20
+
21
+ oba = ConnectionAdapters::OpenBaseAdapter.new(
22
+ OpenBase.new(database, host, username, password), logger
23
+ )
24
+
25
+ oba
26
+ end
27
+
28
+ end
29
+
30
+ module ConnectionAdapters
31
+ class OpenBaseColumn < Column #:nodoc:
32
+ private
33
+ def simplified_type(field_type)
34
+ return :integer if field_type.downcase =~ /long/
35
+ return :float if field_type.downcase == "money"
36
+ return :binary if field_type.downcase == "object"
37
+ super
38
+ end
39
+ end
40
+ # The OpenBase adapter works with the Ruby/Openbase driver by Tetsuya Suzuki.
41
+ # http://www.spice-of-life.net/ruby-openbase/ (needs version 0.7.3+)
42
+ #
43
+ # Options:
44
+ #
45
+ # * <tt>:host</tt> -- Defaults to localhost
46
+ # * <tt>:username</tt> -- Defaults to nothing
47
+ # * <tt>:password</tt> -- Defaults to nothing
48
+ # * <tt>:database</tt> -- The name of the database. No default, must be provided.
49
+ #
50
+ # The OpenBase adapter will make use of OpenBase's ability to generate unique ids
51
+ # for any column with an unique index applied. Thus, if the value of a primary
52
+ # key is not specified at the time an INSERT is performed, the adapter will prefetch
53
+ # a unique id for the primary key. This prefetching is also necessary in order
54
+ # to return the id after an insert.
55
+ #
56
+ # Caveat: Operations involving LIMIT and OFFSET do not yet work!
57
+ #
58
+ # Maintainer: derrickspell@cdmplus.com
59
+ class OpenBaseAdapter < AbstractAdapter
60
+ def adapter_name
61
+ 'OpenBase'
62
+ end
63
+
64
+ def native_database_types
65
+ {
66
+ :primary_key => "integer UNIQUE INDEX DEFAULT _rowid",
67
+ :string => { :name => "char", :limit => 4096 },
68
+ :text => { :name => "text" },
69
+ :integer => { :name => "integer" },
70
+ :float => { :name => "float" },
71
+ :datetime => { :name => "datetime" },
72
+ :timestamp => { :name => "timestamp" },
73
+ :time => { :name => "time" },
74
+ :date => { :name => "date" },
75
+ :binary => { :name => "object" },
76
+ :boolean => { :name => "boolean" }
77
+ }
78
+ end
79
+
80
+ def supports_migrations?
81
+ false
82
+ end
83
+
84
+ def prefetch_primary_key?(table_name = nil)
85
+ true
86
+ end
87
+
88
+ def default_sequence_name(table_name, primary_key) # :nodoc:
89
+ "#{table_name} #{primary_key}"
90
+ end
91
+
92
+ def next_sequence_value(sequence_name)
93
+ ary = sequence_name.split(' ')
94
+ if (!ary[1]) then
95
+ ary[0] =~ /(\w+)_nonstd_seq/
96
+ ary[0] = $1
97
+ end
98
+ @connection.unique_row_id(ary[0], ary[1])
99
+ end
100
+
101
+
102
+ # QUOTING ==================================================
103
+
104
+ def quote(value, column = nil)
105
+ if value.kind_of?(String) && column && column.type == :binary
106
+ "'#{@connection.insert_binary(value)}'"
107
+ else
108
+ super
109
+ end
110
+ end
111
+
112
+ def quoted_true
113
+ "1"
114
+ end
115
+
116
+ def quoted_false
117
+ "0"
118
+ end
119
+
120
+
121
+
122
+ # DATABASE STATEMENTS ======================================
123
+
124
+ def add_limit_offset!(sql, options) #:nodoc
125
+ if limit = options[:limit]
126
+ unless offset = options[:offset]
127
+ sql << " RETURN RESULTS #{limit}"
128
+ else
129
+ limit = limit + offset
130
+ sql << " RETURN RESULTS #{offset} TO #{limit}"
131
+ end
132
+ end
133
+ end
134
+
135
+ def select_all(sql, name = nil) #:nodoc:
136
+ select(sql, name)
137
+ end
138
+
139
+ def select_one(sql, name = nil) #:nodoc:
140
+ add_limit_offset!(sql,{:limit => 1})
141
+ results = select(sql, name)
142
+ results.first if results
143
+ end
144
+
145
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
146
+ execute(sql, name)
147
+ update_nulls_after_insert(sql, name, pk, id_value, sequence_name)
148
+ id_value
149
+ end
150
+
151
+ def execute(sql, name = nil) #:nodoc:
152
+ log(sql, name) { @connection.execute(sql) }
153
+ end
154
+
155
+ def update(sql, name = nil) #:nodoc:
156
+ execute(sql, name).rows_affected
157
+ end
158
+
159
+ alias_method :delete, :update #:nodoc:
160
+ #=begin
161
+ def begin_db_transaction #:nodoc:
162
+ execute "START TRANSACTION"
163
+ rescue Exception
164
+ # Transactions aren't supported
165
+ end
166
+
167
+ def commit_db_transaction #:nodoc:
168
+ execute "COMMIT"
169
+ rescue Exception
170
+ # Transactions aren't supported
171
+ end
172
+
173
+ def rollback_db_transaction #:nodoc:
174
+ execute "ROLLBACK"
175
+ rescue Exception
176
+ # Transactions aren't supported
177
+ end
178
+ #=end
179
+
180
+ # SCHEMA STATEMENTS ========================================
181
+
182
+ # Return the list of all tables in the schema search path.
183
+ def tables(name = nil) #:nodoc:
184
+ tables = @connection.tables
185
+ tables.reject! { |t| /\A_SYS_/ === t }
186
+ tables
187
+ end
188
+
189
+ def columns(table_name, name = nil) #:nodoc:
190
+ sql = "SELECT * FROM _sys_tables "
191
+ sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
192
+ sql << "ORDER BY columnNumber"
193
+ columns = []
194
+ select_all(sql, name).each do |row|
195
+ columns << OpenBaseColumn.new(row["fieldname"],
196
+ default_value(row["defaultvalue"]),
197
+ sql_type_name(row["typename"],row["length"]),
198
+ row["notnull"]
199
+ )
200
+ # breakpoint() if row["fieldname"] == "content"
201
+ end
202
+ columns
203
+ end
204
+
205
+ def indexes(table_name, name = nil)#:nodoc:
206
+ sql = "SELECT fieldname, notnull, searchindex, uniqueindex, clusteredindex FROM _sys_tables "
207
+ sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
208
+ sql << "AND primarykey=0 "
209
+ sql << "AND (searchindex=1 OR uniqueindex=1 OR clusteredindex=1) "
210
+ sql << "ORDER BY columnNumber"
211
+ indexes = []
212
+ execute(sql, name).each do |row|
213
+ indexes << IndexDefinition.new(table_name,index_name(row),row[3]==1,[row[0]])
214
+ end
215
+ indexes
216
+ end
217
+
218
+
219
+ private
220
+ def select(sql, name = nil)
221
+ sql = translate_sql(sql)
222
+ results = execute(sql, name)
223
+
224
+ date_cols = []
225
+ col_names = []
226
+ results.column_infos.each do |info|
227
+ col_names << info.name
228
+ date_cols << info.name if info.type == "date"
229
+ end
230
+
231
+ rows = []
232
+ if ( results.rows_affected )
233
+ results.each do |row| # loop through result rows
234
+ hashed_row = {}
235
+ row.each_index do |index|
236
+ hashed_row["#{col_names[index]}"] = row[index] unless col_names[index] == "_rowid"
237
+ end
238
+ date_cols.each do |name|
239
+ unless hashed_row["#{name}"].nil? or hashed_row["#{name}"].empty?
240
+ hashed_row["#{name}"] = Date.parse(hashed_row["#{name}"],false).to_s
241
+ end
242
+ end
243
+ rows << hashed_row
244
+ end
245
+ end
246
+ rows
247
+ end
248
+
249
+ def default_value(value)
250
+ # Boolean type values
251
+ return true if value =~ /true/
252
+ return false if value =~ /false/
253
+
254
+ # Date / Time magic values
255
+ return Time.now.to_s if value =~ /^now\(\)/i
256
+
257
+ # Empty strings should be set to null
258
+ return nil if value.empty?
259
+
260
+ # Otherwise return what we got from OpenBase
261
+ # and hope for the best...
262
+ return value
263
+ end
264
+
265
+ def sql_type_name(type_name, length)
266
+ return "#{type_name}(#{length})" if ( type_name =~ /char/ )
267
+ type_name
268
+ end
269
+
270
+ def index_name(row = [])
271
+ name = ""
272
+ name << "UNIQUE " if row[3]
273
+ name << "CLUSTERED " if row[4]
274
+ name << "INDEX"
275
+ name
276
+ end
277
+
278
+ def translate_sql(sql)
279
+
280
+ # Change table.* to list of columns in table
281
+ while (sql =~ /SELECT.*\s(\w+)\.\*/)
282
+ table = $1
283
+ cols = columns(table)
284
+ if ( cols.size == 0 ) then
285
+ # Maybe this is a table alias
286
+ sql =~ /FROM(.+?)(?:LEFT|OUTER|JOIN|WHERE|GROUP|HAVING|ORDER|RETURN|$)/
287
+ $1 =~ /[\s|,](\w+)\s+#{table}[\s|,]/ # get the tablename for this alias
288
+ cols = columns($1)
289
+ end
290
+ select_columns = []
291
+ cols.each do |col|
292
+ select_columns << table + '.' + col.name
293
+ end
294
+ sql.gsub!(table + '.*',select_columns.join(", ")) if select_columns
295
+ end
296
+
297
+ # Change JOIN clause to table list and WHERE condition
298
+ while (sql =~ /JOIN/)
299
+ sql =~ /((LEFT )?(OUTER )?JOIN (\w+) ON )(.+?)(?:LEFT|OUTER|JOIN|WHERE|GROUP|HAVING|ORDER|RETURN|$)/
300
+ join_clause = $1 + $5
301
+ is_outer_join = $3
302
+ join_table = $4
303
+ join_condition = $5
304
+ join_condition.gsub!(/=/,"*") if is_outer_join
305
+ if (sql =~ /WHERE/)
306
+ sql.gsub!(/WHERE/,"WHERE (#{join_condition}) AND")
307
+ else
308
+ sql.gsub!(join_clause,"#{join_clause} WHERE #{join_condition}")
309
+ end
310
+ sql =~ /(FROM .+?)(?:LEFT|OUTER|JOIN|WHERE|$)/
311
+ from_clause = $1
312
+ sql.gsub!(from_clause,"#{from_clause}, #{join_table} ")
313
+ sql.gsub!(join_clause,"")
314
+ end
315
+
316
+ # ORDER BY _rowid if no explicit ORDER BY
317
+ # This will ensure that find(:first) returns the first inserted row
318
+ if (sql !~ /(ORDER BY)|(GROUP BY)/)
319
+ if (sql =~ /RETURN RESULTS/)
320
+ sql.sub!(/RETURN RESULTS/,"ORDER BY _rowid RETURN RESULTS")
321
+ else
322
+ sql << " ORDER BY _rowid"
323
+ end
324
+ end
325
+
326
+ sql
327
+ end
328
+
329
+ def update_nulls_after_insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
330
+ sql =~ /INSERT INTO (\w+) \((.*)\) VALUES\s*\((.*)\)/m
331
+ table = $1
332
+ cols = $2
333
+ values = $3
334
+ cols = cols.split(',')
335
+ values.gsub!(/'[^']*'/,"''")
336
+ values.gsub!(/"[^"]*"/,"\"\"")
337
+ values = values.split(',')
338
+ update_cols = []
339
+ values.each_index { |index| update_cols << cols[index] if values[index] =~ /\s*NULL\s*/ }
340
+ update_sql = "UPDATE #{table} SET"
341
+ update_cols.each { |col| update_sql << " #{col}=NULL," unless col.empty? }
342
+ update_sql.chop!()
343
+ update_sql << " WHERE #{pk}=#{quote(id_value)}"
344
+ execute(update_sql, name + " NULL Correction") if update_cols.size > 0
345
+ end
346
+
347
+ end
348
+ end
349
+ end
@@ -1,4 +1,4 @@
1
- # oci_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g
1
+ # oracle_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g
2
2
  #
3
3
  # Original author: Graham Jenkins
4
4
  #
@@ -30,18 +30,28 @@ begin
30
30
 
31
31
  module ActiveRecord
32
32
  class Base
33
- def self.oci_connection(config) #:nodoc:
33
+ def self.oracle_connection(config) #:nodoc:
34
34
  # Use OCI8AutoRecover instead of normal OCI8 driver.
35
- ConnectionAdapters::OCIAdapter.new OCI8AutoRecover.new(config), logger
35
+ ConnectionAdapters::OracleAdapter.new OCI8AutoRecover.new(config), logger
36
+ end
37
+
38
+ # for backwards-compatibility
39
+ def self.oci_connection(config) #:nodoc:
40
+ config[:database] = config[:host]
41
+ self.oracle_connection(config)
36
42
  end
37
43
 
38
44
  # Enable the id column to be bound into the sql later, by the adapter's insert method.
39
45
  # This is preferable to inserting the hard-coded value here, because the insert method
40
46
  # needs to know the id value explicitly.
41
- alias :attributes_with_quotes_pre_oci :attributes_with_quotes #:nodoc:
47
+ def attributes_with_quotes_pre_oracle #:nodoc:
48
+ attributes_with_quotes
49
+ end
50
+
51
+
42
52
  def attributes_with_quotes(creating = true) #:nodoc:
43
- aq = attributes_with_quotes_pre_oci creating
44
- if connection.class == ConnectionAdapters::OCIAdapter
53
+ aq = attributes_with_quotes_pre_oracle creating
54
+ if connection.class == ConnectionAdapters::OracleAdapter
45
55
  aq[self.class.primary_key] = ":id" if creating && aq[self.class.primary_key].nil?
46
56
  end
47
57
  aq
@@ -51,12 +61,12 @@ begin
51
61
  # and write back the data.
52
62
  after_save :write_lobs
53
63
  def write_lobs() #:nodoc:
54
- if connection.is_a?(ConnectionAdapters::OCIAdapter)
64
+ if connection.is_a?(ConnectionAdapters::OracleAdapter)
55
65
  self.class.columns.select { |c| c.type == :binary }.each { |c|
56
66
  value = self[c.name]
57
67
  next if value.nil? || (value == '')
58
68
  lob = connection.select_one(
59
- "select #{ c.name} from #{ self.class.table_name } WHERE #{ self.class.primary_key} = #{quote(id)}",
69
+ "SELECT #{ c.name} FROM #{ self.class.table_name } WHERE #{ self.class.primary_key} = #{quote(id)}",
60
70
  'Writable Large Object')[c.name]
61
71
  lob.write value
62
72
  }
@@ -68,7 +78,7 @@ begin
68
78
 
69
79
 
70
80
  module ConnectionAdapters #:nodoc:
71
- class OCIColumn < Column #:nodoc:
81
+ class OracleColumn < Column #:nodoc:
72
82
  attr_reader :sql_type
73
83
 
74
84
  # overridden to add the concept of scale, required to differentiate
@@ -102,7 +112,8 @@ begin
102
112
  when /char/i : :string
103
113
  when /num|float|double|dec|real|int/i : @scale == 0 ? :integer : :float
104
114
  when /date|time/i : @name =~ /_at$/ ? :time : :datetime
105
- when /lob/i : :binary
115
+ when /clob/i : :text
116
+ when /blob/i : :binary
106
117
  end
107
118
  end
108
119
 
@@ -149,16 +160,19 @@ begin
149
160
  # * Default values that are functions (such as "SYSDATE") are not
150
161
  # supported. This is a restriction of the way ActiveRecord supports
151
162
  # default values.
163
+ # * Support for Oracle8 is limited by Rails' use of ANSI join syntax, which
164
+ # is supported in Oracle9i and later. You will need to use #finder_sql for
165
+ # has_and_belongs_to_many associations to run against Oracle8.
152
166
  #
153
- # Options:
167
+ # Required parameters:
154
168
  #
155
- # * <tt>:username</tt> -- Defaults to root
156
- # * <tt>:password</tt> -- Defaults to nothing
157
- # * <tt>:host</tt> -- Defaults to localhost
158
- class OCIAdapter < AbstractAdapter
169
+ # * <tt>:username</tt>
170
+ # * <tt>:password</tt>
171
+ # * <tt>:database</tt>
172
+ class OracleAdapter < AbstractAdapter
159
173
 
160
174
  def adapter_name #:nodoc:
161
- 'OCI'
175
+ 'Oracle'
162
176
  end
163
177
 
164
178
  def supports_migrations? #:nodoc:
@@ -167,9 +181,9 @@ begin
167
181
 
168
182
  def native_database_types #:nodoc
169
183
  {
170
- :primary_key => "NUMBER(38) NOT NULL",
184
+ :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
171
185
  :string => { :name => "VARCHAR2", :limit => 255 },
172
- :text => { :name => "LONG" },
186
+ :text => { :name => "CLOB" },
173
187
  :integer => { :name => "NUMBER", :limit => 38 },
174
188
  :float => { :name => "NUMBER" },
175
189
  :datetime => { :name => "DATE" },
@@ -181,6 +195,9 @@ begin
181
195
  }
182
196
  end
183
197
 
198
+ def table_alias_length
199
+ 30
200
+ end
184
201
 
185
202
  # QUOTING ==================================================
186
203
  #
@@ -197,7 +214,7 @@ begin
197
214
  end
198
215
 
199
216
  def quote(value, column = nil) #:nodoc:
200
- if column and column.type == :binary then %Q{empty_#{ column.sql_type }()}
217
+ if column && column.type == :binary then %Q{empty_#{ column.sql_type }()}
201
218
  else case value
202
219
  when String then %Q{'#{quote_string(value)}'}
203
220
  when NilClass then 'null'
@@ -211,7 +228,8 @@ begin
211
228
  end
212
229
 
213
230
 
214
- # CONNECTION MANAGEMENT ====================================#
231
+ # CONNECTION MANAGEMENT ====================================
232
+ #
215
233
 
216
234
  # Returns true if the connection is active.
217
235
  def active?
@@ -220,17 +238,23 @@ begin
220
238
  # last known state, which isn't good enough if the connection has
221
239
  # gone stale since the last use.
222
240
  @connection.ping
223
- rescue OCIError
241
+ rescue OCIException
224
242
  false
225
243
  end
226
244
 
227
245
  # Reconnects to the database.
228
246
  def reconnect!
229
247
  @connection.reset!
230
- rescue OCIError => e
248
+ rescue OCIException => e
231
249
  @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}"
232
250
  end
233
251
 
252
+ # Disconnects from the database.
253
+ def disconnect!
254
+ @connection.logoff rescue nil
255
+ @connection.active = false
256
+ end
257
+
234
258
 
235
259
  # DATABASE STATEMENTS ======================================
236
260
  #
@@ -300,6 +324,10 @@ begin
300
324
  #
301
325
  # see: abstract/schema_statements.rb
302
326
 
327
+ def current_database #:nodoc:
328
+ select_one("select sys_context('userenv','db_name') db from dual")["db"]
329
+ end
330
+
303
331
  def tables(name = nil) #:nodoc:
304
332
  select_all("select lower(table_name) from user_tables").inject([]) do | tabs, t |
305
333
  tabs << t.to_a.first.last
@@ -332,10 +360,7 @@ begin
332
360
  end
333
361
 
334
362
  def columns(table_name, name = nil) #:nodoc:
335
- table_name = table_name.to_s.upcase
336
- owner = table_name.include?('.') ? "'#{table_name.split('.').first}'" : "user"
337
- table = "'#{table_name.split('.').last}'"
338
- scope = (owner == "user" ? "user" : "all")
363
+ table_info = @connection.object_info(table_name)
339
364
 
340
365
  table_cols = %Q{
341
366
  select column_name, data_type, data_default, nullable,
@@ -343,26 +368,20 @@ begin
343
368
  'VARCHAR2', data_length,
344
369
  null) as length,
345
370
  decode(data_type, 'NUMBER', data_scale, null) as scale
346
- from #{scope}_catalog cat, #{scope}_synonyms syn, all_tab_columns col
347
- where cat.table_name = #{table}
348
- and syn.synonym_name (+)= cat.table_name
349
- and col.table_name = nvl(syn.table_name, cat.table_name)
350
- and col.owner = nvl(syn.table_owner, #{(scope == "all" ? "cat.owner" : "user")}) }
351
-
352
- if scope == "all"
353
- table_cols << %Q{
354
- and cat.owner = #{owner}
355
- and syn.owner (+)= cat.owner }
356
- end
371
+ from all_tab_columns
372
+ where owner = '#{table_info.schema}'
373
+ and table_name = '#{table_info.name}'
374
+ order by column_id
375
+ }
357
376
 
358
377
  select_all(table_cols, name).map do |row|
359
378
  row['data_default'].sub!(/^'(.*)'\s*$/, '\1') if row['data_default']
360
- OCIColumn.new(
361
- oci_downcase(row['column_name']),
379
+ OracleColumn.new(
380
+ oracle_downcase(row['column_name']),
362
381
  row['data_default'],
363
- row['data_type'],
364
- row['length'],
365
- row['scale'],
382
+ row['data_type'],
383
+ (l = row['length']).nil? ? nil : l.to_i,
384
+ (s = row['scale']).nil? ? nil : s.to_i,
366
385
  row['nullable'] == 'Y'
367
386
  )
368
387
  end
@@ -370,17 +389,17 @@ begin
370
389
 
371
390
  def create_table(name, options = {}) #:nodoc:
372
391
  super(name, options)
373
- execute "CREATE SEQUENCE #{name}_seq"
392
+ execute "CREATE SEQUENCE #{name}_seq" unless options[:id] == false
374
393
  end
375
394
 
376
395
  def rename_table(name, new_name) #:nodoc:
377
396
  execute "RENAME #{name} TO #{new_name}"
378
- execute "RENAME #{name}_seq TO #{new_name}_seq"
397
+ execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
379
398
  end
380
399
 
381
400
  def drop_table(name) #:nodoc:
382
401
  super(name)
383
- execute "DROP SEQUENCE #{name}_seq"
402
+ execute "DROP SEQUENCE #{name}_seq" rescue nil
384
403
  end
385
404
 
386
405
  def remove_index(table_name, options = {}) #:nodoc:
@@ -451,7 +470,7 @@ begin
451
470
 
452
471
  def select(sql, name = nil)
453
472
  cursor = log(sql, name) { @connection.exec sql }
454
- cols = cursor.get_col_names.map { |x| oci_downcase(x) }
473
+ cols = cursor.get_col_names.map { |x| oracle_downcase(x) }
455
474
  rows = []
456
475
 
457
476
  while row = cursor.fetch
@@ -483,7 +502,7 @@ begin
483
502
  # I don't know anybody who does this, but we'll handle the theoretical case of a
484
503
  # camelCase column name. I imagine other dbs handle this different, since there's a
485
504
  # unit test that's currently failing test_oci.
486
- def oci_downcase(column_name)
505
+ def oracle_downcase(column_name)
487
506
  column_name =~ /[a-z]/ ? column_name : column_name.downcase
488
507
  end
489
508
 
@@ -492,27 +511,62 @@ begin
492
511
  end
493
512
 
494
513
 
495
- # This OCI8 patch may not longer be required with the upcoming
496
- # release of version 0.2.
497
514
  class OCI8 #:nodoc:
515
+
516
+ # This OCI8 patch may not longer be required with the upcoming
517
+ # release of version 0.2.
498
518
  class Cursor #:nodoc:
499
519
  alias :define_a_column_pre_ar :define_a_column
500
520
  def define_a_column(i)
501
521
  case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) }
502
522
  when 8 : @stmt.defineByPos(i, String, 65535) # Read LONG values
503
523
  when 187 : @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
524
+ when 108
525
+ if @parms[i - 1].attrGet(OCI_ATTR_TYPE_NAME) == 'XMLTYPE'
526
+ @stmt.defineByPos(i, String, 65535)
527
+ else
528
+ raise 'unsupported datatype'
529
+ end
504
530
  else define_a_column_pre_ar i
505
531
  end
506
532
  end
507
533
  end
534
+
535
+ # missing constant from oci8 < 0.1.14
536
+ OCI_PTYPE_UNK = 0 unless defined?(OCI_PTYPE_UNK)
537
+
538
+ def object_info(name)
539
+ OraObject.new describe(name.to_s, OCI_PTYPE_UNK)
540
+ end
541
+
542
+ def describe(name, type)
543
+ @desc ||= @@env.alloc(OCIDescribe)
544
+ @desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14'
545
+ @desc.describeAny(@svc, name, type)
546
+ @desc.attrGet(OCI_ATTR_PARAM)
547
+ end
548
+
549
+ class OraObject #:nodoc:
550
+ attr_reader :schema, :name
551
+ def initialize(info)
552
+ case info.attrGet(OCI_ATTR_PTYPE)
553
+ when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW
554
+ @schema = info.attrGet(OCI_ATTR_OBJ_SCHEMA)
555
+ @name = info.attrGet(OCI_ATTR_OBJ_NAME)
556
+ when OCI_PTYPE_SYN
557
+ @schema = info.attrGet(OCI_ATTR_SCHEMA_NAME)
558
+ @name = info.attrGet(OCI_ATTR_NAME)
559
+ end
560
+ end
561
+ end
508
562
  end
509
563
 
510
564
 
511
- # The OCIConnectionFactory factors out the code necessary to connect and
512
- # configure an OCI connection.
513
- class OCIConnectionFactory #:nodoc:
514
- def new_connection(username, password, host)
515
- conn = OCI8.new username, password, host
565
+ # The OracleConnectionFactory factors out the code necessary to connect and
566
+ # configure an Oracle/OCI connection.
567
+ class OracleConnectionFactory #:nodoc:
568
+ def new_connection(username, password, database)
569
+ conn = OCI8.new username, password, database
516
570
  conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
517
571
  conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil
518
572
  conn.autocommit = true
@@ -538,11 +592,11 @@ begin
538
592
  end
539
593
  @@auto_retry = false
540
594
 
541
- def initialize(config, factory = OCIConnectionFactory.new)
595
+ def initialize(config, factory = OracleConnectionFactory.new)
542
596
  @active = true
543
- @username, @password, @host = config[:username], config[:password], config[:host]
597
+ @username, @password, @database = config[:username], config[:password], config[:database]
544
598
  @factory = factory
545
- @connection = @factory.new_connection @username, @password, @host
599
+ @connection = @factory.new_connection @username, @password, @database
546
600
  super @connection
547
601
  end
548
602
 
@@ -561,7 +615,7 @@ begin
561
615
  def reset!
562
616
  logoff rescue nil
563
617
  begin
564
- @connection = @factory.new_connection @username, @password, @host
618
+ @connection = @factory.new_connection @username, @password, @database
565
619
  __setobj__ @connection
566
620
  @active = true
567
621
  rescue
@@ -584,7 +638,7 @@ begin
584
638
 
585
639
  begin
586
640
  @connection.exec(sql, *bindvars)
587
- rescue OCIError => e
641
+ rescue OCIException => e
588
642
  raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code)
589
643
  @active = false
590
644
  raise unless should_retry
@@ -598,4 +652,16 @@ begin
598
652
 
599
653
  rescue LoadError
600
654
  # OCI8 driver is unavailable.
655
+ module ActiveRecord # :nodoc:
656
+ class Base
657
+ def self.oracle_connection(config) # :nodoc:
658
+ # Set up a reasonable error message
659
+ raise LoadError, "Oracle/OCI libraries could not be loaded."
660
+ end
661
+ def self.oci_connection(config) # :nodoc:
662
+ # Set up a reasonable error message
663
+ raise LoadError, "Oracle/OCI libraries could not be loaded."
664
+ end
665
+ end
666
+ end
601
667
  end