activerecord 1.15.6 → 2.0.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 (185) hide show
  1. data/CHANGELOG +2454 -34
  2. data/README +1 -1
  3. data/RUNNING_UNIT_TESTS +3 -34
  4. data/Rakefile +98 -77
  5. data/install.rb +1 -1
  6. data/lib/active_record.rb +13 -22
  7. data/lib/active_record/aggregations.rb +38 -49
  8. data/lib/active_record/associations.rb +452 -333
  9. data/lib/active_record/associations/association_collection.rb +66 -20
  10. data/lib/active_record/associations/association_proxy.rb +9 -8
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
  12. data/lib/active_record/associations/has_many_association.rb +21 -57
  13. data/lib/active_record/associations/has_many_through_association.rb +38 -18
  14. data/lib/active_record/associations/has_one_association.rb +30 -14
  15. data/lib/active_record/attribute_methods.rb +253 -0
  16. data/lib/active_record/base.rb +719 -494
  17. data/lib/active_record/calculations.rb +62 -63
  18. data/lib/active_record/callbacks.rb +57 -83
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
  20. data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
  26. data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
  27. data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
  28. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  29. data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
  30. data/lib/active_record/fixtures.rb +503 -113
  31. data/lib/active_record/locking/optimistic.rb +72 -34
  32. data/lib/active_record/migration.rb +80 -57
  33. data/lib/active_record/observer.rb +13 -10
  34. data/lib/active_record/query_cache.rb +16 -57
  35. data/lib/active_record/reflection.rb +35 -38
  36. data/lib/active_record/schema.rb +5 -5
  37. data/lib/active_record/schema_dumper.rb +35 -13
  38. data/lib/active_record/serialization.rb +98 -0
  39. data/lib/active_record/serializers/json_serializer.rb +71 -0
  40. data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
  41. data/lib/active_record/timestamp.rb +20 -21
  42. data/lib/active_record/transactions.rb +39 -43
  43. data/lib/active_record/validations.rb +256 -107
  44. data/lib/active_record/version.rb +3 -3
  45. data/lib/activerecord.rb +1 -0
  46. data/test/aaa_create_tables_test.rb +15 -2
  47. data/test/abstract_unit.rb +24 -17
  48. data/test/active_schema_test_mysql.rb +20 -8
  49. data/test/adapter_test.rb +23 -5
  50. data/test/adapter_test_sqlserver.rb +15 -1
  51. data/test/aggregations_test.rb +16 -1
  52. data/test/all.sh +2 -2
  53. data/test/associations/ar_joins_test.rb +0 -0
  54. data/test/associations/callbacks_test.rb +51 -30
  55. data/test/associations/cascaded_eager_loading_test.rb +1 -29
  56. data/test/associations/eager_singularization_test.rb +145 -0
  57. data/test/associations/eager_test.rb +42 -6
  58. data/test/associations/extension_test.rb +6 -1
  59. data/test/associations/inner_join_association_test.rb +88 -0
  60. data/test/associations/join_model_test.rb +47 -16
  61. data/test/associations_test.rb +449 -226
  62. data/test/attribute_methods_test.rb +97 -0
  63. data/test/base_test.rb +251 -105
  64. data/test/binary_test.rb +22 -27
  65. data/test/calculations_test.rb +37 -5
  66. data/test/callbacks_test.rb +23 -0
  67. data/test/connection_test_firebird.rb +2 -2
  68. data/test/connection_test_mysql.rb +30 -0
  69. data/test/connections/native_mysql/connection.rb +3 -0
  70. data/test/connections/native_sqlite/connection.rb +5 -14
  71. data/test/connections/native_sqlite3/connection.rb +5 -14
  72. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  73. data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
  74. data/test/datatype_test_postgresql.rb +178 -27
  75. data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
  76. data/test/defaults_test.rb +8 -1
  77. data/test/deprecated_finder_test.rb +7 -128
  78. data/test/finder_test.rb +192 -54
  79. data/test/fixtures/all/developers.yml +0 -0
  80. data/test/fixtures/all/people.csv +0 -0
  81. data/test/fixtures/all/tasks.yml +0 -0
  82. data/test/fixtures/author.rb +12 -5
  83. data/test/fixtures/binaries.yml +130 -435
  84. data/test/fixtures/category.rb +6 -0
  85. data/test/fixtures/company.rb +8 -1
  86. data/test/fixtures/computer.rb +1 -0
  87. data/test/fixtures/contact.rb +16 -0
  88. data/test/fixtures/customer.rb +2 -2
  89. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  90. data/test/fixtures/db_definitions/db2.sql +4 -0
  91. data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
  92. data/test/fixtures/db_definitions/firebird.sql +6 -0
  93. data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
  94. data/test/fixtures/db_definitions/frontbase.sql +5 -0
  95. data/test/fixtures/db_definitions/openbase.sql +41 -25
  96. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  97. data/test/fixtures/db_definitions/oracle.sql +5 -0
  98. data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
  99. data/test/fixtures/db_definitions/postgresql.sql +87 -58
  100. data/test/fixtures/db_definitions/postgresql2.sql +1 -2
  101. data/test/fixtures/db_definitions/schema.rb +280 -0
  102. data/test/fixtures/db_definitions/schema2.rb +11 -0
  103. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  104. data/test/fixtures/db_definitions/sqlite.sql +4 -0
  105. data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/sybase.sql +4 -0
  107. data/test/fixtures/developer.rb +10 -0
  108. data/test/fixtures/example.log +1 -0
  109. data/test/fixtures/flowers.jpg +0 -0
  110. data/test/fixtures/item.rb +7 -0
  111. data/test/fixtures/items.yml +4 -0
  112. data/test/fixtures/joke.rb +0 -3
  113. data/test/fixtures/matey.rb +4 -0
  114. data/test/fixtures/mateys.yml +4 -0
  115. data/test/fixtures/minimalistic.rb +2 -0
  116. data/test/fixtures/minimalistics.yml +2 -0
  117. data/test/fixtures/mixins.yml +2 -100
  118. data/test/fixtures/parrot.rb +13 -0
  119. data/test/fixtures/parrots.yml +27 -0
  120. data/test/fixtures/parrots_pirates.yml +7 -0
  121. data/test/fixtures/pirate.rb +5 -0
  122. data/test/fixtures/pirates.yml +9 -0
  123. data/test/fixtures/post.rb +1 -0
  124. data/test/fixtures/project.rb +3 -2
  125. data/test/fixtures/reserved_words/distinct.yml +5 -0
  126. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  127. data/test/fixtures/reserved_words/group.yml +14 -0
  128. data/test/fixtures/reserved_words/select.yml +8 -0
  129. data/test/fixtures/reserved_words/values.yml +7 -0
  130. data/test/fixtures/ship.rb +3 -0
  131. data/test/fixtures/ships.yml +5 -0
  132. data/test/fixtures/tagging.rb +4 -0
  133. data/test/fixtures/taggings.yml +8 -1
  134. data/test/fixtures/topic.rb +13 -1
  135. data/test/fixtures/treasure.rb +4 -0
  136. data/test/fixtures/treasures.yml +10 -0
  137. data/test/fixtures_test.rb +205 -24
  138. data/test/inheritance_test.rb +7 -1
  139. data/test/json_serialization_test.rb +180 -0
  140. data/test/lifecycle_test.rb +1 -1
  141. data/test/locking_test.rb +85 -2
  142. data/test/migration_test.rb +206 -40
  143. data/test/mixin_test.rb +13 -515
  144. data/test/pk_test.rb +3 -6
  145. data/test/query_cache_test.rb +104 -0
  146. data/test/reflection_test.rb +16 -0
  147. data/test/reserved_word_test_mysql.rb +177 -0
  148. data/test/schema_dumper_test.rb +38 -3
  149. data/test/serialization_test.rb +47 -0
  150. data/test/transactions_test.rb +74 -23
  151. data/test/unconnected_test.rb +1 -1
  152. data/test/validations_test.rb +322 -32
  153. data/test/xml_serialization_test.rb +121 -44
  154. metadata +48 -41
  155. data/examples/associations.rb +0 -87
  156. data/examples/shared_setup.rb +0 -15
  157. data/examples/validation.rb +0 -85
  158. data/lib/active_record/acts/list.rb +0 -256
  159. data/lib/active_record/acts/nested_set.rb +0 -211
  160. data/lib/active_record/acts/tree.rb +0 -96
  161. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
  162. data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
  163. data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
  164. data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
  165. data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
  166. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
  167. data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
  168. data/lib/active_record/deprecated_associations.rb +0 -104
  169. data/lib/active_record/deprecated_finders.rb +0 -44
  170. data/lib/active_record/vendor/simple.rb +0 -693
  171. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  172. data/lib/active_record/wrappings.rb +0 -58
  173. data/test/connections/native_sqlserver/connection.rb +0 -23
  174. data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
  175. data/test/deprecated_associations_test.rb +0 -396
  176. data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
  177. data/test/fixtures/db_definitions/mysql.sql +0 -234
  178. data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
  179. data/test/fixtures/db_definitions/mysql2.sql +0 -5
  180. data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
  181. data/test/fixtures/db_definitions/sqlserver.sql +0 -243
  182. data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
  183. data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
  184. data/test/fixtures/mixin.rb +0 -63
  185. data/test/mixin_nested_set_test.rb +0 -196
