activerecord_authorails 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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