]> source.dussan.org Git - sonarqube.git/blob
75144c536f07c19e1cfa79fa85b8707ff5ef2c8f
[sonarqube.git] /
1 module ArJdbc
2   module DB2
3     def self.column_selector
4       [ /(db2|as400)/i,
5         lambda { |cfg, column| column.extend(::ArJdbc::DB2::Column) } ]
6     end
7
8     def self.jdbc_connection_class
9       ::ActiveRecord::ConnectionAdapters::DB2JdbcConnection
10     end
11
12     module Column
13       def type_cast(value)
14         return nil if value.nil? || value =~ /^\s*null\s*$/i
15         case type
16         when :string    then value
17         when :integer   then defined?(value.to_i) ? value.to_i : (value ? 1 : 0)
18         when :primary_key then defined?(value.to_i) ? value.to_i : (value ? 1 : 0)
19         when :float     then value.to_f
20         when :datetime  then ArJdbc::DB2::Column.cast_to_date_or_time(value)
21         when :date      then ArJdbc::DB2::Column.cast_to_date_or_time(value)
22         when :timestamp then ArJdbc::DB2::Column.cast_to_time(value)
23         when :time      then ArJdbc::DB2::Column.cast_to_time(value)
24         # TODO AS400 stores binary strings in EBCDIC (CCSID 65535), need to convert back to ASCII
25         else
26           super
27         end
28       end
29
30       def type_cast_code(var_name)
31         case type
32         when :datetime  then "ArJdbc::DB2::Column.cast_to_date_or_time(#{var_name})"
33         when :date      then "ArJdbc::DB2::Column.cast_to_date_or_time(#{var_name})"
34         when :timestamp then "ArJdbc::DB2::Column.cast_to_time(#{var_name})"
35         when :time      then "ArJdbc::DB2::Column.cast_to_time(#{var_name})"
36         else
37           super
38         end
39       end
40
41       def self.cast_to_date_or_time(value)
42         return value if value.is_a? Date
43         return nil if value.blank?
44         guess_date_or_time((value.is_a? Time) ? value : cast_to_time(value))
45       end
46
47       def self.cast_to_time(value)
48         return value if value.is_a? Time
49         # AS400 returns a 2 digit year, LUW returns a 4 digit year, so comp = true to help out AS400
50         time_array = ParseDate.parsedate(value, true)
51         time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1;
52         Time.send(ActiveRecord::Base.default_timezone, *time_array) rescue nil
53       end
54
55       def self.guess_date_or_time(value)
56         (value.hour == 0 and value.min == 0 and value.sec == 0) ?
57         Date.new(value.year, value.month, value.day) : value
58       end
59
60       private
61       # <b>DEPRECATED:</b> SMALLINT is now used for boolean field types. Please
62       # convert your tables using DECIMAL(5) for boolean values to SMALLINT instead.
63       def use_decimal5_for_boolean
64         warn "[DEPRECATION] using DECIMAL(5) for boolean is deprecated. Convert your columns to SMALLINT instead."
65         :boolean
66       end
67
68       # http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/topic/com.ibm.db2.luw.apdv.java.doc/doc/rjvjdata.html
69       def simplified_type(field_type)
70         case field_type
71         # old jdbc_db2.rb used decimal(5,0) as boolean
72         when /^smallint/i           then :boolean
73         when /^decimal\(5\)$/i      then use_decimal5_for_boolean
74         when /^real/i               then :float
75         when /^timestamp/i          then :datetime
76         else
77           super
78         end
79       end
80
81       # Post process default value from JDBC into a Rails-friendly format (columns{-internal})
82       def default_value(value)
83         # IBM i (AS400) will return an empty string instead of null for no default
84         return nil if value.blank?
85
86         # string defaults are surrounded by single quotes
87         return $1 if value =~ /^'(.*)'$/
88
89         value
90       end
91     end
92
93     def _execute(sql, name = nil)
94       if ActiveRecord::ConnectionAdapters::JdbcConnection::select?(sql)
95         @connection.execute_query(sql)
96       elsif ActiveRecord::ConnectionAdapters::JdbcConnection::insert?(sql)
97         (@connection.execute_insert(sql) or last_insert_id(sql)).to_i
98       else
99         @connection.execute_update(sql)
100       end
101     end
102
103     # holy moly batman! all this to tell AS400 "yes i am sure"
104     def execute_and_auto_confirm(sql)
105       begin
106         @connection.execute_update "call qsys.qcmdexc('QSYS/CHGJOB INQMSGRPY(*SYSRPYL)',0000000031.00000)"
107         @connection.execute_update "call qsys.qcmdexc('ADDRPYLE SEQNBR(9876) MSGID(CPA32B2) RPY(''I'')',0000000045.00000)"
108       rescue Exception => e
109         raise "Could not call CHGJOB INQMSGRPY(*SYSRPYL) and ADDRPYLE SEQNBR(9876) MSGID(CPA32B2) RPY('I').\n" +
110           "Do you have authority to do this?\n\n" + e.to_s
111       end
112
113       r = execute sql
114
115       begin
116         @connection.execute_update "call qsys.qcmdexc('QSYS/CHGJOB INQMSGRPY(*DFT)',0000000027.00000)"
117         @connection.execute_update "call qsys.qcmdexc('RMVRPYLE SEQNBR(9876)',0000000021.00000)"
118       rescue Exception => e
119         raise "Could not call CHGJOB INQMSGRPY(*DFT) and RMVRPYLE SEQNBR(9876).\n" +
120           "Do you have authority to do this?\n\n" + e.to_s
121       end
122       r
123     end
124
125     def last_insert_id(sql)
126       table_name = sql.split(/\s/)[2]
127       result = select(ActiveRecord::Base.send(:sanitize_sql,
128           %[select IDENTITY_VAL_LOCAL() as last_insert_id from #{table_name}],
129           nil))
130       result.last['last_insert_id']
131     end
132
133     def modify_types(tp)
134       tp[:primary_key] = 'int not null generated by default as identity (start with 1) primary key'
135       tp[:string][:limit] = 255
136       tp[:integer][:limit] = nil
137       tp[:boolean] = {:name => "smallint"}
138       tp
139     end
140
141     def type_to_sql(type, limit = nil, precision = nil, scale = nil)
142       limit = nil if type.to_sym == :integer
143       super(type, limit, precision, scale)
144     end
145
146     def adapter_name
147       'DB2'
148     end
149
150     def arel2_visitors
151       require 'arel/visitors/db2'
152       {'db2' => ::Arel::Visitors::DB2, 'as400' => ::Arel::Visitors::DB2}
153     end
154
155     def add_limit_offset!(sql, options)
156       replace_limit_offset!(sql, options[:limit], options[:offset])
157     end
158
159     def replace_limit_offset!(sql, limit, offset)
160       if limit
161         limit = limit.to_i
162         if !offset
163           if limit == 1
164             sql << " FETCH FIRST ROW ONLY"
165           else
166             sql << " FETCH FIRST #{limit} ROWS ONLY"
167           end
168         else
169           offset = offset.to_i
170           sql.gsub!(/SELECT/i, 'SELECT B.* FROM (SELECT A.*, row_number() over () AS internal$rownum FROM (SELECT')
171           sql << ") A ) B WHERE B.internal$rownum > #{offset} AND B.internal$rownum <= #{limit + offset}"
172         end
173       end
174       sql
175     end
176
177     def pk_and_sequence_for(table)
178       # In JDBC/DB2 side, only upcase names of table and column are handled.
179       keys = super(table.upcase)
180       if keys && keys[0]
181         # In ActiveRecord side, only downcase names of table and column are handled.
182         keys[0] = keys[0].downcase
183       end
184       keys
185     end
186
187     def quote_column_name(column_name)
188       column_name
189     end
190
191     def quote(value, column = nil) # :nodoc:
192       if column && column.respond_to?(:primary) && column.primary && column.klass != String
193         return value.to_i.to_s
194       end
195       if column && (column.type == :decimal || column.type == :integer) && value
196         return value.to_s
197       end
198       case value
199       when String
200         if column && column.type == :binary
201           "BLOB('#{quote_string(value)}')"
202         else
203           "'#{quote_string(value)}'"
204         end
205       else super
206       end
207     end
208
209     def quote_string(string)
210       string.gsub(/'/, "''") # ' (for ruby-mode)
211     end
212
213     def quoted_true
214       '1'
215     end
216
217     def quoted_false
218       '0'
219     end
220
221     def reorg_table(table_name)
222       unless as400?
223         @connection.execute_update "call sysproc.admin_cmd ('REORG TABLE #{table_name}')"
224       end
225     end
226
227     def recreate_database(name)
228       tables.each {|table| drop_table("#{db2_schema}.#{table}")}
229     end
230
231     def remove_index(table_name, options = { })
232       execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
233     end
234
235     # http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/topic/com.ibm.db2.luw.admin.dbobj.doc/doc/t0020130.html
236     # ...not supported on IBM i, so we raise in this case
237     def rename_column(table_name, column_name, new_column_name) #:nodoc:
238       if as400?
239         raise NotImplementedError, "rename_column is not supported on IBM i"
240       else
241         execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} TO #{new_column_name}"
242         reorg_table(table_name)
243       end
244     end
245
246     def change_column_null(table_name, column_name, null)
247       if null
248         execute_and_auto_confirm "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} DROP NOT NULL"
249       else
250         execute_and_auto_confirm "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET NOT NULL"
251       end
252       reorg_table(table_name)
253     end
254
255     def change_column_default(table_name, column_name, default)
256       if default.nil?
257         execute_and_auto_confirm "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} DROP DEFAULT"
258       else
259         execute_and_auto_confirm "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET WITH DEFAULT #{quote(default)}"
260       end
261       reorg_table(table_name)
262     end
263
264     def change_column(table_name, column_name, type, options = {})
265       data_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
266       sql = "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DATA TYPE #{data_type}"
267       as400? ? execute_and_auto_confirm(sql) : execute(sql)
268       reorg_table(table_name)
269
270       if options.include?(:default) and options.include?(:null)
271         # which to run first?
272         if options[:null] or options[:default].nil?
273           change_column_null(table_name, column_name, options[:null])
274           change_column_default(table_name, column_name, options[:default])
275         else
276           change_column_default(table_name, column_name, options[:default])
277           change_column_null(table_name, column_name, options[:null])
278         end
279       elsif options.include?(:default)
280         change_column_default(table_name, column_name, options[:default])
281       elsif options.include?(:null)
282         change_column_null(table_name, column_name, options[:null])
283       end
284     end
285
286     # http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/topic/com.ibm.db2.luw.admin.dbobj.doc/doc/t0020132.html
287     def remove_column(table_name, column_name) #:nodoc:
288       sql = "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
289
290       as400? ? execute_and_auto_confirm(sql) : execute(sql)
291       reorg_table(table_name)
292     end
293
294     # http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/topic/com.ibm.db2.luw.sql.ref.doc/doc/r0000980.html
295     def rename_table(name, new_name) #:nodoc:
296       execute "RENAME TABLE #{name} TO #{new_name}"
297       reorg_table(new_name)
298     end
299
300     def tables
301       @connection.tables(nil, db2_schema, nil, ["TABLE"])
302     end
303
304     # only record precision and scale for types that can set
305     # them via CREATE TABLE:
306     # http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/topic/com.ibm.db2.luw.sql.ref.doc/doc/r0000927.html
307     HAVE_LIMIT = %w(FLOAT DECFLOAT CHAR VARCHAR CLOB BLOB NCHAR NCLOB DBCLOB GRAPHIC VARGRAPHIC) #TIMESTAMP
308     HAVE_PRECISION = %w(DECIMAL NUMERIC)
309     HAVE_SCALE = %w(DECIMAL NUMERIC)
310
311     def columns(table_name, name = nil)
312       cols = @connection.columns(table_name, name, db2_schema)
313
314       # scrub out sizing info when CREATE TABLE doesn't support it
315       # but JDBC reports it (doh!)
316       for col in cols
317         base_sql_type = col.sql_type.sub(/\(.*/, "").upcase
318         col.limit = nil unless HAVE_LIMIT.include?(base_sql_type)
319         col.precision = nil unless HAVE_PRECISION.include?(base_sql_type)
320         #col.scale = nil unless HAVE_SCALE.include?(base_sql_type)
321       end
322
323       cols
324     end
325
326     def jdbc_columns(table_name, name = nil)
327       columns(table_name, name)
328     end
329
330     def indexes(table_name, name = nil)
331       @connection.indexes(table_name, name, db2_schema)
332     end
333
334     def add_quotes(name)
335       return name unless name
336       %Q{"#{name}"}
337     end
338
339     def strip_quotes(str)
340       return str unless str
341       return str unless /^(["']).*\1$/ =~ str
342       str[1..-2]
343     end
344
345     def expand_double_quotes(name)
346       return name unless name && name['"']
347       name.gsub(/"/,'""')
348     end
349
350     def structure_dump #:nodoc:
351       definition=""
352       rs = @connection.connection.meta_data.getTables(nil,db2_schema.upcase,nil,["TABLE"].to_java(:string))
353       while rs.next
354         tname = rs.getString(3)
355         definition << "CREATE TABLE #{tname} (\n"
356         rs2 = @connection.connection.meta_data.getColumns(nil,db2_schema.upcase,tname,nil)
357         first_col = true
358         while rs2.next
359           col_name = add_quotes(rs2.getString(4));
360           default = ""
361           d1 = rs2.getString(13)
362           # IBM i (as400 toolbox driver) will return an empty string if there is no default
363           if @config[:url] =~ /^jdbc:as400:/
364             default = !d1.blank? ? " DEFAULT #{d1}" : ""
365           else
366             default = d1 ? " DEFAULT #{d1}" : ""
367           end
368
369           type = rs2.getString(6)
370           col_precision = rs2.getString(7)
371           col_scale = rs2.getString(9)
372           col_size = ""
373           if HAVE_SCALE.include?(type) and col_scale
374             col_size = "(#{col_precision},#{col_scale})"
375           elsif (HAVE_LIMIT + HAVE_PRECISION).include?(type) and col_precision
376             col_size = "(#{col_precision})"
377           end
378           nulling = (rs2.getString(18) == 'NO' ? " NOT NULL" : "")
379           create_col_string = add_quotes(expand_double_quotes(strip_quotes(col_name))) +
380             " " +
381             type +
382             col_size +
383             "" +
384             nulling +
385             default
386           if !first_col
387             create_col_string = ",\n #{create_col_string}"
388           else
389             create_col_string = " #{create_col_string}"
390           end
391
392           definition << create_col_string
393
394           first_col = false
395         end
396         definition << ");\n\n"
397       end
398       definition
399     end
400
401     private
402     def as400?
403         @config[:url] =~ /^jdbc:as400:/
404     end
405
406     def db2_schema
407       if @config[:schema].blank?
408         if as400?
409           # AS400 implementation takes schema from library name (last part of url)
410           schema = @config[:url].split('/').last.strip
411           (schema[-1..-1] == ";") ? schema.chop : schema
412         else
413           # LUW implementation uses schema name of username by default
414           @config[:username] or ENV['USER']
415         end
416       else
417         @config[:schema]
418       end
419     end
420   end
421 end