]> source.dussan.org Git - sonarqube.git/blob
c27a4f2d71499aa382398dbb515b4789f46588c6
[sonarqube.git] /
1 module ActiveRecord::ConnectionAdapters
2   OracleAdapter = Class.new(AbstractAdapter) unless const_defined?(:OracleAdapter)
3 end
4
5 module ::ArJdbc
6   module Oracle
7     def self.extended(mod)
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|
12               value = self[c.name]
13               value = value.to_yaml if unserializable_attribute?(c.name, c)
14               next if value.nil?  || (value == '')
15
16               connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
17             end
18           end
19         end
20
21         ActiveRecord::Base.after_save :after_save_with_oracle_lob
22         @lob_callback_added = true
23       end
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
29       end
30     end
31
32     def self.column_selector
33       [/oracle/i, lambda {|cfg,col| col.extend(::ArJdbc::Oracle::Column)}]
34     end
35
36     def self.jdbc_connection_class
37       ::ActiveRecord::ConnectionAdapters::OracleJdbcConnection
38     end
39
40     module Column
41       def primary=(val)
42         super
43         if val && @sql_type =~ /^NUMBER$/i
44           @type = :integer
45         end
46       end
47
48       #sonar
49       def type_cast(value)
50         return nil if value.nil?
51         case type
52           when :datetime then ArJdbc::Oracle::Column.string_to_time(value, self.class)
53         else
54           super
55         end
56       end
57
58       def self.string_to_time(string, klass)
59         time = klass.string_to_time(string)
60         guess_date_or_time(time)
61       end
62
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
67       end
68
69       def self.string_to_date(string) #:nodoc:
70         return string.to_date if string.is_a?(Time) || string.is_a?(DateTime)
71         super
72       end
73       #/sonar
74
75       private
76       def simplified_type(field_type)
77         case 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
86         end
87       end
88
89       # Post process default value from JDBC into a Rails-friendly format (columns{-internal})
90       def default_value(value)
91         return nil unless value
92
93         # Not sure why we need this for Oracle?
94         value = value.strip
95
96         return nil if value == "null"
97
98         # sysdate default should be treated like a null value
99         return nil if value.downcase == "sysdate"
100
101         # jdbc returns column default strings with actual single quotes around the value.
102         return $1 if value =~ /^'(.*)'$/
103
104         value
105       end
106     end
107
108     def adapter_name
109       'Oracle'
110     end
111
112     def arel2_visitors
113       { 'oracle' => Arel::Visitors::Oracle }
114     end
115
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
119     # end
120
121     def table_alias_length
122       30
123     end
124
125     def default_sequence_name(table, column = nil) #:nodoc:
126       "#{table}_seq"
127     end
128
129     def create_table(name, options = {}) #:nodoc:
130       super(name, options)
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
135     end
136
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
140     end
141
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
146     end
147
148     def recreate_database(name)
149       tables.each{ |table| drop_table(table) }
150     end
151
152     def drop_database(name)
153       recreate_database(name)
154     end
155
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
159     end
160
161     def sql_literal?(value)
162       defined?(::Arel::SqlLiteral) && ::Arel::SqlLiteral === value
163     end
164
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
170         execute sql, name
171       else
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)
177         log(sql, name) do
178           @connection.execute_id_insert(sql,id_value)
179         end
180       end
181       id_value
182     end
183
184     def indexes(table, name = nil)
185       @connection.indexes(table, name, @connection.connection.meta_data.user_name)
186     end
187
188     def _execute(sql, name = nil)
189       case sql.strip
190         when /\A\(?\s*(select|show)/i then
191           @connection.execute_query(sql)
192         else
193           @connection.execute_update(sql)
194         end
195     end
196
197     def modify_types(tp)
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" }
202
203       # sonar
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 }
208       #/sonar
209       tp
210     end
211
212     def add_limit_offset!(sql, options) #:nodoc:
213       offset = options[:offset] || 0
214
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}"
217       elsif offset > 0
218         sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
219       end
220     end
221
222     def current_database #:nodoc:
223       select_one("select sys_context('userenv','db_name') db from dual")["db"]
224     end
225
226     def remove_index(table_name, options = {}) #:nodoc:
227       execute "DROP INDEX #{index_name(table_name, options)}"
228     end
229
230     def change_column_default(table_name, column_name, default) #:nodoc:
231       execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}"
232     end
233
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))}"
238       end
239       super
240     end
241
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)
246     end
247
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}"
250     end
251
252     def remove_column(table_name, column_name) #:nodoc:
253       execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
254     end
255
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"
259       end
260
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}'
267               order by column_id
268             }).map do |row|
269           row = row.inject({}) do |h,args|
270             h[args[0].downcase] = args[1]
271             h
272           end
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?
277             col << ')'
278           elsif row['data_type'].include?('CHAR')
279             col << "(#{row['data_length'].to_i})"
280           end
281           col << " default #{row['data_default']}" if !row['data_default'].nil?
282           col << ' not null' if row['nullable'] == 'N'
283           col
284         end
285         ddl << cols.join(",\n ")
286         ddl << ");\n\n"
287         structure << ddl
288       end
289     end
290
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"
294       end
295
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"
298       end
299     end
300
301     # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
302     #
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.
309     #
310     #   distinct("posts.id", "posts.created_at desc")
311     def distinct(columns, order_by)
312       return "DISTINCT #{columns}" if order_by.blank?
313
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}__"
319       end
320       sql = "DISTINCT #{columns}, "
321       sql << order_columns * ", "
322     end
323
324     # ORDER BY clause for the passed order option.
325     #
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?
329
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(', ')
333
334       sql << "ORDER BY #{order}"
335     end
336
337     def tables
338       @connection.tables(nil, oracle_schema)
339     end
340
341     def ora_columns(table_name, name=nil)
342       @connection.columns_internal(table_name, name, oracle_schema)
343     end
344
345     # QUOTING ==================================================
346     #
347     # see: abstract/quoting.rb
348
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:
352       name.to_s
353     end
354
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}\""
363     end
364
365     def quote_string(string) #:nodoc:
366       string.gsub(/'/, "''")
367     end
368
369     def quote(value, column = nil) #:nodoc:
370       # Arel 2 passes SqlLiterals through
371       return value if sql_literal?(value)
372
373       if column && [:text, :binary].include?(column.type)
374         if /(.*?)\([0-9]+\)/ =~ column.sql_type
375           %Q{empty_#{ $1.downcase }()}
376         else
377           %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
378         end
379       else
380         if column.respond_to?(:primary) && column.primary && column.klass != String
381           return value.to_i.to_s
382         end
383         quoted = super
384         #sonar
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')"
387         end
388         #/sonar
389         quoted
390       end
391     end
392
393     def quoted_true #:nodoc:
394       '1'
395     end
396
397     def quoted_false #:nodoc:
398       '0'
399     end
400
401     private
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)
405     def oracle_schema
406       if @config[:schema]
407         @config[:schema].to_s
408       elsif @config[:username]
409         @config[:username].to_s
410       end
411     end
412
413     def select(sql, name=nil)
414       records = execute(sql,name)
415       records.each do |col|
416           col.delete('raw_rnum_')
417       end
418       records
419     end
420   end
421 end
422