]> source.dussan.org Git - sonarqube.git/blob
57cf87786227d3d8e4b24ae41f1230309aa0297e
[sonarqube.git] /
1 module ::ActiveRecord
2   class Base
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|
6           value = self[c.name]
7           value = value.to_yaml if unserializable_attribute?(c.name, c)
8           next if value.nil?  || (value == '')
9
10           connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
11         }
12       end
13     end
14   end
15 end
16
17 module ::JdbcSpec
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)
24     end
25   end
26
27   module Oracle
28     def self.extended(mod)
29       ActiveRecord::Base.after_save :after_save_with_oracle_lob unless @lob_callback_added
30       @lob_callback_added = true
31     end
32
33     def self.column_selector
34       [/oracle/i, lambda {|cfg,col| col.extend(::JdbcSpec::Oracle::Column)}]
35     end
36
37     def self.adapter_selector
38       [/oracle/i, lambda {|cfg,adapt| adapt.extend(::JdbcSpec::Oracle)
39 =begin
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
46            end
47          end
48 =end
49        }]
50     end
51     
52     module Column
53       def type_cast(value)
54         return nil if value.nil?
55         case type
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)
64         else value
65         end
66       end
67       
68       def type_cast_code(var_name)
69         case type
70         when :string      then nil
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})"
78         else nil
79         end
80       end      
81
82       private
83       def simplified_type(field_type)
84         case 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
91         when /clob/i                           : :text
92         when /blob/i                           : :binary
93         end
94       end
95
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))
100       end
101
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
107       end
108
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
112       end
113     end
114
115     def table_alias_length
116       30
117     end
118
119     def default_sequence_name(table, column) #:nodoc:
120       "#{table}_seq"
121     end
122     
123     def create_table(name, options = {}) #:nodoc:
124       super(name, options)
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
128     end
129
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
133     end  
134
135     def drop_table(name, options = {}) #:nodoc:
136       super(name)
137       seq_name = options[:sequence_name] || "#{name}_seq"
138       execute "DROP SEQUENCE #{seq_name}" rescue nil
139     end
140
141     def recreate_database(name)
142       tables.each{ |table| drop_table(table) }
143     end
144     
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!
147         execute sql, name
148       elsif id_value # Pre-assigned id
149         execute sql, name
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
152         log(sql, name) { 
153           @connection.execute_id_insert(sql,id_value)
154         }
155       end
156       id_value
157     end
158
159     def indexes(table, name = nil)
160       @connection.indexes(table, name, @connection.connection.meta_data.user_name)
161     end
162     
163     def _execute(sql, name = nil)
164       case sql.strip
165         when /\A\(?\s*(select|show)/i:
166           @connection.execute_query(sql)
167         else
168           @connection.execute_update(sql)
169         end
170     end
171     
172     def modify_types(tp)
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" }
179       tp
180     end
181
182     def add_limit_offset!(sql, options) #:nodoc:
183       offset = options[:offset] || 0
184       
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}"
187       elsif offset > 0
188         sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
189       end
190     end
191
192     def current_database #:nodoc:
193       select_one("select sys_context('userenv','db_name') db from dual")["db"]
194     end
195
196     def remove_index(table_name, options = {}) #:nodoc:
197       execute "DROP INDEX #{index_name(table_name, options)}"
198     end
199
200     def change_column_default(table_name, column_name, default) #:nodoc:
201       execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}"
202     end
203
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))}" 
208       end
209       super
210     end
211
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)
216     end
217     
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}"
220     end
221
222     def remove_column(table_name, column_name) #:nodoc:
223       execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
224     end
225
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"
229       end
230
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}'
237               order by column_id
238             }).map do |row|
239           row = row.inject({}) do |h,args| 
240             h[args[0].downcase] = args[1]
241             h 
242           end
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?
247             col << ')'
248           elsif row['data_type'].include?('CHAR')
249             col << "(#{row['data_length'].to_i})"  
250           end
251           col << " default #{row['data_default']}" if !row['data_default'].nil?
252           col << ' not null' if row['nullable'] == 'N'
253           col
254         end
255         ddl << cols.join(",\n ")
256         ddl << ");\n\n"
257         structure << ddl
258       end
259     end
260
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"
264       end
265
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"
268       end
269     end
270     
271     # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
272     #
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.
279     #
280     #   distinct("posts.id", "posts.created_at desc")
281     def distinct(columns, order_by)
282       return "DISTINCT #{columns}" if order_by.blank?
283
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}__"
289       end
290       sql = "DISTINCT #{columns}, "
291       sql << order_columns * ", "
292     end
293
294     # ORDER BY clause for the passed order option.
295     # 
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?
299
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(', ')
303
304       sql << "ORDER BY #{order}"
305     end
306     
307     
308     # QUOTING ==================================================
309     #
310     # see: abstract/quoting.rb
311     
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
316     end
317
318     def quote_string(string) #:nodoc:
319       string.gsub(/'/, "''")
320     end
321     
322     def quote(value, column = nil) #:nodoc:
323       return value.quoted_id if value.respond_to?(:quoted_id)
324       
325       if column && [:text, :binary].include?(column.type)
326         if /(.*?)\([0-9]+\)/ =~ column.sql_type
327           %Q{empty_#{ $1.downcase }()}
328         else
329           %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
330         end
331       else
332         if column && column.type == :primary_key
333           return value.to_s
334         end
335         case value
336         when String, ActiveSupport::Multibyte::Chars
337           if column.type == :datetime
338             %Q{TIMESTAMP'#{value}'}
339           else
340             %Q{'#{quote_string(value)}'}
341           end
342         when NilClass   : 'null'
343         when TrueClass  : '1'
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)}'}
348         end
349       end
350     end
351     
352     def quoted_true #:nodoc:
353       '1'
354     end
355     
356     def quoted_false #:nodoc:
357       '0'
358     end
359     
360     private
361     def select(sql, name=nil)
362       records = execute(sql,name)
363       records.each do |col|
364           col.delete('raw_rnum_')
365       end
366       records
367     end
368   end
369 end