3 def self.column_selector
5 lambda { |cfg, column| column.extend(::ArJdbc::DB2::Column) } ]
8 def self.jdbc_connection_class
9 ::ActiveRecord::ConnectionAdapters::DB2JdbcConnection
14 return nil if value.nil? || value =~ /^\s*null\s*$/i
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
30 def type_cast_code(var_name)
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})"
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))
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
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
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."
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)
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
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?
86 # string defaults are surrounded by single quotes
87 return $1 if value =~ /^'(.*)'$/
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
99 @connection.execute_update(sql)
103 # holy moly batman! all this to tell AS400 "yes i am sure"
104 def execute_and_auto_confirm(sql)
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
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
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}],
130 result.last['last_insert_id']
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"}
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)
151 require 'arel/visitors/db2'
152 {'db2' => ::Arel::Visitors::DB2, 'as400' => ::Arel::Visitors::DB2}
155 def add_limit_offset!(sql, options)
156 replace_limit_offset!(sql, options[:limit], options[:offset])
159 def replace_limit_offset!(sql, limit, offset)
164 sql << " FETCH FIRST ROW ONLY"
166 sql << " FETCH FIRST #{limit} ROWS ONLY"
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}"
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)
181 # In ActiveRecord side, only downcase names of table and column are handled.
182 keys[0] = keys[0].downcase
187 def quote_column_name(column_name)
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
195 if column && (column.type == :decimal || column.type == :integer) && value
200 if column && column.type == :binary
201 "BLOB('#{quote_string(value)}')"
203 "'#{quote_string(value)}'"
209 def quote_string(string)
210 string.gsub(/'/, "''") # ' (for ruby-mode)
221 def reorg_table(table_name)
223 @connection.execute_update "call sysproc.admin_cmd ('REORG TABLE #{table_name}')"
227 def recreate_database(name)
228 tables.each {|table| drop_table("#{db2_schema}.#{table}")}
231 def remove_index(table_name, options = { })
232 execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
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:
239 raise NotImplementedError, "rename_column is not supported on IBM i"
241 execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} TO #{new_column_name}"
242 reorg_table(table_name)
246 def change_column_null(table_name, column_name, null)
248 execute_and_auto_confirm "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} DROP NOT NULL"
250 execute_and_auto_confirm "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET NOT NULL"
252 reorg_table(table_name)
255 def change_column_default(table_name, column_name, default)
257 execute_and_auto_confirm "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} DROP DEFAULT"
259 execute_and_auto_confirm "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET WITH DEFAULT #{quote(default)}"
261 reorg_table(table_name)
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)
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])
276 change_column_default(table_name, column_name, options[:default])
277 change_column_null(table_name, column_name, options[:null])
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])
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}"
290 as400? ? execute_and_auto_confirm(sql) : execute(sql)
291 reorg_table(table_name)
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)
301 @connection.tables(nil, db2_schema, nil, ["TABLE"])
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)
311 def columns(table_name, name = nil)
312 cols = @connection.columns(table_name, name, db2_schema)
314 # scrub out sizing info when CREATE TABLE doesn't support it
315 # but JDBC reports it (doh!)
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)
326 def jdbc_columns(table_name, name = nil)
327 columns(table_name, name)
330 def indexes(table_name, name = nil)
331 @connection.indexes(table_name, name, db2_schema)
335 return name unless name
339 def strip_quotes(str)
340 return str unless str
341 return str unless /^(["']).*\1$/ =~ str
345 def expand_double_quotes(name)
346 return name unless name && name['"']
350 def structure_dump #:nodoc:
352 rs = @connection.connection.meta_data.getTables(nil,db2_schema.upcase,nil,["TABLE"].to_java(:string))
354 tname = rs.getString(3)
355 definition << "CREATE TABLE #{tname} (\n"
356 rs2 = @connection.connection.meta_data.getColumns(nil,db2_schema.upcase,tname,nil)
359 col_name = add_quotes(rs2.getString(4));
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}" : ""
366 default = d1 ? " DEFAULT #{d1}" : ""
369 type = rs2.getString(6)
370 col_precision = rs2.getString(7)
371 col_scale = rs2.getString(9)
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})"
378 nulling = (rs2.getString(18) == 'NO' ? " NOT NULL" : "")
379 create_col_string = add_quotes(expand_double_quotes(strip_quotes(col_name))) +
387 create_col_string = ",\n #{create_col_string}"
389 create_col_string = " #{create_col_string}"
392 definition << create_col_string
396 definition << ");\n\n"
403 @config[:url] =~ /^jdbc:as400:/
407 if @config[:schema].blank?
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
413 # LUW implementation uses schema name of username by default
414 @config[:username] or ENV['USER']