ibm_db 2.5.14-x86-linux

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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +210 -0
  3. data/LICENSE +18 -0
  4. data/MANIFEST +14 -0
  5. data/ParameterizedQueries README +39 -0
  6. data/README +225 -0
  7. data/ext/Makefile.nt32 +181 -0
  8. data/ext/Makefile.nt32.191 +212 -0
  9. data/ext/extconf.rb +127 -0
  10. data/ext/ibm_db.c +11719 -0
  11. data/ext/ruby_ibm_db.h +240 -0
  12. data/ext/ruby_ibm_db_cli.c +845 -0
  13. data/ext/ruby_ibm_db_cli.h +489 -0
  14. data/init.rb +42 -0
  15. data/lib/IBM_DB.rb +2 -0
  16. data/lib/active_record/connection_adapters/ibm_db_adapter.rb +3020 -0
  17. data/lib/active_record/connection_adapters/ibm_db_pstmt.rb +1965 -0
  18. data/lib/active_record/connection_adapters/ibmdb_adapter.rb +2 -0
  19. data/lib/active_record/vendor/db2-i5-zOS.yaml +328 -0
  20. data/lib/ibm_db.rb +8 -0
  21. data/lib/linux/rb18x/ibm_db.so +0 -0
  22. data/lib/linux/rb19x/ibm_db.so +0 -0
  23. data/lib/linux/rb2x/ibm_db.so +0 -0
  24. data/test/cases/adapter_test.rb +207 -0
  25. data/test/cases/associations/belongs_to_associations_test.rb +711 -0
  26. data/test/cases/associations/cascaded_eager_loading_test.rb +181 -0
  27. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +851 -0
  28. data/test/cases/associations/join_model_test.rb +743 -0
  29. data/test/cases/attribute_methods_test.rb +822 -0
  30. data/test/cases/base_test.rb +2133 -0
  31. data/test/cases/calculations_test.rb +482 -0
  32. data/test/cases/migration_test.rb +2408 -0
  33. data/test/cases/persistence_test.rb +642 -0
  34. data/test/cases/query_cache_test.rb +257 -0
  35. data/test/cases/relations_test.rb +1182 -0
  36. data/test/cases/schema_dumper_test.rb +256 -0
  37. data/test/cases/transaction_callbacks_test.rb +300 -0
  38. data/test/cases/validations/uniqueness_validation_test.rb +299 -0
  39. data/test/cases/xml_serialization_test.rb +408 -0
  40. data/test/config.yml +154 -0
  41. data/test/connections/native_ibm_db/connection.rb +44 -0
  42. data/test/ibm_db_test.rb +25 -0
  43. data/test/models/warehouse_thing.rb +5 -0
  44. data/test/schema/i5/ibm_db_specific_schema.rb +137 -0
  45. data/test/schema/ids/ibm_db_specific_schema.rb +140 -0
  46. data/test/schema/luw/ibm_db_specific_schema.rb +137 -0
  47. data/test/schema/schema.rb +751 -0
  48. data/test/schema/zOS/ibm_db_specific_schema.rb +208 -0
  49. metadata +109 -0
