ibmi_db 2.5.14-powerpc-aix-6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/IBM_DB.rb ADDED
@@ -0,0 +1,3 @@
1
+ require (RUBY_PLATFORM =~ /mswin32/ || RUBY_PLATFORM =~ /mingw32/ ) ? 'mswin32/ibm_db' : 'ibm_db.so'
2
+ require 'active_record/connection_adapters/ibm_db_adapter'
3
+
@@ -0,0 +1,3782 @@
1
+ # +----------------------------------------------------------------------+
2
+ # | Licensed Materials - Property of IBM |
3
+ # | |
4
+ # | (C) Copyright IBM Corporation 2006, 2007, 2008, 2009, 2010 |
5
+ # +----------------------------------------------------------------------+
6
+ # | Authors: Antonio Cangiano <cangiano@ca.ibm.com> |
7
+ # | : Mario Ds Briggs <mario.briggs@in.ibm.com> |
8
+ # | : Praveen Devarao <praveendrl@in.ibm.com> |
9
+ # | : Tony Cairns <adc@us.ibm.com> |
10
+ # +----------------------------------------------------------------------+
11
+
12
+ require 'active_record/connection_adapters/abstract_adapter'
13
+ require 'arel/visitors/bind_visitor'
14
+
15
+ module ActiveRecord
16
+ #############################
17
+ # trace -- (Aaron)
18
+ #############################
19
+ trace = TracePoint.new(:call) do |tp|
20
+ if tp.path.include? 'ibm_db_adapter.rb'
21
+ IBM_DB.trace(sprintf("%s %s %-2d", tp.defined_class, tp.method_id, tp.lineno))
22
+ end
23
+ end
24
+ trace.enable
25
+
26
+ #############################
27
+ # Unit testing (SQLLiteDatabaseTasks, MySQLDatabaseTasks, PostgreSQLDatabaseTasks)
28
+ #############################
29
+ module Tasks # :nodoc:
30
+ class IbmDbDatabaseTasks
31
+ delegate :logger, :connection, :establish_connection, to: ActiveRecord::Base
32
+ def initialize(configuration)
33
+ @configuration = configuration
34
+ self.log_start
35
+ end
36
+ def create
37
+ self.log_start
38
+ begin
39
+ establish_connection configuration
40
+ connection.task_create(self)
41
+ rescue Exception => e
42
+ puts e.message
43
+ self.log(e.message)
44
+ # puts e.backtrace.inspect
45
+ end
46
+ end
47
+ def drop
48
+ self.log_start
49
+ begin
50
+ establish_connection configuration
51
+ connection.task_drop(self)
52
+ rescue Exception => e
53
+ puts e.message
54
+ self.log(e.message)
55
+ # puts e.backtrace.inspect
56
+ end
57
+ end
58
+ def purge
59
+ begin
60
+ establish_connection configuration
61
+ connection.task_purge(self)
62
+ rescue Exception => e
63
+ puts e.message
64
+ self.log(e.message)
65
+ # puts e.backtrace.inspect
66
+ end
67
+ end
68
+ def charset
69
+ begin
70
+ establish_connection configuration
71
+ connection.task_charset(self)
72
+ rescue Exception => e
73
+ puts e.message
74
+ self.log(e.message)
75
+ # puts e.backtrace.inspect
76
+ end
77
+ end
78
+ def collation
79
+ begin
80
+ establish_connection configuration
81
+ connection.task_collation(self)
82
+ rescue Exception => e
83
+ puts e.message
84
+ self.log(e.message)
85
+ # puts e.backtrace.inspect
86
+ end
87
+ end
88
+ def structure_dump(filename=nil)
89
+ self.log_start
90
+ begin
91
+ establish_connection configuration
92
+ connection.task_structure_dump(self,filename)
93
+ rescue Exception => e
94
+ puts e.message
95
+ self.log(e.message)
96
+ # puts e.backtrace.inspect
97
+ end
98
+ end
99
+ def structure_load(filename=nil)
100
+ self.log_start
101
+ begin
102
+ establish_connection configuration
103
+ connection.task_structure_load(self,filename)
104
+ rescue Exception => e
105
+ puts e.message
106
+ self.log(e.message)
107
+ # puts e.backtrace.inspect
108
+ end
109
+ end
110
+ def configuration
111
+ @configuration
112
+ end
113
+ def log_start
114
+ @log_time_start = Time.now
115
+ @log_time_end = Time.now
116
+ end
117
+ def log_end
118
+ @log_time_end = Time.now
119
+ end
120
+ def log(msg)
121
+ self.log_end
122
+ log_time_diff = ((@log_time_end - @log_time_start) * 10000.0).to_i/10.0
123
+ logger.info " (#{log_time_diff}ms) #{msg}"
124
+ self.log_start
125
+ end
126
+ end # end IbmDbDatabaseTasks
127
+ module DatabaseTasks
128
+ extend self
129
+ if(self.respond_to?(:register_task))
130
+ register_task(/ibm_db/, ActiveRecord::Tasks::IbmDbDatabaseTasks)
131
+ end
132
+ end # module DatabaseTasks
133
+ end # module Tasks
134
+
135
+ class Relation
136
+ def insert(values)
137
+ primary_key_value = nil
138
+
139
+ if primary_key && Hash === values
140
+ primary_key_value = values[values.keys.find { |k|
141
+ k.name == primary_key
142
+ }]
143
+
144
+ if !primary_key_value && connection.prefetch_primary_key?(klass.table_name)
145
+ primary_key_value = connection.next_sequence_value(klass.sequence_name)
146
+ values[klass.arel_table[klass.primary_key]] = primary_key_value
147
+ end
148
+ end
149
+
150
+ im = arel.create_insert
151
+ im.into @table
152
+
153
+ conn = @klass.connection
154
+
155
+ substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
156
+ binds = substitutes.map do |arel_attr, value|
157
+ [@klass.columns_hash[arel_attr.name], value]
158
+ end
159
+
160
+ substitutes.each_with_index do |tuple, i|
161
+ tuple[1] = conn.substitute_at(binds[i][0], i)
162
+ end
163
+
164
+ if values.empty? # empty insert
165
+ im.values = Arel.sql(connection.empty_insert_statement_value(klass.primary_key))
166
+ else
167
+ im.insert substitutes
168
+ end
169
+
170
+ conn.insert(
171
+ im,
172
+ 'SQL',
173
+ primary_key,
174
+ primary_key_value,
175
+ nil,
176
+ binds)
177
+ end
178
+ end
179
+
180
+ class Base
181
+ # Method required to handle LOBs and XML fields.
182
+ # An after save callback checks if a marker has been inserted through
183
+ # the insert or update, and then proceeds to update that record with
184
+ # the actual large object through a prepared statement (param binding).
185
+ after_save :handle_lobs
186
+ def handle_lobs()
187
+ # T005: if connection.kind_of?(ConnectionAdapters::IBM_DBAdapter)
188
+ if self.class.connection.kind_of?(ConnectionAdapters::IBM_DBAdapter)
189
+ # Checks that the insert or update had at least a BLOB, CLOB or XML field
190
+ #T005: connection.sql.each do |clob_sql|
191
+ self.class.connection.sql.each do |clob_sql|
192
+ if clob_sql =~ /BLOB\('(.*)'\)/i ||
193
+ clob_sql =~ /@@@IBMTEXT@@@/i ||
194
+ clob_sql =~ /@@@IBMXML@@@/i ||
195
+ clob_sql =~ /@@@IBMBINARY@@@/i
196
+ update_query = "UPDATE #{self.class.table_name} SET ("
197
+ counter = 0
198
+ values = []
199
+ params = []
200
+ # Selects only binary, text and xml columns
201
+ self.class.columns.select{|col| col.type == :binary ||
202
+ col.type == :text ||
203
+ col.type == :xml}.each do |col|
204
+ # Adds the selected columns to the query
205
+ if counter == 0
206
+ update_query << "#{col.name}"
207
+ else
208
+ update_query << ",#{col.name}"
209
+ end
210
+
211
+ # Add a '?' for the parameter or a NULL if the value is nil or empty
212
+ # (except for a CLOB field where '' can be a value)
213
+ if self[col.name].nil? ||
214
+ self[col.name] == {} ||
215
+ self[col.name] == [] ||
216
+ (self[col.name] == '' && col.type != :text)
217
+ params << 'NULL'
218
+ else
219
+ if self.class.serialized_attributes[col.name]
220
+ values << YAML.dump(self[col.name])
221
+ else
222
+ values << self[col.name]
223
+ end
224
+ params << '?'
225
+ end
226
+ counter += 1
227
+ end
228
+ # no subsequent update is required if no relevant columns are found
229
+ next if counter == 0
230
+
231
+ update_query << ") = "
232
+ # IBM_DB accepts 'SET (column) = NULL' but not (NULL),
233
+ # therefore the sql needs to be changed for a single NULL field.
234
+ if params.size==1 && params[0] == 'NULL'
235
+ update_query << "NULL"
236
+ else
237
+ update_query << "(" + params.join(',') + ")"
238
+ end
239
+
240
+ update_query << " WHERE #{self.class.primary_key} = ?"
241
+ values << self[self.class.primary_key.downcase]
242
+
243
+ begin
244
+ unless stmt = IBM_DB.prepare(connection.connection, update_query)
245
+ error_msg = IBM_DB.getErrormsg( connection.connection, IBM_DB::DB_CONN )
246
+ if error_msg && !error_msg.empty?
247
+ raise "Statement prepare for updating LOB/XML column failed : #{error_msg}"
248
+ else
249
+ raise StandardError.new('An unexpected error occurred during update of LOB/XML column')
250
+ end
251
+ end
252
+ connection.log_query(update_query,'update of LOB/XML field(s)in handle_lobs')
253
+
254
+ # rollback any failed LOB/XML field updates (and remove associated marker)
255
+ unless IBM_DB.execute(stmt, values)
256
+ error_msg = "Failed to insert/update LOB/XML field(s) due to: #{IBM_DB.getErrormsg( stmt, IBM_DB::DB_STMT )}"
257
+ connection.execute("ROLLBACK")
258
+ raise error_msg
259
+ end
260
+ rescue StandardError => error
261
+ raise error
262
+ ensure
263
+ IBM_DB.free_stmt(stmt) if stmt
264
+ end
265
+ end # if clob_sql
266
+ end #connection.sql.each
267
+ #T005 connection.handle_lobs_triggered = true
268
+ self.class.connection.handle_lobs_triggered = true
269
+ end # if connection.kind_of?
270
+ end # handle_lobs
271
+ private :handle_lobs
272
+
273
+ # *NEW encrypted passwords - generate pass key cipher (see ibm_db_password.rb)
274
+ def self.ibm_db_generate_key()
275
+ require 'active_record/connection_adapters/ibm_db_password'
276
+ key_out = IBMDBPassword::Encrypt.gen_key()
277
+ key_out
278
+ end
279
+ # *NEW encrypted passwords - generate encrypted password (see ibm_db_password.rb)
280
+ # plaintext - password in plain text
281
+ # key - 32 char pass key
282
+ def self.ibm_db_generate_password(plaintext, key="*DEFAULT")
283
+ require 'active_record/connection_adapters/ibm_db_password'
284
+ pwd_out = IBMDBPassword::Encrypt.gen_password(plaintext, key)
285
+ pwd_out
286
+ end
287
+
288
+ # Establishes a connection to a specified database using the credentials provided
289
+ # with the +config+ argument. All the ActiveRecord objects will use this connection
290
+ def self.ibm_db_connection(config)
291
+ # Attempts to load the Ruby driver IBM databases
292
+ # while not already loaded or raises LoadError in case of failure.
293
+ begin
294
+ require 'ibm_db' unless defined? IBM_DB
295
+ rescue LoadError
296
+ raise LoadError, "Failed to load IBM_DB Ruby driver."
297
+ end
298
+
299
+ if( config.has_key?(:parameterized) && config[:parameterized] == true )
300
+ require 'active_record/connection_adapters/ibm_db_pstmt'
301
+ end
302
+ # Check if class TableDefinition responds to indexes method to determine if we are on AR 3 or AR 4.
303
+ # This is a interim hack ti ensure backward compatibility. To remove as we move out of AR 3 support
304
+ # or have a better way to determine which version of AR being run against.
305
+ checkClass = ActiveRecord::ConnectionAdapters::TableDefinition.new(nil)
306
+ if(checkClass.respond_to?(:indexes))
307
+ isAr3 = false
308
+ else
309
+ isAr3 = true
310
+ end
311
+
312
+ # *NEW test by yaml (see ibm_db_password.rb)
313
+ if ENV['TEST_YAML']
314
+ config[:test_yaml] = ENV['TEST_YAML']
315
+ end
316
+ if ENV['TEST_ENV']
317
+ config[:test_env] = ENV['TEST_ENV']
318
+ end
319
+ if config.has_key?(:test_yaml) && config.has_key?(:test_env)
320
+ require 'active_record/connection_adapters/ibm_db_password'
321
+ config = IBMDBPassword::Encrypt.parse_yaml(config,config[:test_yaml],config[:test_env])
322
+ config = IBMDBPassword::Encrypt.symbolize_keys(config)
323
+ else
324
+ # Converts all +config+ keys to symbols
325
+ config = config.symbolize_keys
326
+ end
327
+ # *NEW encrypted passwords - decrypt password or password.yml (see ibm_db_password.rb)
328
+ if config.has_key?(:pwd_yaml) || config.has_key?(:pwd_enc)
329
+ require 'active_record/connection_adapters/ibm_db_password'
330
+ config = IBMDBPassword::Encrypt.parse_user_config(config)
331
+ end
332
+
333
+ # Flag to decide if quoted literal replcement should take place. By default it is ON. Set it to OFF if using Pstmt
334
+ set_quoted_literal_replacement = IBM_DB::QUOTED_LITERAL_REPLACEMENT_ON
335
+
336
+ # Retrieves database user credentials from the +config+ hash
337
+ # or raises ArgumentError in case of failure.
338
+ if !config.has_key?(:username) || !config.has_key?(:password)
339
+ raise ArgumentError, "Missing argument(s): Username/Password for #{config[:database]} is not specified"
340
+ else
341
+ if(config[:username].to_s.nil? || config[:password].to_s.nil?)
342
+ raise ArgumentError, "Username/Password cannot be nil"
343
+ end
344
+ username = config[:username].to_s
345
+ password = config[:password].to_s
346
+ end
347
+
348
+ if(config.has_key?(:dbops) && config[:dbops] == true)
349
+ return ConnectionAdapters::IBM_DBAdapter.new(nil, isAr3, logger, config, {})
350
+ end
351
+
352
+ # Retrieves the database alias (local catalog name) or remote name
353
+ # (for remote TCP/IP connections) from the +config+ hash
354
+ # or raises ArgumentError in case of failure.
355
+ if config.has_key?(:database)
356
+ database = config[:database].to_s
357
+ else
358
+ raise ArgumentError, "Missing argument: a database name needs to be specified."
359
+ end
360
+
361
+ # Providing default schema (username) when not specified
362
+ config[:schema] = config.has_key?(:schema) ? config[:schema].to_s : config[:username].to_s
363
+
364
+ if(config.has_key?(:parameterized) && config[:parameterized] == true )
365
+ set_quoted_literal_replacement = IBM_DB::QUOTED_LITERAL_REPLACEMENT_OFF
366
+ end
367
+
368
+ # Extract connection options from the database configuration
369
+ # (in support to formatting, audit and billing purposes):
370
+ # Retrieve database objects fields in lowercase
371
+ # adc -- for IBM i ...
372
+ # IBM i attributes added (see documentation bottom),
373
+ # "reasonable" name mapping support both ruby/yaml:
374
+ # Ruby (clean namespace) ...
375
+ # ActiveRecord::Base.establish_connection( IBM_DB::SQL_ATTR_DBC_SYS_NAMING=>IBM_DB::SQL_SYSTEM_NAMING_ON, ... )
376
+ # yaml (Your Administrative Monstrosity) ...
377
+ # development:
378
+ # adapter: ibm_db
379
+ # ibm_i_naming: system
380
+ conn_options = {IBM_DB::ATTR_CASE => IBM_DB::CASE_LOWER}
381
+ config.each do |key, value|
382
+ if !value.nil?
383
+ case key
384
+ when :app_user || "app_user" || IBM_DB::SQL_ATTR_INFO_USERID # Set connection's user info
385
+ conn_options[IBM_DB::SQL_ATTR_INFO_USERID] = value
386
+ when :account || "account" || IBM_DB::SQL_ATTR_INFO_ACCTSTR # Set connection's account info
387
+ conn_options[IBM_DB::SQL_ATTR_INFO_ACCTSTR] = value
388
+ when :application || "application" || IBM_DB::SQL_ATTR_INFO_APPLNAME # Set connection's application info
389
+ conn_options[IBM_DB::SQL_ATTR_INFO_APPLNAME] = value
390
+ when :workstation || "workstation" || IBM_DB::SQL_ATTR_INFO_WRKSTNNAME # Set connection's workstation info
391
+ conn_options[IBM_DB::SQL_ATTR_INFO_WRKSTNNAME] = value
392
+ # ibm i connection attributes
393
+ when :ibm_i_naming || "ibm_i_naming" || IBM_DB::SQL_ATTR_DBC_SYS_NAMING # Set connection's i5 naming mode
394
+ case value
395
+ when "sql" || IBM_DB::SQL_SYSTEM_NAMING_OFF
396
+ conn_options[IBM_DB::SQL_ATTR_DBC_SYS_NAMING] = IBM_DB::SQL_SYSTEM_NAMING_OFF
397
+ when "system" || IBM_DB::SQL_SYSTEM_NAMING_ON
398
+ conn_options[IBM_DB::SQL_ATTR_DBC_SYS_NAMING] = IBM_DB::SQL_SYSTEM_NAMING_ON
399
+ end
400
+ when :ibm_i_libl || "ibm_i_libl" || IBM_DB::SQL_ATTR_DBC_LIBL # Set connection's i5 *LIBL - only works under system naming
401
+ conn_options[IBM_DB::SQL_ATTR_DBC_LIBL] = value
402
+ when :ibm_i_curlib || "ibm_i_curlib" || IBM_DB::SQL_ATTR_DBC_CURLIB # Set connection's i5 CURLIB - only works under system naming
403
+ conn_options[IBM_DB::SQL_ATTR_DBC_CURLIB] = value
404
+ when :ibm_i_lib || "ibm_i_lib" || IBM_DB::SQL_ATTR_DBC_DEFAULT_LIB # Set connection's i5 default lib - only works under sql naming
405
+ conn_options[IBM_DB::SQL_ATTR_DBC_DEFAULT_LIB] = value
406
+ when :ibm_i_sort_seq || "ibm_i_sort_seq" || IBM_DB::SQL_ATTR_CONN_SORT_SEQUENCE # Set connection's i5 sort sequence
407
+ case value
408
+ when "job" || IBM_DB::SQL_JOB_SORT_ON
409
+ conn_options[IBM_DB::SQL_ATTR_CONN_SORT_SEQUENCE] = IBM_DB::SQL_JOB_SORT_ON
410
+ when "system" || IBM_DB::SQL_JOB_SORT_OFF
411
+ conn_options[IBM_DB::SQL_ATTR_CONN_SORT_SEQUENCE] = IBM_DB::SQL_JOB_SORT_OFF
412
+ end
413
+ when :ibm_i_isolation || "ibm_i_isolation" || IBM_DB::SQL_ATTR_TXN_ISOLATION # Set connection's i5 isolation level
414
+ case value
415
+ when "none" || IBM_DB::SQL_TXN_NO_COMMIT
416
+ conn_options[IBM_DB::SQL_ATTR_TXN_ISOLATION] = IBM_DB::SQL_TXN_NO_COMMIT
417
+ when "ur" || IBM_DB::SQL_TXN_READ_UNCOMMITTED
418
+ conn_options[IBM_DB::SQL_ATTR_TXN_ISOLATION] = IBM_DB::SQL_TXN_READ_UNCOMMITTED
419
+ when "cs" || IBM_DB::SQL_TXN_READ_COMMITTED
420
+ conn_options[IBM_DB::SQL_ATTR_TXN_ISOLATION] = IBM_DB::SQL_TXN_READ_COMMITTED
421
+ when "rs" || IBM_DB::SQL_TXN_REPEATABLE_READ
422
+ conn_options[IBM_DB::SQL_ATTR_TXN_ISOLATION] = IBM_DB::SQL_TXN_REPEATABLE_READ
423
+ when "rr" || IBM_DB::SQL_TXN_SERIALIZABLE
424
+ conn_options[IBM_DB::SQL_ATTR_TXN_ISOLATION] = IBM_DB::SQL_TXN_SERIALIZABLE
425
+ end
426
+ when :ibm_i_date_fmt || "ibm_i_date_fmt" || IBM_DB::SQL_ATTR_DATE_FMT # Set connection's i5 date format
427
+ case value
428
+ when "iso" || IBM_DB::SQL_FMT_ISO
429
+ conn_options[IBM_DB::SQL_ATTR_DATE_FMT] = IBM_DB::SQL_FMT_ISO
430
+ when "usa" || IBM_DB::SQL_FMT_USA
431
+ conn_options[IBM_DB::SQL_ATTR_DATE_FMT] = IBM_DB::SQL_FMT_USA
432
+ when "eur" || IBM_DB::SQL_FMT_EUR
433
+ conn_options[IBM_DB::SQL_ATTR_DATE_FMT] = IBM_DB::SQL_FMT_EUR
434
+ when "jis" || IBM_DB::SQL_FMT_JIS
435
+ conn_options[IBM_DB::SQL_ATTR_DATE_FMT] = IBM_DB::SQL_FMT_JIS
436
+ when "dmy" || IBM_DB::SQL_FMT_DMY
437
+ conn_options[IBM_DB::SQL_ATTR_DATE_FMT] = IBM_DB::SQL_FMT_DMY
438
+ when "mdy" || IBM_DB::SQL_FMT_MDY
439
+ conn_options[IBM_DB::SQL_ATTR_DATE_FMT] = IBM_DB::SQL_FMT_MDY
440
+ when "ymd" || IBM_DB::SQL_FMT_YMD
441
+ conn_options[IBM_DB::SQL_ATTR_DATE_FMT] = IBM_DB::SQL_FMT_YMD
442
+ when "jul" || IBM_DB::SQL_FMT_JUL
443
+ conn_options[IBM_DB::SQL_ATTR_DATE_FMT] = IBM_DB::SQL_FMT_JUL
444
+ when "job" || IBM_DB::SQL_FMT_JOB
445
+ conn_options[IBM_DB::SQL_ATTR_DATE_FMT] = IBM_DB::SQL_FMT_JOB
446
+ end
447
+ when :ibm_i_time_fmt || "ibm_i_time_fmt" || IBM_DB::SQL_ATTR_TIME_FMT # Set connection's i5 time format
448
+ case value
449
+ when "iso" || IBM_DB::SQL_FMT_ISO
450
+ conn_options[IBM_DB::SQL_ATTR_TIME_FMT] = IBM_DB::SQL_FMT_ISO
451
+ when "usa" || IBM_DB::SQL_FMT_USA
452
+ conn_options[IBM_DB::SQL_ATTR_TIME_FMT] = IBM_DB::SQL_FMT_USA
453
+ when "eur" || IBM_DB::SQL_FMT_EUR
454
+ conn_options[IBM_DB::SQL_ATTR_TIME_FMT] = IBM_DB::SQL_FMT_EUR
455
+ when "jis" || IBM_DB::SQL_FMT_JIS
456
+ conn_options[IBM_DB::SQL_ATTR_TIME_FMT] = IBM_DB::SQL_FMT_JIS
457
+ when "hms" || IBM_DB::SQL_FMT_HMS
458
+ conn_options[IBM_DB::SQL_ATTR_TIME_FMT] = IBM_DB::SQL_FMT_HMS
459
+ when "job" || IBM_DB::SQL_FMT_JOB
460
+ conn_options[IBM_DB::SQL_ATTR_TIME_FMT] = IBM_DB::SQL_FMT_JOB
461
+ end
462
+ when :ibm_i_date_sep || "ibm_i_date_sep" || IBM_DB::SQL_ATTR_DATE_SEP # Set connection's i5 date seperator
463
+ case value
464
+ when "slash" || IBM_DB::SQL_SEP_SLASH
465
+ conn_options[IBM_DB::SQL_ATTR_DATE_SEP] = IBM_DB::SQL_SEP_SLASH
466
+ when "dash" || IBM_DB::SQL_SEP_DASH
467
+ conn_options[IBM_DB::SQL_ATTR_DATE_SEP] = IBM_DB::SQL_SEP_DASH
468
+ when "period" || IBM_DB::SQL_SEP_PERIOD
469
+ conn_options[IBM_DB::SQL_ATTR_DATE_SEP] = IBM_DB::SQL_SEP_PERIOD
470
+ when "comma" || IBM_DB::SQL_SEP_COMMA
471
+ conn_options[IBM_DB::SQL_ATTR_DATE_SEP] = IBM_DB::SQL_SEP_COMMA
472
+ when "blank" || IBM_DB::SQL_SEP_BLANK
473
+ conn_options[IBM_DB::SQL_ATTR_DATE_SEP] = IBM_DB::SQL_SEP_BLANK
474
+ when "job" || IBM_DB::SQL_SEP_JOB
475
+ conn_options[IBM_DB::SQL_ATTR_DATE_SEP] = IBM_DB::SQL_SEP_JOB
476
+ end
477
+ when :ibm_i_time_sep || "ibm_i_time_sep" || IBM_DB::SQL_ATTR_TIME_SEP # Set connection's i5 time seperator
478
+ case value
479
+ when "colon" || IBM_DB::SQL_SEP_COLON
480
+ conn_options[IBM_DB::SQL_ATTR_TIME_SEP] = IBM_DB::SQL_SEP_COLON
481
+ when "period" || IBM_DB::SQL_SEP_PERIOD
482
+ conn_options[IBM_DB::SQL_ATTR_TIME_SEP] = IBM_DB::SQL_SEP_PERIOD
483
+ when "comma" || IBM_DB::SQL_SEP_COMMA
484
+ conn_options[IBM_DB::SQL_ATTR_TIME_SEP] = IBM_DB::SQL_SEP_COMMA
485
+ when "blank" || IBM_DB::SQL_SEP_BLANK
486
+ conn_options[IBM_DB::SQL_ATTR_TIME_SEP] = IBM_DB::SQL_SEP_BLANK
487
+ when "job" || IBM_DB::SQL_SEP_JOB
488
+ conn_options[IBM_DB::SQL_ATTR_TIME_SEP] = IBM_DB::SQL_SEP_JOB
489
+ end
490
+ when :ibm_i_decimal_sep || "ibm_i_decimal_sep" || IBM_DB::SQL_ATTR_DECIMAL_SEP # Set connection's i5 decimal seperator
491
+ case value
492
+ when "period" || IBM_DB::SQL_SEP_PERIOD
493
+ conn_options[IBM_DB::SQL_ATTR_DECIMAL_SEP] = IBM_DB::SQL_SEP_PERIOD
494
+ when "comma" || IBM_DB::SQL_SEP_COMMA
495
+ conn_options[IBM_DB::SQL_ATTR_DECIMAL_SEP] = IBM_DB::SQL_SEP_COMMA
496
+ when "job" || IBM_DB::SQL_SEP_JOB
497
+ conn_options[IBM_DB::SQL_ATTR_DECIMAL_SEP] = IBM_DB::SQL_SEP_JOB
498
+ end
499
+ when :ibm_i_query_goal || "ibm_i_query_goal" || IBM_DB::SQL_ATTR_QUERY_OPTIMIZE_GOAL # Set connection's i5 query optimization goal
500
+ case value
501
+ when "first" || IBM_DB::SQL_FIRST_IO
502
+ conn_options[IBM_DB::SQL_ATTR_QUERY_OPTIMIZE_GOAL] = IBM_DB::SQL_FIRST_IO
503
+ when "all" || IBM_DB::SQL_ALL_IO
504
+ conn_options[IBM_DB::SQL_ATTR_QUERY_OPTIMIZE_GOAL] = IBM_DB::SQL_ALL_IO
505
+ end
506
+ end
507
+ end
508
+ end
509
+
510
+ begin
511
+ # Checks if a host name or address has been specified. If so, this implies a TCP/IP connection
512
+ # Returns IBM_DB.Connection object upon succesful DB connection to the database
513
+ # If otherwise the connection fails, +false+ is returned
514
+ if config.has_key?(:host)
515
+ # Retrieves the host address/name
516
+ host = config[:host]
517
+ # A net address connection requires a port. If no port has been specified, 50000 is used by default
518
+ port = config[:port] || 50000
519
+ # Connects to the database specified using the hostname, port, authentication type, username and password info
520
+ # Starting with DB2 9.1FP5 secure connections using SSL are supported.
521
+ # On the client side using CLI this is supported from CLI version V95FP2 and onwards.
522
+ # This feature is set by specifying SECURITY=SSL in the connection string.
523
+ # Below connection string is constructed and SECURITY parameter is appended if the user has specified the :security option
524
+ conn_string = "DRIVER={IBM DB2 ODBC DRIVER};\
525
+ DATABASE=#{database};\
526
+ HOSTNAME=#{host};\
527
+ PORT=#{port};\
528
+ PROTOCOL=TCPIP;\
529
+ UID=#{username};\
530
+ PWD=#{password};"
531
+ conn_string << "SECURITY=#{config[:security]};" if config.has_key?(:security)
532
+ conn_string << "AUTHENTICATION=#{config[:authentication]};" if config.has_key?(:authentication)
533
+ conn_string << "CONNECTTIMEOUT=#{config[:timeout]};" if config.has_key?(:timeout)
534
+
535
+ connection = IBM_DB.connect( conn_string, '', '', conn_options, set_quoted_literal_replacement )
536
+ else
537
+ # No host implies a local catalog-based connection: +database+ represents catalog alias
538
+ connection = IBM_DB.connect( database, username, password, conn_options, set_quoted_literal_replacement )
539
+ end
540
+ rescue StandardError => connect_err
541
+ raise "Failed to connect to [#{database}] due to: #{connect_err}"
542
+ end
543
+ # Verifies that the connection was successful
544
+ if connection
545
+ # Creates an instance of *IBM_DBAdapter* based on the +connection+
546
+ # and credentials provided in +config+
547
+ ConnectionAdapters::IBM_DBAdapter.new(connection, isAr3, logger, config, conn_options)
548
+ else
549
+ # If the connection failure was not caught previoulsy, it raises a Runtime error
550
+ raise "An unexpected error occured during connect attempt to [#{database}]"
551
+ end
552
+ end # method self.ibm_db_connection
553
+
554
+ def self.ibmdb_connection(config)
555
+ #Method to support alising of adapter name as ibmdb [without underscore]
556
+ self.ibm_db_connection(config)
557
+ end
558
+ end # class Base
559
+
560
+ module ConnectionAdapters
561
+
562
+ module SchemaStatements
563
+ def create_table_definition(name, temporary, options, as=nil)
564
+ TableDefinition.new(self, name, temporary, options, as)
565
+ end
566
+ end
567
+
568
+ class Column
569
+ module Format
570
+ IBMI_SQL_DATETIME = /\A(\d{4})-(\d\d)-(\d\d)-(\d\d).(\d\d).(\d\d)(\.\d+)?\z/ # T014: time and timestamps
571
+ IBMI_TIME = /^\d\d\.\d\d\.\d\d$/ # T006: time
572
+ end
573
+ end
574
+
575
+ class IBM_DBColumn < Column
576
+
577
+ # Casts value (which is a String) to an appropriate instance
578
+ def type_cast(value)
579
+ # Casts the database NULL value to nil
580
+ return nil if value == 'NULL'
581
+ # Invokes parent's method for default casts
582
+ super
583
+ end
584
+
585
+ # Used to convert from BLOBs to Strings
586
+ def self.binary_to_string(value)
587
+ # Returns a string removing the eventual BLOB scalar function
588
+ value.to_s.gsub(/"SYSIBM"."BLOB"\('(.*)'\)/i,'\1')
589
+ end
590
+
591
+ private
592
+ # Mapping IBM data servers SQL datatypes to Ruby data types
593
+ def simplified_type(field_type)
594
+ case field_type
595
+ # if +field_type+ contains 'for bit data' handle it as a binary
596
+ when /for bit data/i
597
+ :binary
598
+ when /smallint/i
599
+ :boolean
600
+ when /int|serial/i
601
+ :integer
602
+ when /decimal|numeric|decfloat/i
603
+ :decimal
604
+ when /float|double|real/i
605
+ :float
606
+ when /timestamp|datetime/i
607
+ :timestamp
608
+ when /time/i
609
+ :time
610
+ when /date/i
611
+ :date
612
+ when /vargraphic/i
613
+ :vargraphic
614
+ when /graphic/i
615
+ :graphic
616
+ when /clob|text/i
617
+ :text
618
+ when /xml/i
619
+ :xml
620
+ when /blob|binary/i
621
+ :binary
622
+ when /char/i
623
+ :string
624
+ when /boolean/i
625
+ :boolean
626
+ when /rowid/i # rowid is a supported datatype on z/OS and i/5
627
+ :rowid
628
+ end
629
+ end # method simplified_type
630
+ end #class IBM_DBColumn
631
+
632
+ class IBM_DB_I5Column < IBM_DBColumn
633
+
634
+ # T006&T014: time and timestamp
635
+ class << self
636
+ def string_to_dummy_time(string)
637
+ if string =~ Column::Format::IBMI_TIME
638
+ fast_string_to_time "2000-01-01-#{string}"
639
+ else
640
+ super(string)
641
+ end
642
+ end
643
+ def fast_string_to_time(string)
644
+ if string =~ Column::Format::ISO_DATETIME || string =~ Column::Format::IBMI_SQL_DATETIME
645
+ microsec = ($7.to_r * 1_000_000).to_i
646
+ new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
647
+ end
648
+ end
649
+ end
650
+ end #class IBM_DB_I5Column
651
+
652
+ class Table
653
+
654
+ #Method to parse the passed arguments and create the ColumnDefinition object of the specified type
655
+ def ibm_parse_column_attributes_args(type, *args)
656
+ options = {}
657
+ if args.last.is_a?(Hash)
658
+ options = args.delete_at(args.length-1)
659
+ end
660
+ args.each do | name |
661
+ column name,type.to_sym,options
662
+ end # end args.each
663
+ end
664
+ private :ibm_parse_column_attributes_args
665
+
666
+ #Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type xml
667
+ #This method is different as compared to def char (sql is being issued explicitly
668
+ #as compared to def char where method column(which will generate the sql is being called)
669
+ #in order to handle the DEFAULT and NULL option for the native XML datatype
670
+ def xml(*args )
671
+ options = {}
672
+ if args.last.is_a?(Hash)
673
+ options = args.delete_at(args.length-1)
674
+ end
675
+ sql_segment = "ALTER TABLE #{@base.quote_table_name(@table_name)} ADD COLUMN "
676
+ args.each do | name |
677
+ sql = sql_segment + " #{@base.quote_column_name(name)} xml"
678
+ @base.execute(sql,"add_xml_column")
679
+ end
680
+ return self
681
+ end
682
+
683
+ #Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type double
684
+ def double(*args)
685
+ ibm_parse_column_attributes_args('double',*args)
686
+ return self
687
+ end
688
+
689
+ #Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type decfloat
690
+ def decfloat(*args)
691
+ ibm_parse_column_attributes_args('decfloat',*args)
692
+ return self
693
+ end
694
+
695
+ def graphic(*args)
696
+ ibm_parse_column_attributes_args('graphic',*args)
697
+ return self
698
+ end
699
+
700
+ def vargraphic(*args)
701
+ ibm_parse_column_attributes_args('vargraphic',*args)
702
+ return self
703
+ end
704
+
705
+ def bigint(*args)
706
+ ibm_parse_column_attributes_args('bigint',*args)
707
+ return self
708
+ end
709
+
710
+ #Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type char [character]
711
+ def char(*args)
712
+ ibm_parse_column_attributes_args('char',*args)
713
+ return self
714
+ end
715
+ alias_method :character, :char
716
+ end
717
+
718
+ class TableDefinition
719
+
720
+ def initialize(base, name=nil, temporary=nil, options=nil, as=nil)
721
+ if(self.respond_to?(:indexes))
722
+ @ar3 = false
723
+ else
724
+ @ar3 = true
725
+ end
726
+ @columns = []
727
+ @columns_hash = {}
728
+ @indexes = {}
729
+ @base = base
730
+ @temporary = temporary
731
+ @options = options
732
+ @name = name
733
+ @as = as
734
+ end
735
+
736
+ def native
737
+ @base.native_database_types
738
+ end
739
+
740
+ #Method to parse the passed arguments and create the ColumnDefinition object of the specified type
741
+ def ibm_parse_column_attributes_args(type, *args)
742
+ options = {}
743
+ if args.last.is_a?(Hash)
744
+ options = args.delete_at(args.length-1)
745
+ end
746
+ args.each do | name |
747
+ column(name,type,options)
748
+ end
749
+ end
750
+ private :ibm_parse_column_attributes_args
751
+
752
+ #Method to support the new syntax of rails 2.0 migrations for columns of type xml
753
+ def xml(*args )
754
+ ibm_parse_column_attributes_args('xml', *args)
755
+ return self
756
+ end
757
+
758
+ #Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type double
759
+ def double(*args)
760
+ ibm_parse_column_attributes_args('double',*args)
761
+ return self
762
+ end
763
+
764
+ #Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type decfloat
765
+ def decfloat(*args)
766
+ ibm_parse_column_attributes_args('decfloat',*args)
767
+ return self
768
+ end
769
+
770
+ def graphic(*args)
771
+ ibm_parse_column_attributes_args('graphic',*args)
772
+ return self
773
+ end
774
+
775
+ def vargraphic(*args)
776
+ ibm_parse_column_attributes_args('vargraphic',*args)
777
+ return self
778
+ end
779
+
780
+ def bigint(*args)
781
+ ibm_parse_column_attributes_args('bigint',*args)
782
+ return self
783
+ end
784
+
785
+ #Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type char [character]
786
+ def char(*args)
787
+ ibm_parse_column_attributes_args('char',*args)
788
+ return self
789
+ end
790
+ alias_method :character, :char
791
+
792
+ # Overrides the abstract adapter in order to handle
793
+ # the DEFAULT option for the native XML datatype
794
+ def column(name, type, options ={})
795
+ # construct a column definition where @base is adaptor instance
796
+ if(@ar3)
797
+ column = ColumnDefinition.new(@base, name, type)
798
+ else
799
+ column = ColumnDefinition.new(name, type)
800
+ end
801
+
802
+ # DB2 does not accept DEFAULT NULL option for XML
803
+ # for table create, but does accept nullable option
804
+ unless type.to_s == 'xml'
805
+ column.null = options[:null]
806
+ column.default = options[:default]
807
+ else
808
+ column.null = options[:null]
809
+ # Override column object's (instance of ColumnDefinition structure)
810
+ # to_s which is expected to return the create_table SQL fragment
811
+ # and bypass DEFAULT NULL option while still appending NOT NULL
812
+ def column.to_s
813
+ sql = "#{base.quote_column_name(name)} #{type}"
814
+ unless self.null == nil
815
+ sql << " NOT NULL" if (self.null == false)
816
+ end
817
+ return sql
818
+ end
819
+ end
820
+
821
+ column.scale = options[:scale] if options[:scale]
822
+ column.precision = options[:precision] if options[:precision]
823
+ # append column's limit option and yield native limits
824
+ if options[:limit]
825
+ column.limit = options[:limit]
826
+ elsif @base.native_database_types[type.to_sym]
827
+ column.limit = @base.native_database_types[type.to_sym][:limit] if @base.native_database_types[type.to_sym].has_key? :limit
828
+ end
829
+
830
+ unless @columns.nil? or @columns.include? column
831
+ @columns << column
832
+ end
833
+
834
+ @columns_hash[name] = column
835
+
836
+ return self
837
+ end
838
+ end
839
+
840
+ # The IBM_DB Adapter requires the native Ruby driver (ibm_db)
841
+ # for IBM data servers (ibm_db.so).
842
+ # +config+ the hash passed as an initializer argument content:
843
+ # == mandatory parameters
844
+ # adapter: 'ibm_db' // IBM_DB Adapter name
845
+ # username: 'db2user' // data server (database) user
846
+ # password: 'secret' // data server (database) password
847
+ # database: 'ARUNIT' // remote database name (or catalog entry alias)
848
+ # == optional (highly recommended for data server auditing and monitoring purposes)
849
+ # schema: 'rails123' // name space qualifier
850
+ # account: 'tester' // OS account (client workstation)
851
+ # app_user: 'test11' // authenticated application user
852
+ # application: 'rtests' // application name
853
+ # workstation: 'plato' // client workstation name
854
+ # == remote TCP/IP connection (required when no local database catalog entry available)
855
+ # host: 'socrates' // fully qualified hostname or IP address
856
+ # port: '50000' // data server TCP/IP port number
857
+ # security: 'SSL' // optional parameter enabling SSL encryption -
858
+ # // - Available only from CLI version V95fp2 and above
859
+ # authentication: 'SERVER' // AUTHENTICATION type which the client uses -
860
+ # // - to connect to the database server. By default value is SERVER
861
+ # timeout: 10 // Specifies the time in seconds (0 - 32767) to wait for a reply from server -
862
+ # //- when trying to establish a connection before generating a timeout
863
+ # == Parameterized Queries Support
864
+ # parameterized: false // Specifies if the prepared statement support of
865
+ # //- the IBM_DB Adapter is to be turned on or off
866
+ #
867
+ # When schema is not specified, the username value is used instead.
868
+ # The default setting of parameterized is false.
869
+ #
870
+ class IBM_DBAdapter < AbstractAdapter
871
+ attr_reader :connection, :servertype
872
+ attr_accessor :sql,:handle_lobs_triggered, :sql_parameter_values
873
+ attr_reader :schema, :app_user, :account, :application, :workstation
874
+ attr_reader :pstmt_support_on, :set_quoted_literal_replacement
875
+ attr_reader :time_sep # T006 time format
876
+
877
+ # Name of the adapter
878
+ def adapter_name
879
+ 'IBM_DB'
880
+ end
881
+
882
+ def translate_exception(exception, message)
883
+ if @servertype.respond_to? :translate_exception
884
+ @servertype.translate_exception(exception, message)
885
+ else
886
+ super(exception, message)
887
+ end
888
+ end
889
+
890
+ # IBM_DataServer helper (see def indexes)
891
+ def serverschema
892
+ @schema
893
+ end
894
+
895
+ class BindSubstitution < Arel::Visitors::IBM_DB # :nodoc:
896
+ include Arel::Visitors::BindVisitor
897
+ end
898
+
899
+ def initialize(connection, ar3, logger, config, conn_options)
900
+ # Caching database connection configuration (+connect+ or +reconnect+ support)
901
+ @connection = connection
902
+ @isAr3 = ar3
903
+ @conn_options = conn_options
904
+ @database = config[:database]
905
+ @username = config[:username]
906
+ @password = config[:password]
907
+ @host = nil
908
+ if config.has_key?(:host)
909
+ @host = config[:host]
910
+ @port = config[:port] || 50000 # default port
911
+ end
912
+ @schema = config[:schema]
913
+ @security = config[:security] || nil
914
+ @authentication = config[:authentication] || nil
915
+ @timeout = config[:timeout] || 0 # default timeout value is 0
916
+
917
+ if( config.has_key?(:parameterized) && config[:parameterized] == true )
918
+ @pstmt_support_on = true
919
+ @set_quoted_literal_replacement = IBM_DB::QUOTED_LITERAL_REPLACEMENT_OFF
920
+ else
921
+ @pstmt_support_on = false
922
+ @set_quoted_literal_replacement = IBM_DB::QUOTED_LITERAL_REPLACEMENT_ON
923
+ end
924
+
925
+ @app_user = @account = @application = @workstation = nil
926
+ # Caching database connection options (auditing and billing support)
927
+ @app_user = conn_options[:app_user] if conn_options.has_key?(:app_user)
928
+ @account = conn_options[:account] if conn_options.has_key?(:account)
929
+ @application = conn_options[:application] if conn_options.has_key?(:application)
930
+ @workstation = conn_options[:workstation] if conn_options.has_key?(:workstation)
931
+ # T006: if time separator is not set, go get it
932
+ if conn_options[IBM_DB::SQL_ATTR_TIME_SEP]
933
+ @time_sep = conn_options[IBM_DB::SQL_ATTR_TIME_SEP].to_s
934
+ else
935
+ @time_sep = IBM_DB.get_option( @connection, IBM_DB::SQL_ATTR_TIME_SEP, 1 ).to_s
936
+ end
937
+
938
+ @sql = []
939
+ @sql_parameter_values = [] #Used only if pstmt support is turned on
940
+
941
+ @handle_lobs_triggered = false
942
+
943
+ # Calls the parent class +ConnectionAdapters+' initializer
944
+ # which sets @connection, @logger, @runtime and @last_verification
945
+ super(@connection, logger)
946
+
947
+ if @connection
948
+ server_info = IBM_DB.server_info( @connection )
949
+ if( server_info )
950
+ case server_info.DBMS_NAME
951
+ when /DB2\//i # DB2 for Linux, Unix and Windows (LUW)
952
+ case server_info.DBMS_VER
953
+ when /09.07/i # DB2 Version 9.7 (Cobra)
954
+ @servertype = IBM_DB2_LUW_COBRA.new(self, isAr3)
955
+ else # DB2 Version 9.5 or below
956
+ @servertype = IBM_DB2_LUW.new(self, @isAr3)
957
+ end
958
+ when /DB2/i # DB2 for zOS
959
+ case server_info.DBMS_VER
960
+ when /09/ # DB2 for zOS version 9 and version 10
961
+ @servertype = IBM_DB2_ZOS.new(self, @isAr3)
962
+ when /10/
963
+ @servertype = IBM_DB2_ZOS.new(self, @isAr3)
964
+ when /08/ # DB2 for zOS version 8
965
+ @servertype = IBM_DB2_ZOS_8.new(self, @isAr3)
966
+ else # DB2 for zOS version 7
967
+ raise "Only DB2 z/OS version 8 and above are currently supported"
968
+ end
969
+ when /AS/i # DB2 for i5 (iSeries)
970
+ @servertype = IBM_DB2_I5.new(self, @isAr3)
971
+ when /IDS/i # Informix Dynamic Server
972
+ @servertype = IBM_IDS.new(self, @isAr3)
973
+ else
974
+ log( "server_info", "Forcing servertype to LUW: DBMS name could not be retrieved. Check if your client version is of the right level")
975
+ warn "Forcing servertype to LUW: DBMS name could not be retrieved. Check if your client version is of the right level"
976
+ @servertype = IBM_DB2_LUW.new(self, @isAr3)
977
+ end
978
+ else
979
+ error_msg = IBM_DB.getErrormsg( @connection, IBM_DB::DB_CONN )
980
+ IBM_DB.close( @connection )
981
+ raise "Cannot retrieve server information: #{error_msg}"
982
+ end
983
+ end
984
+
985
+ # Executes the +set schema+ statement using the schema identifier provided
986
+ @servertype.set_schema(@schema) if @schema && @schema != @username
987
+
988
+ # Check for the start value for id (primary key column). By default it is 1
989
+ if config.has_key?(:start_id)
990
+ @start_id = config[:start_id]
991
+ else
992
+ @start_id = 1
993
+ end
994
+
995
+ #Check Arel version
996
+ begin
997
+ @arelVersion = Arel::VERSION.to_i
998
+ rescue
999
+ @arelVersion = 0
1000
+ end
1001
+
1002
+ if(@arelVersion >= 3 )
1003
+ @visitor = Arel::Visitors::IBM_DB.new self
1004
+ end
1005
+ end
1006
+
1007
+ # Optional connection attribute: database name space qualifier
1008
+ def schema=(name)
1009
+ unless name == @schema
1010
+ @schema = name
1011
+ @servertype.set_schema(@schema)
1012
+ end
1013
+ end
1014
+
1015
+ # Optional connection attribute: authenticated application user
1016
+ def app_user=(name)
1017
+ unless name == @app_user
1018
+ option = {IBM_DB::SQL_ATTR_INFO_USERID => "#{name}"}
1019
+ if IBM_DB.set_option( @connection, option, 1 )
1020
+ @app_user = IBM_DB.get_option( @connection, IBM_DB::SQL_ATTR_INFO_USERID, 1 )
1021
+ end
1022
+ end
1023
+ end
1024
+
1025
+ # Optional connection attribute: OS account (client workstation)
1026
+ def account=(name)
1027
+ unless name == @account
1028
+ option = {IBM_DB::SQL_ATTR_INFO_ACCTSTR => "#{name}"}
1029
+ if IBM_DB.set_option( @connection, option, 1 )
1030
+ @account = IBM_DB.get_option( @connection, IBM_DB::SQL_ATTR_INFO_ACCTSTR, 1 )
1031
+ end
1032
+ end
1033
+ end
1034
+
1035
+ # Optional connection attribute: application name
1036
+ def application=(name)
1037
+ unless name == @application
1038
+ option = {IBM_DB::SQL_ATTR_INFO_APPLNAME => "#{name}"}
1039
+ if IBM_DB.set_option( @connection, option, 1 )
1040
+ @application = IBM_DB.get_option( @connection, IBM_DB::SQL_ATTR_INFO_APPLNAME, 1 )
1041
+ end
1042
+ end
1043
+ end
1044
+
1045
+ # Optional connection attribute: client workstation name
1046
+ def workstation=(name)
1047
+ unless name == @workstation
1048
+ option = {IBM_DB::SQL_ATTR_INFO_WRKSTNNAME => "#{name}"}
1049
+ if IBM_DB.set_option( @connection, option, 1 )
1050
+ @workstation = IBM_DB.get_option( @connection, IBM_DB::SQL_ATTR_INFO_WRKSTNNAME, 1 )
1051
+ end
1052
+ end
1053
+ end
1054
+
1055
+ def self.visitor_for(pool)
1056
+ Arel::Visitors::IBM_DB.new(pool)
1057
+ end
1058
+
1059
+ def to_sql(arel, binds = [])
1060
+ if arel.respond_to?(:ast)
1061
+ visitor.accept(arel.ast) do
1062
+ quote(*binds.shift.reverse)
1063
+ end
1064
+ else
1065
+ arel
1066
+ end
1067
+ end
1068
+
1069
+ # This adapter supports migrations.
1070
+ # Current limitations:
1071
+ # +rename_column+ is not currently supported by the IBM data servers
1072
+ # +remove_column+ is not currently supported by the DB2 for zOS data server
1073
+ # Tables containing columns of XML data type do not support +remove_column+
1074
+ def supports_migrations?
1075
+ true
1076
+ end
1077
+
1078
+ # This Adapter supports DDL transactions.
1079
+ # This means CREATE TABLE and other DDL statements can be carried out as a transaction.
1080
+ # That is the statements executed can be ROLLED BACK in case of any error during the process.
1081
+ def supports_ddl_transactions?
1082
+ true
1083
+ end
1084
+
1085
+ def log_query(sql, name) #:nodoc:
1086
+ # Used by handle_lobs
1087
+ log(sql,name){}
1088
+ end
1089
+
1090
+ #==============================================
1091
+ # CONNECTION MANAGEMENT
1092
+ #==============================================
1093
+
1094
+ # Tests the connection status
1095
+ def active?
1096
+ IBM_DB.active @connection
1097
+ rescue
1098
+ false
1099
+ end
1100
+
1101
+ # Private method used by +reconnect!+.
1102
+ # It connects to the database with the initially provided credentials
1103
+ def connect
1104
+ # If the type of connection is net based
1105
+ if(@username.nil? || @password.nil?)
1106
+ raise ArgumentError, "Username/Password cannot be nil"
1107
+ end
1108
+
1109
+ begin
1110
+ if @host
1111
+ @conn_string = "DRIVER={IBM DB2 ODBC DRIVER};\
1112
+ DATABASE=#{@database};\
1113
+ HOSTNAME=#{@host};\
1114
+ PORT=#{@port};\
1115
+ PROTOCOL=TCPIP;\
1116
+ UID=#{@username};\
1117
+ PWD=#{@password};"
1118
+ @conn_string << "SECURITY=#{@security};" if @security
1119
+ @conn_string << "AUTHENTICATION=#{@authentication};" if @authentication
1120
+ @conn_string << "CONNECTTIMEOUT=#{@timeout};"
1121
+ # Connects and assigns the resulting IBM_DB.Connection to the +@connection+ instance variable
1122
+ @connection = IBM_DB.connect(@conn_string, '', '', @conn_options, @set_quoted_literal_replacement)
1123
+ else
1124
+ # Connects to the database using the local alias (@database)
1125
+ # and assigns the connection object (IBM_DB.Connection) to @connection
1126
+ @connection = IBM_DB.connect(@database, @username, @password, @conn_options, @set_quoted_literal_replacement)
1127
+ end
1128
+ rescue StandardError => connect_err
1129
+ warn "Connection to database #{@database} failed: #{connect_err}"
1130
+ @connection = false
1131
+ end
1132
+ # Sets the schema if different from default (username)
1133
+ if @schema && @schema != @username
1134
+ @servertype.set_schema(@schema)
1135
+ end
1136
+ end
1137
+ private :connect
1138
+
1139
+ # Closes the current connection and opens a new one
1140
+ def reconnect!
1141
+ disconnect!
1142
+ connect
1143
+ end
1144
+
1145
+ # Closes the current connection
1146
+ def disconnect!
1147
+ # Attempts to close the connection. The methods will return:
1148
+ # * true if succesfull
1149
+ # * false if the connection is already closed
1150
+ # * nil if an error is raised
1151
+ return nil if @connection.nil? || @connection == false
1152
+ IBM_DB.close(@connection) rescue nil
1153
+ end
1154
+
1155
+ #==============================================
1156
+ # DATABASE STATEMENTS
1157
+ #==============================================
1158
+
1159
+ def create_table(name, options = {}, as=nil)
1160
+ @servertype.setup_for_lob_table
1161
+ super(name, options)
1162
+
1163
+ #Table definition is complete only when a unique index is created on the primarykey column for DB2 V8 on zOS
1164
+
1165
+ #create index on id column if options[:id] is nil or id ==true
1166
+ #else check if options[:primary_key]is not nil then create an unique index on that column
1167
+ if !options[:id].nil? || !options[:primary_key].nil?
1168
+ if (!options[:id].nil? && options[:id] == true)
1169
+ @servertype.create_index_after_table(name,"id")
1170
+ elsif !options[:primary_key].nil?
1171
+ @servertype.create_index_after_table(name,options[:primary_key].to_s)
1172
+ end
1173
+ else
1174
+ @servertype.create_index_after_table(name,"id")
1175
+ end
1176
+ end
1177
+
1178
+ # Returns an array of hashes with the column names as keys and
1179
+ # column values as values. +sql+ is the select query,
1180
+ # and +name+ is an optional description for logging
1181
+ def prepared_select(sql_param_hash, name = nil)
1182
+ # Replaces {"= NULL" with " IS NULL"} OR {"IN (NULL)" with " IS NULL"}
1183
+ results = []
1184
+ # Invokes the method +prepare+ in order prepare the SQL
1185
+ # IBM_DB.Statement is returned from which the statement is executed and results fetched
1186
+ pstmt = prepare(sql_param_hash["sqlSegment"], name)
1187
+ if(execute_prepared_stmt(pstmt, sql_param_hash["paramArray"]))
1188
+ begin
1189
+ results = @servertype.select(pstmt)
1190
+ rescue StandardError => fetch_error # Handle driver fetch errors
1191
+ error_msg = IBM_DB.getErrormsg(pstmt, IBM_DB::DB_STMT )
1192
+ if error_msg && !error_msg.empty?
1193
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
1194
+ else
1195
+ error_msg = "An unexpected error occurred during data retrieval"
1196
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
1197
+ raise error_msg
1198
+ end
1199
+ ensure
1200
+ # Ensures to free the resources associated with the statement
1201
+ IBM_DB.free_stmt(pstmt) if pstmt
1202
+ end
1203
+ end
1204
+ # The array of record hashes is returned
1205
+ results
1206
+ end
1207
+
1208
+ # Returns an array of hashes with the column names as keys and
1209
+ # column values as values. +sql+ is the select query,
1210
+ # and +name+ is an optional description for logging
1211
+ def prepared_select_values(sql_param_hash, name = nil)
1212
+ # Replaces {"= NULL" with " IS NULL"} OR {"IN (NULL)" with " IS NULL"}
1213
+ results = []
1214
+ # Invokes the method +prepare+ in order prepare the SQL
1215
+ # IBM_DB.Statement is returned from which the statement is executed and results fetched
1216
+ pstmt = prepare(sql_param_hash["sqlSegment"], name)
1217
+ if(execute_prepared_stmt(pstmt, sql_param_hash["paramArray"]))
1218
+ begin
1219
+ results = @servertype.select_rows(sql_param_hash["sqlSegment"], name, pstmt, results)
1220
+ if results
1221
+ return results.map { |v| v[0] }
1222
+ else
1223
+ nil
1224
+ end
1225
+ rescue StandardError => fetch_error # Handle driver fetch errors
1226
+ error_msg = IBM_DB.getErrormsg(pstmt, IBM_DB::DB_STMT )
1227
+ if error_msg && !error_msg.empty?
1228
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
1229
+ else
1230
+ error_msg = "An unexpected error occurred during data retrieval"
1231
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
1232
+ raise error_msg
1233
+ end
1234
+ ensure
1235
+ # Ensures to free the resources associated with the statement
1236
+ IBM_DB.free_stmt(pstmt) if pstmt
1237
+ end
1238
+ end
1239
+ # The array of record hashes is returned
1240
+ results
1241
+ end
1242
+
1243
+ #Calls the servertype select method to fetch the data
1244
+ def fetch_data(stmt)
1245
+ if(stmt)
1246
+ begin
1247
+ return @servertype.select(stmt)
1248
+ rescue StandardError => fetch_error # Handle driver fetch errors
1249
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
1250
+ if error_msg && !error_msg.empty?
1251
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
1252
+ else
1253
+ error_msg = "An unexpected error occurred during data retrieval"
1254
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
1255
+ raise error_msg
1256
+ end
1257
+ ensure
1258
+ # Ensures to free the resources associated with the statement
1259
+ IBM_DB.free_stmt(stmt) if stmt
1260
+ end
1261
+ end
1262
+ end
1263
+ =begin
1264
+ # Returns an array of hashes with the column names as keys and
1265
+ # column values as values. +sql+ is the select query,
1266
+ # and +name+ is an optional description for logging
1267
+ def select(sql, name = nil)
1268
+ # Replaces {"= NULL" with " IS NULL"} OR {"IN (NULL)" with " IS NULL"}
1269
+ sql.gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, " IS NULL" )
1270
+
1271
+ results = []
1272
+ # Invokes the method +execute+ in order to log and execute the SQL
1273
+ # IBM_DB.Statement is returned from which results can be fetched
1274
+ stmt = execute(sql, name)
1275
+
1276
+ results = fetch_data(stmt)
1277
+ # The array of record hashes is returned
1278
+ results
1279
+ end
1280
+ =end
1281
+ def select(sql, name = nil, binds = [])
1282
+ # Replaces {"= NULL" with " IS NULL"} OR {"IN (NULL)" with " IS NULL"}
1283
+ sql.gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, " IS NULL" )
1284
+
1285
+ results = []
1286
+
1287
+ if(binds.nil? || binds.empty?)
1288
+ stmt = execute(sql, name)
1289
+ else
1290
+ stmt = exec_query(sql, name, binds)
1291
+ end
1292
+
1293
+ cols = IBM_DB.resultCols(stmt)
1294
+
1295
+ if( stmt )
1296
+ results = fetch_data(stmt)
1297
+ end
1298
+
1299
+ if(@isAr3)
1300
+ return results
1301
+ else
1302
+ return ActiveRecord::Result.new(cols, results)
1303
+ end
1304
+ end
1305
+
1306
+ #Returns an array of arrays containing the field values.
1307
+ #This is an implementation for the abstract method
1308
+ #+sql+ is the select query and +name+ is an optional description for logging
1309
+ #def select_rows(sql, name = nil) T027: rake test fails in Rails 4.x
1310
+ # TODO figure out what to do with the bind values
1311
+ def select_rows(sql, name = nil, arel_bind_values_AR_4_0_4=nil)
1312
+ # Replaces {"= NULL" with " IS NULL"} OR {"IN (NULL)" with " IS NULL"}
1313
+ sql.gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, " IS NULL" )
1314
+ results = []
1315
+ # Invokes the method +execute+ in order to log and execute the SQL
1316
+ # IBM_DB.Statement is returned from which results can be fetched
1317
+ stmt = execute(sql, name)
1318
+ if(stmt)
1319
+ begin
1320
+ results = @servertype.select_rows(sql, name, stmt, results)
1321
+ rescue StandardError => fetch_error # Handle driver fetch errors
1322
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
1323
+ if error_msg && !error_msg.empty?
1324
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
1325
+ else
1326
+ error_msg = "An unexpected error occurred during data retrieval"
1327
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
1328
+ raise error_msg
1329
+ end
1330
+ ensure
1331
+ # Ensures to free the resources associated with the statement
1332
+ IBM_DB.free_stmt(stmt) if stmt
1333
+ end
1334
+ end
1335
+ # The array of record hashes is returned
1336
+ results
1337
+ end
1338
+
1339
+ # Returns a record hash with the column names as keys and column values
1340
+ # as values.
1341
+ #def select_one(sql, name = nil)
1342
+ # Gets the first hash from the array of hashes returned by
1343
+ # select_all
1344
+ # select_all(sql,name).first
1345
+ #end
1346
+
1347
+ #inserts values from fixtures
1348
+ #overridden to handle LOB's fixture insertion, as, in normal inserts callbacks are triggered but during fixture insertion callbacks are not triggered
1349
+ #hence only markers like @@@IBMBINARY@@@ will be inserted and are not updated to actual data
1350
+ def insert_fixture(fixture, table_name)
1351
+ if(fixture.respond_to?(:keys))
1352
+ insert_query = "INSERT INTO #{quote_table_name(table_name)} ( #{fixture.keys.join(', ')})"
1353
+ else
1354
+ insert_query = "INSERT INTO #{quote_table_name(table_name)} ( #{fixture.key_list})"
1355
+ end
1356
+
1357
+ insert_values = []
1358
+ params = []
1359
+ if @servertype.instance_of? IBM_IDS
1360
+ super
1361
+ return
1362
+ end
1363
+ column_list = columns(table_name)
1364
+ fixture.each do |item|
1365
+ col = nil
1366
+ column_list.each do |column|
1367
+ if column.name.downcase == item.at(0).downcase
1368
+ col= column
1369
+ break
1370
+ end
1371
+ end
1372
+ if item.at(1).nil? ||
1373
+ item.at(1) == {} ||
1374
+ (item.at(1) == '' && !(col.type.to_sym == :text))
1375
+
1376
+ params << 'NULL'
1377
+
1378
+ elsif col.type.to_sym == :xml ||
1379
+ col.type.to_sym == :text ||
1380
+ col.type.to_sym == :binary
1381
+ # Add a '?' for the parameter or a NULL if the value is nil or empty
1382
+ # (except for a CLOB field where '' can be a value)
1383
+ insert_values << quote_value_for_pstmt(item.at(1))
1384
+ params << '?'
1385
+ else
1386
+ insert_values << quote_value_for_pstmt(item.at(1),col)
1387
+ params << '?'
1388
+ end
1389
+ end
1390
+
1391
+ insert_query << " VALUES ("+ params.join(',') + ")"
1392
+ unless stmt = IBM_DB.prepare(@connection, insert_query)
1393
+ error_msg = IBM_DB.getErrormsg( @connection, IBM_DB::DB_CONN )
1394
+ if error_msg && !error_msg.empty?
1395
+ raise "Failed to prepare statement for fixtures insert due to : #{error_msg}"
1396
+ else
1397
+ raise StandardError.new('An unexpected error occurred during preparing SQL for fixture insert')
1398
+ end
1399
+ end
1400
+
1401
+ #log_query(insert_query,'fixture insert')
1402
+ log(insert_query,'fixture insert') do
1403
+ unless IBM_DB.execute(stmt, insert_values)
1404
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
1405
+ IBM_DB.free_stmt(stmt) if stmt
1406
+ raise "Failed to insert due to: #{error_msg}"
1407
+ else
1408
+ IBM_DB.free_stmt(stmt) if stmt
1409
+ end
1410
+ end
1411
+ end
1412
+
1413
+ def empty_insert_statement_value(pkey)
1414
+ "(#{pkey}) VALUES (DEFAULT)"
1415
+ end
1416
+
1417
+ # Perform an insert and returns the last ID generated.
1418
+ # This can be the ID passed to the method or the one auto-generated by the database,
1419
+ # and retrieved by the +last_generated_id+ method.
1420
+ def insert_direct(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
1421
+ if @handle_lobs_triggered #Ensure the array of sql is cleared if they have been handled in the callback
1422
+ @sql = []
1423
+ @handle_lobs_triggered = false
1424
+ end
1425
+
1426
+ clear_query_cache if defined? clear_query_cache
1427
+
1428
+ if stmt = execute(sql, name)
1429
+ begin
1430
+ @sql << sql
1431
+ return id_value || @servertype.last_generated_id(stmt)
1432
+ # Ensures to free the resources associated with the statement
1433
+ ensure
1434
+ IBM_DB.free_stmt(stmt) if stmt
1435
+ end
1436
+ end
1437
+ end
1438
+
1439
+ def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [] )
1440
+ sql, binds = [to_sql(arel),binds]
1441
+
1442
+ #unless IBM_DBAdapter.respond_to?(:exec_insert)
1443
+ if binds.nil? || binds.empty?
1444
+ return insert_direct(sql, name, pk, id_value, sequence_name)
1445
+ end
1446
+
1447
+ clear_query_cache if defined? clear_query_cache
1448
+ if stmt = exec_insert(sql, name, binds)
1449
+ begin
1450
+ @sql << sql
1451
+ return id_value || @servertype.last_generated_id(stmt)
1452
+ ensure
1453
+ IBM_DB.free_stmt(stmt) if stmt
1454
+ end
1455
+ end
1456
+ end
1457
+
1458
+ # Praveen
1459
+ # Performs an insert using the prepared statement and returns the last ID generated.
1460
+ # This can be the ID passed to the method or the one auto-generated by the database,
1461
+ # and retrieved by the +last_generated_id+ method.
1462
+ def prepared_insert(pstmt, param_array = nil, id_value = nil)
1463
+ if @handle_lobs_triggered #Ensure the array of sql is cleared if they have been handled in the callback
1464
+ @sql = []
1465
+ @sql_parameter_values = []
1466
+ @handle_lobs_triggered = false
1467
+ end
1468
+
1469
+ clear_query_cache if defined? clear_query_cache
1470
+
1471
+ begin
1472
+ if execute_prepared_stmt(pstmt, param_array)
1473
+ @sql << @prepared_sql
1474
+ @sql_parameter_values << param_array
1475
+ return id_value || @servertype.last_generated_id(pstmt)
1476
+ end
1477
+ rescue StandardError => insert_err
1478
+ raise insert_err
1479
+ ensure
1480
+ IBM_DB.free_stmt(pstmt) if pstmt
1481
+ end
1482
+ end
1483
+
1484
+ # Praveen
1485
+ # Prepares and logs +sql+ commands and
1486
+ # returns a +IBM_DB.Statement+ object.
1487
+ def prepare(sql,name = nil)
1488
+ @prepared_sql = sql
1489
+ # The +log+ method is defined in the parent class +AbstractAdapter+
1490
+ log(sql,name) do
1491
+ @servertype.prepare(sql, name)
1492
+ end
1493
+ end
1494
+
1495
+ # Praveen
1496
+ #Executes the prepared statement
1497
+ #ReturnsTrue on success and False on Failure
1498
+ def execute_prepared_stmt(pstmt, param_array = nil)
1499
+ if !param_array.nil? && param_array.size < 1
1500
+ param_array = nil
1501
+ end
1502
+
1503
+ if( !IBM_DB.execute(pstmt, param_array) )
1504
+ error_msg = IBM_DB.getErrormsg(pstmt, IBM_DB::DB_STMT)
1505
+ if !error_msg.empty?
1506
+ error_msg = "Statement execution failed: " + error_msg
1507
+ else
1508
+ error_msg = "Statement execution failed"
1509
+ end
1510
+ IBM_DB.free_stmt(pstmt) if pstmt
1511
+ raise StatementInvalid, error_msg
1512
+ else
1513
+ return true
1514
+ end
1515
+ end
1516
+
1517
+ # Executes +sql+ statement in the context of this connection using
1518
+ # +binds+ as the bind substitutes. +name+ is logged along with
1519
+ # the executed +sql+ statement.
1520
+ def exec_query(sql, name = 'SQL', binds = [])
1521
+ begin
1522
+ param_array = binds.map do |column,value|
1523
+ quote_value_for_pstmt(value, column)
1524
+ end
1525
+
1526
+ stmt = prepare(sql, name)
1527
+
1528
+ if( stmt )
1529
+ if(execute_prepared_stmt(stmt, param_array))
1530
+ return stmt
1531
+ end
1532
+ else
1533
+ return false
1534
+ end
1535
+ ensure
1536
+ @offset = @limit = nil
1537
+ end
1538
+ end
1539
+
1540
+ # Executes and logs +sql+ commands and
1541
+ # returns a +IBM_DB.Statement+ object.
1542
+ def execute(sql, name = nil)
1543
+ # Logs and execute the sql instructions.
1544
+ # The +log+ method is defined in the parent class +AbstractAdapter+
1545
+ log(sql, name) do
1546
+ @servertype.execute(sql, name)
1547
+ end
1548
+ end
1549
+
1550
+ # Executes an "UPDATE" SQL statement
1551
+ def update_direct(sql, name = nil)
1552
+ if @handle_lobs_triggered #Ensure the array of sql is cleared if they have been handled in the callback
1553
+ @sql = []
1554
+ @handle_lobs_triggered = false
1555
+ end
1556
+
1557
+ # Logs and execute the given sql query.
1558
+ if stmt = execute(sql, name)
1559
+ begin
1560
+ @sql << sql
1561
+ # Retrieves the number of affected rows
1562
+ IBM_DB.num_rows(stmt)
1563
+ # Ensures to free the resources associated with the statement
1564
+ ensure
1565
+ IBM_DB.free_stmt(stmt) if stmt
1566
+ end
1567
+ end
1568
+ end
1569
+
1570
+ #Praveen
1571
+ def prepared_update(pstmt, param_array = nil )
1572
+ if @handle_lobs_triggered #Ensure the array of sql is cleared if they have been handled in the callback
1573
+ @sql = []
1574
+ @sql_parameter_values = []
1575
+ @handle_lobs_triggered = false
1576
+ end
1577
+
1578
+ clear_query_cache if defined? clear_query_cache
1579
+
1580
+ begin
1581
+ if execute_prepared_stmt(pstmt, param_array)
1582
+ @sql << @prepared_sql
1583
+ @sql_parameter_values << param_array
1584
+ # Retrieves the number of affected rows
1585
+ IBM_DB.num_rows(pstmt)
1586
+ # Ensures to free the resources associated with the statement
1587
+ end
1588
+ rescue StandardError => updt_err
1589
+ raise updt_err
1590
+ ensure
1591
+ IBM_DB.free_stmt(pstmt) if pstmt
1592
+ end
1593
+ end
1594
+ # The delete method executes the delete
1595
+ # statement and returns the number of affected rows.
1596
+ # The method is an alias for +update+
1597
+ alias_method :prepared_delete, :prepared_update
1598
+
1599
+ def update(arel, name = nil, binds = [])
1600
+ sql = to_sql(arel)
1601
+
1602
+ # Make sure the WHERE clause handles NULL's correctly
1603
+ sqlarray = sql.split(/\s*WHERE\s*/)
1604
+ size = sqlarray.size
1605
+ if size > 1
1606
+ sql = sqlarray[0] + " WHERE "
1607
+ if size > 2
1608
+ 1.upto size-2 do |index|
1609
+ sqlarray[index].gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, " IS NULL" ) unless sqlarray[index].nil?
1610
+ sql = sql + sqlarray[index] + " WHERE "
1611
+ end
1612
+ end
1613
+ sqlarray[size-1].gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, " IS NULL" ) unless sqlarray[size-1].nil?
1614
+ sql = sql + sqlarray[size-1]
1615
+ end
1616
+
1617
+ clear_query_cache if defined? clear_query_cache
1618
+
1619
+ if binds.nil? || binds.empty?
1620
+ update_direct(sql, name)
1621
+ else
1622
+ begin
1623
+ if stmt = exec_query(sql,name,binds)
1624
+ IBM_DB.num_rows(stmt)
1625
+ end
1626
+ ensure
1627
+ IBM_DB.free_stmt(stmt) if(stmt)
1628
+ end
1629
+ end
1630
+ end
1631
+
1632
+ alias_method :delete, :update
1633
+
1634
+ # Begins the transaction (and turns off auto-committing)
1635
+ def begin_db_transaction
1636
+ # Turns off the auto-commit
1637
+ IBM_DB.autocommit(@connection, IBM_DB::SQL_AUTOCOMMIT_OFF)
1638
+ end
1639
+
1640
+ # Commits the transaction and turns on auto-committing
1641
+ def commit_db_transaction
1642
+ # Commits the transaction
1643
+ IBM_DB.commit @connection rescue nil
1644
+ # Turns on auto-committing
1645
+ IBM_DB.autocommit @connection, IBM_DB::SQL_AUTOCOMMIT_ON
1646
+ end
1647
+
1648
+ # Rolls back the transaction and turns on auto-committing. Must be
1649
+ # done if the transaction block raises an exception or returns false
1650
+ def rollback_db_transaction
1651
+ # ROLLBACK the transaction
1652
+ IBM_DB.rollback(@connection) rescue nil
1653
+ # Turns on auto-committing
1654
+ IBM_DB.autocommit @connection, IBM_DB::SQL_AUTOCOMMIT_ON
1655
+ end
1656
+
1657
+
1658
+ # Modifies a sql statement in order to implement a LIMIT and an OFFSET.
1659
+ # A LIMIT defines the number of rows that should be fetched, while
1660
+ # an OFFSET defines from what row the records must be fetched.
1661
+ # IBM data servers implement a LIMIT in SQL statements through:
1662
+ # FETCH FIRST n ROWS ONLY, where n is the number of rows required.
1663
+ # The implementation of OFFSET is more elaborate, and requires the usage of
1664
+ # subqueries and the ROW_NUMBER() command in order to add row numbering
1665
+ # as an additional column to a copy of the existing table.
1666
+ # ==== Examples
1667
+ # add_limit_offset!('SELECT * FROM staff', {:limit => 10})
1668
+ # generates: "SELECT * FROM staff FETCH FIRST 10 ROWS ONLY"
1669
+ #
1670
+ # add_limit_offset!('SELECT * FROM staff', {:limit => 10, :offset => 30})
1671
+ # generates "SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_rownum
1672
+ # FROM (SELECT * FROM staff) AS I) AS O WHERE sys_row_num BETWEEN 31 AND 40"
1673
+ def add_limit_offset!(sql, options)
1674
+ limit = options[:limit]
1675
+ offset = options[:offset]
1676
+
1677
+ # if the limit is zero
1678
+ if limit && limit == 0
1679
+ # Returns a query that will always generate zero records
1680
+ # (e.g. WHERE sys_row_num BETWEEN 1 and 0)
1681
+ if( @pstmt_support_on )
1682
+ sql = @servertype.query_offset_limit!(sql, 0, limit, options)
1683
+ else
1684
+ sql = @servertype.query_offset_limit(sql, 0, limit)
1685
+ end
1686
+ # If there is a non-zero limit
1687
+ else
1688
+ # If an offset is specified builds the query with offset and limit,
1689
+ # otherwise retrieves only the first +limit+ rows
1690
+ if( @pstmt_support_on )
1691
+ sql = @servertype.query_offset_limit!(sql, offset, limit, options)
1692
+ else
1693
+ sql = @servertype.query_offset_limit(sql, offset, limit)
1694
+ end
1695
+ end
1696
+ # Returns the sql query in any case
1697
+ sql
1698
+ end # method add_limit_offset!
1699
+
1700
+ def default_sequence_name(table, column) # :nodoc:
1701
+ "#{table}_#{column}_seq"
1702
+ end
1703
+
1704
+
1705
+ #==============================================
1706
+ # QUOTING
1707
+ #==============================================
1708
+
1709
+ #Praveen
1710
+ def quote_value_for_pstmt(value, column=nil)
1711
+
1712
+ return value.quoted_id if value.respond_to?(:quoted_id)
1713
+
1714
+ case value
1715
+ when String, ActiveSupport::Multibyte::Chars then
1716
+ value = value.to_s
1717
+ if column && [:integer, :float].include?(column.type)
1718
+ value = column.type == :integer ? value.to_i : value.to_f
1719
+ value
1720
+ else
1721
+ value
1722
+ end
1723
+ when NilClass then nil
1724
+ when TrueClass then 1
1725
+ when FalseClass then 0
1726
+ when Float, Fixnum, Bignum then value
1727
+ # BigDecimals need to be output in a non-normalized form and quoted.
1728
+ when BigDecimal then value.to_s('F')
1729
+ when Numeric, Symbol then value.to_s
1730
+ else
1731
+ if @servertype.respond_to? :quote_date_time_for_pstmt
1732
+ return @servertype.quote_date_time_for_pstmt(value, column)
1733
+ elsif value.acts_like?(:date) || value.acts_like?(:time)
1734
+ quoted_date(value)
1735
+ else
1736
+ value.to_yaml
1737
+ end
1738
+ end
1739
+ end
1740
+
1741
+ # Properly quotes the various data types.
1742
+ # +value+ contains the data, +column+ is optional and contains info on the field
1743
+ def quote(value, column = nil)
1744
+ return value.quoted_id if value.respond_to?(:quoted_id)
1745
+
1746
+ case value
1747
+ # If it's a numeric value and the column type is not a string, it shouldn't be quoted
1748
+ # (IBM_DB doesn't accept quotes on numeric types)
1749
+ when Numeric
1750
+ # If the column type is text or string, return the quote value
1751
+ if column && column.type.to_sym == :text || column && column.type.to_sym == :string
1752
+ unless caller[0] =~ /insert_fixture/i
1753
+ "'#{value}'"
1754
+ else
1755
+ "#{value}"
1756
+ end
1757
+ else
1758
+ # value is Numeric, column.type is not a string,
1759
+ # therefore it converts the number to string without quoting it
1760
+ value.to_s
1761
+ end
1762
+ when String, ActiveSupport::Multibyte::Chars
1763
+ if column && column.type.to_sym == :binary && !(column.sql_type =~ /for bit data/i)
1764
+ # If quoting is required for the insert/update of a BLOB
1765
+ unless caller[0] =~ /add_column_options/i
1766
+ # Invokes a convertion from string to binary
1767
+ @servertype.set_binary_value
1768
+ else
1769
+ # Quoting required for the default value of a column
1770
+ @servertype.set_binary_default(value)
1771
+ end
1772
+ elsif column && column.type.to_sym == :text
1773
+ unless caller[0] =~ /add_column_options/i
1774
+ "'@@@IBMTEXT@@@'"
1775
+ else
1776
+ @servertype.set_text_default(quote_string(value))
1777
+ end
1778
+ elsif column && column.type.to_sym == :xml
1779
+ unless caller[0] =~ /add_column_options/i
1780
+ "'<ibm>@@@IBMXML@@@</ibm>'"
1781
+ else
1782
+ "#{value}"
1783
+ end
1784
+ else
1785
+ unless caller[0] =~ /insert_fixture/i
1786
+ super
1787
+ else
1788
+ "#{value}"
1789
+ end
1790
+ end
1791
+ when TrueClass then quoted_true # return '1' for true
1792
+ when FalseClass then quoted_false # return '0' for false
1793
+ when nil then "NULL"
1794
+ when Date, Time then "'#{quoted_date(value)}'"
1795
+ when Symbol then "'#{quote_string(value.to_s)}'"
1796
+ else
1797
+ unless caller[0] =~ /insert_fixture/i
1798
+ "'#{quote_string(YAML.dump(value))}'"
1799
+ else
1800
+ "#{quote_string(YAML.dump(value))}"
1801
+ end
1802
+ end
1803
+ end
1804
+
1805
+ # Quotes a given string, escaping single quote (') characters.
1806
+ def quote_string(string)
1807
+ string.gsub(/'/, "''")
1808
+ end
1809
+
1810
+ # *true* is represented by a smallint 1, *false*
1811
+ # by 0, as no native boolean type exists in DB2.
1812
+ # Numerics are not quoted in DB2.
1813
+ def quoted_true
1814
+ "1"
1815
+ end
1816
+
1817
+ def quoted_false
1818
+ "0"
1819
+ end
1820
+
1821
+ def quote_column_name(name)
1822
+ @servertype.check_reserved_words(name)
1823
+ end
1824
+
1825
+ #==============================================
1826
+ # SCHEMA STATEMENTS
1827
+ #==============================================
1828
+
1829
+ # Returns a Hash of mappings from the abstract data types to the native
1830
+ # database types
1831
+ def native_database_types
1832
+ {
1833
+ :primary_key => { :name => @servertype.primary_key_definition(@start_id)},
1834
+ :string => { :name => "varchar", :limit => 255 },
1835
+ :text => { :name => "clob" },
1836
+ :integer => { :name => "integer" },
1837
+ :float => { :name => "float" },
1838
+ :datetime => { :name => @servertype.get_datetime_mapping },
1839
+ :timestamp => { :name => @servertype.get_datetime_mapping },
1840
+ :time => { :name => @servertype.get_time_mapping },
1841
+ :date => { :name => "date" },
1842
+ :binary => { :name => "blob" },
1843
+
1844
+ # IBM data servers don't have a native boolean type.
1845
+ # A boolean can be represented by a smallint,
1846
+ # adopting the convention that False is 0 and True is 1
1847
+ :boolean => { :name => "smallint"},
1848
+ :xml => { :name => "xml"},
1849
+ :decimal => { :name => "decimal" },
1850
+ :rowid => { :name => "rowid" }, # rowid is a supported datatype on z/OS and i/5
1851
+ :serial => { :name => "serial" }, # rowid is a supported datatype on Informix Dynamic Server
1852
+ :char => { :name => "char" },
1853
+ :double => { :name => @servertype.get_double_mapping },
1854
+ :decfloat => { :name => "decfloat"},
1855
+ :graphic => { :name => "graphic", :limit => 1},
1856
+ :vargraphic => { :name => "vargraphic", :limit => 1},
1857
+ :bigint => { :name => "bigint"}
1858
+ }
1859
+ end
1860
+
1861
+ def build_conn_str_for_dbops()
1862
+ connect_str = "DRIVER={IBM DB2 ODBC DRIVER};ATTACH=true;"
1863
+ if(!@host.nil?)
1864
+ connect_str << "HOSTNAME=#{@host};"
1865
+ connect_str << "PORT=#{@port};"
1866
+ connect_str << "PROTOCOL=TCPIP;"
1867
+ end
1868
+ connect_str << "UID=#{@username};PWD=#{@password};"
1869
+ return connect_str
1870
+ end
1871
+
1872
+ # IBM_DataServer allow varied platform implementations
1873
+ def drop_database(dbName)
1874
+ boolrc = @servertype.drop_database(dbName)
1875
+ boolrc
1876
+ end
1877
+ # IBM_DataServer allow varied platform implementations
1878
+ def create_database(dbName, codeSet=nil, mode=nil)
1879
+ boolrc = @servertype.create_database(dbName, codeSet, mode)
1880
+ boolrc
1881
+ end
1882
+
1883
+ #############################
1884
+ # Unit testing (SQLLiteDatabaseTasks, MySQLDatabaseTasks, PostgreSQLDatabaseTasks)
1885
+ #############################
1886
+ def task_create(task)
1887
+ @servertype.task_create(task)
1888
+ end
1889
+ def task_drop(task)
1890
+ @servertype.task_drop(task)
1891
+ end
1892
+ def task_purge(task)
1893
+ @servertype.task_purge(task)
1894
+ end
1895
+ def task_charset(task)
1896
+ @servertype.task_charset(task)
1897
+ end
1898
+ def task_collation(task)
1899
+ @servertype.task_collation(task)
1900
+ end
1901
+ def task_structure_dump(task,filename=nil)
1902
+ @servertype.structure_dump(task,filename)
1903
+ end
1904
+ def task_structure_load(task,filename=nil)
1905
+ @servertype.structure_load(task,filename)
1906
+ end
1907
+
1908
+ def columns_for_distinct(columns, orders)
1909
+ if @servertype.respond_to? :columns_for_distinct
1910
+ @servertype.columns_for_distinct(columns, orders)
1911
+ else
1912
+ super(columns, orders)
1913
+ end
1914
+ end
1915
+
1916
+ # IBM data servers do not support limits on certain data types (unlike MySQL)
1917
+ # Limit is supported for the {float, decimal, numeric, varchar, clob, blob, graphic, vargraphic} data types.
1918
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
1919
+ if type.to_sym == :decfloat
1920
+ sql_segment = native_database_types[type.to_sym][:name].to_s
1921
+ sql_segment << "(#{precision})" if !precision.nil?
1922
+ return sql_segment
1923
+ end
1924
+
1925
+ return super if limit.nil?
1926
+
1927
+ # strip off limits on data types not supporting them
1928
+ if @servertype.limit_not_supported_types.include? type.to_sym
1929
+ return native_database_types[type.to_sym][:name].to_s
1930
+ elsif type.to_sym == :boolean
1931
+ return "smallint"
1932
+ else
1933
+ return super
1934
+ end
1935
+ end
1936
+
1937
+ # Returns the maximum length a table alias identifier can be.
1938
+ # IBM data servers (cross-platform) table limit is 128 characters
1939
+ def table_alias_length
1940
+ 128
1941
+ end
1942
+
1943
+ # Retrieves table's metadata for a specified shema name
1944
+ def tables(name = nil)
1945
+ # Initializes the tables array
1946
+ tables = []
1947
+ # Retrieve table's metadata through IBM_DB driver
1948
+ stmt = IBM_DB.tables(@connection, nil,
1949
+ @servertype.set_case(@schema))
1950
+ if(stmt)
1951
+ begin
1952
+ # Fetches all the records available
1953
+ while tab = IBM_DB.fetch_assoc(stmt)
1954
+ # Adds the lowercase table name to the array
1955
+ if(tab["table_type"]== 'TABLE') #check, so that only tables are dumped,IBM_DB.tables also returns views,alias etc in the schema
1956
+ tables << tab["table_name"].downcase
1957
+ end
1958
+ end
1959
+ rescue StandardError => fetch_error # Handle driver fetch errors
1960
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
1961
+ if error_msg && !error_msg.empty?
1962
+ raise "Failed to retrieve table metadata during fetch: #{error_msg}"
1963
+ else
1964
+ error_msg = "An unexpected error occurred during retrieval of table metadata"
1965
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
1966
+ raise error_msg if !ENV['T011']
1967
+ puts "IBM_DB.fetch_assoc(#{stmt}) erred but hack environment variable T011 said to ignore it" if ENV['T011']
1968
+ end
1969
+ ensure
1970
+ IBM_DB.free_stmt(stmt) if stmt # Free resources associated with the statement
1971
+ end
1972
+ else # Handle driver execution errors
1973
+ error_msg = IBM_DB.getErrormsg(@connection, IBM_DB::DB_CONN )
1974
+ if error_msg && !error_msg.empty?
1975
+ raise "Failed to retrieve tables metadata due to error: #{error_msg}"
1976
+ else
1977
+ raise StandardError.new('An unexpected error occurred during retrieval of table metadata')
1978
+ end
1979
+ end
1980
+ # Returns the tables array
1981
+ return tables
1982
+ end
1983
+
1984
+ # Returns the primary key of the mentioned table
1985
+ def primary_key(table_name)
1986
+ pk_name = nil
1987
+ stmt = IBM_DB.primary_keys( @connection, nil,
1988
+ @servertype.set_case(@schema),
1989
+ @servertype.set_case(table_name))
1990
+ if(stmt)
1991
+ begin
1992
+ if ( pk_index_row = IBM_DB.fetch_array(stmt) )
1993
+ pk_name = pk_index_row[3].downcase
1994
+ end
1995
+ rescue StandardError => fetch_error # Handle driver fetch errors
1996
+ error_msg = IBM_DB.getErrormsg( stmt, IBM_DB::DB_STMT )
1997
+ if error_msg && !error_msg.empty?
1998
+ raise "Failed to retrieve primarykey metadata during fetch: #{error_msg}"
1999
+ else
2000
+ error_msg = "An unexpected error occurred during retrieval of primary key metadata"
2001
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2002
+ raise error_msg
2003
+ end
2004
+ ensure # Free resources associated with the statement
2005
+ IBM_DB.free_stmt(stmt) if stmt
2006
+ end
2007
+ else
2008
+ error_msg = IBM_DB.getErrormsg( @connection, IBM_DB::DB_CONN )
2009
+ if error_msg && !error_msg.empty?
2010
+ raise "Failed to retrieve primary key metadata due to error: #{error_msg}"
2011
+ else
2012
+ raise StandardError.new('An unexpected error occurred during primary key retrieval')
2013
+ end
2014
+ end
2015
+ return pk_name
2016
+ end
2017
+
2018
+ # IBM_DataServer allow varied platform implementations
2019
+ def indexes(table_name, name = nil)
2020
+ indexes = @servertype.indexes(table_name, name)
2021
+ indexes
2022
+ end
2023
+
2024
+ # IBM_DataServer allow varied platform implementations
2025
+ def columns(table_name, name = nil)
2026
+ columns = @servertype.columns(table_name, name)
2027
+ columns
2028
+ end
2029
+
2030
+ # Renames a table.
2031
+ # ==== Example
2032
+ # rename_table('octopuses', 'octopi')
2033
+ # Overriden to satisfy IBM data servers syntax
2034
+ def rename_table(name, new_name)
2035
+ # SQL rename table statement
2036
+ rename_table_sql = "RENAME TABLE #{name} TO #{new_name}"
2037
+ stmt = execute(rename_table_sql)
2038
+ rename_table_indexes(name, new_name) if @servertype.instance_of?(IBM_DB2_I5) # T030
2039
+ # Ensures to free the resources associated with the statement
2040
+ ensure
2041
+ IBM_DB.free_stmt(stmt) if stmt
2042
+ end
2043
+
2044
+ # Renames a column.
2045
+ # ===== Example
2046
+ # rename_column(:suppliers, :description, :name)
2047
+ def rename_column(table_name, column_name, new_column_name)
2048
+ @servertype.rename_column(table_name, column_name, new_column_name)
2049
+ end
2050
+
2051
+ # Removes the column from the table definition.
2052
+ # ===== Examples
2053
+ # remove_column(:suppliers, :qualification)
2054
+ def remove_column(table_name, column_name)
2055
+ @servertype.remove_column(table_name, column_name)
2056
+ end
2057
+ # Changes the column's definition according to the new options.
2058
+ # See TableDefinition#column for details of the options you can use.
2059
+ # ===== Examples
2060
+ # change_column(:suppliers, :name, :string, :limit => 80)
2061
+ # change_column(:accounts, :description, :text)
2062
+ def change_column(table_name, column_name, type, options = {})
2063
+ @servertype.change_column(table_name, column_name, type, options)
2064
+ end
2065
+
2066
+ =begin
2067
+ #overrides the abstract adapter method to generate proper sql
2068
+ #specifying the column options, like default value and nullability clause
2069
+ def add_column_options!(sql,options={})
2070
+ #add default null option only if :default option is not specified and
2071
+ #:null option is not specified or is true
2072
+ if (options[:default].nil? && (options[:null].nil? || options[:null] == true))
2073
+ sql << " DEFAULT NULL"
2074
+ else
2075
+ if( !options[:default].nil?)
2076
+ #check, :column option is passed only in case of create_table but not in case of add_column
2077
+ if (!options[:column].nil?)
2078
+ sql << " DEFAULT #{quote(options[:default],options[:column])}"
2079
+ else
2080
+ sql << " DEFAULT #{quote(options[:default])}"
2081
+ end
2082
+ end
2083
+ #append NOT NULL to sql only---
2084
+ #---if options[:null] is not nil and is equal to false
2085
+ unless options[:null] == nil
2086
+ sql << " NOT NULL" if (options[:null] == false)
2087
+ end
2088
+ end
2089
+ end
2090
+ =end
2091
+
2092
+ #Add distinct clause to the sql if there is no order by specified
2093
+ def distinct(columns, order_by)
2094
+ if order_by.nil?
2095
+ "DISTINCT #{columns}"
2096
+ else
2097
+ "#{columns}"
2098
+ end
2099
+ end
2100
+
2101
+ # Sets a new default value for a column. This does not set the default
2102
+ # value to +NULL+, instead, it needs DatabaseStatements#execute which
2103
+ # can execute the appropriate SQL statement for setting the value.
2104
+ # ==== Examples
2105
+ # change_column_default(:accounts, :authorized, 1)
2106
+ # Method overriden to satisfy IBM data servers syntax.
2107
+ def change_column_default(table_name, column_name, default)
2108
+ @servertype.change_column_default(table_name, column_name, default)
2109
+ end
2110
+
2111
+ #Changes the nullability value of a column
2112
+ def change_column_null(table_name, column_name, null, default = nil)
2113
+ @servertype.change_column_null(table_name, column_name, null, default)
2114
+ end
2115
+
2116
+ # Remove the given index from the table.
2117
+ #
2118
+ # Remove the suppliers_name_index in the suppliers table (legacy support, use the second or third forms).
2119
+ # remove_index :suppliers, :name
2120
+ # Remove the index named accounts_branch_id in the accounts table.
2121
+ # remove_index :accounts, :column => :branch_id
2122
+ # Remove the index named by_branch_party in the accounts table.
2123
+ # remove_index :accounts, :name => :by_branch_party
2124
+ #
2125
+ # You can remove an index on multiple columns by specifying the first column.
2126
+ # add_index :accounts, [:username, :password]
2127
+ # remove_index :accounts, :username
2128
+ # Overriden to use the IBM data servers SQL syntax.
2129
+ def remove_index(table_name, options = {})
2130
+ execute("DROP INDEX #{index_name(table_name, options)}")
2131
+ end
2132
+ end # class IBM_DBAdapter
2133
+
2134
+ # This class contains common code across DB's (DB2 LUW, zOS, i5 and IDS)
2135
+ class IBM_DataServer
2136
+ def initialize(adapter, ar3)
2137
+ @adapter = adapter
2138
+ @isAr3 = ar3
2139
+ end
2140
+
2141
+ def last_generated_id(stmt)
2142
+ end
2143
+
2144
+ def create_index_after_table (table_name,cloumn_name)
2145
+ end
2146
+
2147
+ def setup_for_lob_table ()
2148
+ end
2149
+
2150
+ def reorg_table(table_name)
2151
+ end
2152
+
2153
+ def check_reserved_words(col_name)
2154
+ col_name.to_s
2155
+ end
2156
+
2157
+ # This is supported by the DB2 for Linux, UNIX, Windows data servers
2158
+ # and by the DB2 for i5 data servers
2159
+ def remove_column(table_name, column_name)
2160
+ begin
2161
+ @adapter.execute "ALTER TABLE #{table_name} DROP #{column_name}"
2162
+ reorg_table(table_name)
2163
+ rescue StandardError => exec_err
2164
+ # Provide details on the current XML columns support
2165
+ if exec_err.message.include?('SQLCODE=-1242') && exec_err.message.include?('42997')
2166
+ raise StatementInvalid,
2167
+ "A column that is part of a table containing an XML column cannot be dropped. \
2168
+ To remove the column, the table must be dropped and recreated without the #{column_name} column: #{exec_err}"
2169
+ else
2170
+ raise "#{exec_err}"
2171
+ end
2172
+ end
2173
+ end
2174
+
2175
+ def select(stmt)
2176
+ results = []
2177
+ # Fetches all the results available. IBM_DB.fetch_assoc(stmt) returns
2178
+ # an hash for each single record.
2179
+ # The loop stops when there aren't any more valid records to fetch
2180
+ begin
2181
+ if(@isAr3)
2182
+ while single_hash = IBM_DB.fetch_assoc(stmt)
2183
+ # Add the record to the +results+ array
2184
+ results << single_hash
2185
+ end
2186
+ else
2187
+ while single_hash = IBM_DB.fetch_array(stmt)
2188
+ # Add the record to the +results+ array
2189
+ results << single_hash
2190
+ end
2191
+ end
2192
+ rescue StandardError => fetch_error # Handle driver fetch errors
2193
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2194
+ if error_msg && !error_msg.empty?
2195
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
2196
+ else
2197
+ error_msg = "An unexpected error occurred during data retrieval"
2198
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2199
+ raise error_msg
2200
+ end
2201
+ end
2202
+ return results
2203
+ end
2204
+
2205
+ def select_rows(sql, name, stmt, results)
2206
+ # Fetches all the results available. IBM_DB.fetch_array(stmt) returns
2207
+ # an array representing a row in a result set.
2208
+ # The loop stops when there aren't any more valid records to fetch
2209
+ begin
2210
+ while single_array = IBM_DB.fetch_array(stmt)
2211
+ #Add the array to results array
2212
+ results << single_array
2213
+ end
2214
+ rescue StandardError => fetch_error # Handle driver fetch errors
2215
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2216
+ if error_msg && !error_msg.empty?
2217
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
2218
+ else
2219
+ error_msg = "An unexpected error occurred during data retrieval"
2220
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2221
+ raise error_msg
2222
+ end
2223
+ end
2224
+ return results
2225
+ end
2226
+
2227
+ # Praveen
2228
+ def prepare(sql,name = nil)
2229
+ begin
2230
+ stmt = IBM_DB.prepare(@adapter.connection, sql)
2231
+ if( stmt )
2232
+ stmt
2233
+ else
2234
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2235
+ end
2236
+ rescue StandardError => prep_err
2237
+ if prep_err && !prep_err.message.empty?
2238
+ raise "Failed to prepare sql #{sql} due to: #{prep_err}"
2239
+ else
2240
+ raise
2241
+ end
2242
+ end
2243
+ end
2244
+
2245
+ def execute(sql, name = nil)
2246
+ begin
2247
+ if stmt = IBM_DB.exec(@adapter.connection, sql)
2248
+ stmt # Return the statement object
2249
+ else
2250
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2251
+ end
2252
+ rescue StandardError => exec_err
2253
+ if exec_err && !exec_err.message.empty?
2254
+ raise "Failed to execute statement due to: #{exec_err}"
2255
+ else
2256
+ raise
2257
+ end
2258
+ end
2259
+ end
2260
+
2261
+ def set_schema(schema)
2262
+ @adapter.execute("SET SCHEMA #{schema}")
2263
+ end
2264
+
2265
+ def query_offset_limit(sql, offset, limit)
2266
+ end
2267
+
2268
+ def query_offset_limit!(sql, offset, limit, options)
2269
+ end
2270
+
2271
+ def get_datetime_mapping
2272
+ end
2273
+
2274
+ def get_time_mapping
2275
+ end
2276
+
2277
+ def get_double_mapping
2278
+ end
2279
+
2280
+ def change_column_default(table_name, column_name, default)
2281
+ end
2282
+
2283
+ def change_column_null(table_name, column_name, null, default)
2284
+ end
2285
+
2286
+ def set_binary_default(value)
2287
+ end
2288
+
2289
+ def set_binary_value
2290
+ end
2291
+
2292
+ def set_text_default
2293
+ end
2294
+
2295
+ def set_case(value)
2296
+ end
2297
+
2298
+ def limit_not_supported_types
2299
+ [:integer, :double, :date, :time, :timestamp, :xml, :bigint]
2300
+ end
2301
+
2302
+ def drop_database(dbName)
2303
+ connect_str = build_conn_str_for_dbops()
2304
+
2305
+ #Ensure connection is closed before trying to drop a database.
2306
+ #As a connect call would have been made by call seeing connection in active
2307
+ disconnect!
2308
+
2309
+ begin
2310
+ dropConn = IBM_DB.connect(connect_str, '', '')
2311
+ rescue StandardError => connect_err
2312
+ raise "Failed to connect to server due to: #{connect_err}"
2313
+ end
2314
+
2315
+ if(IBM_DB.dropDB(dropConn,dbName))
2316
+ IBM_DB.close(dropConn)
2317
+ return true
2318
+ else
2319
+ error = IBM_DB.getErrormsg(dropConn, IBM_DB::DB_CONN)
2320
+ IBM_DB.close(dropConn)
2321
+ raise "Could not drop Database due to: #{error}"
2322
+ end
2323
+ end
2324
+
2325
+ def create_database(dbName, codeSet=nil, mode=nil)
2326
+ connect_str = build_conn_str_for_dbops()
2327
+
2328
+ #Ensure connection is closed before trying to drop a database.
2329
+ #As a connect call would have been made by call seeing connection in active
2330
+ disconnect!
2331
+
2332
+ begin
2333
+ createConn = IBM_DB.connect(connect_str, '', '')
2334
+ rescue StandardError => connect_err
2335
+ raise "Failed to connect to server due to: #{connect_err}"
2336
+ end
2337
+
2338
+ if(IBM_DB.createDB(createConn,dbName,codeSet,mode))
2339
+ IBM_DB.close(createConn)
2340
+ return true
2341
+ else
2342
+ error = IBM_DB.getErrormsg(createConn, IBM_DB::DB_CONN)
2343
+ IBM_DB.close(createConn)
2344
+ raise "Could not create Database due to: #{error}"
2345
+ end
2346
+ end
2347
+
2348
+ #############################
2349
+ # Unit testing (SQLLiteDatabaseTasks, MySQLDatabaseTasks, PostgreSQLDatabaseTasks)
2350
+ #############################
2351
+ def task_create(task)
2352
+ create_database(dbName)
2353
+ task.log("create_database #{filename}")
2354
+ end
2355
+ def task_drop(task)
2356
+ drop_database(dbName)
2357
+ task.log("drop_database #{filename}")
2358
+ end
2359
+ def task_purge(task)
2360
+ drop_database(dbName)
2361
+ create_database(dbName)
2362
+ end
2363
+ def task_charset(task)
2364
+ @adapter.connection.charset
2365
+ end
2366
+ def task_collation(task)
2367
+ @adapter.connection.collation
2368
+ end
2369
+ def task_structure_dump(task,filename=nil)
2370
+ task.log("Unsupported function structure_dump #{filename}")
2371
+ end
2372
+ def task_structure_load(task,filename=nil)
2373
+ task.log("Unsupported function structure_load #{filename}")
2374
+ end
2375
+
2376
+ # Returns an array of non-primary key indexes for a specified table name
2377
+ def indexes(table_name, name = nil)
2378
+ # to_s required because +table_name+ may be a symbol.
2379
+ table_name = table_name.to_s
2380
+ # Checks if a blank table name has been given.
2381
+ # If so it returns an empty array of columns.
2382
+ return [] if table_name.strip.empty?
2383
+
2384
+ indexes = []
2385
+ pk_index = nil
2386
+ index_schema = []
2387
+
2388
+ #fetch the primary keys of the table using function primary_keys
2389
+ #TABLE_SCHEM:: pk_index[1]
2390
+ #TABLE_NAME:: pk_index[2]
2391
+ #COLUMN_NAME:: pk_index[3]
2392
+ #PK_NAME:: pk_index[5]
2393
+ stmt = IBM_DB.primary_keys( @adapter.connection, nil,
2394
+ set_case(@adapter.serverschema),
2395
+ set_case(table_name))
2396
+ if(stmt)
2397
+ begin
2398
+ while ( pk_index_row = IBM_DB.fetch_array(stmt) )
2399
+ if pk_index_row[5]
2400
+ pk_index_name = pk_index_row[5].downcase
2401
+ pk_index_columns = [pk_index_row[3].downcase] # COLUMN_NAME
2402
+ if pk_index
2403
+ pk_index.columns = pk_index.columns + pk_index_columns
2404
+ else
2405
+ pk_index = IndexDefinition.new(table_name, pk_index_name, true, pk_index_columns)
2406
+ end
2407
+ end
2408
+ end
2409
+ rescue StandardError => fetch_error # Handle driver fetch errors
2410
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2411
+ if error_msg && !error_msg.empty?
2412
+ raise "Failed to retrieve primarykey metadata during fetch: #{error_msg}"
2413
+ else
2414
+ error_msg = "An unexpected error occurred during retrieval of primary key metadata"
2415
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2416
+ raise error_msg
2417
+ end
2418
+ ensure # Free resources associated with the statement
2419
+ IBM_DB.free_stmt(stmt) if stmt
2420
+ end
2421
+ else # Handle driver execution errors
2422
+ error_msg = IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2423
+ if error_msg && !error_msg.empty?
2424
+ raise "Failed to retrieve primary key metadata due to error: #{error_msg}"
2425
+ else
2426
+ raise StandardError.new('An unexpected error occurred during primary key retrieval')
2427
+ end
2428
+ end
2429
+
2430
+ # Query table statistics for all indexes on the table
2431
+ # "TABLE_NAME: #{index_stats[2]}"
2432
+ # "NON_UNIQUE: #{index_stats[3]}"
2433
+ # "INDEX_NAME: #{index_stats[5]}"
2434
+ # "COLUMN_NAME: #{index_stats[8]}"
2435
+
2436
+ stmt = IBM_DB.statistics( @adapter.connection, nil,
2437
+ set_case(@adapter.serverschema),
2438
+ set_case(table_name), 1 )
2439
+ if(stmt)
2440
+ begin
2441
+ while ( index_stats = IBM_DB.fetch_array(stmt) )
2442
+ is_composite = false
2443
+ if index_stats[5] # INDEX_NAME
2444
+ index_name = index_stats[5].downcase
2445
+ index_unique = (index_stats[3] == 0)
2446
+ index_columns = [index_stats[8].downcase] # COLUMN_NAME
2447
+ index_qualifier = index_stats[4].downcase #Index_Qualifier
2448
+ # Create an IndexDefinition object and add to the indexes array
2449
+ i = 0;
2450
+ indexes.each do |index|
2451
+ if index.name == index_name && index_schema[i] == index_qualifier
2452
+ index.columns = index.columns + index_columns
2453
+ is_composite = true
2454
+ end
2455
+ i = i+1
2456
+ end
2457
+
2458
+ unless is_composite
2459
+ indexes << IndexDefinition.new(table_name, index_name, index_unique, index_columns)
2460
+ index_schema << index_qualifier
2461
+ end
2462
+ end
2463
+ end
2464
+ rescue StandardError => fetch_error # Handle driver fetch errors
2465
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2466
+ if error_msg && !error_msg.empty?
2467
+ raise "Failed to retrieve index metadata during fetch: #{error_msg}"
2468
+ else
2469
+ error_msg = "An unexpected error occurred during retrieval of index metadata"
2470
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2471
+ raise error_msg
2472
+ end
2473
+ ensure # Free resources associated with the statement
2474
+ IBM_DB.free_stmt(stmt) if stmt
2475
+ end
2476
+ else # Handle driver execution errors
2477
+ error_msg = IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2478
+ if error_msg && !error_msg.empty?
2479
+ raise "Failed to retrieve index metadata due to error: #{error_msg}"
2480
+ else
2481
+ raise StandardError.new('An unexpected error occurred during index retrieval')
2482
+ end
2483
+ end
2484
+
2485
+ # remove the primary key index entry.... should not be dumped by the dumper
2486
+ i = 0
2487
+ indexes.each do |index|
2488
+ if pk_index && index.columns == pk_index.columns
2489
+ indexes.delete_at(i)
2490
+ end
2491
+ i = i+1
2492
+ end
2493
+ # Returns the indexes array
2494
+ return indexes
2495
+ end
2496
+
2497
+ # Returns an array of Column objects for the table specified by +table_name+
2498
+ def columns(table_name, name = nil)
2499
+ # to_s required because it may be a symbol.
2500
+ table_name = set_case(table_name.to_s)
2501
+ # Checks if a blank table name has been given.
2502
+ # If so it returns an empty array
2503
+ return [] if table_name.strip.empty?
2504
+ # +columns+ will contain the resulting array
2505
+ columns = []
2506
+
2507
+ # Statement required to access all the columns information
2508
+ stmt = IBM_DB.columns( @adapter.connection, nil,
2509
+ set_case(@adapter.serverschema),
2510
+ set_case(table_name) )
2511
+ if(stmt)
2512
+ begin
2513
+ # Fetches all the columns and assigns them to col.
2514
+ # +col+ is an hash with keys/value pairs for a column
2515
+ while col = IBM_DB.fetch_assoc(stmt)
2516
+ column_name = col["column_name"].downcase
2517
+ # Assigns the column default value.
2518
+ column_default_value = col["column_def"]
2519
+ # If there is no default value, it assigns NIL
2520
+ column_default_value = nil if (column_default_value && column_default_value.upcase == 'NULL')
2521
+ # If default value is IDENTITY GENERATED BY DEFAULT (this value is retrieved in case of id columns)
2522
+ column_default_value = nil if (column_default_value && column_default_value.upcase =~ /IDENTITY GENERATED BY DEFAULT/i)
2523
+ # Removes single quotes from the default value
2524
+ column_default_value.gsub!(/^'(.*)'$/, '\1') unless column_default_value.nil?
2525
+ # Assigns the column type
2526
+ column_type = col["type_name"].downcase
2527
+ # Assigns the field length (size) for the column
2528
+ column_length = col["column_size"]
2529
+ column_scale = col["decimal_digits"]
2530
+ # The initializer of the class Column, requires the +column_length+ to be declared
2531
+ # between brackets after the datatype(e.g VARCHAR(50)) for :string and :text types.
2532
+ # If it's a "for bit data" field it does a subsitution in place, if not
2533
+ # it appends the (column_length) string on the supported data types
2534
+ unless column_length.nil? ||
2535
+ column_length == '' ||
2536
+ column_type.sub!(/ \(\) for bit data/i,"(#{column_length}) FOR BIT DATA") ||
2537
+ !column_type =~ /char|lob|graphic/i
2538
+ if column_type =~ /decimal/i
2539
+ column_type << "(#{column_length},#{column_scale})"
2540
+ elsif column_type =~ /smallint|integer|double|date|time|timestamp|xml|bigint/i
2541
+ column_type << "" # override native limits incompatible with table create
2542
+ else
2543
+ column_type << "(#{column_length})"
2544
+ end
2545
+ end
2546
+ # col["NULLABLE"] is 1 if the field is nullable, 0 if not.
2547
+ column_nullable = (col["nullable"] == 1) ? true : false
2548
+ # Make sure the hidden column (db2_generated_rowid_for_lobs) in DB2 z/OS isn't added to the list
2549
+ if !(column_name =~ /db2_generated_rowid_for_lobs/i)
2550
+ # Pushes into the array the *IBM_DBColumn* object, created by passing to the initializer
2551
+ # +column_name+, +default_value+, +column_type+ and +column_nullable+.
2552
+ if @adapter.servertype.instance_of?(IBM_DB2_I5)
2553
+ # T006&T014: time and Timestamp parsing (which perhaps should be in IBM_DBColumn instead
2554
+ columns << IBM_DB_I5Column.new(column_name, column_default_value, column_type, column_nullable)
2555
+ else
2556
+ columns << IBM_DBColumn.new(column_name, column_default_value, column_type, column_nullable)
2557
+ end
2558
+ end
2559
+ end
2560
+ rescue StandardError => fetch_error # Handle driver fetch errors
2561
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2562
+ if error_msg && !error_msg.empty?
2563
+ raise "Failed to retrieve column metadata during fetch: #{error_msg}"
2564
+ else
2565
+ error_msg = "An unexpected error occurred during retrieval of column metadata for #{table_name}"
2566
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2567
+ raise error_msg
2568
+ end
2569
+ ensure # Free resources associated with the statement
2570
+ IBM_DB.free_stmt(stmt) if stmt
2571
+ end
2572
+ else # Handle driver execution errors
2573
+ error_msg = IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2574
+ if error_msg && !error_msg.empty?
2575
+ raise "Failed to retrieve column metadata due to error: #{error_msg}"
2576
+ else
2577
+ raise StandardError.new('An unexpected error occurred during retrieval of columns metadata')
2578
+ end
2579
+ end
2580
+
2581
+ # Returns the columns array
2582
+ return columns
2583
+ end
2584
+
2585
+ end # class IBM_DataServer
2586
+
2587
+ class IBM_DB2 < IBM_DataServer
2588
+ def initialize(adapter, ar3)
2589
+ super(adapter, ar3)
2590
+ @limit = @offset = nil
2591
+ end
2592
+
2593
+ def rename_column(table_name, column_name, new_column_name)
2594
+ raise NotImplementedError, "rename_column is not implemented yet in the IBM_DB Adapter"
2595
+ end
2596
+
2597
+ def primary_key_definition(start_id)
2598
+ return "INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH #{start_id}) PRIMARY KEY"
2599
+ end
2600
+
2601
+ # Returns the last automatically generated ID.
2602
+ # This method is required by the +insert+ method
2603
+ # The "stmt" parameter is ignored for DB2 but used for IDS
2604
+ def last_generated_id(stmt)
2605
+ # Queries the db to obtain the last ID that was automatically generated
2606
+ sql = "SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1"
2607
+ stmt = IBM_DB.prepare(@adapter.connection, sql)
2608
+ if(stmt)
2609
+ if(IBM_DB.execute(stmt, nil))
2610
+ begin
2611
+ # Fetches the only record available (containing the last id)
2612
+ IBM_DB.fetch_row(stmt)
2613
+ # Retrieves and returns the result of the query with the last id.
2614
+ IBM_DB.result(stmt,0)
2615
+ rescue StandardError => fetch_error # Handle driver fetch errors
2616
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2617
+ if error_msg && !error_msg.empty?
2618
+ raise "Failed to retrieve last generated id: #{error_msg}"
2619
+ else
2620
+ error_msg = "An unexpected error occurred during retrieval of last generated id"
2621
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2622
+ raise error_msg
2623
+ end
2624
+ ensure # Free resources associated with the statement
2625
+ IBM_DB.free_stmt(stmt) if stmt
2626
+ end
2627
+ else
2628
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2629
+ IBM_DB.free_stmt(stmt) if stmt
2630
+ if error_msg && !error_msg.empty?
2631
+ raise "Failed to retrieve last generated id: #{error_msg}"
2632
+ else
2633
+ error_msg = "An unexpected error occurred during retrieval of last generated id"
2634
+ raise error_msg
2635
+ end
2636
+ end
2637
+ else
2638
+ error_msg = IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2639
+ if error_msg && !error_msg.empty?
2640
+ raise "Failed to retrieve last generated id due to error: #{error_msg}"
2641
+ else
2642
+ raise StandardError.new('An unexpected error occurred during retrieval of last generated id')
2643
+ end
2644
+ end
2645
+ end
2646
+
2647
+ def change_column(table_name, column_name, type, options)
2648
+ data_type = @adapter.type_to_sql(type, options[:limit], options[:precision], options[:scale])
2649
+ begin
2650
+ execute "ALTER TABLE #{table_name} ALTER #{column_name} SET DATA TYPE #{data_type}"
2651
+ rescue StandardError => exec_err
2652
+ if exec_err.message.include?('SQLCODE=-190')
2653
+ raise StatementInvalid,
2654
+ "Please consult documentation for compatible data types while changing column datatype. \
2655
+ The column datatype change to [#{data_type}] is not supported by this data server: #{exec_err}"
2656
+ else
2657
+ raise "#{exec_err}"
2658
+ end
2659
+ end
2660
+ reorg_table(table_name)
2661
+ change_column_null(table_name,column_name,options[:null],nil)
2662
+ change_column_default(table_name, column_name, options[:default])
2663
+ reorg_table(table_name)
2664
+ end
2665
+
2666
+ # DB2 specific ALTER TABLE statement to add a default clause
2667
+ def change_column_default(table_name, column_name, default)
2668
+ # SQL statement which alters column's default value
2669
+ change_column_sql = "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} \
2670
+ SET WITH DEFAULT #{@adapter.quote(default)}"
2671
+
2672
+ stmt = execute(change_column_sql)
2673
+ reorg_table(table_name)
2674
+ ensure
2675
+ IBM_DB.free_stmt(stmt) if stmt
2676
+ end
2677
+
2678
+ #DB2 specific ALTER TABLE statement to change the nullability of a column
2679
+ def change_column_null(table_name, column_name, null, default)
2680
+ if !default.nil?
2681
+ change_column_default(table_name, column_name, default)
2682
+ end
2683
+
2684
+ if !null.nil?
2685
+ if null
2686
+ change_column_sql = "ALTER TABLE #{table_name} ALTER #{column_name} DROP NOT NULL"
2687
+ else
2688
+ change_column_sql = "ALTER TABLE #{table_name} ALTER #{column_name} SET NOT NULL"
2689
+ end
2690
+ stmt = execute(change_column_sql)
2691
+ reorg_table(table_name)
2692
+ end
2693
+
2694
+ ensure
2695
+ IBM_DB.free_stmt(stmt) if stmt
2696
+ end
2697
+
2698
+ # This method returns the DB2 SQL type corresponding to the Rails
2699
+ # datetime/timestamp type
2700
+ def get_datetime_mapping
2701
+ return "timestamp"
2702
+ end
2703
+
2704
+ # This method returns the DB2 SQL type corresponding to the Rails
2705
+ # time type
2706
+ def get_time_mapping
2707
+ return "time"
2708
+ end
2709
+
2710
+ #This method returns the DB2 SQL type corresponding to Rails double type
2711
+ def get_double_mapping
2712
+ return "double"
2713
+ end
2714
+ =begin
2715
+ # Commenting this code, as offset handling is now part of sql and we need to handle it in select and also
2716
+ # need not set cursor type during prepare or execute
2717
+ # Fetches all the results available. IBM_DB.fetch_assoc(stmt) returns
2718
+ # an hash for each single record.
2719
+ # The loop stops when there aren't any more valid records to fetch
2720
+ def select(stmt)
2721
+ results = []
2722
+ begin
2723
+ if (!@offset.nil? && @offset >= 0) || (!@limit.nil? && @limit > 0)
2724
+ # We know at this point that there is an offset and/or a limit
2725
+ # Check if the cursor type is set correctly
2726
+ cursor_type = IBM_DB.get_option stmt, IBM_DB::SQL_ATTR_CURSOR_TYPE, 0
2727
+ @offset = 0 if @offset.nil?
2728
+ if (cursor_type == IBM_DB::SQL_CURSOR_STATIC)
2729
+ index = 0
2730
+ # Get @limit rows starting at @offset
2731
+ while (index < @limit)
2732
+ # We increment the offset by 1 because for DB2 the offset of the initial row is 1 instead of 0
2733
+ if single_hash = IBM_DB.fetch_assoc(stmt, @offset + index + 1)
2734
+ # Add the record to the +results+ array
2735
+ results << single_hash
2736
+ index = index + 1
2737
+ else
2738
+ # break from the while loop
2739
+ break
2740
+ end
2741
+ end
2742
+ else # cursor != IBM_DB::SQL_CURSOR_STATIC
2743
+ # If the result set contains a LOB, the cursor type will never be SQL_CURSOR_STATIC
2744
+ # because DB2 does not allow this. We can't use the offset mechanism because the cursor
2745
+ # is not scrollable. In this case, ignore first @offset rows and return rows starting
2746
+ # at @offset to @offset + @limit
2747
+ index = 0
2748
+ while (index < @offset + @limit)
2749
+ if single_hash = IBM_DB.fetch_assoc(stmt)
2750
+ # Add the record to the +results+ array only from row @offset to @offset + @limit
2751
+ if (index >= @offset)
2752
+ results << single_hash
2753
+ end
2754
+ index = index + 1
2755
+ else
2756
+ # break from the while loop
2757
+ break
2758
+ end
2759
+ end
2760
+ end
2761
+ # This is the case where limit is set to zero
2762
+ # Simply return an empty +results+
2763
+ elsif (!@limit.nil? && @limit == 0)
2764
+ results
2765
+ # No limits or offsets specified
2766
+ else
2767
+ while single_hash = IBM_DB.fetch_assoc(stmt)
2768
+ # Add the record to the +results+ array
2769
+ results << single_hash
2770
+ end
2771
+ return results
2772
+ end
2773
+ rescue StandardError => fetch_error # Handle driver fetch errors
2774
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2775
+ if error_msg && !error_msg.empty?
2776
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
2777
+ else
2778
+ error_msg = "An unexpected error occurred during data retrieval"
2779
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2780
+ raise error_msg
2781
+ end
2782
+ ensure
2783
+ # Assign the instance variables to nil. We will not be using them again
2784
+ @offset = nil
2785
+ @limit = nil
2786
+ end
2787
+ end
2788
+
2789
+ # Fetches all the results available. IBM_DB.fetch_array(stmt) returns
2790
+ # an array for each single record.
2791
+ # The loop stops when there aren't any more valid records to fetch
2792
+ def select_rows(sql, name, stmt, results)
2793
+ begin
2794
+ if (!@offset.nil? && @offset >= 0) || (!@limit.nil? && @limit > 0)
2795
+ # We know at this point that there is an offset and/or a limit
2796
+ # Check if the cursor type is set correctly
2797
+ cursor_type = IBM_DB.get_option stmt, IBM_DB::SQL_ATTR_CURSOR_TYPE, 0
2798
+ @offset = 0 if @offset.nil?
2799
+ if (cursor_type == IBM_DB::SQL_CURSOR_STATIC)
2800
+ index = 0
2801
+ # Get @limit rows starting at @offset
2802
+ while (index < @limit)
2803
+ # We increment the offset by 1 because for DB2 the offset of the initial row is 1 instead of 0
2804
+ if single_array = IBM_DB.fetch_array(stmt, @offset + index + 1)
2805
+ # Add the array to the +results+ array
2806
+ results << single_array
2807
+ index = index + 1
2808
+ else
2809
+ # break from the while loop
2810
+ break
2811
+ end
2812
+ end
2813
+ else # cursor != IBM_DB::SQL_CURSOR_STATIC
2814
+ # If the result set contains a LOB, the cursor type will never be SQL_CURSOR_STATIC
2815
+ # because DB2 does not allow this. We can't use the offset mechanism because the cursor
2816
+ # is not scrollable. In this case, ignore first @offset rows and return rows starting
2817
+ # at @offset to @offset + @limit
2818
+ index = 0
2819
+ while (index < @offset + @limit)
2820
+ if single_array = IBM_DB.fetch_array(stmt)
2821
+ # Add the array to the +results+ array only from row @offset to @offset + @limit
2822
+ if (index >= @offset)
2823
+ results << single_array
2824
+ end
2825
+ index = index + 1
2826
+ else
2827
+ # break from the while loop
2828
+ break
2829
+ end
2830
+ end
2831
+ end
2832
+ # This is the case where limit is set to zero
2833
+ # Simply return an empty +results+
2834
+ elsif (!@limit.nil? && @limit == 0)
2835
+ results
2836
+ # No limits or offsets specified
2837
+ else
2838
+ while single_array = IBM_DB.fetch_array(stmt)
2839
+ # Add the array to the +results+ array
2840
+ results << single_array
2841
+ end
2842
+ end
2843
+ rescue StandardError => fetch_error # Handle driver fetch errors
2844
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2845
+ if error_msg && !error_msg.empty?
2846
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
2847
+ else
2848
+ error_msg = "An unexpected error occurred during data retrieval"
2849
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2850
+ raise error_msg
2851
+ end
2852
+ ensure
2853
+ # Assign the instance variables to nil. We will not be using them again
2854
+ @offset = nil
2855
+ @limit = nil
2856
+ end
2857
+ return results
2858
+ end
2859
+
2860
+ # Praveen
2861
+ def prepare(sql,name = nil)
2862
+ # Check if there is a limit and/or an offset
2863
+ # If so then make sure and use a static cursor type
2864
+ begin
2865
+ if (!@offset.nil? && @offset >= 0) || (!@limit.nil? && @limit > 0)
2866
+ # Set the cursor type to static so we can later utilize the offset and limit correctly
2867
+ if stmt = IBM_DB.prepare(@adapter.connection, sql,
2868
+ {IBM_DB::SQL_ATTR_CURSOR_TYPE => IBM_DB::SQL_CURSOR_STATIC})
2869
+ stmt # Return the statement object
2870
+ else
2871
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2872
+ end
2873
+ else
2874
+ if stmt = IBM_DB.prepare(@adapter.connection, sql)
2875
+ stmt # Return the statement object
2876
+ else
2877
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2878
+ end
2879
+ end
2880
+ rescue StandardError => prep_err
2881
+ error_msg = "Failed to prepare sql #{sql}"
2882
+ error_msg = error_msg + ": #{prep_err.message}" if !prep_err.message.empty?
2883
+ raise error_msg
2884
+ end
2885
+ end
2886
+
2887
+ # Praveen
2888
+ def execute(sql, name = nil)
2889
+ # Check if there is a limit and/or an offset
2890
+ # If so then make sure and use a static cursor type
2891
+ begin
2892
+ if (!@offset.nil? && @offset >= 0) || (!@limit.nil? && @limit > 0)
2893
+ # Set the cursor type to static so we can later utilize the offset and limit correctly
2894
+ if stmt = IBM_DB.exec(@adapter.connection, sql,
2895
+ {IBM_DB::SQL_ATTR_CURSOR_TYPE => IBM_DB::SQL_CURSOR_STATIC})
2896
+ stmt # Return the statement object
2897
+ else
2898
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2899
+ end
2900
+ else
2901
+ if stmt = IBM_DB.exec(@adapter.connection, sql)
2902
+ stmt # Return the statement object
2903
+ else
2904
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2905
+ end
2906
+ end
2907
+ rescue StandardError => exec_err
2908
+ error_msg = "Failed to execute statement"
2909
+ error_msg = error_msg + ": #{exec_err.message}" if !exec_err.message.empty?
2910
+ raise error_msg
2911
+ end
2912
+ end
2913
+ =end
2914
+ def query_offset_limit(sql, offset, limit)
2915
+ if(offset.nil? && limit.nil?)
2916
+ return sql
2917
+ end
2918
+
2919
+ if (offset.nil?)
2920
+ return sql << " FETCH FIRST #{limit} ROWS ONLY"
2921
+ end
2922
+
2923
+ if self.instance_of? IBM_DB2_I5
2924
+ # T022: ORDER BY must be at the end of the SQL wrapper, with aliases, if any
2925
+ order_by = nil
2926
+ if sql =~ /( ORDER BY .*)$/
2927
+ order_by = $1
2928
+ sql = sql.gsub(order_by, '')
2929
+ order_by = order_by.gsub(/(\w*)\./, 'O.\1.')
2930
+ # replace ORDER BY column names with alias
2931
+ alias_names = Hash.new
2932
+ order_by.scan(/[UPPER|LOWER]*O\.([.A-Za-z0-9_]*)/).each do |u|
2933
+ if sql =~ /[UPPER|LOWER]?\(?\s*#{u[0]}\)?\s*AS\s(\w*)/
2934
+ alias_names[u[0]] = "#{$1}"
2935
+ end
2936
+ end
2937
+ alias_names.each {|col, aliaz| order_by = order_by.gsub(col, aliaz) }
2938
+ # no need for table name qualifier
2939
+ order_by.gsub!(/O\.\w*\./, 'O.')
2940
+ end
2941
+ end
2942
+
2943
+ if(limit.nil?)
2944
+ sql.sub!(/SELECT/i,"SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_row_num FROM (SELECT")
2945
+ return sql << ") AS I) AS O WHERE sys_row_num > #{offset}"
2946
+ end
2947
+
2948
+ # Defines what will be the last record
2949
+ last_record = offset + limit
2950
+ # Transforms the SELECT query in order to retrieve/fetch only
2951
+ # a number of records after the specified offset.
2952
+ # 'select' or 'SELECT' is replaced with the partial query below that adds the sys_row_num column
2953
+ # to select with the condition of this column being between offset+1 and the offset+limit
2954
+ sql.sub!(/SELECT/i,"SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_row_num FROM (SELECT")
2955
+ # The final part of the query is appended to include a WHERE...BETWEEN...AND condition,
2956
+ # and retrieve only a LIMIT number of records starting from the OFFSET+1
2957
+ sql << ") AS I) AS O WHERE sys_row_num BETWEEN #{offset+1} AND #{last_record}"
2958
+ sql << order_by if order_by && self.instance_of?(IBM_DB2_I5)
2959
+ sql
2960
+ end
2961
+
2962
+ def query_offset_limit!(sql, offset, limit, options)
2963
+ if(offset.nil? && limit.nil?)
2964
+ options[:paramArray] = []
2965
+ return sql
2966
+ end
2967
+
2968
+ if (offset.nil?)
2969
+ options[:paramArray] = []
2970
+ return sql << " FETCH FIRST #{limit} ROWS ONLY"
2971
+ end
2972
+
2973
+ if(limit.nil?)
2974
+ sql.sub!(/SELECT/i,"SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_row_num FROM (SELECT")
2975
+ sql << ") AS I) AS O WHERE sys_row_num > ?"
2976
+ options[:paramArray] = [offset]
2977
+ return
2978
+ end
2979
+
2980
+ # Defines what will be the last record
2981
+ last_record = offset + limit
2982
+ # Transforms the SELECT query in order to retrieve/fetch only
2983
+ # a number of records after the specified offset.
2984
+ # 'select' or 'SELECT' is replaced with the partial query below that adds the sys_row_num column
2985
+ # to select with the condition of this column being between offset+1 and the offset+limit
2986
+ sql.sub!(/SELECT/i,"SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_row_num FROM (SELECT")
2987
+ # The final part of the query is appended to include a WHERE...BETWEEN...AND condition,
2988
+ # and retrieve only a LIMIT number of records starting from the OFFSET+1
2989
+ sql << ") AS I) AS O WHERE sys_row_num BETWEEN ? AND ?"
2990
+ options[:paramArray] = [offset+1, last_record]
2991
+ end
2992
+
2993
+ # This method generates the default blob value specified for
2994
+ # DB2 Dataservers
2995
+ def set_binary_default(value)
2996
+ "BLOB('#{value}')"
2997
+ end
2998
+
2999
+ # This method generates the blob value specified for DB2 Dataservers
3000
+ def set_binary_value
3001
+ "BLOB('?')"
3002
+ end
3003
+
3004
+ # This method generates the default clob value specified for
3005
+ # DB2 Dataservers
3006
+ def set_text_default(value)
3007
+ "'#{value}'"
3008
+ end
3009
+
3010
+ # For DB2 Dataservers , the arguments to the meta-data functions
3011
+ # need to be in upper-case
3012
+ def set_case(value)
3013
+ value.upcase
3014
+ end
3015
+
3016
+ end # class IBM_DB2
3017
+
3018
+ class IBM_DB2_LUW < IBM_DB2
3019
+ # Reorganizes the table for column changes
3020
+ def reorg_table(table_name)
3021
+ execute("CALL ADMIN_CMD('REORG TABLE #{table_name}')")
3022
+ end
3023
+ end # class IBM_DB2_LUW
3024
+
3025
+ class IBM_DB2_LUW_COBRA < IBM_DB2_LUW
3026
+ # Cobra supports parameterised timestamp,
3027
+ # hence overriding following method to allow timestamp datatype to be parameterised
3028
+ def limit_not_supported_types
3029
+ [:integer, :double, :date, :time, :xml, :bigint]
3030
+ end
3031
+
3032
+ # Alter table column for renaming a column
3033
+ # This feature is supported for against DB2 V97 and above only
3034
+ def rename_column(table_name, column_name, new_column_name)
3035
+ _table_name = table_name.to_s
3036
+ _column_name = column_name.to_s
3037
+ _new_column_name = new_column_name.to_s
3038
+
3039
+ nil_condition = _table_name.nil? || _column_name.nil? || _new_column_name.nil?
3040
+ empty_condition = _table_name.empty? ||
3041
+ _column_name.empty? ||
3042
+ _new_column_name.empty? unless nil_condition
3043
+
3044
+ if nil_condition || empty_condition
3045
+ raise ArgumentError,"One of the arguments passed to rename_column is empty or nil"
3046
+ end
3047
+
3048
+ begin
3049
+ rename_column_sql = "ALTER TABLE #{_table_name} RENAME COLUMN #{_column_name} \
3050
+ TO #{_new_column_name}"
3051
+
3052
+ unless stmt = execute(rename_column_sql)
3053
+ error_msg = IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
3054
+ if error_msg && !error_msg.empty?
3055
+ raise "Rename column failed : #{error_msg}"
3056
+ else
3057
+ raise StandardError.new('An unexpected error occurred during renaming the column')
3058
+ end
3059
+ end
3060
+
3061
+ reorg_table(_table_name)
3062
+
3063
+ ensure
3064
+ IBM_DB.free_stmt(stmt) if stmt
3065
+ end #End of begin
3066
+ end # End of rename_column
3067
+ end #IBM_DB2_LUW_COBRA
3068
+
3069
+ module HostedDataServer
3070
+ require 'pathname'
3071
+ #find DB2-i5-zOS rezerved words file relative path
3072
+ rfile = Pathname.new(File.dirname(__FILE__)).parent + 'vendor' + 'db2-i5-zOS.yaml'
3073
+ if rfile
3074
+ RESERVED_WORDS = open(rfile.to_s) {|f| YAML.load(f) }
3075
+ def check_reserved_words(col_name)
3076
+ if RESERVED_WORDS[col_name]
3077
+ '"' + RESERVED_WORDS[col_name] + '"'
3078
+ else
3079
+ col_name.to_s
3080
+ end
3081
+ end
3082
+ else
3083
+ raise "Failed to locate IBM_DB Adapter dependency: #{rfile}"
3084
+ end
3085
+ end # module HostedDataServer
3086
+
3087
+ class IBM_DB2_ZOS < IBM_DB2
3088
+ # since v9 doesn't need, suggest putting it in HostedDataServer?
3089
+ def create_index_after_table(table_name,column_name)
3090
+ @adapter.add_index(table_name, column_name, :unique => true)
3091
+ end
3092
+
3093
+ def remove_column(table_name, column_name)
3094
+ raise NotImplementedError,
3095
+ "remove_column is not supported by the DB2 for zOS data server"
3096
+ end
3097
+
3098
+ #Alter table column for renaming a column
3099
+ def rename_column(table_name, column_name, new_column_name)
3100
+ _table_name = table_name.to_s
3101
+ _column_name = column_name.to_s
3102
+ _new_column_name = new_column_name.to_s
3103
+
3104
+ nil_condition = _table_name.nil? || _column_name.nil? || _new_column_name.nil?
3105
+ empty_condition = _table_name.empty? ||
3106
+ _column_name.empty? ||
3107
+ _new_column_name.empty? unless nil_condition
3108
+
3109
+ if nil_condition || empty_condition
3110
+ raise ArgumentError,"One of the arguments passed to rename_column is empty or nil"
3111
+ end
3112
+
3113
+ begin
3114
+ rename_column_sql = "ALTER TABLE #{_table_name} RENAME COLUMN #{_column_name} \
3115
+ TO #{_new_column_name}"
3116
+
3117
+ unless stmt = execute(rename_column_sql)
3118
+ error_msg = IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
3119
+ if error_msg && !error_msg.empty?
3120
+ raise "Rename column failed : #{error_msg}"
3121
+ else
3122
+ raise StandardError.new('An unexpected error occurred during renaming the column')
3123
+ end
3124
+ end
3125
+
3126
+ reorg_table(_table_name)
3127
+
3128
+ ensure
3129
+ IBM_DB.free_stmt(stmt) if stmt
3130
+ end #End of begin
3131
+ end # End of rename_column
3132
+
3133
+ # DB2 z/OS only allows NULL or "" (empty) string as DEFAULT value for a BLOB column.
3134
+ # For non-empty string and non-NULL values, the server returns error
3135
+ def set_binary_default(value)
3136
+ "#{value}"
3137
+ end
3138
+
3139
+ def change_column_default(table_name, column_name, default)
3140
+ unless default
3141
+ raise NotImplementedError,
3142
+ "DB2 for zOS data server version 9 does not support changing the column default to NULL"
3143
+ else
3144
+ super
3145
+ end
3146
+ end
3147
+
3148
+ def change_column_null(table_name, column_name, null, default)
3149
+ raise NotImplementedError,
3150
+ "DB2 for zOS data server does not support changing the column's nullability"
3151
+ end
3152
+ end # class IBM_DB2_ZOS
3153
+
3154
+ class IBM_DB2_ZOS_8 < IBM_DB2_ZOS
3155
+ include HostedDataServer
3156
+
3157
+ def query_offset_limit(sql, offset, limit)
3158
+ if (!limit.nil?)
3159
+ sql << " FETCH FIRST #{limit} ROWS ONLY"
3160
+ end
3161
+ return sql
3162
+ end
3163
+
3164
+ def query_offset_limit!(sql, offset, limit, options)
3165
+ if (!limit.nil?)
3166
+ sql << " FETCH FIRST #{limit} ROWS ONLY"
3167
+ end
3168
+ options[:paramArray] = []
3169
+ end
3170
+
3171
+ # When issued, this call does the following:
3172
+ # DB2 creates LOB table spaces, auxiliary tables, and indexes on auxiliary
3173
+ # tables for LOB columns.
3174
+ def setup_for_lob_table()
3175
+ execute "SET CURRENT RULES = 'STD'"
3176
+ end
3177
+
3178
+ def rename_column(table_name, column_name, new_column_name)
3179
+ raise NotImplementedError, "rename_column is not implemented for DB2 on zOS 8"
3180
+ end
3181
+
3182
+ def change_column_default(table_name, column_name, default)
3183
+ raise NotImplementedError,
3184
+ "DB2 for zOS data server version 8 does not support changing the column default"
3185
+ end
3186
+
3187
+ end # class IBM_DB2_ZOS_8
3188
+
3189
+ class IBM_DB2_I5 < IBM_DB2
3190
+ include HostedDataServer
3191
+
3192
+ # T032: add column with name of id to table with no index fails
3193
+ # this strategy is an attempt to keep PowerRuby's open classing to IBM_DB2_I5
3194
+ @@open_classing_done = false
3195
+ class << self
3196
+ attr_accessor :open_classing_done
3197
+ end
3198
+ def initialize(adapter, ar3)
3199
+ super(adapter, ar3)
3200
+ return if self.class.open_classing_done
3201
+
3202
+ # Rails 4 added SchemaCreation
3203
+ if defined? ActiveRecord::ConnectionAdapters::AbstractAdapter::SchemaCreation
3204
+ ActiveRecord::ConnectionAdapters::AbstractAdapter::SchemaCreation.class_eval do
3205
+ def visit_AddColumn(o)
3206
+ sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
3207
+ sql = "ADD COLUMN #{quote_column_name(o.name)} #{sql_type}"
3208
+ add_column_options!(sql, column_options(o))
3209
+ end
3210
+ end
3211
+ end
3212
+ self.class.open_classing_done = true
3213
+ end
3214
+
3215
+ # T006 time format/parsing
3216
+ require 'ibm_db' unless defined? IBM_DB
3217
+ TIME_SEP_CHAR = {
3218
+ "#{IBM_DB::SQL_SEP_COLON}" => ':',
3219
+ "#{IBM_DB::SQL_SEP_PERIOD}" => '.',
3220
+ "#{IBM_DB::SQL_SEP_COMMA}" => ',',
3221
+ "#{IBM_DB::SQL_SEP_BLANK}" => ' ',
3222
+ "#{IBM_DB::SQL_SEP_JOB}" => ':' # TODO get separator for job, although Rails is the job
3223
+ }
3224
+
3225
+ # T009: IBM DB requires the ORDER BY columns in the select list for distinct queries, and
3226
+ # requires that the ORDER BY include the distinct column.
3227
+ def columns_for_distinct(columns, orders)
3228
+ order_columns = orders.map{ |s|
3229
+ # Convert Arel node to string
3230
+ s = s.to_sql unless s.is_a?(String)
3231
+ # Remove any ASC/DESC modifiers
3232
+ s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
3233
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
3234
+ [columns, *order_columns].join(', ')
3235
+ end
3236
+
3237
+ # T017|T023: raise the appropriate error
3238
+ def translate_exception(exception, message)
3239
+ case error_number(message)
3240
+ when -803
3241
+ RecordNotUnique.new(message, exception)
3242
+ when -530
3243
+ InvalidForeignKey.new(message, exception)
3244
+ else
3245
+ # would just be "super(x,y)" if this code was in IBM_DBAdapter
3246
+ @adapter.class.superclass.instance_method(:translate_exception).bind(@adapter).call(message, exception)
3247
+ end
3248
+ end
3249
+ def error_number(messsage) # :nodoc:
3250
+ m = /(SQLCODE=)(-\d{1,6})/.match(messsage)
3251
+ return m[2].to_i if m
3252
+ end
3253
+
3254
+ def quote_date_time_for_pstmt(value, column=nil)
3255
+ if column && column.sql_type == 'time' # T006 time format
3256
+ sep = TIME_SEP_CHAR[@adapter.time_sep]
3257
+ value = value.to_s[/\d\d#{sep}\d\d#{sep}\d\d/]
3258
+ elsif value.acts_like?(:date)
3259
+ if column && column.sql_type == 'timestamp' # T013 timestamp format
3260
+ value = value.to_time
3261
+ end
3262
+ value = @adapter.quoted_date(value)
3263
+ elsif value.acts_like?(:time)
3264
+ value = @adapter.quoted_date(value)
3265
+ else
3266
+ value.to_yaml
3267
+ end
3268
+ end
3269
+
3270
+ # Returns an array of non-primary key indexes for a specified table name
3271
+ def indexes(table_name, name = nil)
3272
+ # IBM i issue with fetch lob when autocommit=off (DB2 PTF not applied all machines)
3273
+ hackibmi = IBM_DB.autocommit(@adapter.connection) == IBM_DB::SQL_AUTOCOMMIT_OFF
3274
+ if hackibmi == true
3275
+ IBM_DB.autocommit @adapter.connection, IBM_DB::SQL_AUTOCOMMIT_ON
3276
+ end
3277
+ indexes = super(table_name, name)
3278
+ if hackibmi == true
3279
+ IBM_DB.autocommit @adapter.connection, IBM_DB::SQL_AUTOCOMMIT_OFF
3280
+ end
3281
+ indexes
3282
+ end
3283
+
3284
+ def columns(table_name, name = nil)
3285
+ columns = super(table_name, name)
3286
+ # need a each modified version of below ...
3287
+ # IBM i old PTF IBM i returns 'TOAD,
3288
+ # but Arabic, Hebrew need single quote start,
3289
+ # so hack below not work (maybe add anyway?)
3290
+ # unless column_default_value.nil?
3291
+ # if column_default_value[0] == "'"
3292
+ # column_default_value = column_default_value[1..-1]
3293
+ # end
3294
+ # end
3295
+ columns
3296
+ end
3297
+
3298
+ def qcmdexc(cmd)
3299
+ occur = cmd.count("''")
3300
+ occur = occur/2
3301
+ len = cmd.length - occur
3302
+ sql = "CALL QSYS2.QCMDEXC('#{cmd}',#{len})"
3303
+ execute sql
3304
+ end
3305
+
3306
+ def qcmdexc2(cmd)
3307
+ begin
3308
+ qcmdexc(cmd)
3309
+ rescue Exception => e
3310
+ end
3311
+ end
3312
+
3313
+ def execute_and_auto_confirm(sql)
3314
+ begin
3315
+ qcmdexc2 "QSYS/CHGJOB INQMSGRPY(*SYSRPYL)"
3316
+ qcmdexc2 "ADDRPYLE SEQNBR(9876) MSGID(CPA32B2) RPY(''I'')"
3317
+ rescue Exception => e
3318
+ raise "Could not call CHGJOB INQMSGRPY(*SYSRPYL) and ADDRPYLE SEQNBR(9876) MSGID(CPA32B2) RPY('I').\n" +
3319
+ "Do you have authority to do this?\n\n" + e.to_s
3320
+ end
3321
+
3322
+ r = execute sql
3323
+
3324
+ begin
3325
+ qcmdexc2 "QSYS/CHGJOB INQMSGRPY(*DFT)'"
3326
+ qcmdexc2 "RMVRPYLE SEQNBR(9876)"
3327
+ rescue Exception => e
3328
+ raise "Could not call CHGJOB INQMSGRPY(*DFT) and RMVRPYLE SEQNBR(9876).\n" +
3329
+ "Do you have authority to do this?\n\n" + e.to_s
3330
+ end
3331
+ r
3332
+ end
3333
+
3334
+ def change_column_null(table_name, column_name, null, default)
3335
+ if null && !default.nil?
3336
+ change_column_default(table_name, column_name, default)
3337
+ end
3338
+
3339
+ if !null.nil?
3340
+ if null
3341
+ change_column_sql = "ALTER TABLE #{table_name} ALTER #{column_name} DROP NOT NULL"
3342
+ else
3343
+ change_column_sql = "ALTER TABLE #{table_name} ALTER #{column_name} SET NOT NULL with DEFAULT #{default}"
3344
+ end
3345
+ stmt = execute_and_auto_confirm (change_column_sql)
3346
+ reorg_table(table_name)
3347
+ end
3348
+
3349
+ ensure
3350
+ IBM_DB.free_stmt(stmt) if stmt
3351
+ end
3352
+
3353
+ def change_column_default(table_name, column_name, default)
3354
+ if default.nil?
3355
+ execute_and_auto_confirm "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} DROP DEFAULT"
3356
+ else
3357
+ execute_and_auto_confirm "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET WITH DEFAULT #{@adapter.quote(default)}"
3358
+ end
3359
+ reorg_table(table_name)
3360
+ end
3361
+
3362
+ def change_column(table_name, column_name, type, options = {})
3363
+ data_type = @adapter.type_to_sql(type, options[:limit], options[:precision], options[:scale])
3364
+ sql = "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DATA TYPE #{data_type}"
3365
+ execute_and_auto_confirm(sql)
3366
+ reorg_table(table_name)
3367
+
3368
+ if options.include?(:default) and options.include?(:null)
3369
+ # which to run first?
3370
+ if options[:null] or options[:default].nil?
3371
+ change_column_null(table_name, column_name, type, options[:null])
3372
+ change_column_default(table_name, column_name, options[:default])
3373
+ else
3374
+ change_column_default(table_name, column_name, options[:default])
3375
+ change_column_null(table_name, column_name, type, options[:null])
3376
+ end
3377
+ elsif options.include?(:default)
3378
+ change_column_default(table_name, column_name, options[:default])
3379
+ elsif options.include?(:null)
3380
+ change_column_null(table_name, column_name, type, options[:null])
3381
+ end
3382
+ end
3383
+
3384
+ def drop_database(dbName)
3385
+ qcmdexc2 "QSYS/CHGJOB INQMSGRPY(*SYSRPYL)"
3386
+ qcmdexc2 "ADDRPYLE SEQNBR(4321) MSGID(CPA7025) RPY(''I'')"
3387
+ isolation = IBM_DB.getoption(@adapter.connection,IBM_DB::SQL_ATTR_TXN_ISOLATION,1)
3388
+ IBM_DB.setoption(@adapter.connection,{IBM_DB::SQL_ATTR_TXN_ISOLATION=>IBM_DB::SQL_TXN_NO_COMMIT},1)
3389
+ execute "DROP COLLECTION #{dbName}"
3390
+ qcmdexc2 "QSYS/CHGJOB INQMSGRPY(*DFT)"
3391
+ qcmdexc2 "RMVRPYLE SEQNBR(4321)"
3392
+ IBM_DB.setoption(@adapter.connection,{IBM_DB::SQL_ATTR_TXN_ISOLATION=>isolation},1)
3393
+ true
3394
+ end
3395
+
3396
+ def remove_column(table_name,column_name)
3397
+ qcmdexc2 "QSYS/CHGJOB INQMSGRPY(*SYSRPYL)"
3398
+ qcmdexc2 "ADDRPYLE SEQNBR(9876) MSGID(CPA32B2) RPY(''I'')"
3399
+ super(table_name,column_name)
3400
+ qcmdexc2 "QSYS/CHGJOB INQMSGRPY(*DFT)"
3401
+ qcmdexc2 "RMVRPYLE SEQNBR(9876)"
3402
+ end
3403
+
3404
+ def create_database(dbName, codeSet=nil, mode=nil)
3405
+ execute("CREATE COLLECTION #{dbName}")
3406
+ true
3407
+ end
3408
+
3409
+ #############################
3410
+ # Unit testing (SQLLiteDatabaseTasks, MySQLDatabaseTasks, PostgreSQLDatabaseTasks)
3411
+ #############################
3412
+ def task_schema(task)
3413
+ if task.configuration.has_key?('schema')
3414
+ return task.configuration['schema']
3415
+ end
3416
+ task.configuration['username'];
3417
+ end
3418
+ def task_create(task)
3419
+ filename = task_schema(task)
3420
+ create_database filename
3421
+ task.log("CREATE SCHEMA #{filename}")
3422
+ end
3423
+ def task_drop(task)
3424
+ filename = task_schema(task)
3425
+ task_structure_dump(task,filename)
3426
+ drop_database filename
3427
+ task.log("DROP SCHEMA #{filename}")
3428
+ end
3429
+ def task_purge(task)
3430
+ begin
3431
+ task_drop(task)
3432
+ rescue Exception => e
3433
+ end
3434
+ task_create(task)
3435
+ end
3436
+ def task_charset(task)
3437
+ @adapter.connection.charset
3438
+ end
3439
+ def task_collation(task)
3440
+ @adapter.connection.collation
3441
+ end
3442
+ def task_structure_dump(task,filename=nil)
3443
+ if task.configuration.has_key?('ibm_i_dump_savf')
3444
+ if !task.configuration['ibm_i_dump_savf']
3445
+ return
3446
+ end
3447
+ end
3448
+ if filename.nil?
3449
+ filename = task_schema(task)
3450
+ end
3451
+ savlibtime = Time.now
3452
+ lastyear = Time.new(Time.now.year.to_i - 1)
3453
+ savf = "RB" + (Time.now.to_i - lastyear.to_i).to_s
3454
+ # create savlib
3455
+ begin
3456
+ cmd = "CRTSAVF FILE(QGPL/#{savf}) TEXT(''#{filename} #{savlibtime}'')"
3457
+ qcmdexc cmd
3458
+ task.log(cmd)
3459
+ rescue Exception => e
3460
+ puts e.message
3461
+ task.log("#{cmd} : #{e.message}")
3462
+ end
3463
+ # save the library
3464
+ begin
3465
+ cmd = "SAVLIB LIB(#{filename}) DEV(*SAVF) SAVF(QGPL/#{savf}) CLEAR(*ALL)"
3466
+ qcmdexc cmd
3467
+ task.log(cmd)
3468
+ rescue Exception => e
3469
+ puts e.message
3470
+ task.log("#{cmd} : #{e.message}")
3471
+ end
3472
+ end
3473
+ def task_structure_load(task,filename=nil)
3474
+ if filename.nil?
3475
+ filename = task_schema(task)
3476
+ end
3477
+ # restore the library
3478
+ begin
3479
+ cmd = "RSTLIB SAVLIB(#{filename}) DEV(*SAVF) SAVF(QGPL/#{filename}) MBROPT(*ALL) ALWOBJDIF(*ALL)"
3480
+ qcmdexc cmd
3481
+ task.log(cmd)
3482
+ rescue Exception => e
3483
+ puts e.message
3484
+ task.log("#{cmd} : #{e.message}")
3485
+ end
3486
+ end
3487
+
3488
+ end # class IBM_DB2_I5
3489
+
3490
+ class IBM_IDS < IBM_DataServer
3491
+ # IDS does not support the SET SCHEMA syntax
3492
+ def set_schema(schema)
3493
+ end
3494
+
3495
+ # IDS specific ALTER TABLE statement to rename a column
3496
+ def rename_column(table_name, column_name, new_column_name)
3497
+ _table_name = table_name.to_s
3498
+ _column_name = column_name.to_s
3499
+ _new_column_name = new_column_name.to_s
3500
+
3501
+ nil_condition = _table_name.nil? || _column_name.nil? || _new_column_name.nil?
3502
+ empty_condition = _table_name.empty? ||
3503
+ _column_name.empty? ||
3504
+ _new_column_name.empty? unless nil_condition
3505
+
3506
+ if nil_condition || empty_condition
3507
+ raise ArgumentError,"One of the arguments passed to rename_column is empty or nil"
3508
+ end
3509
+
3510
+ begin
3511
+ rename_column_sql = "RENAME COLUMN #{table_name}.#{column_name} TO \
3512
+ #{new_column_name}"
3513
+
3514
+ unless stmt = execute(rename_column_sql)
3515
+ error_msg = IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
3516
+ if error_msg && !error_msg.empty?
3517
+ raise "Rename column failed : #{error_msg}"
3518
+ else
3519
+ raise StandardError.new('An unexpected error occurred during renaming the column')
3520
+ end
3521
+ end
3522
+
3523
+ reorg_table(_table_name)
3524
+
3525
+ ensure
3526
+ IBM_DB.free_stmt(stmt) if stmt
3527
+ end #End of begin
3528
+ end # End of rename_column
3529
+
3530
+ def primary_key_definition(start_id)
3531
+ return "SERIAL(#{start_id}) PRIMARY KEY"
3532
+ end
3533
+
3534
+ def change_column(table_name, column_name, type, options)
3535
+ if !options[:null].nil? && !options[:null]
3536
+ execute "ALTER TABLE #{table_name} MODIFY #{column_name} #{@adapter.type_to_sql(type, options[:limit], options[:precision], options[:scale])} NOT NULL"
3537
+ else
3538
+ execute "ALTER TABLE #{table_name} MODIFY #{column_name} #{@adapter.type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
3539
+ end
3540
+ if !options[:default].nil?
3541
+ change_column_default(table_name, column_name, options[:default])
3542
+ end
3543
+ reorg_table(table_name)
3544
+ end
3545
+
3546
+ # IDS specific ALTER TABLE statement to add a default clause
3547
+ # IDS requires the data type to be explicitly specified when adding the
3548
+ # DEFAULT clause
3549
+ def change_column_default(table_name, column_name, default)
3550
+ sql_type = nil
3551
+ is_nullable = true
3552
+ @adapter.columns(table_name).select do |col|
3553
+ if (col.name == column_name)
3554
+ sql_type = @adapter.type_to_sql(col.type, col.limit, col.precision, col.scale)
3555
+ is_nullable = col.null
3556
+ end
3557
+ end
3558
+ # SQL statement which alters column's default value
3559
+ change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{sql_type} DEFAULT #{@adapter.quote(default)}"
3560
+ change_column_sql << " NOT NULL" unless is_nullable
3561
+ stmt = execute(change_column_sql)
3562
+ reorg_table(table_name)
3563
+ # Ensures to free the resources associated with the statement
3564
+ ensure
3565
+ IBM_DB.free_stmt(stmt) if stmt
3566
+ end
3567
+
3568
+ # IDS specific ALTER TABLE statement to change the nullability of a column
3569
+ def change_column_null(table_name,column_name,null,default)
3570
+ if !default.nil?
3571
+ change_column_default table_name, column_name, default
3572
+ end
3573
+ sql_type = nil
3574
+ @adapter.columns(table_name).select do |col|
3575
+ if (col.name == column_name)
3576
+ sql_type = @adapter.type_to_sql(col.type, col.limit, col.precision, col.scale)
3577
+ end
3578
+ end
3579
+ if !null.nil?
3580
+ if !null
3581
+ change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{sql_type} NOT NULL"
3582
+ else
3583
+ change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{sql_type}"
3584
+ end
3585
+ stmt = execute(change_column_sql)
3586
+ reorg_table(table_name)
3587
+ end
3588
+
3589
+ ensure
3590
+ IBM_DB.free_stmt(stmt) if stmt
3591
+ end
3592
+
3593
+ # Reorganizes the table for column changes
3594
+ def reorg_table(table_name)
3595
+ execute("UPDATE STATISTICS FOR TABLE #{table_name}")
3596
+ end
3597
+
3598
+ # This method returns the IDS SQL type corresponding to the Rails
3599
+ # datetime/timestamp type
3600
+ def get_datetime_mapping
3601
+ return "datetime year to fraction(5)"
3602
+ end
3603
+
3604
+ # This method returns the IDS SQL type corresponding to the Rails
3605
+ # time type
3606
+ def get_time_mapping
3607
+ return "datetime hour to second"
3608
+ end
3609
+
3610
+ # This method returns the IDS SQL type corresponding to Rails double type
3611
+ def get_double_mapping
3612
+ return "double precision"
3613
+ end
3614
+
3615
+ # Handling offset/limit as per Informix requirements
3616
+ def query_offset_limit(sql, offset, limit)
3617
+ if limit != 0
3618
+ if !offset.nil?
3619
+ # Modifying the SQL to utilize the skip and limit amounts
3620
+ sql.gsub!(/SELECT/i,"SELECT SKIP #{offset} LIMIT #{limit}")
3621
+ else
3622
+ # Modifying the SQL to retrieve only the first #{limit} rows
3623
+ sql = sql.gsub!("SELECT","SELECT FIRST #{limit}")
3624
+ end
3625
+ else
3626
+ # Modifying the SQL to ensure that no rows will be returned
3627
+ sql.gsub!(/SELECT/i,"SELECT * FROM (SELECT")
3628
+ sql << ") WHERE 0 = 1"
3629
+ end
3630
+ end
3631
+
3632
+ # Handling offset/limit as per Informix requirements
3633
+ def query_offset_limit!(sql, offset, limit, options)
3634
+ if limit != 0
3635
+ if !offset.nil?
3636
+ # Modifying the SQL to utilize the skip and limit amounts
3637
+ sql.gsub!(/SELECT/i,"SELECT SKIP #{offset} LIMIT #{limit}")
3638
+ else
3639
+ # Modifying the SQL to retrieve only the first #{limit} rows
3640
+ sql = sql.gsub!("SELECT","SELECT FIRST #{limit}")
3641
+ end
3642
+ else
3643
+ # Modifying the SQL to ensure that no rows will be returned
3644
+ sql.gsub!(/SELECT/i,"SELECT * FROM (SELECT")
3645
+ sql << ") WHERE 0 = 1"
3646
+ end
3647
+ end
3648
+
3649
+ # Method that returns the last automatically generated ID
3650
+ # on the given +@connection+. This method is required by the +insert+
3651
+ # method. IDS returns the last generated serial value in the SQLCA unlike
3652
+ # DB2 where the generated value has to be retrieved using the
3653
+ # IDENTITY_VAL_LOCAL function. We used the "stmt" parameter to identify
3654
+ # the statement resource from which to get the last generated value
3655
+ def last_generated_id(stmt)
3656
+ IBM_DB.get_last_serial_value(stmt)
3657
+ end
3658
+
3659
+ # This method throws an error when trying to create a default value on a
3660
+ # BLOB/CLOB column for IDS. The documentation states: "if the column is a
3661
+ # BLOB or CLOB datatype, NULL is the only valid default value."
3662
+ def set_binary_default(value)
3663
+ unless (value == 'NULL')
3664
+ raise "Informix Dynamic Server only allows NULL as a valid default value for a BLOB data type"
3665
+ end
3666
+ end
3667
+
3668
+ # For Informix Dynamic Server, we treat binary value same as we treat a
3669
+ # text value. We support literals by converting the insert into a dummy
3670
+ # insert and an update (See handle_lobs method above)
3671
+ def set_binary_value
3672
+ "'@@@IBMBINARY@@@'"
3673
+ end
3674
+
3675
+ # This method throws an error when trying to create a default value on a
3676
+ # BLOB/CLOB column for IDS. The documentation states: "if the column is
3677
+ # a BLOB or CLOB datatype, NULL is the only valid default value."
3678
+ def set_text_default(value)
3679
+ unless (value == 'NULL')
3680
+ raise "Informix Dynamic Server only allows NULL as a valid default value for a CLOB data type"
3681
+ end
3682
+ end
3683
+
3684
+ # For Informix Dynamic Server, the arguments to the meta-data functions
3685
+ # need to be in lower-case
3686
+ def set_case(value)
3687
+ value.downcase
3688
+ end
3689
+ end # class IBM_IDS
3690
+
3691
+ end # module ConnectionAdapters
3692
+
3693
+ # T025: format on timestamp
3694
+ class XmlSerializer
3695
+ class Attribute
3696
+ alias_method :orig_compute_type, :compute_type
3697
+ def compute_type
3698
+ type = orig_compute_type
3699
+ { timestamp: :datetime }[type] || type
3700
+ end
3701
+ end
3702
+ end
3703
+ end # module ActiveRecord
3704
+
3705
+ module Arel
3706
+ module Visitors
3707
+ class Visitor #opening and closing the class to ensure backward compatibility
3708
+ end
3709
+
3710
+ class ToSql < Arel::Visitors::Visitor #opening and closing the class to ensure backward compatibility
3711
+ # In case when using Rails-2.3.x there is no arel used due to which the constructor has to be defined explicitly
3712
+ # to ensure the same code works on any version of Rails
3713
+
3714
+ #Check Arel version
3715
+ begin
3716
+ @arelVersion = Arel::VERSION.to_i
3717
+ rescue
3718
+ @arelVersion = 0
3719
+ end
3720
+
3721
+ if(@arelVersion >= 3)
3722
+ def initialize connection
3723
+ @connection = connection
3724
+ @schema_cache = connection.schema_cache if(connection.respond_to?(:schema_cache))
3725
+ @quoted_tables = {}
3726
+ @quoted_columns = {}
3727
+ @last_column = nil
3728
+ end
3729
+ end
3730
+ end
3731
+
3732
+ class IBM_DB < Arel::Visitors::ToSql
3733
+ private
3734
+
3735
+ def visit_Arel_Nodes_Limit o, a=nil
3736
+ visit o.expr
3737
+ end
3738
+
3739
+ def visit_Arel_Nodes_Offset o, a=nil
3740
+ visit o.expr
3741
+ end
3742
+
3743
+ def visit_Arel_Nodes_SelectStatement o, a=nil
3744
+ #Interim fix for backward compatibility [Arel 4.0.0 and below]
3745
+ if self.method(:visit_Arel_Nodes_SelectCore).arity == 1
3746
+ sql = [
3747
+ (visit(o.with) if o.with),
3748
+ o.cores.map { |x| visit_Arel_Nodes_SelectCore x }.join,
3749
+ ("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
3750
+ ].compact.join ' '
3751
+ else
3752
+ sql = [
3753
+ (visit(o.with) if o.with),
3754
+ o.cores.map { |x| visit_Arel_Nodes_SelectCore x,a }.join,
3755
+ ("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
3756
+ ].compact.join ' '
3757
+ end
3758
+
3759
+ if o.limit
3760
+ limit = visit(o.limit)
3761
+ else
3762
+ limit = nil
3763
+ end
3764
+
3765
+ if o.offset
3766
+ offset = visit(o.offset)
3767
+ else
3768
+ offset = nil
3769
+ end
3770
+ sql = @connection.add_limit_offset!(sql, {:limit => limit, :offset => offset})
3771
+ if o.lock
3772
+ sql << " #{visit(o.lock)}"
3773
+ if @connection.instance_variable_get('@servertype').class.name =~ /IBM_DB2_I5/ && sql[/FETCH FIRST \d* ROWS ONLY/]
3774
+ raise(ActiveRecord::ActiveRecordError, "DB2 for IBM i does not support lock with find or first") # T012
3775
+ end
3776
+ end
3777
+ return sql
3778
+ end
3779
+
3780
+ end
3781
+ end
3782
+ end