@@ -1,350 +0,0 @@
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 :decimal 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: derrick.spell@gmail.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
- :decimal => { :name => "decimal" },
72
- :datetime => { :name => "datetime" },
73
- :timestamp => { :name => "timestamp" },
74
- :time => { :name => "time" },
75
- :date => { :name => "date" },
76
- :binary => { :name => "object" },
77
- :boolean => { :name => "boolean" }
78
- }
79
- end
80
-
81
- def supports_migrations?
82
- false
83
- end
84
-
85
- def prefetch_primary_key?(table_name = nil)
86
- true
87
- end
88
-
89
- def default_sequence_name(table_name, primary_key) # :nodoc:
90
- "#{table_name} #{primary_key}"
91
- end
92
-
93
- def next_sequence_value(sequence_name)
94
- ary = sequence_name.split(' ')
95
- if (!ary[1]) then
96
- ary[0] =~ /(\w+)_nonstd_seq/
97
- ary[0] = $1
98
- end
99
- @connection.unique_row_id(ary[0], ary[1])
100
- end
101
-
102
-
103
- # QUOTING ==================================================
104
-
105
- def quote(value, column = nil)
106
- if value.kind_of?(String) && column && column.type == :binary
107
- "'#{@connection.insert_binary(value)}'"
108
- else
109
- super
110
- end
111
- end
112
-
113
- def quoted_true
114
- "1"
115
- end
116
-
117
- def quoted_false
118
- "0"
119
- end
120
-
121
-
122
-
123
- # DATABASE STATEMENTS ======================================
124
-
125
- def add_limit_offset!(sql, options) #:nodoc:
126
- if limit = options[:limit]
127
- unless offset = options[:offset]
128
- sql << " RETURN RESULTS #{limit}"
129
- else
130
- limit = limit + offset
131
- sql << " RETURN RESULTS #{offset} TO #{limit}"
132
- end
133
- end
134
- end
135
-
136
- def select_all(sql, name = nil) #:nodoc:
137
- select(sql, name)
138
- end
139
-
140
- def select_one(sql, name = nil) #:nodoc:
141
- add_limit_offset!(sql,{:limit => 1})
142
- results = select(sql, name)
143
- results.first if results
144
- end
145
-
146
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
147
- execute(sql, name)
148
- update_nulls_after_insert(sql, name, pk, id_value, sequence_name)
149
- id_value
150
- end
151
-
152
- def execute(sql, name = nil) #:nodoc:
153
- log(sql, name) { @connection.execute(sql) }
154
- end
155
-
156
- def update(sql, name = nil) #:nodoc:
157
- execute(sql, name).rows_affected
158
- end
159
-
160
- alias_method :delete, :update #:nodoc:
161
- #=begin
162
- def begin_db_transaction #:nodoc:
163
- execute "START TRANSACTION"
164
- rescue Exception
165
- # Transactions aren't supported
166
- end
167
-
168
- def commit_db_transaction #:nodoc:
169
- execute "COMMIT"
170
- rescue Exception
171
- # Transactions aren't supported
172
- end
173
-
174
- def rollback_db_transaction #:nodoc:
175
- execute "ROLLBACK"
176
- rescue Exception
177
- # Transactions aren't supported
178
- end
179
- #=end
180
-
181
- # SCHEMA STATEMENTS ========================================
182
-
183
- # Return the list of all tables in the schema search path.
184
- def tables(name = nil) #:nodoc:
185
- tables = @connection.tables
186
- tables.reject! { |t| /\A_SYS_/ === t }
187
- tables
188
- end
189
-
190
- def columns(table_name, name = nil) #:nodoc:
191
- sql = "SELECT * FROM _sys_tables "
192
- sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
193
- sql << "ORDER BY columnNumber"
194
- columns = []
195
- select_all(sql, name).each do |row|
196
- columns << OpenBaseColumn.new(row["fieldname"],
197
- default_value(row["defaultvalue"]),
198
- sql_type_name(row["typename"],row["length"]),
199
- row["notnull"]
200
- )
201
- # breakpoint() if row["fieldname"] == "content"
202
- end
203
- columns
204
- end
205
-
206
- def indexes(table_name, name = nil)#:nodoc:
207
- sql = "SELECT fieldname, notnull, searchindex, uniqueindex, clusteredindex FROM _sys_tables "
208
- sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
209
- sql << "AND primarykey=0 "
210
- sql << "AND (searchindex=1 OR uniqueindex=1 OR clusteredindex=1) "
211
- sql << "ORDER BY columnNumber"
212
- indexes = []
213
- execute(sql, name).each do |row|
214
- indexes << IndexDefinition.new(table_name,index_name(row),row[3]==1,[row[0]])
215
- end
216
- indexes
217
- end
218
-
219
-
220
- private
221
- def select(sql, name = nil)
222
- sql = translate_sql(sql)
223
- results = execute(sql, name)
224
-
225
- date_cols = []
226
- col_names = []
227
- results.column_infos.each do |info|
228
- col_names << info.name
229
- date_cols << info.name if info.type == "date"
230
- end
231
-
232
- rows = []
233
- if ( results.rows_affected )
234
- results.each do |row| # loop through result rows
235
- hashed_row = {}
236
- row.each_index do |index|
237
- hashed_row["#{col_names[index]}"] = row[index] unless col_names[index] == "_rowid"
238
- end
239
- date_cols.each do |name|
240
- unless hashed_row["#{name}"].nil? or hashed_row["#{name}"].empty?
241
- hashed_row["#{name}"] = Date.parse(hashed_row["#{name}"],false).to_s
242
- end
243
- end
244
- rows << hashed_row
245
- end
246
- end
247
- rows
248
- end
249
-
250
- def default_value(value)
251
- # Boolean type values
252
- return true if value =~ /true/
253
- return false if value =~ /false/
254
-
255
- # Date / Time magic values
256
- return Time.now.to_s if value =~ /^now\(\)/i
257
-
258
- # Empty strings should be set to null
259
- return nil if value.empty?
260
-
261
- # Otherwise return what we got from OpenBase
262
- # and hope for the best...
263
- return value
264
- end
265
-
266
- def sql_type_name(type_name, length)
267
- return "#{type_name}(#{length})" if ( type_name =~ /char/ )
268
- type_name
269
- end
270
-
271
- def index_name(row = [])
272
- name = ""
273
- name << "UNIQUE " if row[3]
274
- name << "CLUSTERED " if row[4]
275
- name << "INDEX"
276
- name
277
- end
278
-
279
- def translate_sql(sql)
280
-
281
- # Change table.* to list of columns in table
282
- while (sql =~ /SELECT.*\s(\w+)\.\*/)
283
- table = $1
284
- cols = columns(table)
285
- if ( cols.size == 0 ) then
286
- # Maybe this is a table alias
287
- sql =~ /FROM(.+?)(?:LEFT|OUTER|JOIN|WHERE|GROUP|HAVING|ORDER|RETURN|$)/
288
- $1 =~ /[\s|,](\w+)\s+#{table}[\s|,]/ # get the tablename for this alias
289
- cols = columns($1)
290
- end
291
- select_columns = []
292
- cols.each do |col|
293
- select_columns << table + '.' + col.name
294
- end
295
- sql.gsub!(table + '.*',select_columns.join(", ")) if select_columns
296
- end
297
-
298
- # Change JOIN clause to table list and WHERE condition
299
- while (sql =~ /JOIN/)
300
- sql =~ /((LEFT )?(OUTER )?JOIN (\w+) ON )(.+?)(?:LEFT|OUTER|JOIN|WHERE|GROUP|HAVING|ORDER|RETURN|$)/
301
- join_clause = $1 + $5
302
- is_outer_join = $3
303
- join_table = $4
304
- join_condition = $5
305
- join_condition.gsub!(/=/,"*") if is_outer_join
306
- if (sql =~ /WHERE/)
307
- sql.gsub!(/WHERE/,"WHERE (#{join_condition}) AND")
308
- else
309
- sql.gsub!(join_clause,"#{join_clause} WHERE #{join_condition}")
310
- end
311
- sql =~ /(FROM .+?)(?:LEFT|OUTER|JOIN|WHERE|$)/
312
- from_clause = $1
313
- sql.gsub!(from_clause,"#{from_clause}, #{join_table} ")
314
- sql.gsub!(join_clause,"")
315
- end
316
-
317
- # ORDER BY _rowid if no explicit ORDER BY
318
- # This will ensure that find(:first) returns the first inserted row
319
- if (sql !~ /(ORDER BY)|(GROUP BY)/)
320
- if (sql =~ /RETURN RESULTS/)
321
- sql.sub!(/RETURN RESULTS/,"ORDER BY _rowid RETURN RESULTS")
322
- else
323
- sql << " ORDER BY _rowid"
324
- end
325
- end
326
-
327
- sql
328
- end
329
-
330
- def update_nulls_after_insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
331
- sql =~ /INSERT INTO (\w+) \((.*)\) VALUES\s*\((.*)\)/m
332
- table = $1
333
- cols = $2
334
- values = $3
335
- cols = cols.split(',')
336
- values.gsub!(/'[^']*'/,"''")
337
- values.gsub!(/"[^"]*"/,"\"\"")
338
- values = values.split(',')
339
- update_cols = []
340
- values.each_index { |index| update_cols << cols[index] if values[index] =~ /\s*NULL\s*/ }
341
- update_sql = "UPDATE #{table} SET"
342
- update_cols.each { |col| update_sql << " #{col}=NULL," unless col.empty? }
343
- update_sql.chop!()
344
- update_sql << " WHERE #{pk}=#{quote(id_value)}"
345
- execute(update_sql, name + " NULL Correction") if update_cols.size > 0
346
- end
347
-
348
- end
349
- end
350
- end
@@ -1,690 +0,0 @@
1
- # oracle_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
- #
9
- # Implementation notes:
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
19
- #
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
24
-
25
- require 'active_record/connection_adapters/abstract_adapter'
26
- require 'delegate'
27
-
28
- begin
29
- require_library_or_gem 'oci8' unless self.class.const_defined? :OCI8
30
-
31
- module ActiveRecord
32
- class Base
33
- def self.oracle_connection(config) #:nodoc:
34
- # Use OCI8AutoRecover instead of normal OCI8 driver.
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)
42
- end
43
-
44
- # After setting large objects to empty, select the OCI8::LOB
45
- # and write back the data.
46
- after_save :write_lobs
47
- def write_lobs() #:nodoc:
48
- if connection.is_a?(ConnectionAdapters::OracleAdapter)
49
- self.class.columns.select { |c| c.sql_type =~ /LOB$/i }.each { |c|
50
- value = self[c.name]
51
- value = value.to_yaml if unserializable_attribute?(c.name, c)
52
- next if value.nil? || (value == '')
53
- lob = connection.select_one(
54
- "SELECT #{c.name} FROM #{self.class.table_name} WHERE #{self.class.primary_key} = #{quote_value(id)}",
55
- 'Writable Large Object')[c.name]
56
- lob.write value
57
- }
58
- end
59
- end
60
-
61
- private :write_lobs
62
- end
63
-
64
-
65
- module ConnectionAdapters #:nodoc:
66
- class OracleColumn < Column #:nodoc:
67
-
68
- def type_cast(value)
69
- return guess_date_or_time(value) if type == :datetime && OracleAdapter.emulate_dates
70
- super
71
- end
72
-
73
- private
74
- def simplified_type(field_type)
75
- return :boolean if OracleAdapter.emulate_booleans && field_type == 'NUMBER(1)'
76
- case field_type
77
- when /date|time/i then :datetime
78
- else super
79
- end
80
- end
81
-
82
- def guess_date_or_time(value)
83
- (value.hour == 0 and value.min == 0 and value.sec == 0) ?
84
- Date.new(value.year, value.month, value.day) : value
85
- end
86
- end
87
-
88
-
89
- # This is an Oracle/OCI adapter for the ActiveRecord persistence
90
- # framework. It relies upon the OCI8 driver, which works with Oracle 8i
91
- # and above. Most recent development has been on Debian Linux against
92
- # a 10g database, ActiveRecord 1.12.1 and OCI8 0.1.13.
93
- # See: http://rubyforge.org/projects/ruby-oci8/
94
- #
95
- # Usage notes:
96
- # * Key generation assumes a "${table_name}_seq" sequence is available
97
- # for all tables; the sequence name can be changed using
98
- # ActiveRecord::Base.set_sequence_name. When using Migrations, these
99
- # sequences are created automatically.
100
- # * Oracle uses DATE or TIMESTAMP datatypes for both dates and times.
101
- # Consequently some hacks are employed to map data back to Date or Time
102
- # in Ruby. If the column_name ends in _time it's created as a Ruby Time.
103
- # Else if the hours/minutes/seconds are 0, I make it a Ruby Date. Else
104
- # it's a Ruby Time. This is a bit nasty - but if you use Duck Typing
105
- # you'll probably not care very much. In 9i and up it's tempting to
106
- # map DATE to Date and TIMESTAMP to Time, but too many databases use
107
- # DATE for both. Timezones and sub-second precision on timestamps are
108
- # not supported.
109
- # * Default values that are functions (such as "SYSDATE") are not
110
- # supported. This is a restriction of the way ActiveRecord supports
111
- # default values.
112
- # * Support for Oracle8 is limited by Rails' use of ANSI join syntax, which
113
- # is supported in Oracle9i and later. You will need to use #finder_sql for
114
- # has_and_belongs_to_many associations to run against Oracle8.
115
- #
116
- # Required parameters:
117
- #
118
- # * <tt>:username</tt>
119
- # * <tt>:password</tt>
120
- # * <tt>:database</tt>
121
- class OracleAdapter < AbstractAdapter
122
-
123
- @@emulate_booleans = true
124
- cattr_accessor :emulate_booleans
125
-
126
- @@emulate_dates = false
127
- cattr_accessor :emulate_dates
128
-
129
- def adapter_name #:nodoc:
130
- 'Oracle'
131
- end
132
-
133
- def supports_migrations? #:nodoc:
134
- true
135
- end
136
-
137
- def native_database_types #:nodoc:
138
- {
139
- :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
140
- :string => { :name => "VARCHAR2", :limit => 255 },
141
- :text => { :name => "CLOB" },
142
- :integer => { :name => "NUMBER", :limit => 38 },
143
- :float => { :name => "NUMBER" },
144
- :decimal => { :name => "DECIMAL" },
145
- :datetime => { :name => "DATE" },
146
- :timestamp => { :name => "DATE" },
147
- :time => { :name => "DATE" },
148
- :date => { :name => "DATE" },
149
- :binary => { :name => "BLOB" },
150
- :boolean => { :name => "NUMBER", :limit => 1 }
151
- }
152
- end
153
-
154
- def table_alias_length
155
- 30
156
- end
157
-
158
- # QUOTING ==================================================
159
- #
160
- # see: abstract/quoting.rb
161
-
162
- # camelCase column names need to be quoted; not that anyone using Oracle
163
- # would really do this, but handling this case means we pass the test...
164
- def quote_column_name(name) #:nodoc:
165
- name =~ /[A-Z]/ ? "\"#{name}\"" : name
166
- end
167
-
168
- def quote_string(s) #:nodoc:
169
- s.gsub(/'/, "''")
170
- end
171
-
172
- def quote(value, column = nil) #:nodoc:
173
- if column && [:text, :binary].include?(column.type)
174
- %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
175
- else
176
- super
177
- end
178
- end
179
-
180
- def quoted_true
181
- "1"
182
- end
183
-
184
- def quoted_false
185
- "0"
186
- end
187
-
188
-
189
- # CONNECTION MANAGEMENT ====================================
190
- #
191
-
192
- # Returns true if the connection is active.
193
- def active?
194
- # Pings the connection to check if it's still good. Note that an
195
- # #active? method is also available, but that simply returns the
196
- # last known state, which isn't good enough if the connection has
197
- # gone stale since the last use.
198
- @connection.ping
199
- rescue OCIException
200
- false
201
- end
202
-
203
- # Reconnects to the database.
204
- def reconnect!
205
- @connection.reset!
206
- rescue OCIException => e
207
- @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}"
208
- end
209
-
210
- # Disconnects from the database.
211
- def disconnect!
212
- @connection.logoff rescue nil
213
- @connection.active = false
214
- end
215
-
216
-
217
- # DATABASE STATEMENTS ======================================
218
- #
219
- # see: abstract/database_statements.rb
220
-
221
- def execute(sql, name = nil) #:nodoc:
222
- log(sql, name) { @connection.exec sql }
223
- end
224
-
225
- # Returns the next sequence value from a sequence generator. Not generally
226
- # called directly; used by ActiveRecord to get the next primary key value
227
- # when inserting a new database record (see #prefetch_primary_key?).
228
- def next_sequence_value(sequence_name)
229
- id = 0
230
- @connection.exec("select #{sequence_name}.nextval id from dual") { |r| id = r[0].to_i }
231
- id
232
- end
233
-
234
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
235
- execute(sql, name)
236
- id_value
237
- end
238
-
239
- def begin_db_transaction #:nodoc:
240
- @connection.autocommit = false
241
- end
242
-
243
- def commit_db_transaction #:nodoc:
244
- @connection.commit
245
- ensure
246
- @connection.autocommit = true
247
- end
248
-
249
- def rollback_db_transaction #:nodoc:
250
- @connection.rollback
251
- ensure
252
- @connection.autocommit = true
253
- end
254
-
255
- def add_limit_offset!(sql, options) #:nodoc:
256
- offset = options[:offset] || 0
257
-
258
- if limit = options[:limit]
259
- sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
260
- elsif offset > 0
261
- sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
262
- end
263
- end
264
-
265
- # Returns true for Oracle adapter (since Oracle requires primary key
266
- # values to be pre-fetched before insert). See also #next_sequence_value.
267
- def prefetch_primary_key?(table_name = nil)
268
- true
269
- end
270
-
271
- def default_sequence_name(table, column) #:nodoc:
272
- "#{table}_seq"
273
- end
274
-
275
-
276
- # SCHEMA STATEMENTS ========================================
277
- #
278
- # see: abstract/schema_statements.rb
279
-
280
- def current_database #:nodoc:
281
- select_one("select sys_context('userenv','db_name') db from dual")["db"]
282
- end
283
-
284
- def tables(name = nil) #:nodoc:
285
- select_all("select lower(table_name) from user_tables").inject([]) do | tabs, t |
286
- tabs << t.to_a.first.last
287
- end
288
- end
289
-
290
- def indexes(table_name, name = nil) #:nodoc:
291
- result = select_all(<<-SQL, name)
292
- SELECT lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name
293
- FROM user_indexes i, user_ind_columns c
294
- WHERE i.table_name = '#{table_name.to_s.upcase}'
295
- AND c.index_name = i.index_name
296
- AND i.index_name NOT IN (SELECT uc.index_name FROM user_constraints uc WHERE uc.constraint_type = 'P')
297
- ORDER BY i.index_name, c.column_position
298
- SQL
299
-
300
- current_index = nil
301
- indexes = []
302
-
303
- result.each do |row|
304
- if current_index != row['index_name']
305
- indexes << IndexDefinition.new(table_name, row['index_name'], row['uniqueness'] == "UNIQUE", [])
306
- current_index = row['index_name']
307
- end
308
-
309
- indexes.last.columns << row['column_name']
310
- end
311
-
312
- indexes
313
- end
314
-
315
- def columns(table_name, name = nil) #:nodoc:
316
- (owner, table_name) = @connection.describe(table_name)
317
-
318
- table_cols = <<-SQL
319
- select column_name as name, data_type as sql_type, data_default, nullable,
320
- decode(data_type, 'NUMBER', data_precision,
321
- 'FLOAT', data_precision,
322
- 'VARCHAR2', data_length,
323
- 'CHAR', data_length,
324
- null) as limit,
325
- decode(data_type, 'NUMBER', data_scale, null) as scale
326
- from all_tab_columns
327
- where owner = '#{owner}'
328
- and table_name = '#{table_name}'
329
- order by column_id
330
- SQL
331
-
332
- select_all(table_cols, name).map do |row|
333
- limit, scale = row['limit'], row['scale']
334
- if limit || scale
335
- row['sql_type'] << "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
336
- end
337
-
338
- # clean up odd default spacing from Oracle
339
- if row['data_default']
340
- row['data_default'].sub!(/^(.*?)\s*$/, '\1')
341
- row['data_default'].sub!(/^'(.*)'$/, '\1')
342
- row['data_default'] = nil if row['data_default'] =~ /^null$/i
343
- end
344
-
345
- OracleColumn.new(oracle_downcase(row['name']),
346
- row['data_default'],
347
- row['sql_type'],
348
- row['nullable'] == 'Y')
349
- end
350
- end
351
-
352
- def create_table(name, options = {}) #:nodoc:
353
- super(name, options)
354
- seq_name = options[:sequence_name] || "#{name}_seq"
355
- execute "CREATE SEQUENCE #{seq_name} START WITH 10000" unless options[:id] == false
356
- end
357
-
358
- def rename_table(name, new_name) #:nodoc:
359
- execute "RENAME #{name} TO #{new_name}"
360
- execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
361
- end
362
-
363
- def drop_table(name, options = {}) #:nodoc:
364
- super(name)
365
- seq_name = options[:sequence_name] || "#{name}_seq"
366
- execute "DROP SEQUENCE #{seq_name}" rescue nil
367
- end
368
-
369
- def remove_index(table_name, options = {}) #:nodoc:
370
- execute "DROP INDEX #{index_name(table_name, options)}"
371
- end
372
-
373
- def change_column_default(table_name, column_name, default) #:nodoc:
374
- execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}"
375
- end
376
-
377
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
378
- change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
379
- add_column_options!(change_column_sql, options)
380
- execute(change_column_sql)
381
- end
382
-
383
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
384
- execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} to #{new_column_name}"
385
- end
386
-
387
- def remove_column(table_name, column_name) #:nodoc:
388
- execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
389
- end
390
-
391
- # Find a table's primary key and sequence.
392
- # *Note*: Only primary key is implemented - sequence will be nil.
393
- def pk_and_sequence_for(table_name)
394
- (owner, table_name) = @connection.describe(table_name)
395
-
396
- pks = select_values(<<-SQL, 'Primary Key')
397
- select cc.column_name
398
- from all_constraints c, all_cons_columns cc
399
- where c.owner = '#{owner}'
400
- and c.table_name = '#{table_name}'
401
- and c.constraint_type = 'P'
402
- and cc.owner = c.owner
403
- and cc.constraint_name = c.constraint_name
404
- SQL
405
-
406
- # only support single column keys
407
- pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
408
- end
409
-
410
- def structure_dump #:nodoc:
411
- s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
412
- structure << "create sequence #{seq.to_a.first.last};\n\n"
413
- end
414
-
415
- select_all("select table_name from user_tables").inject(s) do |structure, table|
416
- ddl = "create table #{table.to_a.first.last} (\n "
417
- cols = select_all(%Q{
418
- select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
419
- from user_tab_columns
420
- where table_name = '#{table.to_a.first.last}'
421
- order by column_id
422
- }).map do |row|
423
- col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
424
- if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
425
- col << "(#{row['data_precision'].to_i}"
426
- col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
427
- col << ')'
428
- elsif row['data_type'].include?('CHAR')
429
- col << "(#{row['data_length'].to_i})"
430
- end
431
- col << " default #{row['data_default']}" if !row['data_default'].nil?
432
- col << ' not null' if row['nullable'] == 'N'
433
- col
434
- end
435
- ddl << cols.join(",\n ")
436
- ddl << ");\n\n"
437
- structure << ddl
438
- end
439
- end
440
-
441
- def structure_drop #:nodoc:
442
- s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq|
443
- drop << "drop sequence #{seq.to_a.first.last};\n\n"
444
- end
445
-
446
- select_all("select table_name from user_tables").inject(s) do |drop, table|
447
- drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
448
- end
449
- end
450
-
451
- # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
452
- #
453
- # Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
454
- # queries. However, with those columns included in the SELECT DISTINCT list, you
455
- # won't actually get a distinct list of the column you want (presuming the column
456
- # has duplicates with multiple values for the ordered-by columns. So we use the
457
- # FIRST_VALUE function to get a single (first) value for each column, effectively
458
- # making every row the same.
459
- #
460
- # distinct("posts.id", "posts.created_at desc")
461
- def distinct(columns, order_by)
462
- return "DISTINCT #{columns}" if order_by.blank?
463
-
464
- # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
465
- # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
466
- order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
467
- order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
468
- "FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
469
- end
470
- sql = "DISTINCT #{columns}, "
471
- sql << order_columns * ", "
472
- end
473
-
474
- # ORDER BY clause for the passed order option.
475
- #
476
- # Uses column aliases as defined by #distinct.
477
- def add_order_by_for_association_limiting!(sql, options)
478
- return sql if options[:order].blank?
479
-
480
- order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
481
- order.map! {|s| $1 if s =~ / (.*)/}
482
- order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
483
-
484
- sql << "ORDER BY #{order}"
485
- end
486
-
487
- private
488
-
489
- def select(sql, name = nil)
490
- cursor = execute(sql, name)
491
- cols = cursor.get_col_names.map { |x| oracle_downcase(x) }
492
- rows = []
493
-
494
- while row = cursor.fetch
495
- hash = Hash.new
496
-
497
- cols.each_with_index do |col, i|
498
- hash[col] =
499
- case row[i]
500
- when OCI8::LOB
501
- name == 'Writable Large Object' ? row[i]: row[i].read
502
- when OraDate
503
- (row[i].hour == 0 and row[i].minute == 0 and row[i].second == 0) ?
504
- row[i].to_date : row[i].to_time
505
- else row[i]
506
- end unless col == 'raw_rnum_'
507
- end
508
-
509
- rows << hash
510
- end
511
-
512
- rows
513
- ensure
514
- cursor.close if cursor
515
- end
516
-
517
- # Oracle column names by default are case-insensitive, but treated as upcase;
518
- # for neatness, we'll downcase within Rails. EXCEPT that folks CAN quote
519
- # their column names when creating Oracle tables, which makes then case-sensitive.
520
- # I don't know anybody who does this, but we'll handle the theoretical case of a
521
- # camelCase column name. I imagine other dbs handle this different, since there's a
522
- # unit test that's currently failing test_oci.
523
- def oracle_downcase(column_name)
524
- column_name =~ /[a-z]/ ? column_name : column_name.downcase
525
- end
526
-
527
- end
528
- end
529
- end
530
-
531
-
532
- class OCI8 #:nodoc:
533
-
534
- # This OCI8 patch may not longer be required with the upcoming
535
- # release of version 0.2.
536
- class Cursor #:nodoc:
537
- alias :define_a_column_pre_ar :define_a_column
538
- def define_a_column(i)
539
- case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) }
540
- when 8 : @stmt.defineByPos(i, String, 65535) # Read LONG values
541
- when 187 : @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
542
- when 108
543
- if @parms[i - 1].attrGet(OCI_ATTR_TYPE_NAME) == 'XMLTYPE'
544
- @stmt.defineByPos(i, String, 65535)
545
- else
546
- raise 'unsupported datatype'
547
- end
548
- else define_a_column_pre_ar i
549
- end
550
- end
551
- end
552
-
553
- # missing constant from oci8 < 0.1.14
554
- OCI_PTYPE_UNK = 0 unless defined?(OCI_PTYPE_UNK)
555
-
556
- # Uses the describeAny OCI call to find the target owner and table_name
557
- # indicated by +name+, parsing through synonynms as necessary. Returns
558
- # an array of [owner, table_name].
559
- def describe(name)
560
- @desc ||= @@env.alloc(OCIDescribe)
561
- @desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14'
562
- @desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK) rescue raise %Q{"DESC #{name}" failed; does it exist?}
563
- info = @desc.attrGet(OCI_ATTR_PARAM)
564
-
565
- case info.attrGet(OCI_ATTR_PTYPE)
566
- when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW
567
- owner = info.attrGet(OCI_ATTR_OBJ_SCHEMA)
568
- table_name = info.attrGet(OCI_ATTR_OBJ_NAME)
569
- [owner, table_name]
570
- when OCI_PTYPE_SYN
571
- schema = info.attrGet(OCI_ATTR_SCHEMA_NAME)
572
- name = info.attrGet(OCI_ATTR_NAME)
573
- describe(schema + '.' + name)
574
- else raise %Q{"DESC #{name}" failed; not a table or view.}
575
- end
576
- end
577
-
578
- end
579
-
580
-
581
- # The OracleConnectionFactory factors out the code necessary to connect and
582
- # configure an Oracle/OCI connection.
583
- class OracleConnectionFactory #:nodoc:
584
- def new_connection(username, password, database, async, prefetch_rows, cursor_sharing)
585
- conn = OCI8.new username, password, database
586
- conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
587
- conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil
588
- conn.autocommit = true
589
- conn.non_blocking = true if async
590
- conn.prefetch_rows = prefetch_rows
591
- conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil
592
- conn
593
- end
594
- end
595
-
596
-
597
- # The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
598
- # reset functionality. If a call to #exec fails, and autocommit is turned on
599
- # (ie., we're not in the middle of a longer transaction), it will
600
- # automatically reconnect and try again. If autocommit is turned off,
601
- # this would be dangerous (as the earlier part of the implied transaction
602
- # may have failed silently if the connection died) -- so instead the
603
- # connection is marked as dead, to be reconnected on it's next use.
604
- class OCI8AutoRecover < DelegateClass(OCI8) #:nodoc:
605
- attr_accessor :active
606
- alias :active? :active
607
-
608
- cattr_accessor :auto_retry
609
- class << self
610
- alias :auto_retry? :auto_retry
611
- end
612
- @@auto_retry = false
613
-
614
- def initialize(config, factory = OracleConnectionFactory.new)
615
- @active = true
616
- @username, @password, @database, = config[:username], config[:password], config[:database]
617
- @async = config[:allow_concurrency]
618
- @prefetch_rows = config[:prefetch_rows] || 100
619
- @cursor_sharing = config[:cursor_sharing] || 'similar'
620
- @factory = factory
621
- @connection = @factory.new_connection @username, @password, @database, @async, @prefetch_rows, @cursor_sharing
622
- super @connection
623
- end
624
-
625
- # Checks connection, returns true if active. Note that ping actively
626
- # checks the connection, while #active? simply returns the last
627
- # known state.
628
- def ping
629
- @connection.exec("select 1 from dual") { |r| nil }
630
- @active = true
631
- rescue
632
- @active = false
633
- raise
634
- end
635
-
636
- # Resets connection, by logging off and creating a new connection.
637
- def reset!
638
- logoff rescue nil
639
- begin
640
- @connection = @factory.new_connection @username, @password, @database, @async, @prefetch_rows, @cursor_sharing
641
- __setobj__ @connection
642
- @active = true
643
- rescue
644
- @active = false
645
- raise
646
- end
647
- end
648
-
649
- # ORA-00028: your session has been killed
650
- # ORA-01012: not logged on
651
- # ORA-03113: end-of-file on communication channel
652
- # ORA-03114: not connected to ORACLE
653
- LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ]
654
-
655
- # Adds auto-recovery functionality.
656
- #
657
- # See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
658
- def exec(sql, *bindvars, &block)
659
- should_retry = self.class.auto_retry? && autocommit?
660
-
661
- begin
662
- @connection.exec(sql, *bindvars, &block)
663
- rescue OCIException => e
664
- raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code)
665
- @active = false
666
- raise unless should_retry
667
- should_retry = false
668
- reset! rescue nil
669
- retry
670
- end
671
- end
672
-
673
- end
674
-
675
- rescue LoadError
676
- # OCI8 driver is unavailable.
677
- module ActiveRecord # :nodoc:
678
- class Base
679
- @@oracle_error_message = "Oracle/OCI libraries could not be loaded: #{$!.to_s}"
680
- def self.oracle_connection(config) # :nodoc:
681
- # Set up a reasonable error message
682
- raise LoadError, @@oracle_error_message
683
- end
684
- def self.oci_connection(config) # :nodoc:
685
- # Set up a reasonable error message
686
- raise LoadError, @@oracle_error_message
687
- end
688
- end
689
- end
690
- end