Module: ArJdbc::DB2

Defined in:
lib/arjdbc/db2/adapter.rb

Defined Under Namespace

Modules: Column

Constant Summary collapse

NATIVE_DATABASE_TYPES =
{
  :double    => { :name => "double" },
  :bigint    => { :name => "bigint" }
}
HAVE_LIMIT =

only record precision and scale for types that can set them via CREATE TABLE: publib.boulder.ibm.com/infocenter/db2luw/v9r7/topic/com.ibm.db2.luw.sql.ref.doc/doc/r0000927.html

%w(FLOAT DECFLOAT CHAR VARCHAR CLOB BLOB NCHAR NCLOB DBCLOB GRAPHIC VARGRAPHIC)
HAVE_PRECISION =

TIMESTAMP

%w(DECIMAL NUMERIC)
HAVE_SCALE =
%w(DECIMAL NUMERIC)

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.arel2_visitors(config) ⇒ Object



194
195
196
197
# File 'lib/arjdbc/db2/adapter.rb', line 194

def self.arel2_visitors(config)
  require 'arel/visitors/db2'
  {}.tap {|v| %w(db2 as400).each {|a| v[a] = ::Arel::Visitors::DB2 } }
end

.column_selectorObject



29
30
31
32
# File 'lib/arjdbc/db2/adapter.rb', line 29

def self.column_selector
  [ /(db2|as400|zos)/i,
    lambda { |cfg, column| column.extend(::ArJdbc::DB2::Column) } ]
end

.extended(base) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/arjdbc/db2/adapter.rb', line 3

def self.extended(base)
  if base.zos?
    unless @lob_callback_added
      ActiveRecord::Base.class_eval do
        def after_save_with_db2zos_blob
          lobfields = self.class.columns.select { |c| c.sql_type =~ /blob|clob/i }
          lobfields.each do |c|
            value = self[c.name]
            if respond_to?(:unserializable_attribute?)
              value = value.to_yaml if unserializable_attribute?(c.name, c)
            else
              value = value.to_yaml if value.is_a?(Hash)
            end
            next if value.nil?
            connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
          end
        end
      end

      ActiveRecord::Base.after_save :after_save_with_db2zos_blob

      @lob_callback_added = true
    end
  end
end

.jdbc_connection_classObject



34
35
36
# File 'lib/arjdbc/db2/adapter.rb', line 34

def self.jdbc_connection_class
  ::ActiveRecord::ConnectionAdapters::DB2JdbcConnection
end

Instance Method Details

#_execute(sql, name = nil) ⇒ Object



139
140
141
142
143
144
145
146
147
# File 'lib/arjdbc/db2/adapter.rb', line 139

def _execute(sql, name = nil)
  if ActiveRecord::ConnectionAdapters::JdbcConnection::select?(sql)
    @connection.execute_query(sql)
  elsif ActiveRecord::ConnectionAdapters::JdbcConnection::insert?(sql)
    (@connection.execute_insert(sql) or last_insert_id(sql)).to_i
  else
    @connection.execute_update(sql)
  end
end

#adapter_nameObject



190
191
192
# File 'lib/arjdbc/db2/adapter.rb', line 190

def adapter_name
  'DB2'
end

#add_index(table_name, column_name, options = {}) ⇒ Object



321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/arjdbc/db2/adapter.rb', line 321

def add_index(table_name, column_name, options = {})
  if (!zos? || (table_name.to_s ==  ActiveRecord::Migrator.schema_migrations_table_name.to_s))
    column_name = column_name.to_s if column_name.is_a?(Symbol)
    super
  else
    statement ="CREATE"
    statement << " UNIQUE " if options[:unique]
    statement << " INDEX "+"#{ActiveRecord::Base.table_name_prefix}#{options[:name]} "

    statement << " ON #{table_name}(#{column_name})"

    execute statement
  end
end

#add_limit_offset!(sql, options) ⇒ Object



199
200
201
# File 'lib/arjdbc/db2/adapter.rb', line 199

def add_limit_offset!(sql, options)
  replace_limit_offset!(sql, options[:limit], options[:offset])
end

#add_quotes(name) ⇒ Object



