Module: ArJdbc::DB2

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

Defined Under Namespace

Modules: Column

Constant Summary collapse

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

.column_selectorObject



25
26
27
28
# File 'lib/arjdbc/db2/adapter.rb', line 25

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
# 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]
            value = value.to_yaml if unserializable_attribute?(c.name, c)
            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



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

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

Instance Method Details

#_execute(sql, name = nil) ⇒ Object



115
116
117
118
119
120
121
122
123
# File 'lib/arjdbc/db2/adapter.rb', line 115

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



168
169
170
# File 'lib/arjdbc/db2/adapter.rb', line 168

def adapter_name
  'DB2'
end

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



295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/arjdbc/db2/adapter.rb', line 295

def add_index(table_name, column_name, options = {})
  if (!zos? || (table_name.to_s ==  ActiveRecord::Migrator.schema_migrations_table_name.to_s))
    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



177
178
179
# File 'lib/arjdbc/db2/adapter.rb', line 177

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

#add_quotes(name) ⇒ Object



417
418
419
420
# File 'lib/arjdbc/db2/adapter.rb', line 417

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

#arel2_visitorsObject



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

def arel2_visitors
  require 'arel/visitors/db2'
  {'db2' => ::Arel::Visitors::DB2, 'as400' => ::Arel::Visitors::DB2}
end

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



343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/arjdbc/db2/adapter.rb', line 343

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



334
335
336
337
338
339
340
341
# File 'lib/arjdbc/db2/adapter.rb', line 334

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



325
326
327
328
329
330
331
332
# File 'lib/arjdbc/db2/adapter.rb', line 325

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



390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/arjdbc/db2/adapter.rb', line 390

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:



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/arjdbc/db2/adapter.rb', line 182

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]}"

    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”



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/arjdbc/db2/adapter.rb', line 126

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



428
429
430
431
# File 'lib/arjdbc/db2/adapter.rb', line 428

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

#indexes(table_name, name = nil) ⇒ Object



413
414
415
# File 'lib/arjdbc/db2/adapter.rb', line 413

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

#jdbc_columns(table_name, name = nil) ⇒ Object



409
410
411
# File 'lib/arjdbc/db2/adapter.rb', line 409

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

#last_insert_id(sql) ⇒ Object



147
148
149
150
151
152
153
# File 'lib/arjdbc/db2/adapter.rb', line 147

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



155
156
157
158
159
160
161
# File 'lib/arjdbc/db2/adapter.rb', line 155

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 => "smallint"}
  tp
end

#pk_and_sequence_for(table) ⇒ Object



237
238
239
240
241
242
243
244
245
# File 'lib/arjdbc/db2/adapter.rb', line 237

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

#quote(value, column = nil) ⇒ Object

:nodoc:



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/arjdbc/db2/adapter.rb', line 251

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



247
248
249
# File 'lib/arjdbc/db2/adapter.rb', line 247

def quote_column_name(column_name)
  column_name
end

#quote_string(string) ⇒ Object



273
274
275
# File 'lib/arjdbc/db2/adapter.rb', line 273

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

#quoted_falseObject



281
282
283
# File 'lib/arjdbc/db2/adapter.rb', line 281

def quoted_false
  '0'
end

#quoted_trueObject



277
278
279
# File 'lib/arjdbc/db2/adapter.rb', line 277

def quoted_true
  '1'
end

#recreate_database(name) ⇒ Object



291
292
293
# File 'lib/arjdbc/db2/adapter.rb', line 291

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

#remove_column(table_name, column_name) ⇒ Object



366
367
368
369
370
371
# File 'lib/arjdbc/db2/adapter.rb', line 366

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



310
311
312
# File 'lib/arjdbc/db2/adapter.rb', line 310

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



316
317
318
319
320
321
322
323
# File 'lib/arjdbc/db2/adapter.rb', line 316

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



374
375
376
377
# File 'lib/arjdbc/db2/adapter.rb', line 374

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

#reorg_table(table_name) ⇒ Object



285
286
287
288
289
# File 'lib/arjdbc/db2/adapter.rb', line 285

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



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/arjdbc/db2/adapter.rb', line 219

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.gsub!(/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

#strip_quotes(str) ⇒ Object



422
423
424
425
426
# File 'lib/arjdbc/db2/adapter.rb', line 422

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

#structure_dumpObject

:nodoc:



433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
# File 'lib/arjdbc/db2/adapter.rb', line 433

def structure_dump #:nodoc:
  definition=""
  rs = @connection.connection..getTables(nil,db2_schema.upcase,nil,["TABLE"].to_java(:string))
  while rs.next
    tname = rs.getString(3)
    definition << "CREATE TABLE #{tname} (\n"
    rs2 = @connection.connection..getColumns(nil,db2_schema.upcase,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



379
380
381
# File 'lib/arjdbc/db2/adapter.rb', line 379

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

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



163
164
165
166
# File 'lib/arjdbc/db2/adapter.rb', line 163

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)


484
485
486
# File 'lib/arjdbc/db2/adapter.rb', line 484

def zos?
  @config[:driver] == "com.ibm.db2.jcc.DB2Driver"
end