activerecord_authorails 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (270) hide show
  1. data/CHANGELOG +3043 -0
  2. data/README +360 -0
  3. data/RUNNING_UNIT_TESTS +64 -0
  4. data/Rakefile +226 -0
  5. data/examples/associations.png +0 -0
  6. data/examples/associations.rb +87 -0
  7. data/examples/shared_setup.rb +15 -0
  8. data/examples/validation.rb +85 -0
  9. data/install.rb +30 -0
  10. data/lib/active_record.rb +85 -0
  11. data/lib/active_record/acts/list.rb +244 -0
  12. data/lib/active_record/acts/nested_set.rb +211 -0
  13. data/lib/active_record/acts/tree.rb +89 -0
  14. data/lib/active_record/aggregations.rb +191 -0
  15. data/lib/active_record/associations.rb +1637 -0
  16. data/lib/active_record/associations/association_collection.rb +190 -0
  17. data/lib/active_record/associations/association_proxy.rb +158 -0
  18. data/lib/active_record/associations/belongs_to_association.rb +56 -0
  19. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  20. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +169 -0
  21. data/lib/active_record/associations/has_many_association.rb +210 -0
  22. data/lib/active_record/associations/has_many_through_association.rb +247 -0
  23. data/lib/active_record/associations/has_one_association.rb +80 -0
  24. data/lib/active_record/attribute_methods.rb +75 -0
  25. data/lib/active_record/base.rb +2164 -0
  26. data/lib/active_record/calculations.rb +270 -0
  27. data/lib/active_record/callbacks.rb +367 -0
  28. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +279 -0
  29. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +58 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +343 -0
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -0
  33. data/lib/active_record/connection_adapters/abstract_adapter.rb +161 -0
  34. data/lib/active_record/connection_adapters/db2_adapter.rb +228 -0
  35. data/lib/active_record/connection_adapters/firebird_adapter.rb +728 -0
  36. data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
  37. data/lib/active_record/connection_adapters/mysql_adapter.rb +414 -0
  38. data/lib/active_record/connection_adapters/openbase_adapter.rb +350 -0
  39. data/lib/active_record/connection_adapters/oracle_adapter.rb +689 -0
  40. data/lib/active_record/connection_adapters/postgresql_adapter.rb +584 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +407 -0
  42. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +591 -0
  43. data/lib/active_record/connection_adapters/sybase_adapter.rb +662 -0
  44. data/lib/active_record/deprecated_associations.rb +104 -0
  45. data/lib/active_record/deprecated_finders.rb +44 -0
  46. data/lib/active_record/fixtures.rb +628 -0
  47. data/lib/active_record/locking/optimistic.rb +106 -0
  48. data/lib/active_record/locking/pessimistic.rb +77 -0
  49. data/lib/active_record/migration.rb +394 -0
  50. data/lib/active_record/observer.rb +178 -0
  51. data/lib/active_record/query_cache.rb +64 -0
  52. data/lib/active_record/reflection.rb +222 -0
  53. data/lib/active_record/schema.rb +58 -0
  54. data/lib/active_record/schema_dumper.rb +149 -0
  55. data/lib/active_record/timestamp.rb +51 -0
  56. data/lib/active_record/transactions.rb +136 -0
  57. data/lib/active_record/validations.rb +843 -0
  58. data/lib/active_record/vendor/db2.rb +362 -0
  59. data/lib/active_record/vendor/mysql.rb +1214 -0
  60. data/lib/active_record/vendor/simple.rb +693 -0
  61. data/lib/active_record/version.rb +9 -0
  62. data/lib/active_record/wrappers/yaml_wrapper.rb +15 -0
  63. data/lib/active_record/wrappings.rb +58 -0
  64. data/lib/active_record/xml_serialization.rb +308 -0
  65. data/test/aaa_create_tables_test.rb +59 -0
  66. data/test/abstract_unit.rb +77 -0
  67. data/test/active_schema_test_mysql.rb +31 -0
  68. data/test/adapter_test.rb +87 -0
  69. data/test/adapter_test_sqlserver.rb +81 -0
  70. data/test/aggregations_test.rb +95 -0
  71. data/test/all.sh +8 -0
  72. data/test/ar_schema_test.rb +33 -0
  73. data/test/association_inheritance_reload.rb +14 -0
  74. data/test/associations/callbacks_test.rb +126 -0
  75. data/test/associations/cascaded_eager_loading_test.rb +138 -0
  76. data/test/associations/eager_test.rb +393 -0
  77. data/test/associations/extension_test.rb +42 -0
  78. data/test/associations/join_model_test.rb +497 -0
  79. data/test/associations_test.rb +1809 -0
  80. data/test/attribute_methods_test.rb +49 -0
  81. data/test/base_test.rb +1586 -0
  82. data/test/binary_test.rb +37 -0
  83. data/test/calculations_test.rb +219 -0
  84. data/test/callbacks_test.rb +377 -0
  85. data/test/class_inheritable_attributes_test.rb +32 -0
  86. data/test/column_alias_test.rb +17 -0
  87. data/test/connection_test_firebird.rb +8 -0
  88. data/test/connections/native_db2/connection.rb +25 -0
  89. data/test/connections/native_firebird/connection.rb +26 -0
  90. data/test/connections/native_frontbase/connection.rb +27 -0
  91. data/test/connections/native_mysql/connection.rb +24 -0
  92. data/test/connections/native_openbase/connection.rb +21 -0
  93. data/test/connections/native_oracle/connection.rb +27 -0
  94. data/test/connections/native_postgresql/connection.rb +23 -0
  95. data/test/connections/native_sqlite/connection.rb +34 -0
  96. data/test/connections/native_sqlite3/connection.rb +34 -0
  97. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  98. data/test/connections/native_sqlserver/connection.rb +23 -0
  99. data/test/connections/native_sqlserver_odbc/connection.rb +25 -0
  100. data/test/connections/native_sybase/connection.rb +23 -0
  101. data/test/copy_table_sqlite.rb +64 -0
  102. data/test/datatype_test_postgresql.rb +52 -0
  103. data/test/default_test_firebird.rb +16 -0
  104. data/test/defaults_test.rb +60 -0
  105. data/test/deprecated_associations_test.rb +396 -0
  106. data/test/deprecated_finder_test.rb +151 -0
  107. data/test/empty_date_time_test.rb +25 -0
  108. data/test/finder_test.rb +504 -0
  109. data/test/fixtures/accounts.yml +28 -0
  110. data/test/fixtures/author.rb +99 -0
  111. data/test/fixtures/author_favorites.yml +4 -0
  112. data/test/fixtures/authors.yml +7 -0
  113. data/test/fixtures/auto_id.rb +4 -0
  114. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
  115. data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
  116. data/test/fixtures/bad_fixtures/blank_line +3 -0
  117. data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
  118. data/test/fixtures/bad_fixtures/missing_value +1 -0
  119. data/test/fixtures/binary.rb +2 -0
  120. data/test/fixtures/categories.yml +14 -0
  121. data/test/fixtures/categories/special_categories.yml +9 -0
  122. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  123. data/test/fixtures/categories_ordered.yml +7 -0
  124. data/test/fixtures/categories_posts.yml +23 -0
  125. data/test/fixtures/categorization.rb +5 -0
  126. data/test/fixtures/categorizations.yml +17 -0
  127. data/test/fixtures/category.rb +20 -0
  128. data/test/fixtures/column_name.rb +3 -0
  129. data/test/fixtures/comment.rb +23 -0
  130. data/test/fixtures/comments.yml +59 -0
  131. data/test/fixtures/companies.yml +55 -0
  132. data/test/fixtures/company.rb +107 -0
  133. data/test/fixtures/company_in_module.rb +59 -0
  134. data/test/fixtures/computer.rb +3 -0
  135. data/test/fixtures/computers.yml +4 -0
  136. data/test/fixtures/course.rb +3 -0
  137. data/test/fixtures/courses.yml +7 -0
  138. data/test/fixtures/customer.rb +55 -0
  139. data/test/fixtures/customers.yml +17 -0
  140. data/test/fixtures/db_definitions/db2.drop.sql +32 -0
  141. data/test/fixtures/db_definitions/db2.sql +231 -0
  142. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  143. data/test/fixtures/db_definitions/db22.sql +5 -0
  144. data/test/fixtures/db_definitions/firebird.drop.sql +63 -0
  145. data/test/fixtures/db_definitions/firebird.sql +304 -0
  146. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  147. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  148. data/test/fixtures/db_definitions/frontbase.drop.sql +32 -0
  149. data/test/fixtures/db_definitions/frontbase.sql +268 -0
  150. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  151. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  152. data/test/fixtures/db_definitions/mysql.drop.sql +32 -0
  153. data/test/fixtures/db_definitions/mysql.sql +234 -0
  154. data/test/fixtures/db_definitions/mysql2.drop.sql +2 -0
  155. data/test/fixtures/db_definitions/mysql2.sql +5 -0
  156. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  157. data/test/fixtures/db_definitions/openbase.sql +302 -0
  158. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  159. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  160. data/test/fixtures/db_definitions/oracle.drop.sql +65 -0
  161. data/test/fixtures/db_definitions/oracle.sql +325 -0
  162. data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
  163. data/test/fixtures/db_definitions/oracle2.sql +6 -0
  164. data/test/fixtures/db_definitions/postgresql.drop.sql +37 -0
  165. data/test/fixtures/db_definitions/postgresql.sql +263 -0
  166. data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
  167. data/test/fixtures/db_definitions/postgresql2.sql +5 -0
  168. data/test/fixtures/db_definitions/schema.rb +60 -0
  169. data/test/fixtures/db_definitions/sqlite.drop.sql +32 -0
  170. data/test/fixtures/db_definitions/sqlite.sql +215 -0
  171. data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
  172. data/test/fixtures/db_definitions/sqlite2.sql +5 -0
  173. data/test/fixtures/db_definitions/sqlserver.drop.sql +34 -0
  174. data/test/fixtures/db_definitions/sqlserver.sql +243 -0
  175. data/test/fixtures/db_definitions/sqlserver2.drop.sql +2 -0
  176. data/test/fixtures/db_definitions/sqlserver2.sql +5 -0
  177. data/test/fixtures/db_definitions/sybase.drop.sql +34 -0
  178. data/test/fixtures/db_definitions/sybase.sql +218 -0
  179. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  180. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  181. data/test/fixtures/default.rb +2 -0
  182. data/test/fixtures/developer.rb +52 -0
  183. data/test/fixtures/developers.yml +21 -0
  184. data/test/fixtures/developers_projects.yml +17 -0
  185. data/test/fixtures/developers_projects/david_action_controller +3 -0
  186. data/test/fixtures/developers_projects/david_active_record +3 -0
  187. data/test/fixtures/developers_projects/jamis_active_record +2 -0
  188. data/test/fixtures/edge.rb +5 -0
  189. data/test/fixtures/edges.yml +6 -0
  190. data/test/fixtures/entrant.rb +3 -0
  191. data/test/fixtures/entrants.yml +14 -0
  192. data/test/fixtures/fk_test_has_fk.yml +3 -0
  193. data/test/fixtures/fk_test_has_pk.yml +2 -0
  194. data/test/fixtures/flowers.jpg +0 -0
  195. data/test/fixtures/funny_jokes.yml +10 -0
  196. data/test/fixtures/joke.rb +6 -0
  197. data/test/fixtures/keyboard.rb +3 -0
  198. data/test/fixtures/legacy_thing.rb +3 -0
  199. data/test/fixtures/legacy_things.yml +3 -0
  200. data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
  201. data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
  202. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  203. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  204. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  205. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  206. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  207. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  208. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  209. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  210. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  211. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  212. data/test/fixtures/mixed_case_monkey.rb +3 -0
  213. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  214. data/test/fixtures/mixin.rb +63 -0
  215. data/test/fixtures/mixins.yml +127 -0
  216. data/test/fixtures/movie.rb +5 -0
  217. data/test/fixtures/movies.yml +7 -0
  218. data/test/fixtures/naked/csv/accounts.csv +1 -0
  219. data/test/fixtures/naked/yml/accounts.yml +1 -0
  220. data/test/fixtures/naked/yml/companies.yml +1 -0
  221. data/test/fixtures/naked/yml/courses.yml +1 -0
  222. data/test/fixtures/order.rb +4 -0
  223. data/test/fixtures/people.yml +3 -0
  224. data/test/fixtures/person.rb +4 -0
  225. data/test/fixtures/post.rb +58 -0
  226. data/test/fixtures/posts.yml +48 -0
  227. data/test/fixtures/project.rb +27 -0
  228. data/test/fixtures/projects.yml +7 -0
  229. data/test/fixtures/reader.rb +4 -0
  230. data/test/fixtures/readers.yml +4 -0
  231. data/test/fixtures/reply.rb +37 -0
  232. data/test/fixtures/subject.rb +4 -0
  233. data/test/fixtures/subscriber.rb +6 -0
  234. data/test/fixtures/subscribers/first +2 -0
  235. data/test/fixtures/subscribers/second +2 -0
  236. data/test/fixtures/tag.rb +7 -0
  237. data/test/fixtures/tagging.rb +6 -0
  238. data/test/fixtures/taggings.yml +18 -0
  239. data/test/fixtures/tags.yml +7 -0
  240. data/test/fixtures/task.rb +3 -0
  241. data/test/fixtures/tasks.yml +7 -0
  242. data/test/fixtures/topic.rb +25 -0
  243. data/test/fixtures/topics.yml +22 -0
  244. data/test/fixtures/vertex.rb +9 -0
  245. data/test/fixtures/vertices.yml +4 -0
  246. data/test/fixtures_test.rb +401 -0
  247. data/test/inheritance_test.rb +205 -0
  248. data/test/lifecycle_test.rb +137 -0
  249. data/test/locking_test.rb +190 -0
  250. data/test/method_scoping_test.rb +416 -0
  251. data/test/migration_test.rb +768 -0
  252. data/test/migration_test_firebird.rb +124 -0
  253. data/test/mixin_nested_set_test.rb +196 -0
  254. data/test/mixin_test.rb +550 -0
  255. data/test/modules_test.rb +34 -0
  256. data/test/multiple_db_test.rb +60 -0
  257. data/test/pk_test.rb +104 -0
  258. data/test/readonly_test.rb +107 -0
  259. data/test/reflection_test.rb +159 -0
  260. data/test/schema_authorization_test_postgresql.rb +75 -0
  261. data/test/schema_dumper_test.rb +96 -0
  262. data/test/schema_test_postgresql.rb +64 -0
  263. data/test/synonym_test_oracle.rb +17 -0
  264. data/test/table_name_test_sqlserver.rb +23 -0
  265. data/test/threaded_connections_test.rb +48 -0
  266. data/test/transactions_test.rb +230 -0
  267. data/test/unconnected_test.rb +32 -0
  268. data/test/validations_test.rb +1097 -0
  269. data/test/xml_serialization_test.rb +125 -0
  270. metadata +365 -0
