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

.arel2_visitors(config) ⇒ Object



174
175
176
177
# File 'lib/arjdbc/db2/adapter.rb', line 174

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

.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



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

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



170
171
172
# File 'lib/arjdbc/db2/adapter.rb', line 170

def adapter_name
  'DB2'
end

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



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

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



179
180
181
# File 'lib/arjdbc/db2/adapter.rb', line 179

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

#add_quotes(name) ⇒ Object



419
420
421
422
# File 'lib/arjdbc/db2/adapter.rb', line 419

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

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



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

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



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

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



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

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



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

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:



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
218
219
# File 'lib/arjdbc/db2/adapter.rb', line 184

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”



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

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



430
431
432
433
# File 'lib/arjdbc/db2/adapter.rb', line 430

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

#indexes(table_name, name = nil) ⇒ Object



415
416
417
# File 'lib/arjdbc/db2/adapter.rb', line 415

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

#jdbc_columns(table_name, name = nil) ⇒ Object



411
412
413
# File 'lib/arjdbc/db2/adapter.rb', line 411

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

#last_insert_id(sql) ⇒ Object



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

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



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

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



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

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:



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

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



249
250
251
# File 'lib/arjdbc/db2/adapter.rb', line 249

def quote_column_name(column_name)
  column_name
end

#quote_string(string) ⇒ Object



275
276
277
# File 'lib/arjdbc/db2/adapter.rb', line 275

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

#quoted_falseObject



283
284
285
# File 'lib/arjdbc/db2/adapter.rb', line 283

def quoted_false
  '0'
end

#quoted_trueObject



279
280
281
# File 'lib/arjdbc/db2/adapter.rb', line 279

def quoted_true
  '1'
end

#recreate_database(name) ⇒ Object



293
294
295
# File 'lib/arjdbc/db2/adapter.rb', line 293

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

#remove_column(table_name, column_name) ⇒ Object



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

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



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

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



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

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



376
377
378
379
# File 'lib/arjdbc/db2/adapter.rb', line 376

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

#reorg_table(table_name) ⇒ Object



287
288
289
290
291
# File 'lib/arjdbc/db2/adapter.rb', line 287

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



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

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



424
425
426
427
428
# File 'lib/arjdbc/db2/adapter.rb', line 424

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

#structure_dumpObject

:nodoc:



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
483
484
# File 'lib/arjdbc/db2/adapter.rb', line 435

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



381
382
383
# File 'lib/arjdbc/db2/adapter.rb', line 381

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

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



165
166
167
168
# File 'lib/arjdbc/db2/adapter.rb', line 165

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)


486
487
488
# File 'lib/arjdbc/db2/adapter.rb', line 486

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