3 def after_save_with_oracle_lob() #:nodoc:
4 if connection.is_a?(JdbcSpec::Oracle)
5 self.class.columns.select { |c| c.sql_type =~ /LOB\(|LOB$/i }.each { |c|
7 value = value.to_yaml if unserializable_attribute?(c.name, c)
8 next if value.nil? || (value == '')
10 connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
18 module ActiveRecordExtensions
19 def oracle_connection(config)
20 config[:port] ||= 1521
21 config[:url] ||= "jdbc:oracle:thin:@#{config[:host]}:#{config[:port]}:#{config[:database]}"
22 config[:driver] ||= "oracle.jdbc.driver.OracleDriver"
23 jdbc_connection(config)
28 def self.extended(mod)
29 ActiveRecord::Base.after_save :after_save_with_oracle_lob unless @lob_callback_added
30 @lob_callback_added = true
33 def self.column_selector
34 [/oracle/i, lambda {|cfg,col| col.extend(::JdbcSpec::Oracle::Column)}]
37 def self.adapter_selector
38 [/oracle/i, lambda {|cfg,adapt| adapt.extend(::JdbcSpec::Oracle)
40 (adapt.methods - %w(send __send__ id class methods is_a? kind_of? verify! active?)).each do |name|
41 new_name = "__#{name}"
42 (class << adapt; self; end).send :alias_method, new_name, name
43 (class << adapt; self; end).send :define_method, name do |*args|
44 puts "#{name}(#{args.inspect})"
45 adapt.send new_name, *args
54 return nil if value.nil?
56 when :string then value
57 when :integer then defined?(value.to_i) ? value.to_i : (value ? 1 : 0)
58 when :primary_key then defined?(value.to_i) ? value.to_i : (value ? 1 : 0)
59 when :float then value.to_f
60 when :datetime then JdbcSpec::Oracle::Column.cast_to_date_or_time(value)
61 when :time then JdbcSpec::Oracle::Column.cast_to_time(value)
62 when :decimal then self.class.value_to_decimal(value)
63 when :boolean then self.class.value_to_boolean(value)
68 def type_cast_code(var_name)
71 when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
72 when :primary_key then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
73 when :float then "#{var_name}.to_f"
74 when :datetime then "JdbcSpec::Oracle::Column.cast_to_date_or_time(#{var_name})"
75 when :time then "JdbcSpec::Oracle::Column.cast_to_time(#{var_name})"
76 when :decimal then "#{self.class.name}.value_to_decimal(#{var_name})"
77 when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})"
83 def simplified_type(field_type)
85 when /^number\(1\)$/i : :boolean
86 when /char/i : :string
87 when /float|double/i : :float
88 when /int/i : :integer
89 when /num|dec|real/i : @scale == 0 ? :integer : :decimal
90 when /date|time/i : :datetime
92 when /blob/i : :binary
96 def self.cast_to_date_or_time(value)
97 return value if value.is_a? Date
98 return nil if value.blank?
99 guess_date_or_time((value.is_a? Time) ? value : cast_to_time(value))
102 def self.cast_to_time(value)
103 return value if value.is_a? Time
104 time_array = ParseDate.parsedate value
105 time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1;
106 Time.send(ActiveRecord::Base.default_timezone, *time_array) rescue nil
109 def self.guess_date_or_time(value)
110 (value.hour == 0 and value.min == 0 and value.sec == 0) ?
111 Date.new(value.year, value.month, value.day) : value
115 def table_alias_length
119 def default_sequence_name(table, column) #:nodoc:
123 def create_table(name, options = {}) #:nodoc:
125 seq_name = options[:sequence_name] || "#{name}_seq"
126 raise ActiveRecord::StatementInvalid.new("name #{seq_name} too long") if seq_name.length > table_alias_length
127 execute "CREATE SEQUENCE #{seq_name} START WITH 10000" unless options[:id] == false
130 def rename_table(name, new_name) #:nodoc:
131 execute "RENAME #{name} TO #{new_name}"
132 execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
135 def drop_table(name, options = {}) #:nodoc:
137 seq_name = options[:sequence_name] || "#{name}_seq"
138 execute "DROP SEQUENCE #{seq_name}" rescue nil
141 def recreate_database(name)
142 tables.each{ |table| drop_table(table) }
145 def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
146 if pk.nil? # Who called us? What does the sql look like? No idea!
148 elsif id_value # Pre-assigned id
150 else # Assume the sql contains a bind-variable for the id
151 id_value = select_one("select #{sequence_name}.nextval id from dual")['id'].to_i
153 @connection.execute_id_insert(sql,id_value)
159 def indexes(table, name = nil)
160 @connection.indexes(table, name, @connection.connection.meta_data.user_name)
163 def _execute(sql, name = nil)
165 when /\A\(?\s*(select|show)/i:
166 @connection.execute_query(sql)
168 @connection.execute_update(sql)
173 tp[:primary_key] = "NUMBER(38) NOT NULL PRIMARY KEY"
174 tp[:integer] = { :name => "NUMBER", :limit => 38 }
175 tp[:datetime] = { :name => "DATE" }
176 tp[:timestamp] = { :name => "DATE" }
177 tp[:time] = { :name => "DATE" }
178 tp[:date] = { :name => "DATE" }
182 def add_limit_offset!(sql, options) #:nodoc:
183 offset = options[:offset] || 0
185 if limit = options[:limit]
186 sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
188 sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
192 def current_database #:nodoc:
193 select_one("select sys_context('userenv','db_name') db from dual")["db"]
196 def remove_index(table_name, options = {}) #:nodoc:
197 execute "DROP INDEX #{index_name(table_name, options)}"
200 def change_column_default(table_name, column_name, default) #:nodoc:
201 execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}"
204 def add_column_options!(sql, options) #:nodoc:
205 # handle case of defaults for CLOB columns, which would otherwise get "quoted" incorrectly
206 if options_include_default?(options) && (column = options[:column]) && column.type == :text
207 sql << " DEFAULT #{quote(options.delete(:default))}"
212 def change_column(table_name, column_name, type, options = {}) #:nodoc:
213 change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"
214 add_column_options!(change_column_sql, options)
215 execute(change_column_sql)
218 def rename_column(table_name, column_name, new_column_name) #:nodoc:
219 execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} to #{new_column_name}"
222 def remove_column(table_name, column_name) #:nodoc:
223 execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
226 def structure_dump #:nodoc:
227 s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
228 structure << "create sequence #{seq.to_a.first.last};\n\n"
231 select_all("select table_name from user_tables").inject(s) do |structure, table|
232 ddl = "create table #{table.to_a.first.last} (\n "
233 cols = select_all(%Q{
234 select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
235 from user_tab_columns
236 where table_name = '#{table.to_a.first.last}'
239 row = row.inject({}) do |h,args|
240 h[args[0].downcase] = args[1]
243 col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
244 if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
245 col << "(#{row['data_precision'].to_i}"
246 col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
248 elsif row['data_type'].include?('CHAR')
249 col << "(#{row['data_length'].to_i})"
251 col << " default #{row['data_default']}" if !row['data_default'].nil?
252 col << ' not null' if row['nullable'] == 'N'
255 ddl << cols.join(",\n ")
261 def structure_drop #:nodoc:
262 s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq|
263 drop << "drop sequence #{seq.to_a.first.last};\n\n"
266 select_all("select table_name from user_tables").inject(s) do |drop, table|
267 drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
271 # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
273 # Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
274 # queries. However, with those columns included in the SELECT DISTINCT list, you
275 # won't actually get a distinct list of the column you want (presuming the column
276 # has duplicates with multiple values for the ordered-by columns. So we use the
277 # FIRST_VALUE function to get a single (first) value for each column, effectively
278 # making every row the same.
280 # distinct("posts.id", "posts.created_at desc")
281 def distinct(columns, order_by)
282 return "DISTINCT #{columns}" if order_by.blank?
284 # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
285 # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
286 order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
287 order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
288 "FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
290 sql = "DISTINCT #{columns}, "
291 sql << order_columns * ", "
294 # ORDER BY clause for the passed order option.
296 # Uses column aliases as defined by #distinct.
297 def add_order_by_for_association_limiting!(sql, options)
298 return sql if options[:order].blank?
300 order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
301 order.map! {|s| $1 if s =~ / (.*)/}
302 order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
304 sql << "ORDER BY #{order}"
308 # QUOTING ==================================================
310 # see: abstract/quoting.rb
312 # camelCase column names need to be quoted; not that anyone using Oracle
313 # would really do this, but handling this case means we pass the test...
314 def quote_column_name(name) #:nodoc:
315 name.to_s =~ /[A-Z]/ ? "\"#{name}\"" : name.to_s
318 def quote_string(string) #:nodoc:
319 string.gsub(/'/, "''")
322 def quote(value, column = nil) #:nodoc:
323 return value.quoted_id if value.respond_to?(:quoted_id)
325 if column && [:text, :binary].include?(column.type)
326 if /(.*?)\([0-9]+\)/ =~ column.sql_type
327 %Q{empty_#{ $1.downcase }()}
329 %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
332 if column && column.type == :primary_key
336 when String, ActiveSupport::Multibyte::Chars
337 if column.type == :datetime
338 %Q{TIMESTAMP'#{value}'}
340 %Q{'#{quote_string(value)}'}
342 when NilClass : 'null'
344 when FalseClass : '0'
345 when Numeric : value.to_s
346 when Date, Time : %Q{TIMESTAMP'#{value.strftime("%Y-%m-%d %H:%M:%S")}'}
347 else %Q{'#{quote_string(value.to_yaml)}'}
352 def quoted_true #:nodoc:
356 def quoted_false #:nodoc:
361 def select(sql, name=nil)
362 records = execute(sql,name)
363 records.each do |col|
364 col.delete('raw_rnum_')