data/init.rb ADDED
@@ -0,0 +1,42 @@
1
+ # +----------------------------------------------------------------------+
2
+ # | Licensed Materials - Property of IBM |
3
+ # | |
4
+ # | (C) Copyright IBM Corporation 2006, 2007. |
5
+ # +----------------------------------------------------------------------+
6
+
7
+ require 'pathname'
8
+
9
+ begin
10
+ puts ".. Attempt to load IBM_DB Ruby driver for IBM Data Servers for this platform: #{RUBY_PLATFORM}"
11
+ unless defined? IBM_DB
12
+ # find IBM_DB driver path relative init.rb
13
+ drv_path = Pathname.new(File.dirname(__FILE__)) + 'lib'
14
+ drv_path += (RUBY_PLATFORM =~ /mswin32/) ? 'mswin32' : 'linux32'
15
+ puts ".. Locate IBM_DB Ruby driver path: #{drv_path}"
16
+ drv_lib = drv_path + 'ibm_db.so'
17
+ require "#{drv_lib.to_s}"
18
+ puts ".. Successfuly loaded IBM_DB Ruby driver: #{drv_lib}"
19
+ end
20
+ rescue
21
+ raise LoadError, "Failed to load IBM_DB Driver !?"
22
+ end
23
+
24
+ # Include IBM_DB in the list of supported adapters
25
+ RAILS_CONNECTION_ADAPTERS << 'ibm_db'
26
+ # load IBM_DB Adapter provided by the plugin
27
+ require 'active_record/connection_adapters/ibm_db_adapter'
28
+
29
+ # Override the frameworks initialization to re-enable ActiveRecord after being
30
+ # disabled during plugin install (i.e. config.frameworks -= [ :active_record ])
31
+ [:load_environment,\
32
+ :initialize_database,\
33
+ :initialize_logger,\
34
+ :initialize_framework_logging,\
35
+ :initialize_framework_settings,\
36
+ :initialize_framework_views,\
37
+ :initialize_dependency_mechanism,\
38
+ :load_environment ].each do |routine|
39
+ Rails::Initializer.run(routine) do |config|
40
+ config.frameworks = [:active_record]
41
+ end
42
+ end
data/lib/IBM_DB.rb ADDED
@@ -0,0 +1,2 @@
1
+ require (RUBY_PLATFORM =~ /mswin32/ || RUBY_PLATFORM =~ /mingw32/ ) ? 'mswin32/ibm_db' : 'ibm_db.so'
2
+ require 'active_record/connection_adapters/ibm_db_adapter'
@@ -0,0 +1,3020 @@
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
+ # +----------------------------------------------------------------------+
10
+
11
+ require 'active_record/connection_adapters/abstract_adapter'
12
+ require 'arel/visitors/bind_visitor'
13
+
14
+ module ActiveRecord
15
+ class Relation
16
+ def insert(values)
17
+ primary_key_value = nil
18
+
19
+ if primary_key && Hash === values
20
+ primary_key_value = values[values.keys.find { |k|
21
+ k.name == primary_key
22
+ }]
23
+
24
+ if !primary_key_value && connection.prefetch_primary_key?(klass.table_name)
25
+ primary_key_value = connection.next_sequence_value(klass.sequence_name)
26
+ values[klass.arel_table[klass.primary_key]] = primary_key_value
27
+ end
28
+ end
29
+
30
+ im = arel.create_insert
31
+ im.into @table
32
+
33
+ conn = @klass.connection
34
+
35
+ substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
36
+ binds = substitutes.map do |arel_attr, value|
37
+ [@klass.columns_hash[arel_attr.name], value]
38
+ end
39
+
40
+ substitutes.each_with_index do |tuple, i|
41
+ tuple[1] = conn.substitute_at(binds[i][0], i)
42
+ end
43
+
44
+ if values.empty? # empty insert
45
+ im.values = Arel.sql(connection.empty_insert_statement_value(klass.primary_key))
46
+ else
47
+ im.insert substitutes
48
+ end
49
+
50
+ conn.insert(
51
+ im,
52
+ 'SQL',
53
+ primary_key,
54
+ primary_key_value,
55
+ nil,
56
+ binds)
57
+ end
58
+ end
59
+
60
+ class Base
61
+ # Method required to handle LOBs and XML fields.
62
+ # An after save callback checks if a marker has been inserted through
63
+ # the insert or update, and then proceeds to update that record with
64
+ # the actual large object through a prepared statement (param binding).
65
+ after_save :handle_lobs
66
+ def handle_lobs()
67
+ if connection.kind_of?(ConnectionAdapters::IBM_DBAdapter)
68
+ # Checks that the insert or update had at least a BLOB, CLOB or XML field
69
+ connection.sql.each do |clob_sql|
70
+ if clob_sql =~ /BLOB\('(.*)'\)/i ||
71
+ clob_sql =~ /@@@IBMTEXT@@@/i ||
72
+ clob_sql =~ /@@@IBMXML@@@/i ||
73
+ clob_sql =~ /@@@IBMBINARY@@@/i
74
+ update_query = "UPDATE #{self.class.table_name} SET ("
75
+ counter = 0
76
+ values = []
77
+ params = []
78
+ # Selects only binary, text and xml columns
79
+ self.class.columns.select{|col| col.type == :binary ||
80
+ col.type == :text ||
81
+ col.type == :xml}.each do |col|
82
+ # Adds the selected columns to the query
83
+ if counter == 0
84
+ update_query << "#{col.name}"
85
+ else
86
+ update_query << ",#{col.name}"
87
+ end
88
+
89
+ # Add a '?' for the parameter or a NULL if the value is nil or empty
90
+ # (except for a CLOB field where '' can be a value)
91
+ if self[col.name].nil? ||
92
+ self[col.name] == {} ||
93
+ self[col.name] == [] ||
94
+ (self[col.name] == '' && col.type != :text)
95
+ params << 'NULL'
96
+ else
97
+ if self.class.serialized_attributes[col.name]
98
+ values << YAML.dump(self[col.name])
99
+ else
100
+ values << self[col.name]
101
+ end
102
+ params << '?'
103
+ end
104
+ counter += 1
105
+ end
106
+ # no subsequent update is required if no relevant columns are found
107
+ next if counter == 0
108
+
109
+ update_query << ") = "
110
+ # IBM_DB accepts 'SET (column) = NULL' but not (NULL),
111
+ # therefore the sql needs to be changed for a single NULL field.
112
+ if params.size==1 && params[0] == 'NULL'
113
+ update_query << "NULL"
114
+ else
115
+ update_query << "(" + params.join(',') + ")"
116
+ end
117
+
118
+ update_query << " WHERE #{self.class.primary_key} = ?"
119
+ values << self[self.class.primary_key.downcase]
120
+
121
+ begin
122
+ unless stmt = IBM_DB.prepare(connection.connection, update_query)
123
+ error_msg = IBM_DB.getErrormsg( connection.connection, IBM_DB::DB_CONN )
124
+ if error_msg && !error_msg.empty?
125
+ raise "Statement prepare for updating LOB/XML column failed : #{error_msg}"
126
+ else
127
+ raise StandardError.new('An unexpected error occurred during update of LOB/XML column')
128
+ end
129
+ end
130
+ connection.log_query(update_query,'update of LOB/XML field(s)in handle_lobs')
131
+
132
+ # rollback any failed LOB/XML field updates (and remove associated marker)
133
+ unless IBM_DB.execute(stmt, values)
134
+ error_msg = "Failed to insert/update LOB/XML field(s) due to: #{IBM_DB.getErrormsg( stmt, IBM_DB::DB_STMT )}"
135
+ connection.execute("ROLLBACK")
136
+ raise error_msg
137
+ end
138
+ rescue StandardError => error
139
+ raise error
140
+ ensure
141
+ IBM_DB.free_stmt(stmt) if stmt
142
+ end
143
+ end # if clob_sql
144
+ end #connection.sql.each
145
+ connection.handle_lobs_triggered = true
146
+ end # if connection.kind_of?
147
+ end # handle_lobs
148
+ private :handle_lobs
149
+
150
+ # Establishes a connection to a specified database using the credentials provided
151
+ # with the +config+ argument. All the ActiveRecord objects will use this connection
152
+ def self.ibm_db_connection(config)
153
+ # Attempts to load the Ruby driver IBM databases
154
+ # while not already loaded or raises LoadError in case of failure.
155
+ begin
156
+ require 'ibm_db' unless defined? IBM_DB
157
+ rescue LoadError
158
+ raise LoadError, "Failed to load IBM_DB Ruby driver."
159
+ end
160
+
161
+ if( config.has_key?(:parameterized) && config[:parameterized] == true )
162
+ require 'active_record/connection_adapters/ibm_db_pstmt'
163
+ end
164
+
165
+ # Check if class TableDefinition responds to indexes method to determine if we are on AR 3 or AR 4.
166
+ # This is a interim hack ti ensure backward compatibility. To remove as we move out of AR 3 support or have a better way to determine which version of AR being run against.
167
+ checkClass = ActiveRecord::ConnectionAdapters::TableDefinition.new(nil)
168
+ if(checkClass.respond_to?(:indexes))
169
+ isAr3 = false
170
+ else
171
+ isAr3 = true
172
+ end
173
+ # Converts all +config+ keys to symbols
174
+ config = config.symbolize_keys
175
+
176
+ # Flag to decide if quoted literal replcement should take place. By default it is ON. Set it to OFF if using Pstmt
177
+ set_quoted_literal_replacement = IBM_DB::QUOTED_LITERAL_REPLACEMENT_ON
178
+
179
+ # Retrieves database user credentials from the +config+ hash
180
+ # or raises ArgumentError in case of failure.
181
+ if !config.has_key?(:username) || !config.has_key?(:password)
182
+ raise ArgumentError, "Missing argument(s): Username/Password for #{config[:database]} is not specified"
183
+ else
184
+ if(config[:username].to_s.nil? || config[:password].to_s.nil?)
185
+ raise ArgumentError, "Username/Password cannot be nil"
186
+ end
187
+ username = config[:username].to_s
188
+ password = config[:password].to_s
189
+ end
190
+
191
+ if(config.has_key?(:dbops) && config[:dbops] == true)
192
+ return ConnectionAdapters::IBM_DBAdapter.new(nil, isAr3, logger, config, {})
193
+ end
194
+
195
+ # Retrieves the database alias (local catalog name) or remote name
196
+ # (for remote TCP/IP connections) from the +config+ hash
197
+ # or raises ArgumentError in case of failure.
198
+ if config.has_key?(:database)
199
+ database = config[:database].to_s
200
+ else
201
+ raise ArgumentError, "Missing argument: a database name needs to be specified."
202
+ end
203
+
204
+ # Providing default schema (username) when not specified
205
+ config[:schema] = config.has_key?(:schema) ? config[:schema].to_s : config[:username].to_s
206
+
207
+ if(config.has_key?(:parameterized) && config[:parameterized] == true )
208
+ set_quoted_literal_replacement = IBM_DB::QUOTED_LITERAL_REPLACEMENT_OFF
209
+ end
210
+
211
+ # Extract connection options from the database configuration
212
+ # (in support to formatting, audit and billing purposes):
213
+ # Retrieve database objects fields in lowercase
214
+ conn_options = {IBM_DB::ATTR_CASE => IBM_DB::CASE_LOWER}
215
+ config.each do |key, value|
216
+ if !value.nil?
217
+ case key
218
+ when :app_user # Set connection's user info
219
+ conn_options[IBM_DB::SQL_ATTR_INFO_USERID] = value
220
+ when :account # Set connection's account info
221
+ conn_options[IBM_DB::SQL_ATTR_INFO_ACCTSTR] = value
222
+ when :application # Set connection's application info
223
+ conn_options[IBM_DB::SQL_ATTR_INFO_APPLNAME] = value
224
+ when :workstation # Set connection's workstation info
225
+ conn_options[IBM_DB::SQL_ATTR_INFO_WRKSTNNAME] = value
226
+ end
227
+ end
228
+ end
229
+
230
+ begin
231
+ # Checks if a host name or address has been specified. If so, this implies a TCP/IP connection
232
+ # Returns IBM_DB.Connection object upon succesful DB connection to the database
233
+ # If otherwise the connection fails, +false+ is returned
234
+ if config.has_key?(:host)
235
+ # Retrieves the host address/name
236
+ host = config[:host]
237
+ # A net address connection requires a port. If no port has been specified, 50000 is used by default
238
+ port = config[:port] || 50000
239
+ # Connects to the database specified using the hostname, port, authentication type, username and password info
240
+ # Starting with DB2 9.1FP5 secure connections using SSL are supported.
241
+ # On the client side using CLI this is supported from CLI version V95FP2 and onwards.
242
+ # This feature is set by specifying SECURITY=SSL in the connection string.
243
+ # Below connection string is constructed and SECURITY parameter is appended if the user has specified the :security option
244
+ conn_string = "DRIVER={IBM DB2 ODBC DRIVER};\
245
+ DATABASE=#{database};\
246
+ HOSTNAME=#{host};\
247
+ PORT=#{port};\
248
+ PROTOCOL=TCPIP;\
249
+ UID=#{username};\
250
+ PWD=#{password};"
251
+ conn_string << "SECURITY=#{config[:security]};" if config.has_key?(:security)
252
+ conn_string << "AUTHENTICATION=#{config[:authentication]};" if config.has_key?(:authentication)
253
+ conn_string << "CONNECTTIMEOUT=#{config[:timeout]};" if config.has_key?(:timeout)
254
+
255
+ connection = IBM_DB.connect( conn_string, '', '', conn_options, set_quoted_literal_replacement )
256
+ else
257
+ # No host implies a local catalog-based connection: +database+ represents catalog alias
258
+ connection = IBM_DB.connect( database, username, password, conn_options, set_quoted_literal_replacement )
259
+ end
260
+ rescue StandardError => connect_err
261
+ raise "Failed to connect to [#{database}] due to: #{connect_err}"
262
+ end
263
+ # Verifies that the connection was successful
264
+ if connection
265
+ # Creates an instance of *IBM_DBAdapter* based on the +connection+
266
+ # and credentials provided in +config+
267
+ ConnectionAdapters::IBM_DBAdapter.new(connection, isAr3, logger, config, conn_options)
268
+ else
269
+ # If the connection failure was not caught previoulsy, it raises a Runtime error
270
+ raise "An unexpected error occured during connect attempt to [#{database}]"
271
+ end
272
+ end # method self.ibm_db_connection
273
+
274
+ def self.ibmdb_connection(config)
275
+ #Method to support alising of adapter name as ibmdb [without underscore]
276
+ self.ibm_db_connection(config)
277
+ end
278
+ end # class Base
279
+
280
+ module ConnectionAdapters
281
+ module SchemaStatements
282
+ def create_table_definition(name, temporary, options)
283
+ TableDefinition.new self, name, temporary, options
284
+ end
285
+ end
286
+ class IBM_DBColumn < Column
287
+
288
+ # Casts value (which is a String) to an appropriate instance
289
+ def type_cast(value)
290
+ # Casts the database NULL value to nil
291
+ return nil if value == 'NULL'
292
+ # Invokes parent's method for default casts
293
+ super
294
+ end
295
+
296
+ # Used to convert from BLOBs to Strings
297
+ def self.binary_to_string(value)
298
+ # Returns a string removing the eventual BLOB scalar function
299
+ value.to_s.gsub(/"SYSIBM"."BLOB"\('(.*)'\)/i,'\1')
300
+ end
301
+
302
+ private
303
+ # Mapping IBM data servers SQL datatypes to Ruby data types
304
+ def simplified_type(field_type)
305
+ case field_type
306
+ # if +field_type+ contains 'for bit data' handle it as a binary
307
+ when /for bit data/i
308
+ :binary
309
+ when /smallint/i
310
+ :boolean
311
+ when /int|serial/i
312
+ :integer
313
+ when /decimal|numeric|decfloat/i
314
+ :decimal
315
+ when /float|double|real/i
316
+ :float
317
+ when /timestamp|datetime/i
318
+ :timestamp
319
+ when /time/i
320
+ :time
321
+ when /date/i
322
+ :date
323
+ when /vargraphic/i
324
+ :vargraphic
325
+ when /graphic/i
326
+ :graphic
327
+ when /clob|text/i
328
+ :text
329
+ when /xml/i
330
+ :xml
331
+ when /blob|binary/i
332
+ :binary
333
+ when /char/i
334
+ :string
335
+ when /boolean/i
336
+ :boolean
337
+ when /rowid/i # rowid is a supported datatype on z/OS and i/5
338
+ :rowid
339
+ end
340
+ end # method simplified_type
341
+ end #class IBM_DBColumn
342
+
343
+ class Table
344
+
345
+ #Method to parse the passed arguments and create the ColumnDefinition object of the specified type
346
+ def ibm_parse_column_attributes_args(type, *args)
347
+ options = {}
348
+ if args.last.is_a?(Hash)
349
+ options = args.delete_at(args.length-1)
350
+ end
351
+ args.each do | name |
352
+ column name,type.to_sym,options
353
+ end # end args.each
354
+ end
355
+ private :ibm_parse_column_attributes_args
356
+
357
+ #Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type xml
358
+ #This method is different as compared to def char (sql is being issued explicitly
359
+ #as compared to def char where method column(which will generate the sql is being called)
360
+ #in order to handle the DEFAULT and NULL option for the native XML datatype
361
+ def xml(*args )
362
+ options = {}
363
+ if args.last.is_a?(Hash)
364
+ options = args.delete_at(args.length-1)
365
+ end
366
+ sql_segment = "ALTER TABLE #{@base.quote_table_name(@table_name)} ADD COLUMN "
367
+ args.each do | name |
368
+ sql = sql_segment + " #{@base.quote_column_name(name)} xml"
369
+ @base.execute(sql,"add_xml_column")
370
+ end
371
+ return self
372
+ end
373
+
374
+ #Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type double
375
+ def double(*args)
376
+ ibm_parse_column_attributes_args('double',*args)
377
+ return self
378
+ end
379
+
380
+ #Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type decfloat
381
+ def decfloat(*args)
382
+ ibm_parse_column_attributes_args('decfloat',*args)
383
+ return self
384
+ end
385
+
386
+ def graphic(*args)
387
+ ibm_parse_column_attributes_args('graphic',*args)
388
+ return self
389
+ end
390
+
391
+ def vargraphic(*args)
392
+ ibm_parse_column_attributes_args('vargraphic',*args)
393
+ return self
394
+ end
395
+
396
+ def bigint(*args)
397
+ ibm_parse_column_attributes_args('bigint',*args)
398
+ return self
399
+ end
400
+
401
+ #Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type char [character]
402
+ def char(*args)
403
+ ibm_parse_column_attributes_args('char',*args)
404
+ return self
405
+ end
406
+ alias_method :character, :char
407
+ end
408
+
409
+ class TableDefinition
410
+
411
+ def initialize(base, name=nil, temporary=nil, options=nil)
412
+ if(self.respond_to?(:indexes))
413
+ @ar3 = false
414
+ else
415
+ @ar3 = true
416
+ end
417
+ @columns = []
418
+ @columns_hash = {}
419
+ @indexes = {}
420
+ @base = base
421
+ @temporary = temporary
422
+ @options = options
423
+ @name = name
424
+ end
425
+
426
+ def native
427
+ @base.native_database_types
428
+ end
429
+
430
+ #Method to parse the passed arguments and create the ColumnDefinition object of the specified type
431
+ def ibm_parse_column_attributes_args(type, *args)
432
+ options = {}
433
+ if args.last.is_a?(Hash)
434
+ options = args.delete_at(args.length-1)
435
+ end
436
+ args.each do | name |
437
+ column(name,type,options)
438
+ end
439
+ end
440
+ private :ibm_parse_column_attributes_args
441
+
442
+ #Method to support the new syntax of rails 2.0 migrations for columns of type xml
443
+ def xml(*args )
444
+ ibm_parse_column_attributes_args('xml', *args)
445
+ return self
446
+ end
447
+
448
+ #Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type double
449
+ def double(*args)
450
+ ibm_parse_column_attributes_args('double',*args)
451
+ return self
452
+ end
453
+
454
+ #Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type decfloat
455
+ def decfloat(*args)
456
+ ibm_parse_column_attributes_args('decfloat',*args)
457
+ return self
458
+ end
459
+
460
+ def graphic(*args)
461
+ ibm_parse_column_attributes_args('graphic',*args)
462
+ return self
463
+ end
464
+
465
+ def vargraphic(*args)
466
+ ibm_parse_column_attributes_args('vargraphic',*args)
467
+ return self
468
+ end
469
+
470
+ def bigint(*args)
471
+ ibm_parse_column_attributes_args('bigint',*args)
472
+ return self
473
+ end
474
+
475
+ #Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type char [character]
476
+ def char(*args)
477
+ ibm_parse_column_attributes_args('char',*args)
478
+ return self
479
+ end
480
+ alias_method :character, :char
481
+
482
+ # Overrides the abstract adapter in order to handle
483
+ # the DEFAULT option for the native XML datatype
484
+ def column(name, type, options ={})
485
+ # construct a column definition where @base is adaptor instance
486
+ if(@ar3)
487
+ column = ColumnDefinition.new(@base, name, type)
488
+ else
489
+ column = ColumnDefinition.new(name, type)
490
+ end
491
+ # DB2 does not accept DEFAULT NULL option for XML
492
+ # for table create, but does accept nullable option
493
+ unless type.to_s == 'xml'
494
+ column.null = options[:null]
495
+ column.default = options[:default]
496
+ else
497
+ column.null = options[:null]
498
+ # Override column object's (instance of ColumnDefinition structure)
499
+ # to_s which is expected to return the create_table SQL fragment
500
+ # and bypass DEFAULT NULL option while still appending NOT NULL
501
+ def column.to_s
502
+ sql = "#{base.quote_column_name(name)} #{type}"
503
+ unless self.null == nil
504
+ sql << " NOT NULL" if (self.null == false)
505
+ end
506
+ return sql
507
+ end
508
+ end
509
+
510
+ column.scale = options[:scale] if options[:scale]
511
+ column.precision = options[:precision] if options[:precision]
512
+ # append column's limit option and yield native limits
513
+ if options[:limit]
514
+ column.limit = options[:limit]
515
+ elsif @base.native_database_types[type.to_sym]
516
+ column.limit = @base.native_database_types[type.to_sym][:limit] if @base.native_database_types[type.to_sym].has_key? :limit
517
+ end
518
+
519
+ unless @columns.nil? or @columns.include? column
520
+ @columns << column
521
+ end
522
+
523
+ @columns_hash[name] = column
524
+
525
+ return self
526
+ end
527
+ end
528
+
529
+ # The IBM_DB Adapter requires the native Ruby driver (ibm_db)
530
+ # for IBM data servers (ibm_db.so).
531
+ # +config+ the hash passed as an initializer argument content:
532
+ # == mandatory parameters
533
+ # adapter: 'ibm_db' // IBM_DB Adapter name
534
+ # username: 'db2user' // data server (database) user
535
+ # password: 'secret' // data server (database) password
536
+ # database: 'ARUNIT' // remote database name (or catalog entry alias)
537
+ # == optional (highly recommended for data server auditing and monitoring purposes)
538
+ # schema: 'rails123' // name space qualifier
539
+ # account: 'tester' // OS account (client workstation)
540
+ # app_user: 'test11' // authenticated application user
541
+ # application: 'rtests' // application name
542
+ # workstation: 'plato' // client workstation name
543
+ # == remote TCP/IP connection (required when no local database catalog entry available)
544
+ # host: 'socrates' // fully qualified hostname or IP address
545
+ # port: '50000' // data server TCP/IP port number
546
+ # security: 'SSL' // optional parameter enabling SSL encryption -
547
+ # // - Available only from CLI version V95fp2 and above
548
+ # authentication: 'SERVER' // AUTHENTICATION type which the client uses -
549
+ # // - to connect to the database server. By default value is SERVER
550
+ # timeout: 10 // Specifies the time in seconds (0 - 32767) to wait for a reply from server -
551
+ # //- when trying to establish a connection before generating a timeout
552
+ # == Parameterized Queries Support
553
+ # parameterized: false // Specifies if the prepared statement support of
554
+ # //- the IBM_DB Adapter is to be turned on or off
555
+ #
556
+ # When schema is not specified, the username value is used instead.
557
+ # The default setting of parameterized is false.
558
+ #
559
+ class IBM_DBAdapter < AbstractAdapter
560
+ attr_reader :connection, :servertype
561
+ attr_accessor :sql,:handle_lobs_triggered, :sql_parameter_values
562
+ attr_reader :schema, :app_user, :account, :application, :workstation
563
+ attr_reader :pstmt_support_on, :set_quoted_literal_replacement
564
+
565
+ # Name of the adapter
566
+ def adapter_name
567
+ 'IBM_DB'
568
+ end
569
+
570
+ class BindSubstitution < Arel::Visitors::IBM_DB # :nodoc:
571
+ include Arel::Visitors::BindVisitor
572
+ end
573
+
574
+ def initialize(connection, ar3, logger, config, conn_options)
575
+ # Caching database connection configuration (+connect+ or +reconnect+ support)
576
+ @connection = connection
577
+ @isAr3 = ar3
578
+ @conn_options = conn_options
579
+ @database = config[:database]
580
+ @username = config[:username]
581
+ @password = config[:password]
582
+ if config.has_key?(:host)
583
+ @host = config[:host]
584
+ @port = config[:port] || 50000 # default port
585
+ end
586
+ @schema = config[:schema]
587
+ @security = config[:security] || nil
588
+ @authentication = config[:authentication] || nil
589
+ @timeout = config[:timeout] || 0 # default timeout value is 0
590
+
591
+ if( config.has_key?(:parameterized) && config[:parameterized] == true )
592
+ @pstmt_support_on = true
593
+ @set_quoted_literal_replacement = IBM_DB::QUOTED_LITERAL_REPLACEMENT_OFF
594
+ else
595
+ @pstmt_support_on = false
596
+ @set_quoted_literal_replacement = IBM_DB::QUOTED_LITERAL_REPLACEMENT_ON
597
+ end
598
+
599
+ @app_user = @account = @application = @workstation = nil
600
+ # Caching database connection options (auditing and billing support)
601
+ @app_user = conn_options[:app_user] if conn_options.has_key?(:app_user)
602
+ @account = conn_options[:account] if conn_options.has_key?(:account)
603
+ @application = conn_options[:application] if conn_options.has_key?(:application)
604
+ @workstation = conn_options[:workstation] if conn_options.has_key?(:workstation)
605
+
606
+ @sql = []
607
+ @sql_parameter_values = [] #Used only if pstmt support is turned on
608
+
609
+ @handle_lobs_triggered = false
610
+
611
+ # Calls the parent class +ConnectionAdapters+' initializer
612
+ # which sets @connection, @logger, @runtime and @last_verification
613
+ super(@connection, logger)
614
+
615
+ if @connection
616
+ server_info = IBM_DB.server_info( @connection )
617
+ if( server_info )
618
+ case server_info.DBMS_NAME
619
+ when /DB2\//i # DB2 for Linux, Unix and Windows (LUW)
620
+ case server_info.DBMS_VER
621
+ when /09.07/i # DB2 Version 9.7 (Cobra)
622
+ @servertype = IBM_DB2_LUW_COBRA.new(self, isAr3)
623
+ else # DB2 Version 9.5 or below
624
+ @servertype = IBM_DB2_LUW.new(self, @isAr3)
625
+ end
626
+ when /DB2/i # DB2 for zOS
627
+ case server_info.DBMS_VER
628
+ when /09/ # DB2 for zOS version 9 and version 10
629
+ @servertype = IBM_DB2_ZOS.new(self, @isAr3)
630
+ when /10/
631
+ @servertype = IBM_DB2_ZOS.new(self, @isAr3)
632
+ when /08/ # DB2 for zOS version 8
633
+ @servertype = IBM_DB2_ZOS_8.new(self, @isAr3)
634
+ else # DB2 for zOS version 7
635
+ raise "Only DB2 z/OS version 8 and above are currently supported"
636
+ end
637
+ when /AS/i # DB2 for i5 (iSeries)
638
+ @servertype = IBM_DB2_I5.new(self, @isAr3)
639
+ when /IDS/i # Informix Dynamic Server
640
+ @servertype = IBM_IDS.new(self, @isAr3)
641
+ else
642
+ log( "server_info", "Forcing servertype to LUW: DBMS name could not be retrieved. Check if your client version is of the right level")
643
+ warn "Forcing servertype to LUW: DBMS name could not be retrieved. Check if your client version is of the right level"
644
+ @servertype = IBM_DB2_LUW.new(self, @isAr3)
645
+ end
646
+ else
647
+ error_msg = IBM_DB.getErrormsg( @connection, IBM_DB::DB_CONN )
648
+ IBM_DB.close( @connection )
649
+ raise "Cannot retrieve server information: #{error_msg}"
650
+ end
651
+ end
652
+
653
+ # Executes the +set schema+ statement using the schema identifier provided
654
+ @servertype.set_schema(@schema) if @schema && @schema != @username
655
+
656
+ # Check for the start value for id (primary key column). By default it is 1
657
+ if config.has_key?(:start_id)
658
+ @start_id = config[:start_id]
659
+ else
660
+ @start_id = 1
661
+ end
662
+
663
+ #Check Arel version
664
+ begin
665
+ @arelVersion = Arel::VERSION.to_i
666
+ rescue
667
+ @arelVersion = 0
668
+ end
669
+
670
+ if(@arelVersion >= 3 )
671
+ @visitor = Arel::Visitors::IBM_DB.new self
672
+ end
673
+ end
674
+
675
+ # Optional connection attribute: database name space qualifier
676
+ def schema=(name)
677
+ unless name == @schema
678
+ @schema = name
679
+ @servertype.set_schema(@schema)
680
+ end
681
+ end
682
+
683
+ # Optional connection attribute: authenticated application user
684
+ def app_user=(name)
685
+ unless name == @app_user
686
+ option = {IBM_DB::SQL_ATTR_INFO_USERID => "#{name}"}
687
+ if IBM_DB.set_option( @connection, option, 1 )
688
+ @app_user = IBM_DB.get_option( @connection, IBM_DB::SQL_ATTR_INFO_USERID, 1 )
689
+ end
690
+ end
691
+ end
692
+
693
+ # Optional connection attribute: OS account (client workstation)
694
+ def account=(name)
695
+ unless name == @account
696
+ option = {IBM_DB::SQL_ATTR_INFO_ACCTSTR => "#{name}"}
697
+ if IBM_DB.set_option( @connection, option, 1 )
698
+ @account = IBM_DB.get_option( @connection, IBM_DB::SQL_ATTR_INFO_ACCTSTR, 1 )
699
+ end
700
+ end
701
+ end
702
+
703
+ # Optional connection attribute: application name
704
+ def application=(name)
705
+ unless name == @application
706
+ option = {IBM_DB::SQL_ATTR_INFO_APPLNAME => "#{name}"}
707
+ if IBM_DB.set_option( @connection, option, 1 )
708
+ @application = IBM_DB.get_option( @connection, IBM_DB::SQL_ATTR_INFO_APPLNAME, 1 )
709
+ end
710
+ end
711
+ end
712
+
713
+ # Optional connection attribute: client workstation name
714
+ def workstation=(name)
715
+ unless name == @workstation
716
+ option = {IBM_DB::SQL_ATTR_INFO_WRKSTNNAME => "#{name}"}
717
+ if IBM_DB.set_option( @connection, option, 1 )
718
+ @workstation = IBM_DB.get_option( @connection, IBM_DB::SQL_ATTR_INFO_WRKSTNNAME, 1 )
719
+ end
720
+ end
721
+ end
722
+
723
+ def self.visitor_for(pool)
724
+ Arel::Visitors::IBM_DB.new(pool)
725
+ end
726
+
727
+ def to_sql(arel, binds = [])
728
+ if arel.respond_to?(:ast)
729
+ visitor.accept(arel.ast) do
730
+ quote(*binds.shift.reverse)
731
+ end
732
+ else
733
+ arel
734
+ end
735
+ end
736
+
737
+ # This adapter supports migrations.
738
+ # Current limitations:
739
+ # +rename_column+ is not currently supported by the IBM data servers
740
+ # +remove_column+ is not currently supported by the DB2 for zOS data server
741
+ # Tables containing columns of XML data type do not support +remove_column+
742
+ def supports_migrations?
743
+ true
744
+ end
745
+
746
+ # This Adapter supports DDL transactions.
747
+ # This means CREATE TABLE and other DDL statements can be carried out as a transaction.
748
+ # That is the statements executed can be ROLLED BACK in case of any error during the process.
749
+ def supports_ddl_transactions?
750
+ true
751
+ end
752
+
753
+ def log_query(sql, name) #:nodoc:
754
+ # Used by handle_lobs
755
+ log(sql,name){}
756
+ end
757
+
758
+ #==============================================
759
+ # CONNECTION MANAGEMENT
760
+ #==============================================
761
+
762
+ # Tests the connection status
763
+ def active?
764
+ IBM_DB.active @connection
765
+ rescue
766
+ false
767
+ end
768
+
769
+ # Private method used by +reconnect!+.
770
+ # It connects to the database with the initially provided credentials
771
+ def connect
772
+ # If the type of connection is net based
773
+ if(@username.nil? || @password.nil?)
774
+ raise ArgumentError, "Username/Password cannot be nil"
775
+ end
776
+
777
+ begin
778
+ if @host
779
+ @conn_string = "DRIVER={IBM DB2 ODBC DRIVER};\
780
+ DATABASE=#{@database};\
781
+ HOSTNAME=#{@host};\
782
+ PORT=#{@port};\
783
+ PROTOCOL=TCPIP;\
784
+ UID=#{@username};\
785
+ PWD=#{@password};"
786
+ @conn_string << "SECURITY=#{@security};" if @security
787
+ @conn_string << "AUTHENTICATION=#{@authentication};" if @authentication
788
+ @conn_string << "CONNECTTIMEOUT=#{@timeout};"
789
+ # Connects and assigns the resulting IBM_DB.Connection to the +@connection+ instance variable
790
+ @connection = IBM_DB.connect(@conn_string, '', '', @conn_options, @set_quoted_literal_replacement)
791
+ else
792
+ # Connects to the database using the local alias (@database)
793
+ # and assigns the connection object (IBM_DB.Connection) to @connection
794
+ @connection = IBM_DB.connect(@database, @username, @password, @conn_options, @set_quoted_literal_replacement)
795
+ end
796
+ rescue StandardError => connect_err
797
+ warn "Connection to database #{@database} failed: #{connect_err}"
798
+ @connection = false
799
+ end
800
+ # Sets the schema if different from default (username)
801
+ if @schema && @schema != @username
802
+ @servertype.set_schema(@schema)
803
+ end
804
+ end
805
+ private :connect
806
+
807
+ # Closes the current connection and opens a new one
808
+ def reconnect!
809
+ disconnect!
810
+ connect
811
+ end
812
+
813
+ # Closes the current connection
814
+ def disconnect!
815
+ # Attempts to close the connection. The methods will return:
816
+ # * true if succesfull
817
+ # * false if the connection is already closed
818
+ # * nil if an error is raised
819
+ return nil if @connection.nil? || @connection == false
820
+ IBM_DB.close(@connection) rescue nil
821
+ end
822
+
823
+ #==============================================
824
+ # DATABASE STATEMENTS
825
+ #==============================================
826
+
827
+ def create_table(name, options = {})
828
+ @servertype.setup_for_lob_table
829
+ super
830
+
831
+ #Table definition is complete only when a unique index is created on the primarykey column for DB2 V8 on zOS
832
+
833
+ #create index on id column if options[:id] is nil or id ==true
834
+ #else check if options[:primary_key]is not nil then create an unique index on that column
835
+ if !options[:id].nil? || !options[:primary_key].nil?
836
+ if (!options[:id].nil? && options[:id] == true)
837
+ @servertype.create_index_after_table(name,"id")
838
+ elsif !options[:primary_key].nil?
839
+ @servertype.create_index_after_table(name,options[:primary_key].to_s)
840
+ end
841
+ else
842
+ @servertype.create_index_after_table(name,"id")
843
+ end
844
+ end
845
+
846
+ # Returns an array of hashes with the column names as keys and
847
+ # column values as values. +sql+ is the select query,
848
+ # and +name+ is an optional description for logging
849
+ def prepared_select(sql_param_hash, name = nil)
850
+ # Replaces {"= NULL" with " IS NULL"} OR {"IN (NULL)" with " IS NULL"}
851
+
852
+ results = []
853
+ # Invokes the method +prepare+ in order prepare the SQL
854
+ # IBM_DB.Statement is returned from which the statement is executed and results fetched
855
+ pstmt = prepare(sql_param_hash["sqlSegment"], name)
856
+ if(execute_prepared_stmt(pstmt, sql_param_hash["paramArray"]))
857
+ begin
858
+ results = @servertype.select(pstmt)
859
+ rescue StandardError => fetch_error # Handle driver fetch errors
860
+ error_msg = IBM_DB.getErrormsg(pstmt, IBM_DB::DB_STMT )
861
+ if error_msg && !error_msg.empty?
862
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
863
+ else
864
+ error_msg = "An unexpected error occurred during data retrieval"
865
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
866
+ raise error_msg
867
+ end
868
+ ensure
869
+ # Ensures to free the resources associated with the statement
870
+ IBM_DB.free_stmt(pstmt) if pstmt
871
+ end
872
+ end
873
+ # The array of record hashes is returned
874
+ results
875
+ end
876
+
877
+ # Returns an array of hashes with the column names as keys and
878
+ # column values as values. +sql+ is the select query,
879
+ # and +name+ is an optional description for logging
880
+ def prepared_select_values(sql_param_hash, name = nil)
881
+ # Replaces {"= NULL" with " IS NULL"} OR {"IN (NULL)" with " IS NULL"}
882
+ results = []
883
+ # Invokes the method +prepare+ in order prepare the SQL
884
+ # IBM_DB.Statement is returned from which the statement is executed and results fetched
885
+ pstmt = prepare(sql_param_hash["sqlSegment"], name)
886
+ if(execute_prepared_stmt(pstmt, sql_param_hash["paramArray"]))
887
+ begin
888
+ results = @servertype.select_rows(sql_param_hash["sqlSegment"], name, pstmt, results)
889
+ if results
890
+ return results.map { |v| v[0] }
891
+ else
892
+ nil
893
+ end
894
+ rescue StandardError => fetch_error # Handle driver fetch errors
895
+ error_msg = IBM_DB.getErrormsg(pstmt, IBM_DB::DB_STMT )
896
+ if error_msg && !error_msg.empty?
897
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
898
+ else
899
+ error_msg = "An unexpected error occurred during data retrieval"
900
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
901
+ raise error_msg
902
+ end
903
+ ensure
904
+ # Ensures to free the resources associated with the statement
905
+ IBM_DB.free_stmt(pstmt) if pstmt
906
+ end
907
+ end
908
+ # The array of record hashes is returned
909
+ results
910
+ end
911
+
912
+ #Calls the servertype select method to fetch the data
913
+ def fetch_data(stmt)
914
+ if(stmt)
915
+ begin
916
+ return @servertype.select(stmt)
917
+ rescue StandardError => fetch_error # Handle driver fetch errors
918
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
919
+ if error_msg && !error_msg.empty?
920
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
921
+ else
922
+ error_msg = "An unexpected error occurred during data retrieval"
923
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
924
+ raise error_msg
925
+ end
926
+ ensure
927
+ # Ensures to free the resources associated with the statement
928
+ IBM_DB.free_stmt(stmt) if stmt
929
+ end
930
+ end
931
+ end
932
+ =begin
933
+ # Returns an array of hashes with the column names as keys and
934
+ # column values as values. +sql+ is the select query,
935
+ # and +name+ is an optional description for logging
936
+ def select(sql, name = nil)
937
+ # Replaces {"= NULL" with " IS NULL"} OR {"IN (NULL)" with " IS NULL"}
938
+ sql.gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, " IS NULL" )
939
+
940
+ results = []
941
+ # Invokes the method +execute+ in order to log and execute the SQL
942
+ # IBM_DB.Statement is returned from which results can be fetched
943
+ stmt = execute(sql, name)
944
+
945
+ results = fetch_data(stmt)
946
+ # The array of record hashes is returned
947
+ results
948
+ end
949
+ =end
950
+ def select(sql, name = nil, binds = [])
951
+ # Replaces {"= NULL" with " IS NULL"} OR {"IN (NULL)" with " IS NULL"}
952
+ sql.gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, " IS NULL" )
953
+
954
+ results = []
955
+
956
+ if(binds.nil? || binds.empty?)
957
+ stmt = execute(sql, name)
958
+ else
959
+ stmt = exec_query(sql, name, binds)
960
+ end
961
+
962
+ cols = IBM_DB.resultCols(stmt)
963
+
964
+ if( stmt )
965
+ results = fetch_data(stmt)
966
+ end
967
+
968
+ if(@isAr3)
969
+ return results
970
+ else
971
+ return ActiveRecord::Result.new(cols, results)
972
+ end
973
+ end
974
+
975
+ #Returns an array of arrays containing the field values.
976
+ #This is an implementation for the abstract method
977
+ #+sql+ is the select query and +name+ is an optional description for logging
978
+ def select_rows(sql, name = nil)
979
+ # Replaces {"= NULL" with " IS NULL"} OR {"IN (NULL)" with " IS NULL"}
980
+ sql.gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, " IS NULL" )
981
+
982
+ results = []
983
+ # Invokes the method +execute+ in order to log and execute the SQL
984
+ # IBM_DB.Statement is returned from which results can be fetched
985
+ stmt = execute(sql, name)
986
+ if(stmt)
987
+ begin
988
+ results = @servertype.select_rows(sql, name, stmt, results)
989
+ rescue StandardError => fetch_error # Handle driver fetch errors
990
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
991
+ if error_msg && !error_msg.empty?
992
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
993
+ else
994
+ error_msg = "An unexpected error occurred during data retrieval"
995
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
996
+ raise error_msg
997
+ end
998
+ ensure
999
+ # Ensures to free the resources associated with the statement
1000
+ IBM_DB.free_stmt(stmt) if stmt
1001
+ end
1002
+ end
1003
+ # The array of record hashes is returned
1004
+ results
1005
+ end
1006
+
1007
+ # Returns a record hash with the column names as keys and column values
1008
+ # as values.
1009
+ #def select_one(sql, name = nil)
1010
+ # Gets the first hash from the array of hashes returned by
1011
+ # select_all
1012
+ # select_all(sql,name).first
1013
+ #end
1014
+
1015
+ #inserts values from fixtures
1016
+ #overridden to handle LOB's fixture insertion, as, in normal inserts callbacks are triggered but during fixture insertion callbacks are not triggered
1017
+ #hence only markers like @@@IBMBINARY@@@ will be inserted and are not updated to actual data
1018
+ def insert_fixture(fixture, table_name)
1019
+ if(fixture.respond_to?(:keys))
1020
+ insert_query = "INSERT INTO #{quote_table_name(table_name)} ( #{fixture.keys.join(', ')})"
1021
+ else
1022
+ insert_query = "INSERT INTO #{quote_table_name(table_name)} ( #{fixture.key_list})"
1023
+ end
1024
+
1025
+ insert_values = []
1026
+ params = []
1027
+ if @servertype.instance_of? IBM_IDS
1028
+ super
1029
+ return
1030
+ end
1031
+ column_list = columns(table_name)
1032
+ fixture.each do |item|
1033
+ col = nil
1034
+ column_list.each do |column|
1035
+ if column.name.downcase == item.at(0).downcase
1036
+ col= column
1037
+ break
1038
+ end
1039
+ end
1040
+ if item.at(1).nil? ||
1041
+ item.at(1) == {} ||
1042
+ (item.at(1) == '' && !(col.type.to_sym == :text))
1043
+
1044
+ params << 'NULL'
1045
+
1046
+ elsif col.type.to_sym == :xml ||
1047
+ col.type.to_sym == :text ||
1048
+ col.type.to_sym == :binary
1049
+ # Add a '?' for the parameter or a NULL if the value is nil or empty
1050
+ # (except for a CLOB field where '' can be a value)
1051
+ insert_values << quote_value_for_pstmt(item.at(1))
1052
+ params << '?'
1053
+ else
1054
+ insert_values << quote_value_for_pstmt(item.at(1),col)
1055
+ params << '?'
1056
+ end
1057
+ end
1058
+
1059
+ insert_query << " VALUES ("+ params.join(',') + ")"
1060
+ unless stmt = IBM_DB.prepare(@connection, insert_query)
1061
+ error_msg = IBM_DB.getErrormsg( @connection, IBM_DB::DB_CONN )
1062
+ if error_msg && !error_msg.empty?
1063
+ raise "Failed to prepare statement for fixtures insert due to : #{error_msg}"
1064
+ else
1065
+ raise StandardError.new('An unexpected error occurred during preparing SQL for fixture insert')
1066
+ end
1067
+ end
1068
+
1069
+ #log_query(insert_query,'fixture insert')
1070
+ log(insert_query,'fixture insert') do
1071
+ unless IBM_DB.execute(stmt, insert_values)
1072
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
1073
+ IBM_DB.free_stmt(stmt) if stmt
1074
+ raise "Failed to insert due to: #{error_msg}"
1075
+ else
1076
+ IBM_DB.free_stmt(stmt) if stmt
1077
+ end
1078
+ end
1079
+ end
1080
+
1081
+ def empty_insert_statement_value(pkey)
1082
+ "(#{pkey}) VALUES (DEFAULT)"
1083
+ end
1084
+
1085
+ # Perform an insert and returns the last ID generated.
1086
+ # This can be the ID passed to the method or the one auto-generated by the database,
1087
+ # and retrieved by the +last_generated_id+ method.
1088
+ def insert_direct(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
1089
+ if @handle_lobs_triggered #Ensure the array of sql is cleared if they have been handled in the callback
1090
+ @sql = []
1091
+ @handle_lobs_triggered = false
1092
+ end
1093
+
1094
+ clear_query_cache if defined? clear_query_cache
1095
+
1096
+ if stmt = execute(sql, name)
1097
+ begin
1098
+ @sql << sql
1099
+ return id_value || @servertype.last_generated_id(stmt)
1100
+ # Ensures to free the resources associated with the statement
1101
+ ensure
1102
+ IBM_DB.free_stmt(stmt) if stmt
1103
+ end
1104
+ end
1105
+ end
1106
+
1107
+ def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [] )
1108
+ sql, binds = [to_sql(arel),binds]
1109
+
1110
+ #unless IBM_DBAdapter.respond_to?(:exec_insert)
1111
+ if binds.nil? || binds.empty?
1112
+ return insert_direct(sql, name, pk, id_value, sequence_name)
1113
+ end
1114
+
1115
+ clear_query_cache if defined? clear_query_cache
1116
+ if stmt = exec_insert(sql, name, binds)
1117
+ begin
1118
+ @sql << sql
1119
+ return id_value || @servertype.last_generated_id(stmt)
1120
+ ensure
1121
+ IBM_DB.free_stmt(stmt) if stmt
1122
+ end
1123
+ end
1124
+ end
1125
+
1126
+ # Praveen
1127
+ # Performs an insert using the prepared statement and returns the last ID generated.
1128
+ # This can be the ID passed to the method or the one auto-generated by the database,
1129
+ # and retrieved by the +last_generated_id+ method.
1130
+ def prepared_insert(pstmt, param_array = nil, id_value = nil)
1131
+ if @handle_lobs_triggered #Ensure the array of sql is cleared if they have been handled in the callback
1132
+ @sql = []
1133
+ @sql_parameter_values = []
1134
+ @handle_lobs_triggered = false
1135
+ end
1136
+
1137
+ clear_query_cache if defined? clear_query_cache
1138
+
1139
+ begin
1140
+ if execute_prepared_stmt(pstmt, param_array)
1141
+ @sql << @prepared_sql
1142
+ @sql_parameter_values << param_array
1143
+ return id_value || @servertype.last_generated_id(pstmt)
1144
+ end
1145
+ rescue StandardError => insert_err
1146
+ raise insert_err
1147
+ ensure
1148
+ IBM_DB.free_stmt(pstmt) if pstmt
1149
+ end
1150
+ end
1151
+
1152
+ # Praveen
1153
+ # Prepares and logs +sql+ commands and
1154
+ # returns a +IBM_DB.Statement+ object.
1155
+ def prepare(sql,name = nil)
1156
+ # The +log+ method is defined in the parent class +AbstractAdapter+
1157
+ @prepared_sql = sql
1158
+ log(sql,name) do
1159
+ @servertype.prepare(sql, name)
1160
+ end
1161
+ end
1162
+
1163
+ # Praveen
1164
+ #Executes the prepared statement
1165
+ #ReturnsTrue on success and False on Failure
1166
+ def execute_prepared_stmt(pstmt, param_array = nil)
1167
+ if !param_array.nil? && param_array.size < 1
1168
+ param_array = nil
1169
+ end
1170
+
1171
+ if( !IBM_DB.execute(pstmt, param_array) )
1172
+ error_msg = IBM_DB.getErrormsg(pstmt, IBM_DB::DB_STMT)
1173
+ if !error_msg.empty?
1174
+ error_msg = "Statement execution failed: " + error_msg
1175
+ else
1176
+ error_msg = "Statement execution failed"
1177
+ end
1178
+ IBM_DB.free_stmt(pstmt) if pstmt
1179
+ raise StatementInvalid, error_msg
1180
+ else
1181
+ return true
1182
+ end
1183
+ end
1184
+
1185
+ # Executes +sql+ statement in the context of this connection using
1186
+ # +binds+ as the bind substitutes. +name+ is logged along with
1187
+ # the executed +sql+ statement.
1188
+ def exec_query(sql, name = 'SQL', binds = [])
1189
+ begin
1190
+ param_array = binds.map do |column,value|
1191
+ quote_value_for_pstmt(value, column)
1192
+ end
1193
+
1194
+ stmt = prepare(sql, name)
1195
+
1196
+ if( stmt )
1197
+ if(execute_prepared_stmt(stmt, param_array))
1198
+ return stmt
1199
+ end
1200
+ else
1201
+ return false
1202
+ end
1203
+ ensure
1204
+ @offset = @limit = nil
1205
+ end
1206
+ end
1207
+
1208
+ # Executes and logs +sql+ commands and
1209
+ # returns a +IBM_DB.Statement+ object.
1210
+ def execute(sql, name = nil)
1211
+ # Logs and execute the sql instructions.
1212
+ # The +log+ method is defined in the parent class +AbstractAdapter+
1213
+ log(sql, name) do
1214
+ @servertype.execute(sql, name)
1215
+ end
1216
+ end
1217
+
1218
+ # Executes an "UPDATE" SQL statement
1219
+ def update_direct(sql, name = nil)
1220
+ if @handle_lobs_triggered #Ensure the array of sql is cleared if they have been handled in the callback
1221
+ @sql = []
1222
+ @handle_lobs_triggered = false
1223
+ end
1224
+
1225
+ # Logs and execute the given sql query.
1226
+ if stmt = execute(sql, name)
1227
+ begin
1228
+ @sql << sql
1229
+ # Retrieves the number of affected rows
1230
+ IBM_DB.num_rows(stmt)
1231
+ # Ensures to free the resources associated with the statement
1232
+ ensure
1233
+ IBM_DB.free_stmt(stmt) if stmt
1234
+ end
1235
+ end
1236
+ end
1237
+
1238
+ #Praveen
1239
+ def prepared_update(pstmt, param_array = nil )
1240
+ if @handle_lobs_triggered #Ensure the array of sql is cleared if they have been handled in the callback
1241
+ @sql = []
1242
+ @sql_parameter_values = []
1243
+ @handle_lobs_triggered = false
1244
+ end
1245
+
1246
+ clear_query_cache if defined? clear_query_cache
1247
+
1248
+ begin
1249
+ if execute_prepared_stmt(pstmt, param_array)
1250
+ @sql << @prepared_sql
1251
+ @sql_parameter_values << param_array
1252
+ # Retrieves the number of affected rows
1253
+ IBM_DB.num_rows(pstmt)
1254
+ # Ensures to free the resources associated with the statement
1255
+ end
1256
+ rescue StandardError => updt_err
1257
+ raise updt_err
1258
+ ensure
1259
+ IBM_DB.free_stmt(pstmt) if pstmt
1260
+ end
1261
+ end
1262
+ # The delete method executes the delete
1263
+ # statement and returns the number of affected rows.
1264
+ # The method is an alias for +update+
1265
+ alias_method :prepared_delete, :prepared_update
1266
+
1267
+ def update(arel, name = nil, binds = [])
1268
+ sql = to_sql(arel)
1269
+
1270
+ # Make sure the WHERE clause handles NULL's correctly
1271
+ sqlarray = sql.split(/\s*WHERE\s*/)
1272
+ size = sqlarray.size
1273
+ if size > 1
1274
+ sql = sqlarray[0] + " WHERE "
1275
+ if size > 2
1276
+ 1.upto size-2 do |index|
1277
+ sqlarray[index].gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, " IS NULL" ) unless sqlarray[index].nil?
1278
+ sql = sql + sqlarray[index] + " WHERE "
1279
+ end
1280
+ end
1281
+ sqlarray[size-1].gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, " IS NULL" ) unless sqlarray[size-1].nil?
1282
+ sql = sql + sqlarray[size-1]
1283
+ end
1284
+
1285
+ clear_query_cache if defined? clear_query_cache
1286
+
1287
+ if binds.nil? || binds.empty?
1288
+ update_direct(sql, name)
1289
+ else
1290
+ begin
1291
+ if stmt = exec_query(sql,name,binds)
1292
+ IBM_DB.num_rows(stmt)
1293
+ end
1294
+ ensure
1295
+ IBM_DB.free_stmt(stmt) if(stmt)
1296
+ end
1297
+ end
1298
+ end
1299
+
1300
+ alias_method :delete, :update
1301
+
1302
+ # Begins the transaction (and turns off auto-committing)
1303
+ def begin_db_transaction
1304
+ # Turns off the auto-commit
1305
+ IBM_DB.autocommit(@connection, IBM_DB::SQL_AUTOCOMMIT_OFF)
1306
+ end
1307
+
1308
+ # Commits the transaction and turns on auto-committing
1309
+ def commit_db_transaction
1310
+ # Commits the transaction
1311
+ IBM_DB.commit @connection rescue nil
1312
+ # Turns on auto-committing
1313
+ IBM_DB.autocommit @connection, IBM_DB::SQL_AUTOCOMMIT_ON
1314
+ end
1315
+
1316
+ # Rolls back the transaction and turns on auto-committing. Must be
1317
+ # done if the transaction block raises an exception or returns false
1318
+ def rollback_db_transaction
1319
+ # ROLLBACK the transaction
1320
+ IBM_DB.rollback(@connection) rescue nil
1321
+ # Turns on auto-committing
1322
+ IBM_DB.autocommit @connection, IBM_DB::SQL_AUTOCOMMIT_ON
1323
+ end
1324
+
1325
+
1326
+ # Modifies a sql statement in order to implement a LIMIT and an OFFSET.
1327
+ # A LIMIT defines the number of rows that should be fetched, while
1328
+ # an OFFSET defines from what row the records must be fetched.
1329
+ # IBM data servers implement a LIMIT in SQL statements through:
1330
+ # FETCH FIRST n ROWS ONLY, where n is the number of rows required.
1331
+ # The implementation of OFFSET is more elaborate, and requires the usage of
1332
+ # subqueries and the ROW_NUMBER() command in order to add row numbering
1333
+ # as an additional column to a copy of the existing table.
1334
+ # ==== Examples
1335
+ # add_limit_offset!('SELECT * FROM staff', {:limit => 10})
1336
+ # generates: "SELECT * FROM staff FETCH FIRST 10 ROWS ONLY"
1337
+ #
1338
+ # add_limit_offset!('SELECT * FROM staff', {:limit => 10, :offset => 30})
1339
+ # generates "SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_rownum
1340
+ # FROM (SELECT * FROM staff) AS I) AS O WHERE sys_row_num BETWEEN 31 AND 40"
1341
+ def add_limit_offset!(sql, options)
1342
+ limit = options[:limit]
1343
+ offset = options[:offset]
1344
+
1345
+ # if the limit is zero
1346
+ if limit && limit == 0
1347
+ # Returns a query that will always generate zero records
1348
+ # (e.g. WHERE sys_row_num BETWEEN 1 and 0)
1349
+ if( @pstmt_support_on )
1350
+ sql = @servertype.query_offset_limit!(sql, 0, limit, options)
1351
+ else
1352
+ sql = @servertype.query_offset_limit(sql, 0, limit)
1353
+ end
1354
+ # If there is a non-zero limit
1355
+ else
1356
+ # If an offset is specified builds the query with offset and limit,
1357
+ # otherwise retrieves only the first +limit+ rows
1358
+ if( @pstmt_support_on )
1359
+ sql = @servertype.query_offset_limit!(sql, offset, limit, options)
1360
+ else
1361
+ sql = @servertype.query_offset_limit(sql, offset, limit)
1362
+ end
1363
+ end
1364
+ # Returns the sql query in any case
1365
+ sql
1366
+ end # method add_limit_offset!
1367
+
1368
+ def default_sequence_name(table, column) # :nodoc:
1369
+ "#{table}_#{column}_seq"
1370
+ end
1371
+
1372
+
1373
+ #==============================================
1374
+ # QUOTING
1375
+ #==============================================
1376
+
1377
+ #Praveen
1378
+ def quote_value_for_pstmt(value, column=nil)
1379
+
1380
+ return value.quoted_id if value.respond_to?(:quoted_id)
1381
+
1382
+ case value
1383
+ when String, ActiveSupport::Multibyte::Chars then
1384
+ value = value.to_s
1385
+ if column && [:integer, :float].include?(column.type)
1386
+ value = column.type == :integer ? value.to_i : value.to_f
1387
+ value
1388
+ else
1389
+ value
1390
+ end
1391
+ when NilClass then nil
1392
+ when TrueClass then 1
1393
+ when FalseClass then 0
1394
+ when Float, Fixnum, Bignum then value
1395
+ # BigDecimals need to be output in a non-normalized form and quoted.
1396
+ when BigDecimal then value.to_s('F')
1397
+ when Numeric, Symbol then value.to_s
1398
+ else
1399
+ if value.acts_like?(:date) || value.acts_like?(:time)
1400
+ quoted_date(value)
1401
+ else
1402
+ value.to_yaml
1403
+ end
1404
+ end
1405
+ end
1406
+
1407
+ # Properly quotes the various data types.
1408
+ # +value+ contains the data, +column+ is optional and contains info on the field
1409
+ def quote(value, column = nil)
1410
+ return value.quoted_id if value.respond_to?(:quoted_id)
1411
+
1412
+ case value
1413
+ # If it's a numeric value and the column type is not a string, it shouldn't be quoted
1414
+ # (IBM_DB doesn't accept quotes on numeric types)
1415
+ when Numeric
1416
+ # If the column type is text or string, return the quote value
1417
+ if column && column.type.to_sym == :text || column && column.type.to_sym == :string
1418
+ unless caller[0] =~ /insert_fixture/i
1419
+ "'#{value}'"
1420
+ else
1421
+ "#{value}"
1422
+ end
1423
+ else
1424
+ # value is Numeric, column.type is not a string,
1425
+ # therefore it converts the number to string without quoting it
1426
+ value.to_s
1427
+ end
1428
+ when String, ActiveSupport::Multibyte::Chars
1429
+ if column && column.type.to_sym == :binary && !(column.sql_type =~ /for bit data/i)
1430
+ # If quoting is required for the insert/update of a BLOB
1431
+ unless caller[0] =~ /add_column_options/i
1432
+ # Invokes a convertion from string to binary
1433
+ @servertype.set_binary_value
1434
+ else
1435
+ # Quoting required for the default value of a column
1436
+ @servertype.set_binary_default(value)
1437
+ end
1438
+ elsif column && column.type.to_sym == :text
1439
+ unless caller[0] =~ /add_column_options/i
1440
+ "'@@@IBMTEXT@@@'"
1441
+ else
1442
+ @servertype.set_text_default(quote_string(value))
1443
+ end
1444
+ elsif column && column.type.to_sym == :xml
1445
+ unless caller[0] =~ /add_column_options/i
1446
+ "'<ibm>@@@IBMXML@@@</ibm>'"
1447
+ else
1448
+ "#{value}"
1449
+ end
1450
+ else
1451
+ unless caller[0] =~ /insert_fixture/i
1452
+ super
1453
+ else
1454
+ "#{value}"
1455
+ end
1456
+ end
1457
+ when TrueClass then quoted_true # return '1' for true
1458
+ when FalseClass then quoted_false # return '0' for false
1459
+ when nil then "NULL"
1460
+ when Date, Time then "'#{quoted_date(value)}'"
1461
+ when Symbol then "'#{quote_string(value.to_s)}'"
1462
+ else
1463
+ unless caller[0] =~ /insert_fixture/i
1464
+ "'#{quote_string(YAML.dump(value))}'"
1465
+ else
1466
+ "#{quote_string(YAML.dump(value))}"
1467
+ end
1468
+ end
1469
+ end
1470
+
1471
+ # Quotes a given string, escaping single quote (') characters.
1472
+ def quote_string(string)
1473
+ string.gsub(/'/, "''")
1474
+ end
1475
+
1476
+ # *true* is represented by a smallint 1, *false*
1477
+ # by 0, as no native boolean type exists in DB2.
1478
+ # Numerics are not quoted in DB2.
1479
+ def quoted_true
1480
+ "1"
1481
+ end
1482
+
1483
+ def quoted_false
1484
+ "0"
1485
+ end
1486
+
1487
+ def quote_column_name(name)
1488
+ @servertype.check_reserved_words(name)
1489
+ end
1490
+
1491
+ #==============================================
1492
+ # SCHEMA STATEMENTS
1493
+ #==============================================
1494
+
1495
+ # Returns a Hash of mappings from the abstract data types to the native
1496
+ # database types
1497
+ def native_database_types
1498
+ {
1499
+ :primary_key => { :name => @servertype.primary_key_definition(@start_id)},
1500
+ :string => { :name => "varchar", :limit => 255 },
1501
+ :text => { :name => "clob" },
1502
+ :integer => { :name => "integer" },
1503
+ :float => { :name => "float" },
1504
+ :datetime => { :name => @servertype.get_datetime_mapping },
1505
+ :timestamp => { :name => @servertype.get_datetime_mapping },
1506
+ :time => { :name => @servertype.get_time_mapping },
1507
+ :date => { :name => "date" },
1508
+ :binary => { :name => "blob" },
1509
+
1510
+ # IBM data servers don't have a native boolean type.
1511
+ # A boolean can be represented by a smallint,
1512
+ # adopting the convention that False is 0 and True is 1
1513
+ :boolean => { :name => "smallint"},
1514
+ :xml => { :name => "xml"},
1515
+ :decimal => { :name => "decimal" },
1516
+ :rowid => { :name => "rowid" }, # rowid is a supported datatype on z/OS and i/5
1517
+ :serial => { :name => "serial" }, # rowid is a supported datatype on Informix Dynamic Server
1518
+ :char => { :name => "char" },
1519
+ :double => { :name => @servertype.get_double_mapping },
1520
+ :decfloat => { :name => "decfloat"},
1521
+ :graphic => { :name => "graphic", :limit => 1},
1522
+ :vargraphic => { :name => "vargraphic", :limit => 1},
1523
+ :bigint => { :name => "bigint"}
1524
+ }
1525
+ end
1526
+
1527
+ def build_conn_str_for_dbops()
1528
+ connect_str = "DRIVER={IBM DB2 ODBC DRIVER};ATTACH=true;"
1529
+ if(!@host.nil?)
1530
+ connect_str << "HOSTNAME=#{@host};"
1531
+ connect_str << "PORT=#{@port};"
1532
+ connect_str << "PROTOCOL=TCPIP;"
1533
+ end
1534
+ connect_str << "UID=#{@username};PWD=#{@password};"
1535
+ return connect_str
1536
+ end
1537
+
1538
+ def drop_database(dbName)
1539
+ connect_str = build_conn_str_for_dbops()
1540
+
1541
+ #Ensure connection is closed before trying to drop a database.
1542
+ #As a connect call would have been made by call seeing connection in active
1543
+ disconnect!
1544
+
1545
+ begin
1546
+ dropConn = IBM_DB.connect(connect_str, '', '')
1547
+ rescue StandardError => connect_err
1548
+ raise "Failed to connect to server due to: #{connect_err}"
1549
+ end
1550
+
1551
+ if(IBM_DB.dropDB(dropConn,dbName))
1552
+ IBM_DB.close(dropConn)
1553
+ return true
1554
+ else
1555
+ error = IBM_DB.getErrormsg(dropConn, IBM_DB::DB_CONN)
1556
+ IBM_DB.close(dropConn)
1557
+ raise "Could not drop Database due to: #{error}"
1558
+ end
1559
+ end
1560
+
1561
+ def create_database(dbName, codeSet=nil, mode=nil)
1562
+ connect_str = build_conn_str_for_dbops()
1563
+
1564
+ #Ensure connection is closed before trying to drop a database.
1565
+ #As a connect call would have been made by call seeing connection in active
1566
+ disconnect!
1567
+
1568
+ begin
1569
+ createConn = IBM_DB.connect(connect_str, '', '')
1570
+ rescue StandardError => connect_err
1571
+ raise "Failed to connect to server due to: #{connect_err}"
1572
+ end
1573
+
1574
+ if(IBM_DB.createDB(createConn,dbName,codeSet,mode))
1575
+ IBM_DB.close(createConn)
1576
+ return true
1577
+ else
1578
+ error = IBM_DB.getErrormsg(createConn, IBM_DB::DB_CONN)
1579
+ IBM_DB.close(createConn)
1580
+ raise "Could not create Database due to: #{error}"
1581
+ end
1582
+ end
1583
+
1584
+ # IBM data servers do not support limits on certain data types (unlike MySQL)
1585
+ # Limit is supported for the {float, decimal, numeric, varchar, clob, blob, graphic, vargraphic} data types.
1586
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
1587
+ if type.to_sym == :decfloat
1588
+ sql_segment = native_database_types[type.to_sym][:name].to_s
1589
+ sql_segment << "(#{precision})" if !precision.nil?
1590
+ return sql_segment
1591
+ end
1592
+
1593
+ return super if limit.nil?
1594
+
1595
+ # strip off limits on data types not supporting them
1596
+ if @servertype.limit_not_supported_types.include? type.to_sym
1597
+ return native_database_types[type.to_sym][:name].to_s
1598
+ elsif type.to_sym == :boolean
1599
+ return "smallint"
1600
+ else
1601
+ return super
1602
+ end
1603
+ end
1604
+
1605
+ # Returns the maximum length a table alias identifier can be.
1606
+ # IBM data servers (cross-platform) table limit is 128 characters
1607
+ def table_alias_length
1608
+ 128
1609
+ end
1610
+
1611
+ # Retrieves table's metadata for a specified shema name
1612
+ def tables(name = nil)
1613
+ # Initializes the tables array
1614
+ tables = []
1615
+ # Retrieve table's metadata through IBM_DB driver
1616
+ stmt = IBM_DB.tables(@connection, nil,
1617
+ @servertype.set_case(@schema))
1618
+ if(stmt)
1619
+ begin
1620
+ # Fetches all the records available
1621
+ while tab = IBM_DB.fetch_assoc(stmt)
1622
+ # Adds the lowercase table name to the array
1623
+ if(tab["table_type"]== 'TABLE') #check, so that only tables are dumped,IBM_DB.tables also returns views,alias etc in the schema
1624
+ tables << tab["table_name"].downcase
1625
+ end
1626
+ end
1627
+ rescue StandardError => fetch_error # Handle driver fetch errors
1628
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
1629
+ if error_msg && !error_msg.empty?
1630
+ raise "Failed to retrieve table metadata during fetch: #{error_msg}"
1631
+ else
1632
+ error_msg = "An unexpected error occurred during retrieval of table metadata"
1633
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
1634
+ raise error_msg
1635
+ end
1636
+ ensure
1637
+ IBM_DB.free_stmt(stmt) if stmt # Free resources associated with the statement
1638
+ end
1639
+ else # Handle driver execution errors
1640
+ error_msg = IBM_DB.getErrormsg(@connection, IBM_DB::DB_CONN )
1641
+ if error_msg && !error_msg.empty?
1642
+ raise "Failed to retrieve tables metadata due to error: #{error_msg}"
1643
+ else
1644
+ raise StandardError.new('An unexpected error occurred during retrieval of table metadata')
1645
+ end
1646
+ end
1647
+ # Returns the tables array
1648
+ return tables
1649
+ end
1650
+
1651
+ # Returns the primary key of the mentioned table
1652
+ def primary_key(table_name)
1653
+ pk_name = nil
1654
+ stmt = IBM_DB.primary_keys( @connection, nil,
1655
+ @servertype.set_case(@schema),
1656
+ @servertype.set_case(table_name))
1657
+ if(stmt)
1658
+ begin
1659
+ if ( pk_index_row = IBM_DB.fetch_array(stmt) )
1660
+ pk_name = pk_index_row[3].downcase
1661
+ end
1662
+ rescue StandardError => fetch_error # Handle driver fetch errors
1663
+ error_msg = IBM_DB.getErrormsg( stmt, IBM_DB::DB_STMT )
1664
+ if error_msg && !error_msg.empty?
1665
+ raise "Failed to retrieve primarykey metadata during fetch: #{error_msg}"
1666
+ else
1667
+ error_msg = "An unexpected error occurred during retrieval of primary key metadata"
1668
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
1669
+ raise error_msg
1670
+ end
1671
+ ensure # Free resources associated with the statement
1672
+ IBM_DB.free_stmt(stmt) if stmt
1673
+ end
1674
+ else
1675
+ error_msg = IBM_DB.getErrormsg( @connection, IBM_DB::DB_CONN )
1676
+ if error_msg && !error_msg.empty?
1677
+ raise "Failed to retrieve primary key metadata due to error: #{error_msg}"
1678
+ else
1679
+ raise StandardError.new('An unexpected error occurred during primary key retrieval')
1680
+ end
1681
+ end
1682
+ return pk_name
1683
+ end
1684
+
1685
+ # Returns an array of non-primary key indexes for a specified table name
1686
+ def indexes(table_name, name = nil)
1687
+ # to_s required because +table_name+ may be a symbol.
1688
+ table_name = table_name.to_s
1689
+ # Checks if a blank table name has been given.
1690
+ # If so it returns an empty array of columns.
1691
+ return [] if table_name.strip.empty?
1692
+
1693
+ indexes = []
1694
+ pk_index = nil
1695
+ index_schema = []
1696
+
1697
+ #fetch the primary keys of the table using function primary_keys
1698
+ #TABLE_SCHEM:: pk_index[1]
1699
+ #TABLE_NAME:: pk_index[2]
1700
+ #COLUMN_NAME:: pk_index[3]
1701
+ #PK_NAME:: pk_index[5]
1702
+ stmt = IBM_DB.primary_keys( @connection, nil,
1703
+ @servertype.set_case(@schema),
1704
+ @servertype.set_case(table_name))
1705
+ if(stmt)
1706
+ begin
1707
+ while ( pk_index_row = IBM_DB.fetch_array(stmt) )
1708
+ if pk_index_row[5]
1709
+ pk_index_name = pk_index_row[5].downcase
1710
+ pk_index_columns = [pk_index_row[3].downcase] # COLUMN_NAME
1711
+ if pk_index
1712
+ pk_index.columns = pk_index.columns + pk_index_columns
1713
+ else
1714
+ pk_index = IndexDefinition.new(table_name, pk_index_name, true, pk_index_columns)
1715
+ end
1716
+ end
1717
+ end
1718
+ rescue StandardError => fetch_error # Handle driver fetch errors
1719
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
1720
+ if error_msg && !error_msg.empty?
1721
+ raise "Failed to retrieve primarykey metadata during fetch: #{error_msg}"
1722
+ else
1723
+ error_msg = "An unexpected error occurred during retrieval of primary key metadata"
1724
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
1725
+ raise error_msg
1726
+ end
1727
+ ensure # Free resources associated with the statement
1728
+ IBM_DB.free_stmt(stmt) if stmt
1729
+ end
1730
+ else # Handle driver execution errors
1731
+ error_msg = IBM_DB.getErrormsg(@connection, IBM_DB::DB_CONN )
1732
+ if error_msg && !error_msg.empty?
1733
+ raise "Failed to retrieve primary key metadata due to error: #{error_msg}"
1734
+ else
1735
+ raise StandardError.new('An unexpected error occurred during primary key retrieval')
1736
+ end
1737
+ end
1738
+
1739
+ # Query table statistics for all indexes on the table
1740
+ # "TABLE_NAME: #{index_stats[2]}"
1741
+ # "NON_UNIQUE: #{index_stats[3]}"
1742
+ # "INDEX_NAME: #{index_stats[5]}"
1743
+ # "COLUMN_NAME: #{index_stats[8]}"
1744
+ stmt = IBM_DB.statistics( @connection, nil,
1745
+ @servertype.set_case(@schema),
1746
+ @servertype.set_case(table_name), 1 )
1747
+ if(stmt)
1748
+ begin
1749
+ while ( index_stats = IBM_DB.fetch_array(stmt) )
1750
+ is_composite = false
1751
+ if index_stats[5] # INDEX_NAME
1752
+ index_name = index_stats[5].downcase
1753
+ index_unique = (index_stats[3] == 0)
1754
+ index_columns = [index_stats[8].downcase] # COLUMN_NAME
1755
+ index_qualifier = index_stats[4].downcase #Index_Qualifier
1756
+ # Create an IndexDefinition object and add to the indexes array
1757
+ i = 0;
1758
+ indexes.each do |index|
1759
+ if index.name == index_name && index_schema[i] == index_qualifier
1760
+ index.columns = index.columns + index_columns
1761
+ is_composite = true
1762
+ end
1763
+ i = i+1
1764
+ end
1765
+
1766
+ unless is_composite
1767
+ indexes << IndexDefinition.new(table_name, index_name, index_unique, index_columns)
1768
+ index_schema << index_qualifier
1769
+ end
1770
+ end
1771
+ end
1772
+ rescue StandardError => fetch_error # Handle driver fetch errors
1773
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
1774
+ if error_msg && !error_msg.empty?
1775
+ raise "Failed to retrieve index metadata during fetch: #{error_msg}"
1776
+ else
1777
+ error_msg = "An unexpected error occurred during retrieval of index metadata"
1778
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
1779
+ raise error_msg
1780
+ end
1781
+ ensure # Free resources associated with the statement
1782
+ IBM_DB.free_stmt(stmt) if stmt
1783
+ end
1784
+ else # Handle driver execution errors
1785
+ error_msg = IBM_DB.getErrormsg(@connection, IBM_DB::DB_CONN )
1786
+ if error_msg && !error_msg.empty?
1787
+ raise "Failed to retrieve index metadata due to error: #{error_msg}"
1788
+ else
1789
+ raise StandardError.new('An unexpected error occurred during index retrieval')
1790
+ end
1791
+ end
1792
+
1793
+ # remove the primary key index entry.... should not be dumped by the dumper
1794
+
1795
+ i = 0
1796
+ indexes.each do |index|
1797
+ if pk_index && index.columns == pk_index.columns
1798
+ indexes.delete_at(i)
1799
+ end
1800
+ i = i+1
1801
+ end
1802
+ # Returns the indexes array
1803
+ return indexes
1804
+ end
1805
+
1806
+ # Returns an array of Column objects for the table specified by +table_name+
1807
+ def columns(table_name, name = nil)
1808
+ # to_s required because it may be a symbol.
1809
+ table_name = @servertype.set_case(table_name.to_s)
1810
+ # Checks if a blank table name has been given.
1811
+ # If so it returns an empty array
1812
+ return [] if table_name.strip.empty?
1813
+ # +columns+ will contain the resulting array
1814
+ columns = []
1815
+ # Statement required to access all the columns information
1816
+ stmt = IBM_DB.columns( @connection, nil,
1817
+ @servertype.set_case(@schema),
1818
+ @servertype.set_case(table_name) )
1819
+ if(stmt)
1820
+ begin
1821
+ # Fetches all the columns and assigns them to col.
1822
+ # +col+ is an hash with keys/value pairs for a column
1823
+ while col = IBM_DB.fetch_assoc(stmt)
1824
+ column_name = col["column_name"].downcase
1825
+ # Assigns the column default value.
1826
+ column_default_value = col["column_def"]
1827
+ # If there is no default value, it assigns NIL
1828
+ column_default_value = nil if (column_default_value && column_default_value.upcase == 'NULL')
1829
+ # If default value is IDENTITY GENERATED BY DEFAULT (this value is retrieved in case of id columns)
1830
+ column_default_value = nil if (column_default_value && column_default_value.upcase =~ /IDENTITY GENERATED BY DEFAULT/i)
1831
+ # Removes single quotes from the default value
1832
+ column_default_value.gsub!(/^'(.*)'$/, '\1') unless column_default_value.nil?
1833
+ # Assigns the column type
1834
+ column_type = col["type_name"].downcase
1835
+ # Assigns the field length (size) for the column
1836
+ column_length = col["column_size"]
1837
+ column_scale = col["decimal_digits"]
1838
+ # The initializer of the class Column, requires the +column_length+ to be declared
1839
+ # between brackets after the datatype(e.g VARCHAR(50)) for :string and :text types.
1840
+ # If it's a "for bit data" field it does a subsitution in place, if not
1841
+ # it appends the (column_length) string on the supported data types
1842
+ unless column_length.nil? ||
1843
+ column_length == '' ||
1844
+ column_type.sub!(/ \(\) for bit data/i,"(#{column_length}) FOR BIT DATA") ||
1845
+ !column_type =~ /char|lob|graphic/i
1846
+ if column_type =~ /decimal/i
1847
+ column_type << "(#{column_length},#{column_scale})"
1848
+ elsif column_type =~ /smallint|integer|double|date|time|timestamp|xml|bigint/i
1849
+ column_type << "" # override native limits incompatible with table create
1850
+ else
1851
+ column_type << "(#{column_length})"
1852
+ end
1853
+ end
1854
+ # col["NULLABLE"] is 1 if the field is nullable, 0 if not.
1855
+ column_nullable = (col["nullable"] == 1) ? true : false
1856
+ # Make sure the hidden column (db2_generated_rowid_for_lobs) in DB2 z/OS isn't added to the list
1857
+ if !(column_name =~ /db2_generated_rowid_for_lobs/i)
1858
+ # Pushes into the array the *IBM_DBColumn* object, created by passing to the initializer
1859
+ # +column_name+, +default_value+, +column_type+ and +column_nullable+.
1860
+ columns << IBM_DBColumn.new(column_name, column_default_value, column_type, column_nullable)
1861
+ end
1862
+ end
1863
+ rescue StandardError => fetch_error # Handle driver fetch errors
1864
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
1865
+ if error_msg && !error_msg.empty?
1866
+ raise "Failed to retrieve column metadata during fetch: #{error_msg}"
1867
+ else
1868
+ error_msg = "An unexpected error occurred during retrieval of column metadata"
1869
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
1870
+ raise error_msg
1871
+ end
1872
+ ensure # Free resources associated with the statement
1873
+ IBM_DB.free_stmt(stmt) if stmt
1874
+ end
1875
+ else # Handle driver execution errors
1876
+ error_msg = IBM_DB.getErrormsg(@connection, IBM_DB::DB_CONN )
1877
+ if error_msg && !error_msg.empty?
1878
+ raise "Failed to retrieve column metadata due to error: #{error_msg}"
1879
+ else
1880
+ raise StandardError.new('An unexpected error occurred during retrieval of columns metadata')
1881
+ end
1882
+ end
1883
+ # Returns the columns array
1884
+ return columns
1885
+ end
1886
+
1887
+ # Renames a table.
1888
+ # ==== Example
1889
+ # rename_table('octopuses', 'octopi')
1890
+ # Overriden to satisfy IBM data servers syntax
1891
+ def rename_table(name, new_name)
1892
+ # SQL rename table statement
1893
+ rename_table_sql = "RENAME TABLE #{name} TO #{new_name}"
1894
+ stmt = execute(rename_table_sql)
1895
+ # Ensures to free the resources associated with the statement
1896
+ ensure
1897
+ IBM_DB.free_stmt(stmt) if stmt
1898
+ end
1899
+
1900
+ # Renames a column.
1901
+ # ===== Example
1902
+ # rename_column(:suppliers, :description, :name)
1903
+ def rename_column(table_name, column_name, new_column_name)
1904
+ @servertype.rename_column(table_name, column_name, new_column_name)
1905
+ end
1906
+
1907
+ # Removes the column from the table definition.
1908
+ # ===== Examples
1909
+ # remove_column(:suppliers, :qualification)
1910
+ def remove_column(table_name, column_name)
1911
+ @servertype.remove_column(table_name, column_name)
1912
+ end
1913
+
1914
+ # Changes the column's definition according to the new options.
1915
+ # See TableDefinition#column for details of the options you can use.
1916
+ # ===== Examples
1917
+ # change_column(:suppliers, :name, :string, :limit => 80)
1918
+ # change_column(:accounts, :description, :text)
1919
+ def change_column(table_name, column_name, type, options = {})
1920
+ @servertype.change_column(table_name, column_name, type, options)
1921
+ end
1922
+
1923
+ =begin
1924
+ #overrides the abstract adapter method to generate proper sql
1925
+ #specifying the column options, like default value and nullability clause
1926
+ def add_column_options!(sql,options={})
1927
+ #add default null option only if :default option is not specified and
1928
+ #:null option is not specified or is true
1929
+ if (options[:default].nil? && (options[:null].nil? || options[:null] == true))
1930
+ sql << " DEFAULT NULL"
1931
+ else
1932
+ if( !options[:default].nil?)
1933
+ #check, :column option is passed only in case of create_table but not in case of add_column
1934
+ if (!options[:column].nil?)
1935
+ sql << " DEFAULT #{quote(options[:default],options[:column])}"
1936
+ else
1937
+ sql << " DEFAULT #{quote(options[:default])}"
1938
+ end
1939
+ end
1940
+ #append NOT NULL to sql only---
1941
+ #---if options[:null] is not nil and is equal to false
1942
+ unless options[:null] == nil
1943
+ sql << " NOT NULL" if (options[:null] == false)
1944
+ end
1945
+ end
1946
+ end
1947
+ =end
1948
+
1949
+ #Add distinct clause to the sql if there is no order by specified
1950
+ def distinct(columns, order_by)
1951
+ if order_by.nil?
1952
+ "DISTINCT #{columns}"
1953
+ else
1954
+ "#{columns}"
1955
+ end
1956
+ end
1957
+
1958
+ # Sets a new default value for a column. This does not set the default
1959
+ # value to +NULL+, instead, it needs DatabaseStatements#execute which
1960
+ # can execute the appropriate SQL statement for setting the value.
1961
+ # ==== Examples
1962
+ # change_column_default(:suppliers, :qualification, 'new')
1963
+ # change_column_default(:accounts, :authorized, 1)
1964
+ # Method overriden to satisfy IBM data servers syntax.
1965
+ def change_column_default(table_name, column_name, default)
1966
+ @servertype.change_column_default(table_name, column_name, default)
1967
+ end
1968
+
1969
+ #Changes the nullability value of a column
1970
+ def change_column_null(table_name, column_name, null, default = nil)
1971
+ @servertype.change_column_null(table_name, column_name, null, default)
1972
+ end
1973
+
1974
+ # Remove the given index from the table.
1975
+ #
1976
+ # Remove the suppliers_name_index in the suppliers table (legacy support, use the second or third forms).
1977
+ # remove_index :suppliers, :name
1978
+ # Remove the index named accounts_branch_id in the accounts table.
1979
+ # remove_index :accounts, :column => :branch_id
1980
+ # Remove the index named by_branch_party in the accounts table.
1981
+ # remove_index :accounts, :name => :by_branch_party
1982
+ #
1983
+ # You can remove an index on multiple columns by specifying the first column.
1984
+ # add_index :accounts, [:username, :password]
1985
+ # remove_index :accounts, :username
1986
+ # Overriden to use the IBM data servers SQL syntax.
1987
+ def remove_index(table_name, options = {})
1988
+ execute("DROP INDEX #{index_name(table_name, options)}")
1989
+ end
1990
+ end # class IBM_DBAdapter
1991
+
1992
+ # This class contains common code across DB's (DB2 LUW, zOS, i5 and IDS)
1993
+ class IBM_DataServer
1994
+ def initialize(adapter, ar3)
1995
+ @adapter = adapter
1996
+ @isAr3 = ar3
1997
+ end
1998
+
1999
+ def last_generated_id(stmt)
2000
+ end
2001
+
2002
+ def create_index_after_table (table_name,cloumn_name)
2003
+ end
2004
+
2005
+ def setup_for_lob_table ()
2006
+ end
2007
+
2008
+ def reorg_table(table_name)
2009
+ end
2010
+
2011
+ def check_reserved_words(col_name)
2012
+ col_name.to_s
2013
+ end
2014
+
2015
+ # This is supported by the DB2 for Linux, UNIX, Windows data servers
2016
+ # and by the DB2 for i5 data servers
2017
+ def remove_column(table_name, column_name)
2018
+ begin
2019
+ @adapter.execute "ALTER TABLE #{table_name} DROP #{column_name}"
2020
+ reorg_table(table_name)
2021
+ rescue StandardError => exec_err
2022
+ # Provide details on the current XML columns support
2023
+ if exec_err.message.include?('SQLCODE=-1242') && exec_err.message.include?('42997')
2024
+ raise StatementInvalid,
2025
+ "A column that is part of a table containing an XML column cannot be dropped. \
2026
+ To remove the column, the table must be dropped and recreated without the #{column_name} column: #{exec_err}"
2027
+ else
2028
+ raise "#{exec_err}"
2029
+ end
2030
+ end
2031
+ end
2032
+
2033
+ def select(stmt)
2034
+ results = []
2035
+ # Fetches all the results available. IBM_DB.fetch_assoc(stmt) returns
2036
+ # an hash for each single record.
2037
+ # The loop stops when there aren't any more valid records to fetch
2038
+ begin
2039
+ if(@isAr3)
2040
+ while single_hash = IBM_DB.fetch_assoc(stmt)
2041
+ # Add the record to the +results+ array
2042
+ results << single_hash
2043
+ end
2044
+ else
2045
+ while single_hash = IBM_DB.fetch_array(stmt)
2046
+ # Add the record to the +results+ array
2047
+ results << single_hash
2048
+ end
2049
+ end
2050
+ rescue StandardError => fetch_error # Handle driver fetch errors
2051
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2052
+ if error_msg && !error_msg.empty?
2053
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
2054
+ else
2055
+ error_msg = "An unexpected error occurred during data retrieval"
2056
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2057
+ raise error_msg
2058
+ end
2059
+ end
2060
+ return results
2061
+ end
2062
+
2063
+ def select_rows(sql, name, stmt, results)
2064
+ # Fetches all the results available. IBM_DB.fetch_array(stmt) returns
2065
+ # an array representing a row in a result set.
2066
+ # The loop stops when there aren't any more valid records to fetch
2067
+ begin
2068
+ while single_array = IBM_DB.fetch_array(stmt)
2069
+ #Add the array to results array
2070
+ results << single_array
2071
+ end
2072
+ rescue StandardError => fetch_error # Handle driver fetch errors
2073
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2074
+ if error_msg && !error_msg.empty?
2075
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
2076
+ else
2077
+ error_msg = "An unexpected error occurred during data retrieval"
2078
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2079
+ raise error_msg
2080
+ end
2081
+ end
2082
+ return results
2083
+ end
2084
+
2085
+ # Praveen
2086
+ def prepare(sql,name = nil)
2087
+ begin
2088
+ stmt = IBM_DB.prepare(@adapter.connection, sql)
2089
+ if( stmt )
2090
+ stmt
2091
+ else
2092
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2093
+ end
2094
+ rescue StandardError => prep_err
2095
+ if prep_err && !prep_err.message.empty?
2096
+ raise "Failed to prepare sql #{sql} due to: #{prep_err}"
2097
+ else
2098
+ raise
2099
+ end
2100
+ end
2101
+ end
2102
+
2103
+ def execute(sql, name = nil)
2104
+ begin
2105
+ if stmt = IBM_DB.exec(@adapter.connection, sql)
2106
+ stmt # Return the statement object
2107
+ else
2108
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2109
+ end
2110
+ rescue StandardError => exec_err
2111
+ if exec_err && !exec_err.message.empty?
2112
+ raise "Failed to execute statement due to: #{exec_err}"
2113
+ else
2114
+ raise
2115
+ end
2116
+ end
2117
+ end
2118
+
2119
+ def set_schema(schema)
2120
+ @adapter.execute("SET SCHEMA #{schema}")
2121
+ end
2122
+
2123
+ def query_offset_limit(sql, offset, limit)
2124
+ end
2125
+
2126
+ def query_offset_limit!(sql, offset, limit, options)
2127
+ end
2128
+
2129
+ def get_datetime_mapping
2130
+ end
2131
+
2132
+ def get_time_mapping
2133
+ end
2134
+
2135
+ def get_double_mapping
2136
+ end
2137
+
2138
+ def change_column_default(table_name, column_name, default)
2139
+ end
2140
+
2141
+ def change_column_null(table_name, column_name, null, default)
2142
+ end
2143
+
2144
+ def set_binary_default(value)
2145
+ end
2146
+
2147
+ def set_binary_value
2148
+ end
2149
+
2150
+ def set_text_default
2151
+ end
2152
+
2153
+ def set_case(value)
2154
+ end
2155
+
2156
+ def limit_not_supported_types
2157
+ [:integer, :double, :date, :time, :timestamp, :xml, :bigint]
2158
+ end
2159
+ end # class IBM_DataServer
2160
+
2161
+ class IBM_DB2 < IBM_DataServer
2162
+ def initialize(adapter, ar3)
2163
+ super(adapter,ar3)
2164
+ @limit = @offset = nil
2165
+ end
2166
+
2167
+ def rename_column(table_name, column_name, new_column_name)
2168
+ raise NotImplementedError, "rename_column is not implemented yet in the IBM_DB Adapter"
2169
+ end
2170
+
2171
+ def primary_key_definition(start_id)
2172
+ return "INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH #{start_id}) PRIMARY KEY"
2173
+ end
2174
+
2175
+ # Returns the last automatically generated ID.
2176
+ # This method is required by the +insert+ method
2177
+ # The "stmt" parameter is ignored for DB2 but used for IDS
2178
+ def last_generated_id(stmt)
2179
+ # Queries the db to obtain the last ID that was automatically generated
2180
+ sql = "SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1"
2181
+ stmt = IBM_DB.prepare(@adapter.connection, sql)
2182
+ if(stmt)
2183
+ if(IBM_DB.execute(stmt, nil))
2184
+ begin
2185
+ # Fetches the only record available (containing the last id)
2186
+ IBM_DB.fetch_row(stmt)
2187
+ # Retrieves and returns the result of the query with the last id.
2188
+ IBM_DB.result(stmt,0)
2189
+ rescue StandardError => fetch_error # Handle driver fetch errors
2190
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2191
+ if error_msg && !error_msg.empty?
2192
+ raise "Failed to retrieve last generated id: #{error_msg}"
2193
+ else
2194
+ error_msg = "An unexpected error occurred during retrieval of last generated id"
2195
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2196
+ raise error_msg
2197
+ end
2198
+ ensure # Free resources associated with the statement
2199
+ IBM_DB.free_stmt(stmt) if stmt
2200
+ end
2201
+ else
2202
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2203
+ IBM_DB.free_stmt(stmt) if stmt
2204
+ if error_msg && !error_msg.empty?
2205
+ raise "Failed to retrieve last generated id: #{error_msg}"
2206
+ else
2207
+ error_msg = "An unexpected error occurred during retrieval of last generated id"
2208
+ raise error_msg
2209
+ end
2210
+ end
2211
+ else
2212
+ error_msg = IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2213
+ if error_msg && !error_msg.empty?
2214
+ raise "Failed to retrieve last generated id due to error: #{error_msg}"
2215
+ else
2216
+ raise StandardError.new('An unexpected error occurred during retrieval of last generated id')
2217
+ end
2218
+ end
2219
+ end
2220
+
2221
+ def change_column(table_name, column_name, type, options)
2222
+ data_type = @adapter.type_to_sql(type, options[:limit], options[:precision], options[:scale])
2223
+ begin
2224
+ execute "ALTER TABLE #{table_name} ALTER #{column_name} SET DATA TYPE #{data_type}"
2225
+ rescue StandardError => exec_err
2226
+ if exec_err.message.include?('SQLCODE=-190')
2227
+ raise StatementInvalid,
2228
+ "Please consult documentation for compatible data types while changing column datatype. \
2229
+ The column datatype change to [#{data_type}] is not supported by this data server: #{exec_err}"
2230
+ else
2231
+ raise "#{exec_err}"
2232
+ end
2233
+ end
2234
+ reorg_table(table_name)
2235
+ change_column_null(table_name,column_name,options[:null],nil)
2236
+ change_column_default(table_name, column_name, options[:default])
2237
+ reorg_table(table_name)
2238
+ end
2239
+
2240
+ # DB2 specific ALTER TABLE statement to add a default clause
2241
+ def change_column_default(table_name, column_name, default)
2242
+ # SQL statement which alters column's default value
2243
+ change_column_sql = "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} \
2244
+ SET WITH DEFAULT #{@adapter.quote(default)}"
2245
+
2246
+ stmt = execute(change_column_sql)
2247
+ reorg_table(table_name)
2248
+ ensure
2249
+ IBM_DB.free_stmt(stmt) if stmt
2250
+ end
2251
+
2252
+ #DB2 specific ALTER TABLE statement to change the nullability of a column
2253
+ def change_column_null(table_name, column_name, null, default)
2254
+ if !default.nil?
2255
+ change_column_default(table_name, column_name, default)
2256
+ end
2257
+
2258
+ if !null.nil?
2259
+ if null
2260
+ change_column_sql = "ALTER TABLE #{table_name} ALTER #{column_name} DROP NOT NULL"
2261
+ else
2262
+ change_column_sql = "ALTER TABLE #{table_name} ALTER #{column_name} SET NOT NULL"
2263
+ end
2264
+ stmt = execute(change_column_sql)
2265
+ reorg_table(table_name)
2266
+ end
2267
+
2268
+ ensure
2269
+ IBM_DB.free_stmt(stmt) if stmt
2270
+ end
2271
+
2272
+ # This method returns the DB2 SQL type corresponding to the Rails
2273
+ # datetime/timestamp type
2274
+ def get_datetime_mapping
2275
+ return "timestamp"
2276
+ end
2277
+
2278
+ # This method returns the DB2 SQL type corresponding to the Rails
2279
+ # time type
2280
+ def get_time_mapping
2281
+ return "time"
2282
+ end
2283
+
2284
+ #This method returns the DB2 SQL type corresponding to Rails double type
2285
+ def get_double_mapping
2286
+ return "double"
2287
+ end
2288
+ =begin
2289
+ # Commenting this code, as offset handling is now part of sql and we need to handle it in select and also
2290
+ # need not set cursor type during prepare or execute
2291
+ # Fetches all the results available. IBM_DB.fetch_assoc(stmt) returns
2292
+ # an hash for each single record.
2293
+ # The loop stops when there aren't any more valid records to fetch
2294
+ def select(stmt)
2295
+ results = []
2296
+ begin
2297
+ if (!@offset.nil? && @offset >= 0) || (!@limit.nil? && @limit > 0)
2298
+ # We know at this point that there is an offset and/or a limit
2299
+ # Check if the cursor type is set correctly
2300
+ cursor_type = IBM_DB.get_option stmt, IBM_DB::SQL_ATTR_CURSOR_TYPE, 0
2301
+ @offset = 0 if @offset.nil?
2302
+ if (cursor_type == IBM_DB::SQL_CURSOR_STATIC)
2303
+ index = 0
2304
+ # Get @limit rows starting at @offset
2305
+ while (index < @limit)
2306
+ # We increment the offset by 1 because for DB2 the offset of the initial row is 1 instead of 0
2307
+ if single_hash = IBM_DB.fetch_assoc(stmt, @offset + index + 1)
2308
+ # Add the record to the +results+ array
2309
+ results << single_hash
2310
+ index = index + 1
2311
+ else
2312
+ # break from the while loop
2313
+ break
2314
+ end
2315
+ end
2316
+ else # cursor != IBM_DB::SQL_CURSOR_STATIC
2317
+ # If the result set contains a LOB, the cursor type will never be SQL_CURSOR_STATIC
2318
+ # because DB2 does not allow this. We can't use the offset mechanism because the cursor
2319
+ # is not scrollable. In this case, ignore first @offset rows and return rows starting
2320
+ # at @offset to @offset + @limit
2321
+ index = 0
2322
+ while (index < @offset + @limit)
2323
+ if single_hash = IBM_DB.fetch_assoc(stmt)
2324
+ # Add the record to the +results+ array only from row @offset to @offset + @limit
2325
+ if (index >= @offset)
2326
+ results << single_hash
2327
+ end
2328
+ index = index + 1
2329
+ else
2330
+ # break from the while loop
2331
+ break
2332
+ end
2333
+ end
2334
+ end
2335
+ # This is the case where limit is set to zero
2336
+ # Simply return an empty +results+
2337
+ elsif (!@limit.nil? && @limit == 0)
2338
+ results
2339
+ # No limits or offsets specified
2340
+ else
2341
+ while single_hash = IBM_DB.fetch_assoc(stmt)
2342
+ # Add the record to the +results+ array
2343
+ results << single_hash
2344
+ end
2345
+ return results
2346
+ end
2347
+ rescue StandardError => fetch_error # Handle driver fetch errors
2348
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2349
+ if error_msg && !error_msg.empty?
2350
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
2351
+ else
2352
+ error_msg = "An unexpected error occurred during data retrieval"
2353
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2354
+ raise error_msg
2355
+ end
2356
+ ensure
2357
+ # Assign the instance variables to nil. We will not be using them again
2358
+ @offset = nil
2359
+ @limit = nil
2360
+ end
2361
+ end
2362
+
2363
+ # Fetches all the results available. IBM_DB.fetch_array(stmt) returns
2364
+ # an array for each single record.
2365
+ # The loop stops when there aren't any more valid records to fetch
2366
+ def select_rows(sql, name, stmt, results)
2367
+ begin
2368
+ if (!@offset.nil? && @offset >= 0) || (!@limit.nil? && @limit > 0)
2369
+ # We know at this point that there is an offset and/or a limit
2370
+ # Check if the cursor type is set correctly
2371
+ cursor_type = IBM_DB.get_option stmt, IBM_DB::SQL_ATTR_CURSOR_TYPE, 0
2372
+ @offset = 0 if @offset.nil?
2373
+ if (cursor_type == IBM_DB::SQL_CURSOR_STATIC)
2374
+ index = 0
2375
+ # Get @limit rows starting at @offset
2376
+ while (index < @limit)
2377
+ # We increment the offset by 1 because for DB2 the offset of the initial row is 1 instead of 0
2378
+ if single_array = IBM_DB.fetch_array(stmt, @offset + index + 1)
2379
+ # Add the array to the +results+ array
2380
+ results << single_array
2381
+ index = index + 1
2382
+ else
2383
+ # break from the while loop
2384
+ break
2385
+ end
2386
+ end
2387
+ else # cursor != IBM_DB::SQL_CURSOR_STATIC
2388
+ # If the result set contains a LOB, the cursor type will never be SQL_CURSOR_STATIC
2389
+ # because DB2 does not allow this. We can't use the offset mechanism because the cursor
2390
+ # is not scrollable. In this case, ignore first @offset rows and return rows starting
2391
+ # at @offset to @offset + @limit
2392
+ index = 0
2393
+ while (index < @offset + @limit)
2394
+ if single_array = IBM_DB.fetch_array(stmt)
2395
+ # Add the array to the +results+ array only from row @offset to @offset + @limit
2396
+ if (index >= @offset)
2397
+ results << single_array
2398
+ end
2399
+ index = index + 1
2400
+ else
2401
+ # break from the while loop
2402
+ break
2403
+ end
2404
+ end
2405
+ end
2406
+ # This is the case where limit is set to zero
2407
+ # Simply return an empty +results+
2408
+ elsif (!@limit.nil? && @limit == 0)
2409
+ results
2410
+ # No limits or offsets specified
2411
+ else
2412
+ while single_array = IBM_DB.fetch_array(stmt)
2413
+ # Add the array to the +results+ array
2414
+ results << single_array
2415
+ end
2416
+ end
2417
+ rescue StandardError => fetch_error # Handle driver fetch errors
2418
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2419
+ if error_msg && !error_msg.empty?
2420
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
2421
+ else
2422
+ error_msg = "An unexpected error occurred during data retrieval"
2423
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2424
+ raise error_msg
2425
+ end
2426
+ ensure
2427
+ # Assign the instance variables to nil. We will not be using them again
2428
+ @offset = nil
2429
+ @limit = nil
2430
+ end
2431
+ return results
2432
+ end
2433
+
2434
+ # Praveen
2435
+ def prepare(sql,name = nil)
2436
+ # Check if there is a limit and/or an offset
2437
+ # If so then make sure and use a static cursor type
2438
+ begin
2439
+ if (!@offset.nil? && @offset >= 0) || (!@limit.nil? && @limit > 0)
2440
+ # Set the cursor type to static so we can later utilize the offset and limit correctly
2441
+ if stmt = IBM_DB.prepare(@adapter.connection, sql,
2442
+ {IBM_DB::SQL_ATTR_CURSOR_TYPE => IBM_DB::SQL_CURSOR_STATIC})
2443
+ stmt # Return the statement object
2444
+ else
2445
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2446
+ end
2447
+ else
2448
+ if stmt = IBM_DB.prepare(@adapter.connection, sql)
2449
+ stmt # Return the statement object
2450
+ else
2451
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2452
+ end
2453
+ end
2454
+ rescue StandardError => prep_err
2455
+ error_msg = "Failed to prepare sql #{sql}"
2456
+ error_msg = error_msg + ": #{prep_err.message}" if !prep_err.message.empty?
2457
+ raise error_msg
2458
+ end
2459
+ end
2460
+
2461
+ # Praveen
2462
+ def execute(sql, name = nil)
2463
+ # Check if there is a limit and/or an offset
2464
+ # If so then make sure and use a static cursor type
2465
+ begin
2466
+ if (!@offset.nil? && @offset >= 0) || (!@limit.nil? && @limit > 0)
2467
+ # Set the cursor type to static so we can later utilize the offset and limit correctly
2468
+ if stmt = IBM_DB.exec(@adapter.connection, sql,
2469
+ {IBM_DB::SQL_ATTR_CURSOR_TYPE => IBM_DB::SQL_CURSOR_STATIC})
2470
+ stmt # Return the statement object
2471
+ else
2472
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2473
+ end
2474
+ else
2475
+ if stmt = IBM_DB.exec(@adapter.connection, sql)
2476
+ stmt # Return the statement object
2477
+ else
2478
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2479
+ end
2480
+ end
2481
+ rescue StandardError => exec_err
2482
+ error_msg = "Failed to execute statement"
2483
+ error_msg = error_msg + ": #{exec_err.message}" if !exec_err.message.empty?
2484
+ raise error_msg
2485
+ end
2486
+ end
2487
+ =end
2488
+ def query_offset_limit(sql, offset, limit)
2489
+ if(offset.nil? && limit.nil?)
2490
+ return sql
2491
+ end
2492
+
2493
+ if (offset.nil?)
2494
+ return sql << " FETCH FIRST #{limit} ROWS ONLY"
2495
+ end
2496
+
2497
+ if(limit.nil?)
2498
+ sql.sub!(/SELECT/i,"SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_row_num FROM (SELECT")
2499
+ return sql << ") AS I) AS O WHERE sys_row_num > #{offset}"
2500
+ end
2501
+
2502
+ # Defines what will be the last record
2503
+ last_record = offset + limit
2504
+ # Transforms the SELECT query in order to retrieve/fetch only
2505
+ # a number of records after the specified offset.
2506
+ # 'select' or 'SELECT' is replaced with the partial query below that adds the sys_row_num column
2507
+ # to select with the condition of this column being between offset+1 and the offset+limit
2508
+ sql.sub!(/SELECT/i,"SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_row_num FROM (SELECT")
2509
+ # The final part of the query is appended to include a WHERE...BETWEEN...AND condition,
2510
+ # and retrieve only a LIMIT number of records starting from the OFFSET+1
2511
+ sql << ") AS I) AS O WHERE sys_row_num BETWEEN #{offset+1} AND #{last_record}"
2512
+ end
2513
+
2514
+ def query_offset_limit!(sql, offset, limit, options)
2515
+ if(offset.nil? && limit.nil?)
2516
+ options[:paramArray] = []
2517
+ return sql
2518
+ end
2519
+
2520
+ if (offset.nil?)
2521
+ options[:paramArray] = []
2522
+ return sql << " FETCH FIRST #{limit} ROWS ONLY"
2523
+ end
2524
+
2525
+ if(limit.nil?)
2526
+ sql.sub!(/SELECT/i,"SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_row_num FROM (SELECT")
2527
+ sql << ") AS I) AS O WHERE sys_row_num > ?"
2528
+ options[:paramArray] = [offset]
2529
+ return
2530
+ end
2531
+
2532
+ # Defines what will be the last record
2533
+ last_record = offset + limit
2534
+ # Transforms the SELECT query in order to retrieve/fetch only
2535
+ # a number of records after the specified offset.
2536
+ # 'select' or 'SELECT' is replaced with the partial query below that adds the sys_row_num column
2537
+ # to select with the condition of this column being between offset+1 and the offset+limit
2538
+ sql.sub!(/SELECT/i,"SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_row_num FROM (SELECT")
2539
+ # The final part of the query is appended to include a WHERE...BETWEEN...AND condition,
2540
+ # and retrieve only a LIMIT number of records starting from the OFFSET+1
2541
+ sql << ") AS I) AS O WHERE sys_row_num BETWEEN ? AND ?"
2542
+ options[:paramArray] = [offset+1, last_record]
2543
+ end
2544
+
2545
+ # This method generates the default blob value specified for
2546
+ # DB2 Dataservers
2547
+ def set_binary_default(value)
2548
+ "BLOB('#{value}')"
2549
+ end
2550
+
2551
+ # This method generates the blob value specified for DB2 Dataservers
2552
+ def set_binary_value
2553
+ "BLOB('?')"
2554
+ end
2555
+
2556
+ # This method generates the default clob value specified for
2557
+ # DB2 Dataservers
2558
+ def set_text_default(value)
2559
+ "'#{value}'"
2560
+ end
2561
+
2562
+ # For DB2 Dataservers , the arguments to the meta-data functions
2563
+ # need to be in upper-case
2564
+ def set_case(value)
2565
+ value.upcase
2566
+ end
2567
+ end # class IBM_DB2
2568
+
2569
+ class IBM_DB2_LUW < IBM_DB2
2570
+ # Reorganizes the table for column changes
2571
+ def reorg_table(table_name)
2572
+ execute("CALL ADMIN_CMD('REORG TABLE #{table_name}')")
2573
+ end
2574
+ end # class IBM_DB2_LUW
2575
+
2576
+ class IBM_DB2_LUW_COBRA < IBM_DB2_LUW
2577
+ # Cobra supports parameterised timestamp,
2578
+ # hence overriding following method to allow timestamp datatype to be parameterised
2579
+ def limit_not_supported_types
2580
+ [:integer, :double, :date, :time, :xml, :bigint]
2581
+ end
2582
+
2583
+ # Alter table column for renaming a column
2584
+ # This feature is supported for against DB2 V97 and above only
2585
+ def rename_column(table_name, column_name, new_column_name)
2586
+ _table_name = table_name.to_s
2587
+ _column_name = column_name.to_s
2588
+ _new_column_name = new_column_name.to_s
2589
+
2590
+ nil_condition = _table_name.nil? || _column_name.nil? || _new_column_name.nil?
2591
+ empty_condition = _table_name.empty? ||
2592
+ _column_name.empty? ||
2593
+ _new_column_name.empty? unless nil_condition
2594
+
2595
+ if nil_condition || empty_condition
2596
+ raise ArgumentError,"One of the arguments passed to rename_column is empty or nil"
2597
+ end
2598
+
2599
+ begin
2600
+ rename_column_sql = "ALTER TABLE #{_table_name} RENAME COLUMN #{_column_name} \
2601
+ TO #{_new_column_name}"
2602
+
2603
+ unless stmt = execute(rename_column_sql)
2604
+ error_msg = IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2605
+ if error_msg && !error_msg.empty?
2606
+ raise "Rename column failed : #{error_msg}"
2607
+ else
2608
+ raise StandardError.new('An unexpected error occurred during renaming the column')
2609
+ end
2610
+ end
2611
+
2612
+ reorg_table(_table_name)
2613
+
2614
+ ensure
2615
+ IBM_DB.free_stmt(stmt) if stmt
2616
+ end #End of begin
2617
+ end # End of rename_column
2618
+ end #IBM_DB2_LUW_COBRA
2619
+
2620
+ module HostedDataServer
2621
+ require 'pathname'
2622
+ #find DB2-i5-zOS rezerved words file relative path
2623
+ rfile = Pathname.new(File.dirname(__FILE__)).parent + 'vendor' + 'db2-i5-zOS.yaml'
2624
+ if rfile
2625
+ RESERVED_WORDS = open(rfile.to_s) {|f| YAML.load(f) }
2626
+ def check_reserved_words(col_name)
2627
+ if RESERVED_WORDS[col_name]
2628
+ '"' + RESERVED_WORDS[col_name] + '"'
2629
+ else
2630
+ col_name.to_s
2631
+ end
2632
+ end
2633
+ else
2634
+ raise "Failed to locate IBM_DB Adapter dependency: #{rfile}"
2635
+ end
2636
+ end # module HostedDataServer
2637
+
2638
+ class IBM_DB2_ZOS < IBM_DB2
2639
+ # since v9 doesn't need, suggest putting it in HostedDataServer?
2640
+ def create_index_after_table(table_name,column_name)
2641
+ @adapter.add_index(table_name, column_name, :unique => true)
2642
+ end
2643
+
2644
+ def remove_column(table_name, column_name)
2645
+ raise NotImplementedError,
2646
+ "remove_column is not supported by the DB2 for zOS data server"
2647
+ end
2648
+
2649
+ #Alter table column for renaming a column
2650
+ def rename_column(table_name, column_name, new_column_name)
2651
+ _table_name = table_name.to_s
2652
+ _column_name = column_name.to_s
2653
+ _new_column_name = new_column_name.to_s
2654
+
2655
+ nil_condition = _table_name.nil? || _column_name.nil? || _new_column_name.nil?
2656
+ empty_condition = _table_name.empty? ||
2657
+ _column_name.empty? ||
2658
+ _new_column_name.empty? unless nil_condition
2659
+
2660
+ if nil_condition || empty_condition
2661
+ raise ArgumentError,"One of the arguments passed to rename_column is empty or nil"
2662
+ end
2663
+
2664
+ begin
2665
+ rename_column_sql = "ALTER TABLE #{_table_name} RENAME COLUMN #{_column_name} \
2666
+ TO #{_new_column_name}"
2667
+
2668
+ unless stmt = execute(rename_column_sql)
2669
+ error_msg = IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2670
+ if error_msg && !error_msg.empty?
2671
+ raise "Rename column failed : #{error_msg}"
2672
+ else
2673
+ raise StandardError.new('An unexpected error occurred during renaming the column')
2674
+ end
2675
+ end
2676
+
2677
+ reorg_table(_table_name)
2678
+
2679
+ ensure
2680
+ IBM_DB.free_stmt(stmt) if stmt
2681
+ end #End of begin
2682
+ end # End of rename_column
2683
+
2684
+ # DB2 z/OS only allows NULL or "" (empty) string as DEFAULT value for a BLOB column.
2685
+ # For non-empty string and non-NULL values, the server returns error
2686
+ def set_binary_default(value)
2687
+ "#{value}"
2688
+ end
2689
+
2690
+ def change_column_default(table_name, column_name, default)
2691
+ unless default
2692
+ raise NotImplementedError,
2693
+ "DB2 for zOS data server version 9 does not support changing the column default to NULL"
2694
+ else
2695
+ super
2696
+ end
2697
+ end
2698
+
2699
+ def change_column_null(table_name, column_name, null, default)
2700
+ raise NotImplementedError,
2701
+ "DB2 for zOS data server does not support changing the column's nullability"
2702
+ end
2703
+ end # class IBM_DB2_ZOS
2704
+
2705
+ class IBM_DB2_ZOS_8 < IBM_DB2_ZOS
2706
+ include HostedDataServer
2707
+
2708
+ def query_offset_limit(sql, offset, limit)
2709
+ if (!limit.nil?)
2710
+ sql << " FETCH FIRST #{limit} ROWS ONLY"
2711
+ end
2712
+ return sql
2713
+ end
2714
+
2715
+ def query_offset_limit!(sql, offset, limit, options)
2716
+ if (!limit.nil?)
2717
+ sql << " FETCH FIRST #{limit} ROWS ONLY"
2718
+ end
2719
+ options[:paramArray] = []
2720
+ end
2721
+
2722
+ # This call is needed on DB2 z/OS v8 for the creation of tables
2723
+ # with LOBs. When issued, this call does the following:
2724
+ # DB2 creates LOB table spaces, auxiliary tables, and indexes on auxiliary
2725
+ # tables for LOB columns.
2726
+ def setup_for_lob_table()
2727
+ execute "SET CURRENT RULES = 'STD'"
2728
+ end
2729
+
2730
+ def rename_column(table_name, column_name, new_column_name)
2731
+ raise NotImplementedError, "rename_column is not implemented for DB2 on zOS 8"
2732
+ end
2733
+
2734
+ def change_column_default(table_name, column_name, default)
2735
+ raise NotImplementedError,
2736
+ "DB2 for zOS data server version 8 does not support changing the column default"
2737
+ end
2738
+
2739
+ end # class IBM_DB2_ZOS_8
2740
+
2741
+ class IBM_DB2_I5 < IBM_DB2
2742
+ include HostedDataServer
2743
+ end # class IBM_DB2_I5
2744
+
2745
+ class IBM_IDS < IBM_DataServer
2746
+ # IDS does not support the SET SCHEMA syntax
2747
+ def set_schema(schema)
2748
+ end
2749
+
2750
+ # IDS specific ALTER TABLE statement to rename a column
2751
+ def rename_column(table_name, column_name, new_column_name)
2752
+ _table_name = table_name.to_s
2753
+ _column_name = column_name.to_s
2754
+ _new_column_name = new_column_name.to_s
2755
+
2756
+ nil_condition = _table_name.nil? || _column_name.nil? || _new_column_name.nil?
2757
+ empty_condition = _table_name.empty? ||
2758
+ _column_name.empty? ||
2759
+ _new_column_name.empty? unless nil_condition
2760
+
2761
+ if nil_condition || empty_condition
2762
+ raise ArgumentError,"One of the arguments passed to rename_column is empty or nil"
2763
+ end
2764
+
2765
+ begin
2766
+ rename_column_sql = "RENAME COLUMN #{table_name}.#{column_name} TO \
2767
+ #{new_column_name}"
2768
+
2769
+ unless stmt = execute(rename_column_sql)
2770
+ error_msg = IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2771
+ if error_msg && !error_msg.empty?
2772
+ raise "Rename column failed : #{error_msg}"
2773
+ else
2774
+ raise StandardError.new('An unexpected error occurred during renaming the column')
2775
+ end
2776
+ end
2777
+
2778
+ reorg_table(_table_name)
2779
+
2780
+ ensure
2781
+ IBM_DB.free_stmt(stmt) if stmt
2782
+ end #End of begin
2783
+ end # End of rename_column
2784
+
2785
+ def primary_key_definition(start_id)
2786
+ return "SERIAL(#{start_id}) PRIMARY KEY"
2787
+ end
2788
+
2789
+ def change_column(table_name, column_name, type, options)
2790
+ if !options[:null].nil? && !options[:null]
2791
+ execute "ALTER TABLE #{table_name} MODIFY #{column_name} #{@adapter.type_to_sql(type, options[:limit], options[:precision], options[:scale])} NOT NULL"
2792
+ else
2793
+ execute "ALTER TABLE #{table_name} MODIFY #{column_name} #{@adapter.type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
2794
+ end
2795
+ if !options[:default].nil?
2796
+ change_column_default(table_name, column_name, options[:default])
2797
+ end
2798
+ reorg_table(table_name)
2799
+ end
2800
+
2801
+ # IDS specific ALTER TABLE statement to add a default clause
2802
+ # IDS requires the data type to be explicitly specified when adding the
2803
+ # DEFAULT clause
2804
+ def change_column_default(table_name, column_name, default)
2805
+ sql_type = nil
2806
+ is_nullable = true
2807
+ @adapter.columns(table_name).select do |col|
2808
+ if (col.name == column_name)
2809
+ sql_type = @adapter.type_to_sql(col.type, col.limit, col.precision, col.scale)
2810
+ is_nullable = col.null
2811
+ end
2812
+ end
2813
+ # SQL statement which alters column's default value
2814
+ change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{sql_type} DEFAULT #{@adapter.quote(default)}"
2815
+ change_column_sql << " NOT NULL" unless is_nullable
2816
+ stmt = execute(change_column_sql)
2817
+ reorg_table(table_name)
2818
+ # Ensures to free the resources associated with the statement
2819
+ ensure
2820
+ IBM_DB.free_stmt(stmt) if stmt
2821
+ end
2822
+
2823
+ # IDS specific ALTER TABLE statement to change the nullability of a column
2824
+ def change_column_null(table_name,column_name,null,default)
2825
+ if !default.nil?
2826
+ change_column_default table_name, column_name, default
2827
+ end
2828
+ sql_type = nil
2829
+ @adapter.columns(table_name).select do |col|
2830
+ if (col.name == column_name)
2831
+ sql_type = @adapter.type_to_sql(col.type, col.limit, col.precision, col.scale)
2832
+ end
2833
+ end
2834
+ if !null.nil?
2835
+ if !null
2836
+ change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{sql_type} NOT NULL"
2837
+ else
2838
+ change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{sql_type}"
2839
+ end
2840
+ stmt = execute(change_column_sql)
2841
+ reorg_table(table_name)
2842
+ end
2843
+
2844
+ ensure
2845
+ IBM_DB.free_stmt(stmt) if stmt
2846
+ end
2847
+
2848
+ # Reorganizes the table for column changes
2849
+ def reorg_table(table_name)
2850
+ execute("UPDATE STATISTICS FOR TABLE #{table_name}")
2851
+ end
2852
+
2853
+ # This method returns the IDS SQL type corresponding to the Rails
2854
+ # datetime/timestamp type
2855
+ def get_datetime_mapping
2856
+ return "datetime year to fraction(5)"
2857
+ end
2858
+
2859
+ # This method returns the IDS SQL type corresponding to the Rails
2860
+ # time type
2861
+ def get_time_mapping
2862
+ return "datetime hour to second"
2863
+ end
2864
+
2865
+ # This method returns the IDS SQL type corresponding to Rails double type
2866
+ def get_double_mapping
2867
+ return "double precision"
2868
+ end
2869
+
2870
+ # Handling offset/limit as per Informix requirements
2871
+ def query_offset_limit(sql, offset, limit)
2872
+ if limit != 0
2873
+ if !offset.nil?
2874
+ # Modifying the SQL to utilize the skip and limit amounts
2875
+ sql.gsub!(/SELECT/i,"SELECT SKIP #{offset} LIMIT #{limit}")
2876
+ else
2877
+ # Modifying the SQL to retrieve only the first #{limit} rows
2878
+ sql = sql.gsub!("SELECT","SELECT FIRST #{limit}")
2879
+ end
2880
+ else
2881
+ # Modifying the SQL to ensure that no rows will be returned
2882
+ sql.gsub!(/SELECT/i,"SELECT * FROM (SELECT")
2883
+ sql << ") WHERE 0 = 1"
2884
+ end
2885
+ end
2886
+
2887
+ # Handling offset/limit as per Informix requirements
2888
+ def query_offset_limit!(sql, offset, limit, options)
2889
+ if limit != 0
2890
+ if !offset.nil?
2891
+ # Modifying the SQL to utilize the skip and limit amounts
2892
+ sql.gsub!(/SELECT/i,"SELECT SKIP #{offset} LIMIT #{limit}")
2893
+ else
2894
+ # Modifying the SQL to retrieve only the first #{limit} rows
2895
+ sql = sql.gsub!("SELECT","SELECT FIRST #{limit}")
2896
+ end
2897
+ else
2898
+ # Modifying the SQL to ensure that no rows will be returned
2899
+ sql.gsub!(/SELECT/i,"SELECT * FROM (SELECT")
2900
+ sql << ") WHERE 0 = 1"
2901
+ end
2902
+ end
2903
+
2904
+ # Method that returns the last automatically generated ID
2905
+ # on the given +@connection+. This method is required by the +insert+
2906
+ # method. IDS returns the last generated serial value in the SQLCA unlike
2907
+ # DB2 where the generated value has to be retrieved using the
2908
+ # IDENTITY_VAL_LOCAL function. We used the "stmt" parameter to identify
2909
+ # the statement resource from which to get the last generated value
2910
+ def last_generated_id(stmt)
2911
+ IBM_DB.get_last_serial_value(stmt)
2912
+ end
2913
+
2914
+ # This method throws an error when trying to create a default value on a
2915
+ # BLOB/CLOB column for IDS. The documentation states: "if the column is a
2916
+ # BLOB or CLOB datatype, NULL is the only valid default value."
2917
+ def set_binary_default(value)
2918
+ unless (value == 'NULL')
2919
+ raise "Informix Dynamic Server only allows NULL as a valid default value for a BLOB data type"
2920
+ end
2921
+ end
2922
+
2923
+ # For Informix Dynamic Server, we treat binary value same as we treat a
2924
+ # text value. We support literals by converting the insert into a dummy
2925
+ # insert and an update (See handle_lobs method above)
2926
+ def set_binary_value
2927
+ "'@@@IBMBINARY@@@'"
2928
+ end
2929
+
2930
+ # This method throws an error when trying to create a default value on a
2931
+ # BLOB/CLOB column for IDS. The documentation states: "if the column is
2932
+ # a BLOB or CLOB datatype, NULL is the only valid default value."
2933
+ def set_text_default(value)
2934
+ unless (value == 'NULL')
2935
+ raise "Informix Dynamic Server only allows NULL as a valid default value for a CLOB data type"
2936
+ end
2937
+ end
2938
+
2939
+ # For Informix Dynamic Server, the arguments to the meta-data functions
2940
+ # need to be in lower-case
2941
+ def set_case(value)
2942
+ value.downcase
2943
+ end
2944
+ end # class IBM_IDS
2945
+ end # module ConnectionAdapters
2946
+ end # module ActiveRecord
2947
+
2948
+ module Arel
2949
+ module Visitors
2950
+ class Visitor #opening and closing the class to ensure backward compatibility
2951
+ end
2952
+
2953
+ class ToSql < Arel::Visitors::Visitor #opening and closing the class to ensure backward compatibility
2954
+ # In case when using Rails-2.3.x there is no arel used due to which the constructor has to be defined explicitly
2955
+ # to ensure the same code works on any version of Rails
2956
+
2957
+ #Check Arel version
2958
+ begin
2959
+ @arelVersion = Arel::VERSION.to_i
2960
+ rescue
2961
+ @arelVersion = 0
2962
+ end
2963
+
2964
+ if(@arelVersion >= 3)
2965
+ def initialize connection
2966
+ @connection = connection
2967
+ @schema_cache = connection.schema_cache if(connection.respond_to?(:schema_cache))
2968
+ @quoted_tables = {}
2969
+ @quoted_columns = {}
2970
+ @last_column = nil
2971
+ end
2972
+ end
2973
+ end
2974
+
2975
+ class IBM_DB < Arel::Visitors::ToSql
2976
+ private
2977
+
2978
+ def visit_Arel_Nodes_Limit o, a=nil
2979
+ visit o.expr
2980
+ end
2981
+
2982
+ def visit_Arel_Nodes_Offset o, a=nil
2983
+ visit o.expr
2984
+ end
2985
+
2986
+ def visit_Arel_Nodes_SelectStatement o, a=nil
2987
+ #Interim fix for backward compatibility [Arel 4.0.0 and below]
2988
+ if self.method(:visit_Arel_Nodes_SelectCore).arity == 1
2989
+ sql = [
2990
+ (visit(o.with) if o.with),
2991
+ o.cores.map { |x| visit_Arel_Nodes_SelectCore x }.join,
2992
+ ("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
2993
+ ].compact.join ' '
2994
+ else
2995
+ sql = [
2996
+ (visit(o.with) if o.with),
2997
+ o.cores.map { |x| visit_Arel_Nodes_SelectCore x,a }.join,
2998
+ ("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
2999
+ ].compact.join ' '
3000
+ end
3001
+
3002
+ if o.limit
3003
+ limit = visit(o.limit)
3004
+ else
3005
+ limit = nil
3006
+ end
3007
+
3008
+ if o.offset
3009
+ offset = visit(o.offset)
3010
+ else
3011
+ offset = nil
3012
+ end
3013
+ @connection.add_limit_offset!(sql, {:limit => limit, :offset => offset})
3014
+ sql << " #{(visit(o.lock) if o.lock)}"
3015
+ return sql
3016
+ end
3017
+
3018
+ end
3019
+ end
3020
+ end