444
445
446
447
# File 'lib/arjdbc/db2/adapter.rb', line 444

def add_quotes(name)
  return name unless name
  %Q{"#{name}"}
end

#change_column(table_name, column_name, type, options = {}) ⇒ Object



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/arjdbc/db2/adapter.rb', line 370

def change_column(table_name, column_name, type, options = {})
  data_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
  sql = "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DATA TYPE #{data_type}"
  as400? ? execute_and_auto_confirm(sql) : execute(sql)
  reorg_table(table_name)

  if options.include?(:default) and options.include?(:null)
    # which to run first?
    if options[:null] or options[:default].nil?
      change_column_null(table_name, column_name, options[:null])
      change_column_default(table_name, column_name, options[:default])
    else
      change_column_default(table_name, column_name, options[:default])
      change_column_null(table_name, column_name, options[:null])
    end
  elsif options.include?(:default)
    change_column_default(table_name, column_name, options[:default])
  elsif options.include?(:null)
    change_column_null(table_name, column_name, options[:null])
  end
end

#change_column_default(table_name, column_name, default) ⇒ Object



361
362
363
364
365
366
367
368
# File 'lib/arjdbc/db2/adapter.rb', line 361

def change_column_default(table_name, column_name, default)
  if default.nil?
    execute_and_auto_confirm "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} DROP DEFAULT"
  else
    execute_and_auto_confirm "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET WITH DEFAULT #{quote(default)}"
  end
  reorg_table(table_name)
end

#change_column_null(table_name, column_name, null) ⇒ Object



352
353
354
355
356
357
358
359
# File 'lib/arjdbc/db2/adapter.rb', line 352

def change_column_null(table_name, column_name, null)
  if null
    execute_and_auto_confirm "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} DROP NOT NULL"
  else
    execute_and_auto_confirm "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET NOT NULL"
  end
  reorg_table(table_name)
end

#columns(table_name, name = nil) ⇒ Object



417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/arjdbc/db2/adapter.rb', line 417

