1 module ActiveRecord::ConnectionAdapters
2 OracleAdapter = Class.new(AbstractAdapter) unless const_defined?(:OracleAdapter)
8 unless @lob_callback_added
9 ActiveRecord::Base.class_eval do
10 def after_save_with_oracle_lob
11 self.class.columns.select { |c| c.sql_type =~ /LOB\(|LOB$/i }.each do |c|
13 value = value.to_yaml if unserializable_attribute?(c.name, c)
14 next if value.nil? || (value == '')
16 connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
21 ActiveRecord::Base.after_save :after_save_with_oracle_lob
22 @lob_callback_added = true
24 require 'arjdbc/jdbc/quoted_primary_key'
25 ActiveRecord::Base.extend ArJdbc::QuotedPrimaryKeyExtension
26 (class << mod; self; end).class_eval do
27 alias_chained_method :insert, :query_dirty, :ora_insert
28 alias_chained_method :columns, :query_cache, :ora_columns
32 def self.column_selector
33 [/oracle/i, lambda {|cfg,col| col.extend(::ArJdbc::Oracle::Column)}]
36 def self.jdbc_connection_class
37 ::ActiveRecord::ConnectionAdapters::OracleJdbcConnection
43 if val && @sql_type =~ /^NUMBER$/i
49 return nil if value.nil?
51 when :datetime then ArJdbc::Oracle::Column.string_to_time(value, self.class)
57 def type_cast_code(var_name)
59 when :datetime then "ArJdbc::Oracle::Column.string_to_time(#{var_name}, self.class)"
65 def self.string_to_time(string, klass)
66 time = klass.string_to_time(string)
67 guess_date_or_time(time)
70 def self.guess_date_or_time(value)
71 return value if Date === value
72 (value && value.hour == 0 && value.min == 0 && value.sec == 0) ?
73 Date.new(value.year, value.month, value.day) : value
77 def simplified_type(field_type)
79 when /^number\(1\)$/i then :boolean
80 when /char/i then :string
81 when /float|double/i then :float
82 when /int/i then :integer
83 when /num|dec|real/i then extract_scale(field_type) == 0 ? :integer : :decimal
84 when /date|time/i then :datetime
85 when /clob/i then :text
86 when /blob/i then :binary
90 # Post process default value from JDBC into a Rails-friendly format (columns{-internal})
91 def default_value(value)
92 return nil unless value
94 # Not sure why we need this for Oracle?
97 return nil if value == "null"
99 # sysdate default should be treated like a null value
100 return nil if value.downcase == "sysdate"
102 # jdbc returns column default strings with actual single quotes around the value.
103 return $1 if value =~ /^'(.*)'$/
114 { 'oracle' => Arel::Visitors::Oracle }
117 # TODO: use this instead of the QuotedPrimaryKey logic and execute_id_insert?
118 # def prefetch_primary_key?(table_name = nil)
119 # columns(table_name).detect {|c| c.primary } if table_name
122 def table_alias_length
126 def default_sequence_name(table, column = nil) #:nodoc:
130 def create_table(name, options = {}) #:nodoc:
132 seq_name = options[:sequence_name] || default_sequence_name(name)
133 start_value = options[:sequence_start_value] || 10000
134 raise ActiveRecord::StatementInvalid.new("name #{seq_name} too long") if seq_name.length > table_alias_length
135 execute "CREATE SEQUENCE #{seq_name} START WITH #{start_value}" unless options[:id] == false
138 def rename_table(name, new_name) #:nodoc:
139 execute "RENAME #{name} TO #{new_name}"
140 execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
143 def drop_table(name, options = {}) #:nodoc:
144 super(name) rescue nil
145 seq_name = options[:sequence_name] || default_sequence_name(name)
146 execute "DROP SEQUENCE #{seq_name}" rescue nil
149 def recreate_database(name)
150 tables.each{ |table| drop_table(table) }
153 def drop_database(name)
154 recreate_database(name)
157 def next_sequence_value(sequence_name)
158 # avoid #select or #select_one so that the sequence values aren't cached
159 execute("select #{sequence_name}.nextval id from dual").first['id'].to_i
162 def sql_literal?(value)
163 defined?(::Arel::SqlLiteral) && ::Arel::SqlLiteral === value
166 def ora_insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
167 if (id_value && !sql_literal?(id_value)) || pk.nil?
168 # Pre-assigned id or table without a primary key
169 # Presence of #to_sql means an Arel literal bind variable
170 # that should use #execute_id_insert below
173 # Assume the sql contains a bind-variable for the id
174 # Extract the table from the insert sql. Yuck.
175 table = sql.split(" ", 4)[2].gsub('"', '')
176 sequence_name ||= default_sequence_name(table)
177 id_value = next_sequence_value(sequence_name)
179 @connection.execute_id_insert(sql,id_value)
185 def indexes(table, name = nil)
186 @connection.indexes(table, name, @connection.connection.meta_data.user_name)
189 def _execute(sql, name = nil)
191 when /\A\(?\s*(select|show)/i then
192 @connection.execute_query(sql)
194 @connection.execute_update(sql)
199 tp[:primary_key] = "NUMBER(38) NOT NULL PRIMARY KEY"
200 tp[:integer] = { :name => "NUMBER", :limit => 38 }
203 tp[:datetime] = { :name => "TIMESTAMP" }
204 tp[:timestamp] = { :name => "TIMESTAMP" }
207 tp[:time] = { :name => "DATE" }
208 tp[:date] = { :name => "DATE" }
212 tp[:big_integer] = { :name => "NUMBER", :limit => 38 }
217 def add_limit_offset!(sql, options) #:nodoc:
218 offset = options[:offset] || 0
220 if limit = options[:limit]
221 sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
223 sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
227 def current_database #:nodoc:
228 select_one("select sys_context('userenv','db_name') db from dual")["db"]
231 def remove_index(table_name, options = {}) #:nodoc:
232 execute "DROP INDEX #{index_name(table_name, options)}"
235 def change_column_default(table_name, column_name, default) #:nodoc:
236 execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}"
239 def add_column_options!(sql, options) #:nodoc:
240 # handle case of defaults for CLOB columns, which would otherwise get "quoted" incorrectly
241 if options_include_default?(options) && (column = options[:column]) && column.type == :text
242 sql << " DEFAULT #{quote(options.delete(:default))}"
247 def change_column(table_name, column_name, type, options = {}) #:nodoc:
248 change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"
249 add_column_options!(change_column_sql, options)
250 execute(change_column_sql)
253 def rename_column(table_name, column_name, new_column_name) #:nodoc:
254 execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} to #{new_column_name}"
257 def remove_column(table_name, column_name) #:nodoc:
258 execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
261 def structure_dump #:nodoc:
262 s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
263 structure << "create sequence #{seq.to_a.first.last};\n\n"
266 select_all("select table_name from user_tables").inject(s) do |structure, table|
267 ddl = "create table #{table.to_a.first.last} (\n "
268 cols = select_all(%Q{
269 select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
270 from user_tab_columns
271 where table_name = '#{table.to_a.first.last}'
274 row = row.inject({}) do |h,args|
275 h[args[0].downcase] = args[1]
278 col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
279 if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
280 col << "(#{row['data_precision'].to_i}"
281 col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
283 elsif row['data_type'].include?('CHAR')
284 col << "(#{row['data_length'].to_i})"
286 col << " default #{row['data_default']}" if !row['data_default'].nil?
287 col << ' not null' if row['nullable'] == 'N'
290 ddl << cols.join(",\n ")
296 def structure_drop #:nodoc:
297 s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq|
298 drop << "drop sequence #{seq.to_a.first.last};\n\n"
301 select_all("select table_name from user_tables").inject(s) do |drop, table|
302 drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
306 # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
308 # Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
309 # queries. However, with those columns included in the SELECT DISTINCT list, you
310 # won't actually get a distinct list of the column you want (presuming the column
311 # has duplicates with multiple values for the ordered-by columns. So we use the
312 # FIRST_VALUE function to get a single (first) value for each column, effectively
313 # making every row the same.
315 # distinct("posts.id", "posts.created_at desc")
316 def distinct(columns, order_by)
317 return "DISTINCT #{columns}" if order_by.blank?
319 # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
320 # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
321 order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
322 order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
323 "FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
325 sql = "DISTINCT #{columns}, "
326 sql << order_columns * ", "
329 # ORDER BY clause for the passed order option.
331 # Uses column aliases as defined by #distinct.
332 def add_order_by_for_association_limiting!(sql, options)
333 return sql if options[:order].blank?
335 order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
336 order.map! {|s| $1 if s =~ / (.*)/}
337 order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
339 sql << "ORDER BY #{order}"
343 @connection.tables(nil, oracle_schema)
346 def ora_columns(table_name, name=nil)
347 @connection.columns_internal(table_name, name, oracle_schema)
350 # QUOTING ==================================================
352 # see: abstract/quoting.rb
354 # See ACTIVERECORD_JDBC-33 for details -- better to not quote
355 # table names, esp. if they have schemas.
356 def quote_table_name(name) #:nodoc:
360 # Camelcase column names need to be quoted.
361 # Nonquoted identifiers can contain only alphanumeric characters from your
362 # database character set and the underscore (_), dollar sign ($), and pound sign (#).
363 # Database links can also contain periods (.) and "at" signs (@).
364 # Oracle strongly discourages you from using $ and # in nonquoted identifiers.
365 # Source: http://download.oracle.com/docs/cd/B28359_01/server.111/b28286/sql_elements008.htm
366 def quote_column_name(name) #:nodoc:
367 name.to_s =~ /^[a-z0-9_$#]+$/ ? name.to_s : "\"#{name}\""
370 def quote_string(string) #:nodoc:
371 string.gsub(/'/, "''")
374 def quote(value, column = nil) #:nodoc:
375 # Arel 2 passes SqlLiterals through
376 return value if sql_literal?(value)
378 if column && [:text, :binary].include?(column.type)
379 if /(.*?)\([0-9]+\)/ =~ column.sql_type
380 %Q{empty_#{ $1.downcase }()}
382 %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
385 if column.respond_to?(:primary) && column.primary && column.klass != String
386 return value.to_i.to_s
389 if value.acts_like?(:date)
390 quoted = %Q{DATE'#{quoted_date(value)}'}
391 elsif value.acts_like?(:time)
392 quoted = %Q{TIMESTAMP'#{quoted_date(value)}'}
398 def quoted_true #:nodoc:
402 def quoted_false #:nodoc:
407 # In Oracle, schemas are usually created under your username:
408 # http://www.oracle.com/technology/obe/2day_dba/schema/schema.htm
409 # But allow separate configuration as "schema:" anyway (GH #53)
412 @config[:schema].to_s
413 elsif @config[:username]
414 @config[:username].to_s
418 def select(sql, name=nil)
419 records = execute(sql,name)
420 records.each do |col|
421 col.delete('raw_rnum_')