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
50 return nil if value.nil?
52 when :datetime then ArJdbc::Oracle::Column.string_to_time(value, self.class)
58 def self.string_to_time(string, klass)
59 time = klass.string_to_time(string)
60 guess_date_or_time(time)
63 def self.guess_date_or_time(value)
64 return value if Date === value
65 (value && value.hour == 0 && value.min == 0 && value.sec == 0) ?
66 Date.new(value.year, value.month, value.day) : value
69 def self.string_to_date(string) #:nodoc:
70 return string.to_date if string.is_a?(Time) || string.is_a?(DateTime)
76 def simplified_type(field_type)
78 when /^number\(1\)$/i then :boolean
79 when /char/i then :string
80 when /float|double/i then :float
81 when /int/i then :integer
82 when /num|dec|real/i then extract_scale(field_type) == 0 ? :integer : :decimal
83 when /date|time/i then :datetime
84 when /clob/i then :text
85 when /blob/i then :binary
89 # Post process default value from JDBC into a Rails-friendly format (columns{-internal})
90 def default_value(value)
91 return nil unless value
93 # Not sure why we need this for Oracle?
96 return nil if value == "null"
98 # sysdate default should be treated like a null value
99 return nil if value.downcase == "sysdate"
101 # jdbc returns column default strings with actual single quotes around the value.
102 return $1 if value =~ /^'(.*)'$/
113 { 'oracle' => Arel::Visitors::Oracle }
116 # TODO: use this instead of the QuotedPrimaryKey logic and execute_id_insert?
117 # def prefetch_primary_key?(table_name = nil)
118 # columns(table_name).detect {|c| c.primary } if table_name
121 def table_alias_length
125 def default_sequence_name(table, column = nil) #:nodoc:
129 def create_table(name, options = {}) #:nodoc:
131 seq_name = options[:sequence_name] || default_sequence_name(name)
132 start_value = options[:sequence_start_value] || 10000
133 raise ActiveRecord::StatementInvalid.new("name #{seq_name} too long") if seq_name.length > table_alias_length
134 execute "CREATE SEQUENCE #{seq_name} START WITH #{start_value}" unless options[:id] == false
137 def rename_table(name, new_name) #:nodoc:
138 execute "RENAME #{name} TO #{new_name}"
139 execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
142 def drop_table(name, options = {}) #:nodoc:
143 super(name) rescue nil
144 seq_name = options[:sequence_name] || default_sequence_name(name)
145 execute "DROP SEQUENCE #{seq_name}" rescue nil
148 def recreate_database(name)
149 tables.each{ |table| drop_table(table) }
152 def drop_database(name)
153 recreate_database(name)
156 def next_sequence_value(sequence_name)
157 # avoid #select or #select_one so that the sequence values aren't cached
158 execute("select #{sequence_name}.nextval id from dual").first['id'].to_i
161 def sql_literal?(value)
162 defined?(::Arel::SqlLiteral) && ::Arel::SqlLiteral === value
165 def ora_insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
166 if (id_value && !sql_literal?(id_value)) || pk.nil?
167 # Pre-assigned id or table without a primary key
168 # Presence of #to_sql means an Arel literal bind variable
169 # that should use #execute_id_insert below
172 # Assume the sql contains a bind-variable for the id
173 # Extract the table from the insert sql. Yuck.
174 table = sql.split(" ", 4)[2].gsub('"', '')
175 sequence_name ||= default_sequence_name(table)
176 id_value = next_sequence_value(sequence_name)
178 @connection.execute_id_insert(sql,id_value)
184 def indexes(table, name = nil)
185 @connection.indexes(table, name, @connection.connection.meta_data.user_name)
188 def _execute(sql, name = nil)
190 when /\A\(?\s*(select|show)/i then
191 @connection.execute_query(sql)
193 @connection.execute_update(sql)
198 tp[:primary_key] = "NUMBER(38) NOT NULL PRIMARY KEY"
199 tp[:integer] = { :name => "NUMBER", :limit => 38 }
200 tp[:time] = { :name => "DATE" }
201 tp[:date] = { :name => "DATE" }
204 tp[:datetime]={ :name => "TIMESTAMP" }
205 tp[:timestamp]={ :name => "TIMESTAMP" }
206 tp[:boolean]={ :name => "NUMBER", :limit => 1 }
207 tp[:big_integer]={ :name => "NUMBER", :limit => 38 }
212 def add_limit_offset!(sql, options) #:nodoc:
213 offset = options[:offset] || 0
215 if limit = options[:limit]
216 sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
218 sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
222 def current_database #:nodoc:
223 select_one("select sys_context('userenv','db_name') db from dual")["db"]
226 def remove_index(table_name, options = {}) #:nodoc:
227 execute "DROP INDEX #{index_name(table_name, options)}"
230 def change_column_default(table_name, column_name, default) #:nodoc:
231 execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}"
234 def add_column_options!(sql, options) #:nodoc:
235 # handle case of defaults for CLOB columns, which would otherwise get "quoted" incorrectly
236 if options_include_default?(options) && (column = options[:column]) && column.type == :text
237 sql << " DEFAULT #{quote(options.delete(:default))}"
242 def change_column(table_name, column_name, type, options = {}) #:nodoc:
243 change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"
244 add_column_options!(change_column_sql, options)
245 execute(change_column_sql)
248 def rename_column(table_name, column_name, new_column_name) #:nodoc:
249 execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} to #{new_column_name}"
252 def remove_column(table_name, column_name) #:nodoc:
253 execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
256 def structure_dump #:nodoc:
257 s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
258 structure << "create sequence #{seq.to_a.first.last};\n\n"
261 select_all("select table_name from user_tables").inject(s) do |structure, table|
262 ddl = "create table #{table.to_a.first.last} (\n "
263 cols = select_all(%Q{
264 select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
265 from user_tab_columns
266 where table_name = '#{table.to_a.first.last}'
269 row = row.inject({}) do |h,args|
270 h[args[0].downcase] = args[1]
273 col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
274 if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
275 col << "(#{row['data_precision'].to_i}"
276 col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
278 elsif row['data_type'].include?('CHAR')
279 col << "(#{row['data_length'].to_i})"
281 col << " default #{row['data_default']}" if !row['data_default'].nil?
282 col << ' not null' if row['nullable'] == 'N'
285 ddl << cols.join(",\n ")
291 def structure_drop #:nodoc:
292 s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq|
293 drop << "drop sequence #{seq.to_a.first.last};\n\n"
296 select_all("select table_name from user_tables").inject(s) do |drop, table|
297 drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
301 # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
303 # Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
304 # queries. However, with those columns included in the SELECT DISTINCT list, you
305 # won't actually get a distinct list of the column you want (presuming the column
306 # has duplicates with multiple values for the ordered-by columns. So we use the
307 # FIRST_VALUE function to get a single (first) value for each column, effectively
308 # making every row the same.
310 # distinct("posts.id", "posts.created_at desc")
311 def distinct(columns, order_by)
312 return "DISTINCT #{columns}" if order_by.blank?
314 # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
315 # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
316 order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
317 order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
318 "FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
320 sql = "DISTINCT #{columns}, "
321 sql << order_columns * ", "
324 # ORDER BY clause for the passed order option.
326 # Uses column aliases as defined by #distinct.
327 def add_order_by_for_association_limiting!(sql, options)
328 return sql if options[:order].blank?
330 order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
331 order.map! {|s| $1 if s =~ / (.*)/}
332 order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
334 sql << "ORDER BY #{order}"
338 @connection.tables(nil, oracle_schema)
341 def ora_columns(table_name, name=nil)
342 @connection.columns_internal(table_name, name, oracle_schema)
345 # QUOTING ==================================================
347 # see: abstract/quoting.rb
349 # See ACTIVERECORD_JDBC-33 for details -- better to not quote
350 # table names, esp. if they have schemas.
351 def quote_table_name(name) #:nodoc:
355 # Camelcase column names need to be quoted.
356 # Nonquoted identifiers can contain only alphanumeric characters from your
357 # database character set and the underscore (_), dollar sign ($), and pound sign (#).
358 # Database links can also contain periods (.) and "at" signs (@).
359 # Oracle strongly discourages you from using $ and # in nonquoted identifiers.
360 # Source: http://download.oracle.com/docs/cd/B28359_01/server.111/b28286/sql_elements008.htm
361 def quote_column_name(name) #:nodoc:
362 name.to_s =~ /^[a-z0-9_$#]+$/ ? name.to_s : "\"#{name}\""
365 def quote_string(string) #:nodoc:
366 string.gsub(/'/, "''")
369 def quote(value, column = nil) #:nodoc:
370 # Arel 2 passes SqlLiterals through
371 return value if sql_literal?(value)
373 if column && [:text, :binary].include?(column.type)
374 if /(.*?)\([0-9]+\)/ =~ column.sql_type
375 %Q{empty_#{ $1.downcase }()}
377 %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
380 if column.respond_to?(:primary) && column.primary && column.klass != String
381 return value.to_i.to_s
385 if value.acts_like?(:date) || value.acts_like?(:time) || value.acts_like?(:timestamp)
386 quoted = "TO_TIMESTAMP('#{value.strftime('%Y-%m-%d %H:%M:%S')}:#{("%.6f"%value.to_f).split('.')[1]}','YYYY-MM-DD HH24:MI:SS:FF6')"
393 def quoted_true #:nodoc:
397 def quoted_false #:nodoc:
402 # In Oracle, schemas are usually created under your username:
403 # http://www.oracle.com/technology/obe/2day_dba/schema/schema.htm
404 # But allow separate configuration as "schema:" anyway (GH #53)
407 @config[:schema].to_s
408 elsif @config[:username]
409 @config[:username].to_s
413 def select(sql, name=nil)
414 records = execute(sql,name)
415 records.each do |col|
416 col.delete('raw_rnum_')