@@ -0,0 +1,689 @@
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
+ null) as limit,
324
+ decode(data_type, 'NUMBER', data_scale, null) as scale
325
+ from all_tab_columns
326
+ where owner = '#{owner}'
327
+ and table_name = '#{table_name}'
328
+ order by column_id
329
+ SQL
330
+
331
+ select_all(table_cols, name).map do |row|
332
+ limit, scale = row['limit'], row['scale']
333
+ if limit || scale
334
+ row['sql_type'] << "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
335
+ end
336
+
337
+ # clean up odd default spacing from Oracle
338
+ if row['data_default']
339
+ row['data_default'].sub!(/^(.*?)\s*$/, '\1')
340
+ row['data_default'].sub!(/^'(.*)'$/, '\1')
341
+ row['data_default'] = nil if row['data_default'] =~ /^null$/i
342
+ end
343
+
344
+ OracleColumn.new(oracle_downcase(row['name']),
345
+ row['data_default'],
346
+ row['sql_type'],
347
+ row['nullable'] == 'Y')
348
+ end
349
+ end
350
+
351
+ def create_table(name, options = {}) #:nodoc:
352
+ super(name, options)
353
+ seq_name = options[:sequence_name] || "#{name}_seq"
354
+ execute "CREATE SEQUENCE #{seq_name} START WITH 10000" unless options[:id] == false
355
+ end
356
+
357
+ def rename_table(name, new_name) #:nodoc:
358
+ execute "RENAME #{name} TO #{new_name}"
359
+ execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
360
+ end
361
+
362
+ def drop_table(name, options = {}) #:nodoc:
363
+ super(name)
364
+ seq_name = options[:sequence_name] || "#{name}_seq"
365
+ execute "DROP SEQUENCE #{seq_name}" rescue nil
366
+ end
367
+
368
+ def remove_index(table_name, options = {}) #:nodoc:
369
+ execute "DROP INDEX #{index_name(table_name, options)}"
370
+ end
371
+
372
+ def change_column_default(table_name, column_name, default) #:nodoc:
373
+ execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}"
374
+ end
375
+
376
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
377
+ change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
378
+ add_column_options!(change_column_sql, options)
379
+ execute(change_column_sql)
380
+ end
381
+
382
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
383
+ execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} to #{new_column_name}"
384
+ end
385
+
386
+ def remove_column(table_name, column_name) #:nodoc:
387
+ execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
388
+ end
389
+
390
+ # Find a table's primary key and sequence.
391
+ # *Note*: Only primary key is implemented - sequence will be nil.
392
+ def pk_and_sequence_for(table_name)
393
+ (owner, table_name) = @connection.describe(table_name)
394
+
395
+ pks = select_values(<<-SQL, 'Primary Key')
396
+ select cc.column_name
397
+ from all_constraints c, all_cons_columns cc
398
+ where c.owner = '#{owner}'
399
+ and c.table_name = '#{table_name}'
400
+ and c.constraint_type = 'P'
401
+ and cc.owner = c.owner
402
+ and cc.constraint_name = c.constraint_name
403
+ SQL
404
+
405
+ # only support single column keys
406
+ pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
407
+ end
408
+
409
+ def structure_dump #:nodoc:
410
+ s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
411
+ structure << "create sequence #{seq.to_a.first.last};\n\n"
412
+ end
413
+
414
+ select_all("select table_name from user_tables").inject(s) do |structure, table|
415
+ ddl = "create table #{table.to_a.first.last} (\n "
416
+ cols = select_all(%Q{
417
+ select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
418
+ from user_tab_columns
419
+ where table_name = '#{table.to_a.first.last}'
420
+ order by column_id
421
+ }).map do |row|
422
+ col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
423
+ if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
424
+ col << "(#{row['data_precision'].to_i}"
425
+ col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
426
+ col << ')'
427
+ elsif row['data_type'].include?('CHAR')
428
+ col << "(#{row['data_length'].to_i})"
429
+ end
430
+ col << " default #{row['data_default']}" if !row['data_default'].nil?
431
+ col << ' not null' if row['nullable'] == 'N'
432
+ col
433
+ end
434
+ ddl << cols.join(",\n ")
435
+ ddl << ");\n\n"
436
+ structure << ddl
437
+ end
438
+ end
439
+
440
+ def structure_drop #:nodoc:
441
+ s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq|
442
+ drop << "drop sequence #{seq.to_a.first.last};\n\n"
443
+ end
444
+
445
+ select_all("select table_name from user_tables").inject(s) do |drop, table|
446
+ drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
447
+ end
448
+ end
449
+
450
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
451
+ #
452
+ # Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
453
+ # queries. However, with those columns included in the SELECT DISTINCT list, you
454
+ # won't actually get a distinct list of the column you want (presuming the column
455
+ # has duplicates with multiple values for the ordered-by columns. So we use the
456
+ # FIRST_VALUE function to get a single (first) value for each column, effectively
457
+ # making every row the same.
458
+ #
459
+ # distinct("posts.id", "posts.created_at desc")
460
+ def distinct(columns, order_by)
461
+ return "DISTINCT #{columns}" if order_by.blank?
462
+
463
+ # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
464
+ # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
465
+ order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
466
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
467
+ "FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
468
+ end
469
+ sql = "DISTINCT #{columns}, "
470
+ sql << order_columns * ", "
471
+ end
472
+
473
+ # ORDER BY clause for the passed order option.
474
+ #
475
+ # Uses column aliases as defined by #distinct.
476
+ def add_order_by_for_association_limiting!(sql, options)
477
+ return sql if options[:order].blank?
478
+
479
+ order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
480
+ order.map! {|s| $1 if s =~ / (.*)/}
481
+ order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
482
+
483
+ sql << "ORDER BY #{order}"
484
+ end
485
+
486
+ private
487
+
488
+ def select(sql, name = nil)
489
+ cursor = execute(sql, name)
490
+ cols = cursor.get_col_names.map { |x| oracle_downcase(x) }
491
+ rows = []
492
+
493
+ while row = cursor.fetch
494
+ hash = Hash.new
495
+
496
+ cols.each_with_index do |col, i|
497
+ hash[col] =
498
+ case row[i]
499
+ when OCI8::LOB
500
+ name == 'Writable Large Object' ? row[i]: row[i].read
501
+ when OraDate
502
+ (row[i].hour == 0 and row[i].minute == 0 and row[i].second == 0) ?
503
+ row[i].to_date : row[i].to_time
504
+ else row[i]
505
+ end unless col == 'raw_rnum_'
506
+ end
507
+
508
+ rows << hash
509
+ end
510
+
511
+ rows
512
+ ensure
513
+ cursor.close if cursor
514
+ end
515
+
516
+ # Oracle column names by default are case-insensitive, but treated as upcase;
517
+ # for neatness, we'll downcase within Rails. EXCEPT that folks CAN quote
518
+ # their column names when creating Oracle tables, which makes then case-sensitive.
519
+ # I don't know anybody who does this, but we'll handle the theoretical case of a
520
+ # camelCase column name. I imagine other dbs handle this different, since there's a
521
+ # unit test that's currently failing test_oci.
522
+ def oracle_downcase(column_name)
523
+ column_name =~ /[a-z]/ ? column_name : column_name.downcase
524
+ end
525
+
526
+ end
527
+ end
528
+ end
529
+
530
+
531
+ class OCI8 #:nodoc:
532
+
533
+ # This OCI8 patch may not longer be required with the upcoming
534
+ # release of version 0.2.
535
+ class Cursor #:nodoc:
536
+ alias :define_a_column_pre_ar :define_a_column
537
+ def define_a_column(i)
538
+ case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) }
539
+ when 8 : @stmt.defineByPos(i, String, 65535) # Read LONG values
540
+ when 187 : @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
541
+ when 108
542
+ if @parms[i - 1].attrGet(OCI_ATTR_TYPE_NAME) == 'XMLTYPE'
543
+ @stmt.defineByPos(i, String, 65535)
544
+ else
545
+ raise 'unsupported datatype'
546
+ end
547
+ else define_a_column_pre_ar i
548
+ end
549
+ end
550
+ end
551
+
552
+ # missing constant from oci8 < 0.1.14
553
+ OCI_PTYPE_UNK = 0 unless defined?(OCI_PTYPE_UNK)
554
+
555
+ # Uses the describeAny OCI call to find the target owner and table_name
556
+ # indicated by +name+, parsing through synonynms as necessary. Returns
557
+ # an array of [owner, table_name].
558
+ def describe(name)
559
+ @desc ||= @@env.alloc(OCIDescribe)
560
+ @desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14'
561
+ @desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK) rescue raise %Q{"DESC #{name}" failed; does it exist?}
562
+ info = @desc.attrGet(OCI_ATTR_PARAM)
563
+
564
+ case info.attrGet(OCI_ATTR_PTYPE)
565
+ when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW
566
+ owner = info.attrGet(OCI_ATTR_OBJ_SCHEMA)
567
+ table_name = info.attrGet(OCI_ATTR_OBJ_NAME)
568
+ [owner, table_name]
569
+ when OCI_PTYPE_SYN
570
+ schema = info.attrGet(OCI_ATTR_SCHEMA_NAME)
571
+ name = info.attrGet(OCI_ATTR_NAME)
572
+ describe(schema + '.' + name)
573
+ else raise %Q{"DESC #{name}" failed; not a table or view.}
574
+ end
575
+ end
576
+
577
+ end
578
+
579
+
580
+ # The OracleConnectionFactory factors out the code necessary to connect and
581
+ # configure an Oracle/OCI connection.
582
+ class OracleConnectionFactory #:nodoc:
583
+ def new_connection(username, password, database, async, prefetch_rows, cursor_sharing)
584
+ conn = OCI8.new username, password, database
585
+ conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
586
+ conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil
587
+ conn.autocommit = true
588
+ conn.non_blocking = true if async
589
+ conn.prefetch_rows = prefetch_rows
590
+ conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil
591
+ conn
592
+ end
593
+ end
594
+
595
+
596
+ # The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
597
+ # reset functionality. If a call to #exec fails, and autocommit is turned on
598
+ # (ie., we're not in the middle of a longer transaction), it will
599
+ # automatically reconnect and try again. If autocommit is turned off,
600
+ # this would be dangerous (as the earlier part of the implied transaction
601
+ # may have failed silently if the connection died) -- so instead the
602
+ # connection is marked as dead, to be reconnected on it's next use.
603
+ class OCI8AutoRecover < DelegateClass(OCI8) #:nodoc:
604
+ attr_accessor :active
605
+ alias :active? :active
606
+
607
+ cattr_accessor :auto_retry
608
+ class << self
609
+ alias :auto_retry? :auto_retry
610
+ end
611
+ @@auto_retry = false
612
+
613
+ def initialize(config, factory = OracleConnectionFactory.new)
614
+ @active = true
615
+ @username, @password, @database, = config[:username], config[:password], config[:database]
616
+ @async = config[:allow_concurrency]
617
+ @prefetch_rows = config[:prefetch_rows] || 100
618
+ @cursor_sharing = config[:cursor_sharing] || 'similar'
619
+ @factory = factory
620
+ @connection = @factory.new_connection @username, @password, @database, @async, @prefetch_rows, @cursor_sharing
621
+ super @connection
622
+ end
623
+
624
+ # Checks connection, returns true if active. Note that ping actively
625
+ # checks the connection, while #active? simply returns the last
626
+ # known state.
627
+ def ping
628
+ @connection.exec("select 1 from dual") { |r| nil }
629
+ @active = true
630
+ rescue
631
+ @active = false
632
+ raise
633
+ end
634
+
635
+ # Resets connection, by logging off and creating a new connection.
636
+ def reset!
637
+ logoff rescue nil
638
+ begin
639
+ @connection = @factory.new_connection @username, @password, @database, @async, @prefetch_rows, @cursor_sharing
640
+ __setobj__ @connection
641
+ @active = true
642
+ rescue
643
+ @active = false
644
+ raise
645
+ end
646
+ end
647
+
648
+ # ORA-00028: your session has been killed
649
+ # ORA-01012: not logged on
650
+ # ORA-03113: end-of-file on communication channel
651
+ # ORA-03114: not connected to ORACLE
652
+ LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ]
653
+
654
+ # Adds auto-recovery functionality.
655
+ #
656
+ # See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
657
+ def exec(sql, *bindvars, &block)
658
+ should_retry = self.class.auto_retry? && autocommit?
659
+
660
+ begin
661
+ @connection.exec(sql, *bindvars, &block)
662
+ rescue OCIException => e
663
+ raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code)
664
+ @active = false
665
+ raise unless should_retry
666
+ should_retry = false
667
+ reset! rescue nil
668
+ retry
669
+ end
670
+ end
671
+
672
+ end
673
+
674
+ rescue LoadError
675
+ # OCI8 driver is unavailable.
676
+ module ActiveRecord # :nodoc:
677
+ class Base
678
+ @@oracle_error_message = "Oracle/OCI libraries could not be loaded: #{$!.to_s}"
679
+ def self.oracle_connection(config) # :nodoc:
680
+ # Set up a reasonable error message
681
+ raise LoadError, @@oracle_error_message
682
+ end
683
+ def self.oci_connection(config) # :nodoc:
684
+ # Set up a reasonable error message
685
+ raise LoadError, @@oracle_error_message
686
+ end
687
+ end
688
+ end
689
+ end