]> source.dussan.org Git - sonarqube.git/blob
6ed95b45aa0865a4af4e1caf1d20f5f285f22a7c
[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       def type_cast(value)
49         return nil if value.nil?
50         case type
51         when :datetime then ArJdbc::Oracle::Column.string_to_time(value, self.class)
52         else
53           super
54         end
55       end
56
57       def type_cast_code(var_name)
58         case type
59         when :datetime  then "ArJdbc::Oracle::Column.string_to_time(#{var_name}, self.class)"
60         else
61           super
62         end
63       end
64
65       def self.string_to_time(string, klass)
66         time = klass.string_to_time(string)
67         guess_date_or_time(time)
68       end
69
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
74       end
75
76       private
77       def simplified_type(field_type)
78         case 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
87         end
88       end
89
90       # Post process default value from JDBC into a Rails-friendly format (columns{-internal})
91       def default_value(value)
92         return nil unless value
93
94         # Not sure why we need this for Oracle?
95         value = value.strip
96
97         return nil if value == "null"
98
99         # sysdate default should be treated like a null value
100         return nil if value.downcase == "sysdate"
101
102         # jdbc returns column default strings with actual single quotes around the value.
103         return $1 if value =~ /^'(.*)'$/
104
105         value
106       end
107     end
108
109     def adapter_name
110       'Oracle'
111     end
112
113     def arel2_visitors
114       { 'oracle' => Arel::Visitors::Oracle }
115     end
116
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
120     # end
121
122     def table_alias_length
123       30
124     end
125
126     def default_sequence_name(table, column = nil) #:nodoc:
127       "#{table}_seq"
128     end
129
130     def create_table(name, options = {}) #:nodoc:
131       super(name, options)
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
136     end
137
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
141     end
142
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
147     end
148
149     def recreate_database(name)
150       tables.each{ |table| drop_table(table) }
151     end
152
153     def drop_database(name)
154       recreate_database(name)
155     end
156
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
160     end
161
162     def sql_literal?(value)
163       defined?(::Arel::SqlLiteral) && ::Arel::SqlLiteral === value
164     end
165
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
171         execute sql, name
172       else
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)
178         log(sql, name) do
179           @connection.execute_id_insert(sql,id_value)
180         end
181       end
182       id_value
183     end
184
185     def indexes(table, name = nil)
186       @connection.indexes(table, name, @connection.connection.meta_data.user_name)
187     end
188
189     def _execute(sql, name = nil)
190       case sql.strip
191         when /\A\(?\s*(select|show)/i then
192           @connection.execute_query(sql)
193         else
194           @connection.execute_update(sql)
195         end
196     end
197
198     def modify_types(tp)
199       tp[:primary_key] = "NUMBER(38) NOT NULL PRIMARY KEY"
200       tp[:integer] = { :name => "NUMBER", :limit => 38 }
201
202       # sonar
203       tp[:datetime] = { :name => "TIMESTAMP" }
204       tp[:timestamp] = { :name => "TIMESTAMP" }
205       # /sonar
206
207       tp[:time] = { :name => "DATE" }
208       tp[:date] = { :name => "DATE" }
209
210       # sonar
211       # New type
212       tp[:big_integer] = { :name => "NUMBER", :limit => 38 }
213       # /sonar
214       tp
215     end
216
217     def add_limit_offset!(sql, options) #:nodoc:
218       offset = options[:offset] || 0
219
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}"
222       elsif offset > 0
223         sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
224       end
225     end
226
227     def current_database #:nodoc:
228       select_one("select sys_context('userenv','db_name') db from dual")["db"]
229     end
230
231     def remove_index(table_name, options = {}) #:nodoc:
232       execute "DROP INDEX #{index_name(table_name, options)}"
233     end
234
235     def change_column_default(table_name, column_name, default) #:nodoc:
236       execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}"
237     end
238
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))}"
243       end
244       super
245     end
246
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)
251     end
252
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}"
255     end
256
257     def remove_column(table_name, column_name) #:nodoc:
258       execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
259     end
260
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"
264       end
265
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}'
272               order by column_id
273             }).map do |row|
274           row = row.inject({}) do |h,args|
275             h[args[0].downcase] = args[1]
276             h
277           end
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?
282             col << ')'
283           elsif row['data_type'].include?('CHAR')
284             col << "(#{row['data_length'].to_i})"
285           end
286           col << " default #{row['data_default']}" if !row['data_default'].nil?
287           col << ' not null' if row['nullable'] == 'N'
288           col
289         end
290         ddl << cols.join(",\n ")
291         ddl << ");\n\n"
292         structure << ddl
293       end
294     end
295
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"
299       end
300
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"
303       end
304     end
305
306     # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
307     #
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.
314     #
315     #   distinct("posts.id", "posts.created_at desc")
316     def distinct(columns, order_by)
317       return "DISTINCT #{columns}" if order_by.blank?
318
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}__"
324       end
325       sql = "DISTINCT #{columns}, "
326       sql << order_columns * ", "
327     end
328
329     # ORDER BY clause for the passed order option.
330     #
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?
334
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(', ')
338
339       sql << "ORDER BY #{order}"
340     end
341
342     def tables
343       @connection.tables(nil, oracle_schema)
344     end
345
346     def ora_columns(table_name, name=nil)
347       @connection.columns_internal(table_name, name, oracle_schema)
348     end
349
350     # QUOTING ==================================================
351     #
352     # see: abstract/quoting.rb
353
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:
357       name.to_s
358     end
359
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}\""
368     end
369
370     def quote_string(string) #:nodoc:
371       string.gsub(/'/, "''")
372     end
373
374     def quote(value, column = nil) #:nodoc:
375       # Arel 2 passes SqlLiterals through
376       return value if sql_literal?(value)
377
378       if column && [:text, :binary].include?(column.type)
379         if /(.*?)\([0-9]+\)/ =~ column.sql_type
380           %Q{empty_#{ $1.downcase }()}
381         else
382           %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
383         end
384       else
385         if column.respond_to?(:primary) && column.primary && column.klass != String
386           return value.to_i.to_s
387         end
388         quoted = super
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)}'}
393         end
394         quoted
395       end
396     end
397
398     def quoted_true #:nodoc:
399       '1'
400     end
401
402     def quoted_false #:nodoc:
403       '0'
404     end
405
406     private
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)
410     def oracle_schema
411       if @config[:schema]
412         @config[:schema].to_s
413       elsif @config[:username]
414         @config[:username].to_s
415       end
416     end
417
418     def select(sql, name=nil)
419       records = execute(sql,name)
420       records.each do |col|
421           col.delete('raw_rnum_')
422       end
423       records
424     end
425   end
426 end
427