def columns(table_name, name = nil)
  cols = @connection.columns(table_name, name, db2_schema)

  if zos?
    # Remove the mighty db2_generated_rowid_for_lobs from the list of columns
    cols = cols.reject { |col| "db2_generated_rowid_for_lobs" == col.name }
  end
  # scrub out sizing info when CREATE TABLE doesn't support it
  # but JDBC reports it (doh!)
  for col in cols
    base_sql_type = col.sql_type.sub(/\(.*/, "").upcase
    col.limit = nil unless HAVE_LIMIT.include?(base_sql_type)
    col.precision = nil unless HAVE_PRECISION.include?(base_sql_type)
    #col.scale = nil unless HAVE_SCALE.include?(base_sql_type)
  end

  cols
end

#create_table(name, options = {}) ⇒ Object

:nodoc:



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/arjdbc/db2/adapter.rb', line 204

def create_table(name, options = {}) #:nodoc:
  if zos?
    table_definition = ActiveRecord::ConnectionAdapters::TableDefinition.new(self)

    table_definition.primary_key(options[:primary_key] || ActiveRecord::Base.get_primary_key(name)) unless options[:id] == false

    yield table_definition

    # Clobs in DB2 Host have to be created after the Table with an auxiliary Table.
    # First: Save them for later in Array "clobs"
    clobs =table_definition.columns.select { |x| x.type == "text" }

    # Second: and delete them from the original Colums-Array
    table_definition.columns.delete_if { |x| x.type=="text" }

    if options[:force] && table_exists?(name)
      super.drop_table(name, options)
    end

    create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
    create_sql << "#{quote_table_name(name)} ("
    create_sql << table_definition.to_sql
    create_sql << ") #{options[:options]}"
    create_sql << " IN #{@config[:database]}.#{@config[:tablespace]}" if @config[:database] && @config[:tablespace]

    execute create_sql

    clobs.each do |clob_column|
      execute "ALTER TABLE #{name+" ADD COLUMN "+clob_column.name.to_s+" clob"}"
      execute "CREATE AUXILIARY TABLE #{name+"_"+clob_column.name.to_s+"_CD_"} IN #{@config[:database]}.#{@config[:lob_tablespaces][name.split(".")[1]]} STORES #{name} COLUMN "+clob_column.name.to_s
      execute "CREATE UNIQUE INDEX #{name+"_"+clob_column.name.to_s+"_CD_"} ON #{name+"_"+clob_column.name.to_s+"_CD_"};"
    end
  else
    super(name, options)
  end
end

#execute_and_auto_confirm(sql) ⇒ Object

holy moly batman! all this to tell AS400 “yes i am sure”



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/arjdbc/db2/adapter.rb', line 150

def execute_and_auto_confirm(sql)
  begin
    @connection.execute_update "call qsys.qcmdexc('QSYS/CHGJOB INQMSGRPY(*SYSRPYL)',0000000031.00000)"
    @connection.execute_update "call qsys.qcmdexc('ADDRPYLE SEQNBR(9876) MSGID(CPA32B2) RPY(''I'')',0000000045.00000)"
  rescue Exception => e
    raise "Could not call CHGJOB INQMSGRPY(*SYSRPYL) and ADDRPYLE SEQNBR(9876) MSGID(CPA32B2) RPY('I').\n" +
      "Do you have authority to do this?\n\n" + e.to_s
  end

  r = execute sql

  begin
    @connection.execute_update "call qsys.qcmdexc('QSYS/CHGJOB INQMSGRPY(*DFT)',0000000027.00000)"
    @connection.execute_update "call qsys.qcmdexc('RMVRPYLE SEQNBR(9876)',0000000021.00000)"
  rescue Exception => e
    raise "Could not call CHGJOB INQMSGRPY(*DFT) and RMVRPYLE SEQNBR(9876).\n" +
      "Do you have authority to do this?\n\n" + e.to_s
  end
  r
end

#expand_double_quotes(name) ⇒ Object



455
456
457
458
# File 'lib/arjdbc/db2/adapter.rb', line 455

def expand_double_quotes(name)
  return name unless name && name['"']
  name.gsub(/"/,'""')
end

#explain(query, *binds) ⇒ Object



47
48
49
# File 'lib/arjdbc/db2/adapter.rb', line 47

def explain(query, *binds)
  # TODO: Explain this!
end

#indexes(table_name, name = nil) ⇒ Object



440
441
442
# File 'lib/arjdbc/db2/adapter.rb', line 440

def indexes(table_name, name = nil)
  @connection.indexes(table_name, name, db2_schema)
end

#jdbc_columns(table_name, name = nil) ⇒ Object



436
437
438
# File 'lib/arjdbc/db2/adapter.rb', line 436

def jdbc_columns(table_name, name = nil)
  columns(table_name, name)
end

#last_insert_id(sql) ⇒ Object



171
172
173
174
175
# File 'lib/arjdbc/db2/adapter.rb', line 171

def last_insert_id(sql)
  table_name = sql.split(/\s/)[2]
  result = select(ActiveRecord::Base.send(:sanitize_sql, %[select IDENTITY_VAL_LOCAL() as last_insert_id from #{table_name}], nil))
  result.last['last_insert_id']
end

#modify_types(tp) ⇒ Object



177
178
179
180
181
182
183
# File 'lib/arjdbc/db2/adapter.rb', line 177

def modify_types(tp)
  tp[:primary_key] = 'int not null generated by default as identity (start with 1) primary key'
  tp[:string][:limit] = 255
  tp[:integer][:limit] = nil
  tp[:boolean] = {:name => "decimal(1)"}
  tp
end

#native_database_typesObject



43
44
45
# File 'lib/arjdbc/db2/adapter.rb', line 43

def native_database_types
  super.merge(NATIVE_DATABASE_TYPES)
end

#next_sequence_value(sequence_name) ⇒ Object



60
61
62
# File 'lib/arjdbc/db2/adapter.rb', line 60

def next_sequence_value(sequence_name)
  select_value("select next value for #{sequence_name} from sysibm.sysdummy1")
end

#pk_and_sequence_for(table) ⇒ Object



259
260
261
262
263
264
265
266
267
# File 'lib/arjdbc/db2/adapter.rb', line 259

def pk_and_sequence_for(table)
  # In JDBC/DB2 side, only upcase names of table and column are handled.
  keys = super(table.upcase)
  if keys && keys[0]
    # In ActiveRecord side, only downcase names of table and column are handled.
    keys[0] = keys[0].downcase
  end
  keys
end

#prefetch_primary_key?(table_name = nil) ⇒ Boolean

Returns:

  • (Boolean)


51
52
53
54
55
56
57
58
# File 'lib/arjdbc/db2/adapter.rb', line 51

def prefetch_primary_key?(table_name = nil)
  # TRUE if the table has no identity column
  names = table_name.upcase.split(".")
  sql = "SELECT 1 FROM SYSCAT.COLUMNS WHERE IDENTITY = 'Y' "
  sql += "AND TABSCHEMA = '#{names.first}' " if names.size == 2
  sql += "AND TABNAME = '#{names.last}'"
  select_one(sql).nil?
end

#quote(value, column = nil) ⇒ Object

:nodoc:



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/arjdbc/db2/adapter.rb', line 273

def quote(value, column = nil) # :nodoc:
  if column && column.respond_to?(:primary) && column.primary && column.klass != String
    return value.to_i.to_s
  end
  if column && (column.type == :decimal || column.type == :integer) && value
    return value.to_s
  end
  case value
  when String
    if column && column.type == :binary
      "BLOB('#{quote_string(value)}')"
    else
      if zos? && column.type == :text
        "'if_you_see_this_value_the_after_save_hook_in_db2_zos_adapter_went_wrong'"
      else
        "'#{quote_string(value)}'"
      end
    end
  else super
  end
end

#quote_column_name(column_name) ⇒ Object



269
270
271
# File 'lib/arjdbc/db2/adapter.rb', line 269

def quote_column_name(column_name)
  column_name.to_s
end

#quote_string(string) ⇒ Object



295
296
297
# File 'lib/arjdbc/db2/adapter.rb', line 295

def quote_string(string)
  string.gsub(/'/, "''") # ' (for ruby-mode)
end

#quoted_falseObject



303
304
305
# File 'lib/arjdbc/db2/adapter.rb', line 303

def quoted_false
  '0'
end

#quoted_trueObject



299
300
301
# File 'lib/arjdbc/db2/adapter.rb', line 299

def quoted_true
  '1'
end

#recreate_database(name) ⇒ Object



317
318
319
# File 'lib/arjdbc/db2/adapter.rb', line 317

def recreate_database(name)
  tables.each {|table| drop_table("#{db2_schema}.#{table}")}
end

#remove_column(table_name, column_name) ⇒ Object



393
394
395
396
397
398
# File 'lib/arjdbc/db2/adapter.rb', line 393

def remove_column(table_name, column_name) #:nodoc:
  sql = "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"

  as400? ? execute_and_auto_confirm(sql) : execute(sql)
  reorg_table(table_name)
end

#remove_index(table_name, options = { }) ⇒ Object



337
338
339
# File 'lib/arjdbc/db2/adapter.rb', line 337

def remove_index(table_name, options = { })
  execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
end

#rename_column(table_name, column_name, new_column_name) ⇒ Object



343
344
345
346
347
348
349
350
# File 'lib/arjdbc/db2/adapter.rb', line 343

def rename_column(table_name, column_name, new_column_name) #:nodoc:
  if as400?
    raise NotImplementedError, "rename_column is not supported on IBM i"
  else
    execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} TO #{new_column_name}"
    reorg_table(table_name)
  end
end

#rename_table(name, new_name) ⇒ Object



401
402
403
404
# File 'lib/arjdbc/db2/adapter.rb', line 401

def rename_table(name, new_name) #:nodoc:
  execute "RENAME TABLE #{name} TO #{new_name}"
  reorg_table(new_name)
end

#reorg_table(table_name) ⇒ Object



307
308
309
310
311
# File 'lib/arjdbc/db2/adapter.rb', line 307

def reorg_table(table_name)
  unless as400?
    @connection.execute_update "call sysproc.admin_cmd ('REORG TABLE #{table_name}')"
  end
end

#replace_limit_offset!(sql, limit, offset) ⇒ Object



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/arjdbc/db2/adapter.rb', line 241

def replace_limit_offset!(sql, limit, offset)
  if limit
    limit = limit.to_i
    if !offset
      if limit == 1
        sql << " FETCH FIRST ROW ONLY"
      else
        sql << " FETCH FIRST #{limit} ROWS ONLY"
      end
    else
      offset = offset.to_i
      sql.sub!(/SELECT/i, 'SELECT B.* FROM (SELECT A.*, row_number() over () AS internal$rownum FROM (SELECT')
      sql << ") A ) B WHERE B.internal$rownum > #{offset} AND B.internal$rownum <= #{limit + offset}"
    end
  end
  sql
end

#runstats_for_table(tablename, priority = 10) ⇒ Object



313
314
315
# File 'lib/arjdbc/db2/adapter.rb', line 313

def runstats_for_table(tablename, priority=10)
  @connection.execute_update "call sysproc.admin_cmd('RUNSTATS ON TABLE #{tablename} WITH DISTRIBUTION AND DETAILED INDEXES ALL UTIL_IMPACT_PRIORITY #{priority}')"
end

#strip_quotes(str) ⇒ Object



449
450
451
452
453
# File 'lib/arjdbc/db2/adapter.rb', line 449

def strip_quotes(str)
  return str unless str
  return str unless /^(["']).*\1$/ =~ str
  str[1..-2]
end

#structure_dumpObject

:nodoc:



460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
# File 'lib/arjdbc/db2/adapter.rb', line 460

def structure_dump #:nodoc:
  definition=""
  db2_schema = db2_schema.upcase if db2_schema.present?
  rs = @connection.connection..getTables(nil,db2_schema,nil,["TABLE"].to_java(:string))
  while rs.next
    tname = rs.getString(3)
    definition << "CREATE TABLE #{tname} (\n"
    rs2 = @connection.connection..getColumns(nil,db2_schema,tname,nil)
    first_col = true
    while rs2.next
      col_name = add_quotes(rs2.getString(4));
      default = ""
      d1 = rs2.getString(13)
      # IBM i (as400 toolbox driver) will return an empty string if there is no default
      if @config[:url] =~ /^jdbc:as400:/
        default = !d1.blank? ? " DEFAULT #{d1}" : ""
      else
        default = d1 ? " DEFAULT #{d1}" : ""
      end

      type = rs2.getString(6)
      col_precision = rs2.getString(7)
      col_scale = rs2.getString(9)
      col_size = ""
      if HAVE_SCALE.include?(type) and col_scale
        col_size = "(#{col_precision},#{col_scale})"
      elsif (HAVE_LIMIT + HAVE_PRECISION).include?(type) and col_precision
        col_size = "(#{col_precision})"
      end
      nulling = (rs2.getString(18) == 'NO' ? " NOT NULL" : "")
      create_col_string = add_quotes(expand_double_quotes(strip_quotes(col_name))) +
        " " +
        type +
        col_size +
        "" +
        nulling +
        default
      if !first_col
        create_col_string = ",\n #{create_col_string}"
      else
        create_col_string = " #{create_col_string}"
      end

      definition << create_col_string

      first_col = false
    end
    definition << ");\n\n"
  end
  definition
end

#tablesObject



406
407
408
# File 'lib/arjdbc/db2/adapter.rb', line 406

def tables
  @connection.tables(nil, db2_schema, nil, ["TABLE"])
end

#type_to_sql(type, limit = nil, precision = nil, scale = nil) ⇒ Object



185
186
187
188
# File 'lib/arjdbc/db2/adapter.rb', line 185

def type_to_sql(type, limit = nil, precision = nil, scale = nil)
  limit = nil if type.to_sym == :integer
  super(type, limit, precision, scale)
end

#zos?Boolean

Returns:

  • (Boolean)


512
513
514
# File 'lib/arjdbc/db2/adapter.rb', line 512

def zos?
  @config[:url] =~ /^jdbc:db2j:net:/ && @config[:driver] == "com.ibm.db2.jcc.DB2Driver"
end