ibm_db 2.5.17-universal-darwin-13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +221 -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 +16 -0
  16. data/lib/active_record/connection_adapters/ibm_db_adapter.rb +3031 -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/linux/rb18x/ibm_db.bundle +0 -0
  21. data/lib/linux/rb19x/ibm_db.bundle +0 -0
  22. data/lib/linux/rb20x/ibm_db.bundle +0 -0
  23. data/lib/linux/rb21x/ibm_db.bundle +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,16 @@
1
+ if (RUBY_PLATFORM =~ /mswin32/ || RUBY_PLATFORM =~ /mingw32/ )
2
+ require 'mswin32/ibm_db'
3
+ else
4
+ if (RUBY_VERSION =~ /1.9/ )
5
+ require 'linux/rb19x/ibm_db.bundle'
6
+ elsif (RUBY_VERSION =~ /2.0/ )
7
+ require 'linux/rb20x/ibm_db.bundle'
8
+ elsif (RUBY_VERSION =~ /2.1/ )
9
+ require 'linux/rb21x/ibm_db.bundle'
10
+ else
11
+ require 'linux/rb18x/ibm_db.bundle'
12
+ end
13
+ end
14
+ require 'active_record'
15
+ require 'active_record/connection_adapters/ibm_db_adapter'
16
+
@@ -0,0 +1,3031 @@
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
+ when /10./i #DB2 version 10.1 and above
624
+ @servertype = IBM_DB2_LUW_COBRA.new(self, @isAr3)
625
+ else # DB2 Version 9.5 or below
626
+ @servertype = IBM_DB2_LUW.new(self, @isAr3)
627
+ end
628
+ when /DB2/i # DB2 for zOS
629
+ case server_info.DBMS_VER
630
+ when /09/ # DB2 for zOS version 9 and version 10
631
+ @servertype = IBM_DB2_ZOS.new(self, @isAr3)
632
+ when /10/
633
+ @servertype = IBM_DB2_ZOS.new(self, @isAr3)
634
+ when /08/ # DB2 for zOS version 8
635
+ @servertype = IBM_DB2_ZOS_8.new(self, @isAr3)
636
+ else # DB2 for zOS version 7
637
+ raise "Only DB2 z/OS version 8 and above are currently supported"
638
+ end
639
+ when /AS/i # DB2 for i5 (iSeries)
640
+ @servertype = IBM_DB2_I5.new(self, @isAr3)
641
+ when /IDS/i # Informix Dynamic Server
642
+ @servertype = IBM_IDS.new(self, @isAr3)
643
+ else
644
+ log( "server_info", "Forcing servertype to LUW: DBMS name could not be retrieved. Check if your client version is of the right level")
645
+ warn "Forcing servertype to LUW: DBMS name could not be retrieved. Check if your client version is of the right level"
646
+ @servertype = IBM_DB2_LUW.new(self, @isAr3)
647
+ end
648
+ else
649
+ error_msg = IBM_DB.getErrormsg( @connection, IBM_DB::DB_CONN )
650
+ IBM_DB.close( @connection )
651
+ raise "Cannot retrieve server information: #{error_msg}"
652
+ end
653
+ end
654
+
655
+ # Executes the +set schema+ statement using the schema identifier provided
656
+ @servertype.set_schema(@schema) if @schema && @schema != @username
657
+
658
+ # Check for the start value for id (primary key column). By default it is 1
659
+ if config.has_key?(:start_id)
660
+ @start_id = config[:start_id]
661
+ else
662
+ @start_id = 1
663
+ end
664
+
665
+ #Check Arel version
666
+ begin
667
+ @arelVersion = Arel::VERSION.to_i
668
+ rescue
669
+ @arelVersion = 0
670
+ end
671
+
672
+ if(@arelVersion >= 3 )
673
+ @visitor = Arel::Visitors::IBM_DB.new self
674
+ end
675
+ end
676
+
677
+ # Optional connection attribute: database name space qualifier
678
+ def schema=(name)
679
+ unless name == @schema
680
+ @schema = name
681
+ @servertype.set_schema(@schema)
682
+ end
683
+ end
684
+
685
+ # Optional connection attribute: authenticated application user
686
+ def app_user=(name)
687
+ unless name == @app_user
688
+ option = {IBM_DB::SQL_ATTR_INFO_USERID => "#{name}"}
689
+ if IBM_DB.set_option( @connection, option, 1 )
690
+ @app_user = IBM_DB.get_option( @connection, IBM_DB::SQL_ATTR_INFO_USERID, 1 )
691
+ end
692
+ end
693
+ end
694
+
695
+ # Optional connection attribute: OS account (client workstation)
696
+ def account=(name)
697
+ unless name == @account
698
+ option = {IBM_DB::SQL_ATTR_INFO_ACCTSTR => "#{name}"}
699
+ if IBM_DB.set_option( @connection, option, 1 )
700
+ @account = IBM_DB.get_option( @connection, IBM_DB::SQL_ATTR_INFO_ACCTSTR, 1 )
701
+ end
702
+ end
703
+ end
704
+
705
+ # Optional connection attribute: application name
706
+ def application=(name)
707
+ unless name == @application
708
+ option = {IBM_DB::SQL_ATTR_INFO_APPLNAME => "#{name}"}
709
+ if IBM_DB.set_option( @connection, option, 1 )
710
+ @application = IBM_DB.get_option( @connection, IBM_DB::SQL_ATTR_INFO_APPLNAME, 1 )
711
+ end
712
+ end
713
+ end
714
+
715
+ # Optional connection attribute: client workstation name
716
+ def workstation=(name)
717
+ unless name == @workstation
718
+ option = {IBM_DB::SQL_ATTR_INFO_WRKSTNNAME => "#{name}"}
719
+ if IBM_DB.set_option( @connection, option, 1 )
720
+ @workstation = IBM_DB.get_option( @connection, IBM_DB::SQL_ATTR_INFO_WRKSTNNAME, 1 )
721
+ end
722
+ end
723
+ end
724
+
725
+ def self.visitor_for(pool)
726
+ Arel::Visitors::IBM_DB.new(pool)
727
+ end
728
+
729
+ def to_sql(arel, binds = [])
730
+ if arel.respond_to?(:ast)
731
+ visitor.accept(arel.ast) do
732
+ quote(*binds.shift.reverse)
733
+ end
734
+ else
735
+ arel
736
+ end
737
+ end
738
+
739
+ # This adapter supports migrations.
740
+ # Current limitations:
741
+ # +rename_column+ is not currently supported by the IBM data servers
742
+ # +remove_column+ is not currently supported by the DB2 for zOS data server
743
+ # Tables containing columns of XML data type do not support +remove_column+
744
+ def supports_migrations?
745
+ true
746
+ end
747
+
748
+ # This Adapter supports DDL transactions.
749
+ # This means CREATE TABLE and other DDL statements can be carried out as a transaction.
750
+ # That is the statements executed can be ROLLED BACK in case of any error during the process.
751
+ def supports_ddl_transactions?
752
+ true
753
+ end
754
+
755
+ def log_query(sql, name) #:nodoc:
756
+ # Used by handle_lobs
757
+ log(sql,name){}
758
+ end
759
+
760
+ #==============================================
761
+ # CONNECTION MANAGEMENT
762
+ #==============================================
763
+
764
+ # Tests the connection status
765
+ def active?
766
+ IBM_DB.active @connection
767
+ rescue
768
+ false
769
+ end
770
+
771
+ # Private method used by +reconnect!+.
772
+ # It connects to the database with the initially provided credentials
773
+ def connect
774
+ # If the type of connection is net based
775
+ if(@username.nil? || @password.nil?)
776
+ raise ArgumentError, "Username/Password cannot be nil"
777
+ end
778
+
779
+ begin
780
+ if @host
781
+ @conn_string = "DRIVER={IBM DB2 ODBC DRIVER};\
782
+ DATABASE=#{@database};\
783
+ HOSTNAME=#{@host};\
784
+ PORT=#{@port};\
785
+ PROTOCOL=TCPIP;\
786
+ UID=#{@username};\
787
+ PWD=#{@password};"
788
+ @conn_string << "SECURITY=#{@security};" if @security
789
+ @conn_string << "AUTHENTICATION=#{@authentication};" if @authentication
790
+ @conn_string << "CONNECTTIMEOUT=#{@timeout};"
791
+ # Connects and assigns the resulting IBM_DB.Connection to the +@connection+ instance variable
792
+ @connection = IBM_DB.connect(@conn_string, '', '', @conn_options, @set_quoted_literal_replacement)
793
+ else
794
+ # Connects to the database using the local alias (@database)
795
+ # and assigns the connection object (IBM_DB.Connection) to @connection
796
+ @connection = IBM_DB.connect(@database, @username, @password, @conn_options, @set_quoted_literal_replacement)
797
+ end
798
+ rescue StandardError => connect_err
799
+ warn "Connection to database #{@database} failed: #{connect_err}"
800
+ @connection = false
801
+ end
802
+ # Sets the schema if different from default (username)
803
+ if @schema && @schema != @username
804
+ @servertype.set_schema(@schema)
805
+ end
806
+ end
807
+ private :connect
808
+
809
+ # Closes the current connection and opens a new one
810
+ def reconnect!
811
+ disconnect!
812
+ connect
813
+ end
814
+
815
+ # Closes the current connection
816
+ def disconnect!
817
+ # Attempts to close the connection. The methods will return:
818
+ # * true if succesfull
819
+ # * false if the connection is already closed
820
+ # * nil if an error is raised
821
+ return nil if @connection.nil? || @connection == false
822
+ IBM_DB.close(@connection) rescue nil
823
+ end
824
+
825
+ #==============================================
826
+ # DATABASE STATEMENTS
827
+ #==============================================
828
+
829
+ def create_table(name, options = {})
830
+ @servertype.setup_for_lob_table
831
+ super
832
+
833
+ #Table definition is complete only when a unique index is created on the primarykey column for DB2 V8 on zOS
834
+
835
+ #create index on id column if options[:id] is nil or id ==true
836
+ #else check if options[:primary_key]is not nil then create an unique index on that column
837
+ if !options[:id].nil? || !options[:primary_key].nil?
838
+ if (!options[:id].nil? && options[:id] == true)
839
+ @servertype.create_index_after_table(name,"id")
840
+ elsif !options[:primary_key].nil?
841
+ @servertype.create_index_after_table(name,options[:primary_key].to_s)
842
+ end
843
+ else
844
+ @servertype.create_index_after_table(name,"id")
845
+ end
846
+ end
847
+
848
+ # Returns an array of hashes with the column names as keys and
849
+ # column values as values. +sql+ is the select query,
850
+ # and +name+ is an optional description for logging
851
+ def prepared_select(sql_param_hash, name = nil)
852
+ # Replaces {"= NULL" with " IS NULL"} OR {"IN (NULL)" with " IS NULL"}
853
+
854
+ results = []
855
+ # Invokes the method +prepare+ in order prepare the SQL
856
+ # IBM_DB.Statement is returned from which the statement is executed and results fetched
857
+ pstmt = prepare(sql_param_hash["sqlSegment"], name)
858
+ if(execute_prepared_stmt(pstmt, sql_param_hash["paramArray"]))
859
+ begin
860
+ results = @servertype.select(pstmt)
861
+ rescue StandardError => fetch_error # Handle driver fetch errors
862
+ error_msg = IBM_DB.getErrormsg(pstmt, IBM_DB::DB_STMT )
863
+ if error_msg && !error_msg.empty?
864
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
865
+ else
866
+ error_msg = "An unexpected error occurred during data retrieval"
867
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
868
+ raise error_msg
869
+ end
870
+ ensure
871
+ # Ensures to free the resources associated with the statement
872
+ IBM_DB.free_stmt(pstmt) if pstmt
873
+ end
874
+ end
875
+ # The array of record hashes is returned
876
+ results
877
+ end
878
+
879
+ # Returns an array of hashes with the column names as keys and
880
+ # column values as values. +sql+ is the select query,
881
+ # and +name+ is an optional description for logging
882
+ def prepared_select_values(sql_param_hash, name = nil)
883
+ # Replaces {"= NULL" with " IS NULL"} OR {"IN (NULL)" with " IS NULL"}
884
+ results = []
885
+ # Invokes the method +prepare+ in order prepare the SQL
886
+ # IBM_DB.Statement is returned from which the statement is executed and results fetched
887
+ pstmt = prepare(sql_param_hash["sqlSegment"], name)
888
+ if(execute_prepared_stmt(pstmt, sql_param_hash["paramArray"]))
889
+ begin
890
+ results = @servertype.select_rows(sql_param_hash["sqlSegment"], name, pstmt, results)
891
+ if results
892
+ return results.map { |v| v[0] }
893
+ else
894
+ nil
895
+ end
896
+ rescue StandardError => fetch_error # Handle driver fetch errors
897
+ error_msg = IBM_DB.getErrormsg(pstmt, IBM_DB::DB_STMT )
898
+ if error_msg && !error_msg.empty?
899
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
900
+ else
901
+ error_msg = "An unexpected error occurred during data retrieval"
902
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
903
+ raise error_msg
904
+ end
905
+ ensure
906
+ # Ensures to free the resources associated with the statement
907
+ IBM_DB.free_stmt(pstmt) if pstmt
908
+ end
909
+ end
910
+ # The array of record hashes is returned
911
+ results
912
+ end
913
+
914
+ #Calls the servertype select method to fetch the data
915
+ def fetch_data(stmt)
916
+ if(stmt)
917
+ begin
918
+ return @servertype.select(stmt)
919
+ rescue StandardError => fetch_error # Handle driver fetch errors
920
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
921
+ if error_msg && !error_msg.empty?
922
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
923
+ else
924
+ error_msg = "An unexpected error occurred during data retrieval"
925
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
926
+ raise error_msg
927
+ end
928
+ ensure
929
+ # Ensures to free the resources associated with the statement
930
+ IBM_DB.free_stmt(stmt) if stmt
931
+ end
932
+ end
933
+ end
934
+ =begin
935
+ # Returns an array of hashes with the column names as keys and
936
+ # column values as values. +sql+ is the select query,
937
+ # and +name+ is an optional description for logging
938
+ def select(sql, name = nil)
939
+ # Replaces {"= NULL" with " IS NULL"} OR {"IN (NULL)" with " IS NULL"}
940
+ sql.gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, " IS NULL" )
941
+
942
+ results = []
943
+ # Invokes the method +execute+ in order to log and execute the SQL
944
+ # IBM_DB.Statement is returned from which results can be fetched
945
+ stmt = execute(sql, name)
946
+
947
+ results = fetch_data(stmt)
948
+ # The array of record hashes is returned
949
+ results
950
+ end
951
+ =end
952
+ def select(sql, name = nil, binds = [])
953
+ # Replaces {"= NULL" with " IS NULL"} OR {"IN (NULL)" with " IS NULL"}
954
+ sql.gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, " IS NULL" )
955
+
956
+ results = []
957
+
958
+ if(binds.nil? || binds.empty?)
959
+ stmt = execute(sql, name)
960
+ else
961
+ stmt = exec_query(sql, name, binds)
962
+ end
963
+
964
+ cols = IBM_DB.resultCols(stmt)
965
+
966
+ if( stmt )
967
+ results = fetch_data(stmt)
968
+ end
969
+
970
+ if(@isAr3)
971
+ return results
972
+ else
973
+ return ActiveRecord::Result.new(cols, results)
974
+ end
975
+ end
976
+
977
+ #Returns an array of arrays containing the field values.
978
+ #This is an implementation for the abstract method
979
+ #+sql+ is the select query and +name+ is an optional description for logging
980
+ def select_rows(sql, name = nil)
981
+ # Replaces {"= NULL" with " IS NULL"} OR {"IN (NULL)" with " IS NULL"}
982
+ sql.gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, " IS NULL" )
983
+
984
+ results = []
985
+ # Invokes the method +execute+ in order to log and execute the SQL
986
+ # IBM_DB.Statement is returned from which results can be fetched
987
+ stmt = execute(sql, name)
988
+ if(stmt)
989
+ begin
990
+ results = @servertype.select_rows(sql, name, stmt, results)
991
+ rescue StandardError => fetch_error # Handle driver fetch errors
992
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
993
+ if error_msg && !error_msg.empty?
994
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
995
+ else
996
+ error_msg = "An unexpected error occurred during data retrieval"
997
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
998
+ raise error_msg
999
+ end
1000
+ ensure
1001
+ # Ensures to free the resources associated with the statement
1002
+ IBM_DB.free_stmt(stmt) if stmt
1003
+ end
1004
+ end
1005
+ # The array of record hashes is returned
1006
+ results
1007
+ end
1008
+
1009
+ # Returns a record hash with the column names as keys and column values
1010
+ # as values.
1011
+ #def select_one(sql, name = nil)
1012
+ # Gets the first hash from the array of hashes returned by
1013
+ # select_all
1014
+ # select_all(sql,name).first
1015
+ #end
1016
+
1017
+ #inserts values from fixtures
1018
+ #overridden to handle LOB's fixture insertion, as, in normal inserts callbacks are triggered but during fixture insertion callbacks are not triggered
1019
+ #hence only markers like @@@IBMBINARY@@@ will be inserted and are not updated to actual data
1020
+ def insert_fixture(fixture, table_name)
1021
+ if(fixture.respond_to?(:keys))
1022
+ insert_query = "INSERT INTO #{quote_table_name(table_name)} ( #{fixture.keys.join(', ')})"
1023
+ else
1024
+ insert_query = "INSERT INTO #{quote_table_name(table_name)} ( #{fixture.key_list})"
1025
+ end
1026
+
1027
+ insert_values = []
1028
+ params = []
1029
+ if @servertype.instance_of? IBM_IDS
1030
+ super
1031
+ return
1032
+ end
1033
+ column_list = columns(table_name)
1034
+ fixture.each do |item|
1035
+ col = nil
1036
+ column_list.each do |column|
1037
+ if column.name.downcase == item.at(0).downcase
1038
+ col= column
1039
+ break
1040
+ end
1041
+ end
1042
+ if item.at(1).nil? ||
1043
+ item.at(1) == {} ||
1044
+ (item.at(1) == '' && !(col.type.to_sym == :text))
1045
+
1046
+ params << 'NULL'
1047
+
1048
+ elsif col.type.to_sym == :xml ||
1049
+ col.type.to_sym == :text ||
1050
+ col.type.to_sym == :binary
1051
+ # Add a '?' for the parameter or a NULL if the value is nil or empty
1052
+ # (except for a CLOB field where '' can be a value)
1053
+ insert_values << quote_value_for_pstmt(item.at(1))
1054
+ params << '?'
1055
+ else
1056
+ insert_values << quote_value_for_pstmt(item.at(1),col)
1057
+ params << '?'
1058
+ end
1059
+ end
1060
+
1061
+ insert_query << " VALUES ("+ params.join(',') + ")"
1062
+ unless stmt = IBM_DB.prepare(@connection, insert_query)
1063
+ error_msg = IBM_DB.getErrormsg( @connection, IBM_DB::DB_CONN )
1064
+ if error_msg && !error_msg.empty?
1065
+ raise "Failed to prepare statement for fixtures insert due to : #{error_msg}"
1066
+ else
1067
+ raise StandardError.new('An unexpected error occurred during preparing SQL for fixture insert')
1068
+ end
1069
+ end
1070
+
1071
+ #log_query(insert_query,'fixture insert')
1072
+ log(insert_query,'fixture insert') do
1073
+ unless IBM_DB.execute(stmt, insert_values)
1074
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
1075
+ IBM_DB.free_stmt(stmt) if stmt
1076
+ raise "Failed to insert due to: #{error_msg}"
1077
+ else
1078
+ IBM_DB.free_stmt(stmt) if stmt
1079
+ end
1080
+ end
1081
+ end
1082
+
1083
+ def empty_insert_statement_value(pkey)
1084
+ "(#{pkey}) VALUES (DEFAULT)"
1085
+ end
1086
+
1087
+ # Perform an insert and returns the last ID generated.
1088
+ # This can be the ID passed to the method or the one auto-generated by the database,
1089
+ # and retrieved by the +last_generated_id+ method.
1090
+ def insert_direct(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
1091
+ if @handle_lobs_triggered #Ensure the array of sql is cleared if they have been handled in the callback
1092
+ @sql = []
1093
+ @handle_lobs_triggered = false
1094
+ end
1095
+
1096
+ clear_query_cache if defined? clear_query_cache
1097
+
1098
+ if stmt = execute(sql, name)
1099
+ begin
1100
+ @sql << sql
1101
+ return id_value || @servertype.last_generated_id(stmt)
1102
+ # Ensures to free the resources associated with the statement
1103
+ ensure
1104
+ IBM_DB.free_stmt(stmt) if stmt
1105
+ end
1106
+ end
1107
+ end
1108
+
1109
+ def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [] )
1110
+ sql, binds = [to_sql(arel),binds]
1111
+
1112
+ #unless IBM_DBAdapter.respond_to?(:exec_insert)
1113
+ if binds.nil? || binds.empty?
1114
+ return insert_direct(sql, name, pk, id_value, sequence_name)
1115
+ end
1116
+
1117
+ clear_query_cache if defined? clear_query_cache
1118
+ if stmt = exec_insert(sql, name, binds)
1119
+ begin
1120
+ @sql << sql
1121
+ return id_value || @servertype.last_generated_id(stmt)
1122
+ ensure
1123
+ IBM_DB.free_stmt(stmt) if stmt
1124
+ end
1125
+ end
1126
+ end
1127
+
1128
+ # Praveen
1129
+ # Performs an insert using the prepared statement and returns the last ID generated.
1130
+ # This can be the ID passed to the method or the one auto-generated by the database,
1131
+ # and retrieved by the +last_generated_id+ method.
1132
+ def prepared_insert(pstmt, param_array = nil, id_value = nil)
1133
+ if @handle_lobs_triggered #Ensure the array of sql is cleared if they have been handled in the callback
1134
+ @sql = []
1135
+ @sql_parameter_values = []
1136
+ @handle_lobs_triggered = false
1137
+ end
1138
+
1139
+ clear_query_cache if defined? clear_query_cache
1140
+
1141
+ begin
1142
+ if execute_prepared_stmt(pstmt, param_array)
1143
+ @sql << @prepared_sql
1144
+ @sql_parameter_values << param_array
1145
+ return id_value || @servertype.last_generated_id(pstmt)
1146
+ end
1147
+ rescue StandardError => insert_err
1148
+ raise insert_err
1149
+ ensure
1150
+ IBM_DB.free_stmt(pstmt) if pstmt
1151
+ end
1152
+ end
1153
+
1154
+ # Praveen
1155
+ # Prepares and logs +sql+ commands and
1156
+ # returns a +IBM_DB.Statement+ object.
1157
+ def prepare(sql,name = nil)
1158
+ # The +log+ method is defined in the parent class +AbstractAdapter+
1159
+ @prepared_sql = sql
1160
+ log(sql,name) do
1161
+ @servertype.prepare(sql, name)
1162
+ end
1163
+ end
1164
+
1165
+ # Praveen
1166
+ #Executes the prepared statement
1167
+ #ReturnsTrue on success and False on Failure
1168
+ def execute_prepared_stmt(pstmt, param_array = nil)
1169
+ if !param_array.nil? && param_array.size < 1
1170
+ param_array = nil
1171
+ end
1172
+
1173
+ if( !IBM_DB.execute(pstmt, param_array) )
1174
+ error_msg = IBM_DB.getErrormsg(pstmt, IBM_DB::DB_STMT)
1175
+ if !error_msg.empty?
1176
+ error_msg = "Statement execution failed: " + error_msg
1177
+ else
1178
+ error_msg = "Statement execution failed"
1179
+ end
1180
+ IBM_DB.free_stmt(pstmt) if pstmt
1181
+ raise StatementInvalid, error_msg
1182
+ else
1183
+ return true
1184
+ end
1185
+ end
1186
+
1187
+ # Executes +sql+ statement in the context of this connection using
1188
+ # +binds+ as the bind substitutes. +name+ is logged along with
1189
+ # the executed +sql+ statement.
1190
+ def exec_query(sql, name = 'SQL', binds = [])
1191
+ begin
1192
+ param_array = binds.map do |column,value|
1193
+ quote_value_for_pstmt(value, column)
1194
+ end
1195
+
1196
+ stmt = prepare(sql, name)
1197
+
1198
+ if( stmt )
1199
+ if(execute_prepared_stmt(stmt, param_array))
1200
+ return stmt
1201
+ end
1202
+ else
1203
+ return false
1204
+ end
1205
+ ensure
1206
+ @offset = @limit = nil
1207
+ end
1208
+ end
1209
+
1210
+ # Executes and logs +sql+ commands and
1211
+ # returns a +IBM_DB.Statement+ object.
1212
+ def execute(sql, name = nil)
1213
+ # Logs and execute the sql instructions.
1214
+ # The +log+ method is defined in the parent class +AbstractAdapter+
1215
+ log(sql, name) do
1216
+ @servertype.execute(sql, name)
1217
+ end
1218
+ end
1219
+
1220
+ # Executes an "UPDATE" SQL statement
1221
+ def update_direct(sql, name = nil)
1222
+ if @handle_lobs_triggered #Ensure the array of sql is cleared if they have been handled in the callback
1223
+ @sql = []
1224
+ @handle_lobs_triggered = false
1225
+ end
1226
+
1227
+ # Logs and execute the given sql query.
1228
+ if stmt = execute(sql, name)
1229
+ begin
1230
+ @sql << sql
1231
+ # Retrieves the number of affected rows
1232
+ IBM_DB.num_rows(stmt)
1233
+ # Ensures to free the resources associated with the statement
1234
+ ensure
1235
+ IBM_DB.free_stmt(stmt) if stmt
1236
+ end
1237
+ end
1238
+ end
1239
+
1240
+ #Praveen
1241
+ def prepared_update(pstmt, param_array = nil )
1242
+ if @handle_lobs_triggered #Ensure the array of sql is cleared if they have been handled in the callback
1243
+ @sql = []
1244
+ @sql_parameter_values = []
1245
+ @handle_lobs_triggered = false
1246
+ end
1247
+
1248
+ clear_query_cache if defined? clear_query_cache
1249
+
1250
+ begin
1251
+ if execute_prepared_stmt(pstmt, param_array)
1252
+ @sql << @prepared_sql
1253
+ @sql_parameter_values << param_array
1254
+ # Retrieves the number of affected rows
1255
+ IBM_DB.num_rows(pstmt)
1256
+ # Ensures to free the resources associated with the statement
1257
+ end
1258
+ rescue StandardError => updt_err
1259
+ raise updt_err
1260
+ ensure
1261
+ IBM_DB.free_stmt(pstmt) if pstmt
1262
+ end
1263
+ end
1264
+ # The delete method executes the delete
1265
+ # statement and returns the number of affected rows.
1266
+ # The method is an alias for +update+
1267
+ alias_method :prepared_delete, :prepared_update
1268
+
1269
+ def update(arel, name = nil, binds = [])
1270
+ sql = to_sql(arel)
1271
+
1272
+ # Make sure the WHERE clause handles NULL's correctly
1273
+ sqlarray = sql.split(/\s*WHERE\s*/)
1274
+ size = sqlarray.size
1275
+ if size > 1
1276
+ sql = sqlarray[0] + " WHERE "
1277
+ if size > 2
1278
+ 1.upto size-2 do |index|
1279
+ sqlarray[index].gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, " IS NULL" ) unless sqlarray[index].nil?
1280
+ sql = sql + sqlarray[index] + " WHERE "
1281
+ end
1282
+ end
1283
+ sqlarray[size-1].gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, " IS NULL" ) unless sqlarray[size-1].nil?
1284
+ sql = sql + sqlarray[size-1]
1285
+ end
1286
+
1287
+ clear_query_cache if defined? clear_query_cache
1288
+
1289
+ if binds.nil? || binds.empty?
1290
+ update_direct(sql, name)
1291
+ else
1292
+ begin
1293
+ if stmt = exec_query(sql,name,binds)
1294
+ IBM_DB.num_rows(stmt)
1295
+ end
1296
+ ensure
1297
+ IBM_DB.free_stmt(stmt) if(stmt)
1298
+ end
1299
+ end
1300
+ end
1301
+
1302
+ alias_method :delete, :update
1303
+
1304
+ # Begins the transaction (and turns off auto-committing)
1305
+ def begin_db_transaction
1306
+ # Turns off the auto-commit
1307
+ IBM_DB.autocommit(@connection, IBM_DB::SQL_AUTOCOMMIT_OFF)
1308
+ end
1309
+
1310
+ # Commits the transaction and turns on auto-committing
1311
+ def commit_db_transaction
1312
+ # Commits the transaction
1313
+ IBM_DB.commit @connection rescue nil
1314
+ # Turns on auto-committing
1315
+ IBM_DB.autocommit @connection, IBM_DB::SQL_AUTOCOMMIT_ON
1316
+ end
1317
+
1318
+ # Rolls back the transaction and turns on auto-committing. Must be
1319
+ # done if the transaction block raises an exception or returns false
1320
+ def rollback_db_transaction
1321
+ # ROLLBACK the transaction
1322
+ IBM_DB.rollback(@connection) rescue nil
1323
+ # Turns on auto-committing
1324
+ IBM_DB.autocommit @connection, IBM_DB::SQL_AUTOCOMMIT_ON
1325
+ end
1326
+
1327
+
1328
+ # Modifies a sql statement in order to implement a LIMIT and an OFFSET.
1329
+ # A LIMIT defines the number of rows that should be fetched, while
1330
+ # an OFFSET defines from what row the records must be fetched.
1331
+ # IBM data servers implement a LIMIT in SQL statements through:
1332
+ # FETCH FIRST n ROWS ONLY, where n is the number of rows required.
1333
+ # The implementation of OFFSET is more elaborate, and requires the usage of
1334
+ # subqueries and the ROW_NUMBER() command in order to add row numbering
1335
+ # as an additional column to a copy of the existing table.
1336
+ # ==== Examples
1337
+ # add_limit_offset!('SELECT * FROM staff', {:limit => 10})
1338
+ # generates: "SELECT * FROM staff FETCH FIRST 10 ROWS ONLY"
1339
+ #
1340
+ # add_limit_offset!('SELECT * FROM staff', {:limit => 10, :offset => 30})
1341
+ # generates "SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_rownum
1342
+ # FROM (SELECT * FROM staff) AS I) AS O WHERE sys_row_num BETWEEN 31 AND 40"
1343
+ def add_limit_offset!(sql, options)
1344
+ limit = options[:limit]
1345
+ offset = options[:offset]
1346
+
1347
+ # if the limit is zero
1348
+ if limit && limit == 0
1349
+ # Returns a query that will always generate zero records
1350
+ # (e.g. WHERE sys_row_num BETWEEN 1 and 0)
1351
+ if( @pstmt_support_on )
1352
+ sql = @servertype.query_offset_limit!(sql, 0, limit, options)
1353
+ else
1354
+ sql = @servertype.query_offset_limit(sql, 0, limit)
1355
+ end
1356
+ # If there is a non-zero limit
1357
+ else
1358
+ # If an offset is specified builds the query with offset and limit,
1359
+ # otherwise retrieves only the first +limit+ rows
1360
+ if( @pstmt_support_on )
1361
+ sql = @servertype.query_offset_limit!(sql, offset, limit, options)
1362
+ else
1363
+ sql = @servertype.query_offset_limit(sql, offset, limit)
1364
+ end
1365
+ end
1366
+ # Returns the sql query in any case
1367
+ sql
1368
+ end # method add_limit_offset!
1369
+
1370
+ def default_sequence_name(table, column) # :nodoc:
1371
+ "#{table}_#{column}_seq"
1372
+ end
1373
+
1374
+
1375
+ #==============================================
1376
+ # QUOTING
1377
+ #==============================================
1378
+
1379
+ # Quote date/time values for use in SQL input.
1380
+ # Includes microseconds, if the value is a Time responding to usec.
1381
+ def quoted_date(value) #:nodoc:
1382
+ if value.respond_to?(:usec)
1383
+ "#{super}.#{sprintf("%06d", value.usec)}"
1384
+ else
1385
+ super
1386
+ end
1387
+ end
1388
+
1389
+ def quote_value_for_pstmt(value, column=nil)
1390
+
1391
+ return value.quoted_id if value.respond_to?(:quoted_id)
1392
+
1393
+ case value
1394
+ when String, ActiveSupport::Multibyte::Chars then
1395
+ value = value.to_s
1396
+ if column && [:integer, :float].include?(column.type)
1397
+ value = column.type == :integer ? value.to_i : value.to_f
1398
+ value
1399
+ else
1400
+ value
1401
+ end
1402
+ when NilClass then nil
1403
+ when TrueClass then 1
1404
+ when FalseClass then 0
1405
+ when Float, Fixnum, Bignum then value
1406
+ # BigDecimals need to be output in a non-normalized form and quoted.
1407
+ when BigDecimal then value.to_s('F')
1408
+ when Numeric, Symbol then value.to_s
1409
+ else
1410
+ if value.acts_like?(:date) || value.acts_like?(:time)
1411
+ quoted_date(value)
1412
+ else
1413
+ value.to_yaml
1414
+ end
1415
+ end
1416
+ end
1417
+
1418
+ # Properly quotes the various data types.
1419
+ # +value+ contains the data, +column+ is optional and contains info on the field
1420
+ def quote(value, column = nil)
1421
+ return value.quoted_id if value.respond_to?(:quoted_id)
1422
+
1423
+ case value
1424
+ # If it's a numeric value and the column type is not a string, it shouldn't be quoted
1425
+ # (IBM_DB doesn't accept quotes on numeric types)
1426
+ when Numeric
1427
+ # If the column type is text or string, return the quote value
1428
+ if column && column.type.to_sym == :text || column && column.type.to_sym == :string
1429
+ unless caller[0] =~ /insert_fixture/i
1430
+ "'#{value}'"
1431
+ else
1432
+ "#{value}"
1433
+ end
1434
+ else
1435
+ # value is Numeric, column.type is not a string,
1436
+ # therefore it converts the number to string without quoting it
1437
+ value.to_s
1438
+ end
1439
+ when String, ActiveSupport::Multibyte::Chars
1440
+ if column && column.type.to_sym == :binary && !(column.sql_type =~ /for bit data/i)
1441
+ # If quoting is required for the insert/update of a BLOB
1442
+ unless caller[0] =~ /add_column_options/i
1443
+ # Invokes a convertion from string to binary
1444
+ @servertype.set_binary_value
1445
+ else
1446
+ # Quoting required for the default value of a column
1447
+ @servertype.set_binary_default(value)
1448
+ end
1449
+ elsif column && column.type.to_sym == :text
1450
+ unless caller[0] =~ /add_column_options/i
1451
+ "'@@@IBMTEXT@@@'"
1452
+ else
1453
+ @servertype.set_text_default(quote_string(value))
1454
+ end
1455
+ elsif column && column.type.to_sym == :xml
1456
+ unless caller[0] =~ /add_column_options/i
1457
+ "'<ibm>@@@IBMXML@@@</ibm>'"
1458
+ else
1459
+ "#{value}"
1460
+ end
1461
+ else
1462
+ unless caller[0] =~ /insert_fixture/i
1463
+ super
1464
+ else
1465
+ "#{value}"
1466
+ end
1467
+ end
1468
+ when TrueClass then quoted_true # return '1' for true
1469
+ when FalseClass then quoted_false # return '0' for false
1470
+ when nil then "NULL"
1471
+ when Date, Time then "'#{quoted_date(value)}'"
1472
+ when Symbol then "'#{quote_string(value.to_s)}'"
1473
+ else
1474
+ unless caller[0] =~ /insert_fixture/i
1475
+ "'#{quote_string(YAML.dump(value))}'"
1476
+ else
1477
+ "#{quote_string(YAML.dump(value))}"
1478
+ end
1479
+ end
1480
+ end
1481
+
1482
+ # Quotes a given string, escaping single quote (') characters.
1483
+ def quote_string(string)
1484
+ string.gsub(/'/, "''")
1485
+ end
1486
+
1487
+ # *true* is represented by a smallint 1, *false*
1488
+ # by 0, as no native boolean type exists in DB2.
1489
+ # Numerics are not quoted in DB2.
1490
+ def quoted_true
1491
+ "1"
1492
+ end
1493
+
1494
+ def quoted_false
1495
+ "0"
1496
+ end
1497
+
1498
+ def quote_column_name(name)
1499
+ @servertype.check_reserved_words(name)
1500
+ end
1501
+
1502
+ #==============================================
1503
+ # SCHEMA STATEMENTS
1504
+ #==============================================
1505
+
1506
+ # Returns a Hash of mappings from the abstract data types to the native
1507
+ # database types
1508
+ def native_database_types
1509
+ {
1510
+ :primary_key => { :name => @servertype.primary_key_definition(@start_id)},
1511
+ :string => { :name => "varchar", :limit => 255 },
1512
+ :text => { :name => "clob" },
1513
+ :integer => { :name => "integer" },
1514
+ :float => { :name => "float" },
1515
+ :datetime => { :name => @servertype.get_datetime_mapping },
1516
+ :timestamp => { :name => @servertype.get_datetime_mapping },
1517
+ :time => { :name => @servertype.get_time_mapping },
1518
+ :date => { :name => "date" },
1519
+ :binary => { :name => "blob" },
1520
+
1521
+ # IBM data servers don't have a native boolean type.
1522
+ # A boolean can be represented by a smallint,
1523
+ # adopting the convention that False is 0 and True is 1
1524
+ :boolean => { :name => "smallint"},
1525
+ :xml => { :name => "xml"},
1526
+ :decimal => { :name => "decimal" },
1527
+ :rowid => { :name => "rowid" }, # rowid is a supported datatype on z/OS and i/5
1528
+ :serial => { :name => "serial" }, # rowid is a supported datatype on Informix Dynamic Server
1529
+ :char => { :name => "char" },
1530
+ :double => { :name => @servertype.get_double_mapping },
1531
+ :decfloat => { :name => "decfloat"},
1532
+ :graphic => { :name => "graphic", :limit => 1},
1533
+ :vargraphic => { :name => "vargraphic", :limit => 1},
1534
+ :bigint => { :name => "bigint"}
1535
+ }
1536
+ end
1537
+
1538
+ def build_conn_str_for_dbops()
1539
+ connect_str = "DRIVER={IBM DB2 ODBC DRIVER};ATTACH=true;"
1540
+ if(!@host.nil?)
1541
+ connect_str << "HOSTNAME=#{@host};"
1542
+ connect_str << "PORT=#{@port};"
1543
+ connect_str << "PROTOCOL=TCPIP;"
1544
+ end
1545
+ connect_str << "UID=#{@username};PWD=#{@password};"
1546
+ return connect_str
1547
+ end
1548
+
1549
+ def drop_database(dbName)
1550
+ connect_str = build_conn_str_for_dbops()
1551
+
1552
+ #Ensure connection is closed before trying to drop a database.
1553
+ #As a connect call would have been made by call seeing connection in active
1554
+ disconnect!
1555
+
1556
+ begin
1557
+ dropConn = IBM_DB.connect(connect_str, '', '')
1558
+ rescue StandardError => connect_err
1559
+ raise "Failed to connect to server due to: #{connect_err}"
1560
+ end
1561
+
1562
+ if(IBM_DB.dropDB(dropConn,dbName))
1563
+ IBM_DB.close(dropConn)
1564
+ return true
1565
+ else
1566
+ error = IBM_DB.getErrormsg(dropConn, IBM_DB::DB_CONN)
1567
+ IBM_DB.close(dropConn)
1568
+ raise "Could not drop Database due to: #{error}"
1569
+ end
1570
+ end
1571
+
1572
+ def create_database(dbName, codeSet=nil, mode=nil)
1573
+ connect_str = build_conn_str_for_dbops()
1574
+
1575
+ #Ensure connection is closed before trying to drop a database.
1576
+ #As a connect call would have been made by call seeing connection in active
1577
+ disconnect!
1578
+
1579
+ begin
1580
+ createConn = IBM_DB.connect(connect_str, '', '')
1581
+ rescue StandardError => connect_err
1582
+ raise "Failed to connect to server due to: #{connect_err}"
1583
+ end
1584
+
1585
+ if(IBM_DB.createDB(createConn,dbName,codeSet,mode))
1586
+ IBM_DB.close(createConn)
1587
+ return true
1588
+ else
1589
+ error = IBM_DB.getErrormsg(createConn, IBM_DB::DB_CONN)
1590
+ IBM_DB.close(createConn)
1591
+ raise "Could not create Database due to: #{error}"
1592
+ end
1593
+ end
1594
+
1595
+ # IBM data servers do not support limits on certain data types (unlike MySQL)
1596
+ # Limit is supported for the {float, decimal, numeric, varchar, clob, blob, graphic, vargraphic} data types.
1597
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
1598
+ if type.to_sym == :decfloat
1599
+ sql_segment = native_database_types[type.to_sym][:name].to_s
1600
+ sql_segment << "(#{precision})" if !precision.nil?
1601
+ return sql_segment
1602
+ end
1603
+
1604
+ return super if limit.nil?
1605
+
1606
+ # strip off limits on data types not supporting them
1607
+ if @servertype.limit_not_supported_types.include? type.to_sym
1608
+ return native_database_types[type.to_sym][:name].to_s
1609
+ elsif type.to_sym == :boolean
1610
+ return "smallint"
1611
+ else
1612
+ return super
1613
+ end
1614
+ end
1615
+
1616
+ # Returns the maximum length a table alias identifier can be.
1617
+ # IBM data servers (cross-platform) table limit is 128 characters
1618
+ def table_alias_length
1619
+ 128
1620
+ end
1621
+
1622
+ # Retrieves table's metadata for a specified shema name
1623
+ def tables(name = nil)
1624
+ # Initializes the tables array
1625
+ tables = []
1626
+ # Retrieve table's metadata through IBM_DB driver
1627
+ stmt = IBM_DB.tables(@connection, nil,
1628
+ @servertype.set_case(@schema))
1629
+ if(stmt)
1630
+ begin
1631
+ # Fetches all the records available
1632
+ while tab = IBM_DB.fetch_assoc(stmt)
1633
+ # Adds the lowercase table name to the array
1634
+ if(tab["table_type"]== 'TABLE') #check, so that only tables are dumped,IBM_DB.tables also returns views,alias etc in the schema
1635
+ tables << tab["table_name"].downcase
1636
+ end
1637
+ end
1638
+ rescue StandardError => fetch_error # Handle driver fetch errors
1639
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
1640
+ if error_msg && !error_msg.empty?
1641
+ raise "Failed to retrieve table metadata during fetch: #{error_msg}"
1642
+ else
1643
+ error_msg = "An unexpected error occurred during retrieval of table metadata"
1644
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
1645
+ raise error_msg
1646
+ end
1647
+ ensure
1648
+ IBM_DB.free_stmt(stmt) if stmt # Free resources associated with the statement
1649
+ end
1650
+ else # Handle driver execution errors
1651
+ error_msg = IBM_DB.getErrormsg(@connection, IBM_DB::DB_CONN )
1652
+ if error_msg && !error_msg.empty?
1653
+ raise "Failed to retrieve tables metadata due to error: #{error_msg}"
1654
+ else
1655
+ raise StandardError.new('An unexpected error occurred during retrieval of table metadata')
1656
+ end
1657
+ end
1658
+ # Returns the tables array
1659
+ return tables
1660
+ end
1661
+
1662
+ # Returns the primary key of the mentioned table
1663
+ def primary_key(table_name)
1664
+ pk_name = nil
1665
+ stmt = IBM_DB.primary_keys( @connection, nil,
1666
+ @servertype.set_case(@schema),
1667
+ @servertype.set_case(table_name))
1668
+ if(stmt)
1669
+ begin
1670
+ if ( pk_index_row = IBM_DB.fetch_array(stmt) )
1671
+ pk_name = pk_index_row[3].downcase
1672
+ end
1673
+ rescue StandardError => fetch_error # Handle driver fetch errors
1674
+ error_msg = IBM_DB.getErrormsg( stmt, IBM_DB::DB_STMT )
1675
+ if error_msg && !error_msg.empty?
1676
+ raise "Failed to retrieve primarykey metadata during fetch: #{error_msg}"
1677
+ else
1678
+ error_msg = "An unexpected error occurred during retrieval of primary key metadata"
1679
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
1680
+ raise error_msg
1681
+ end
1682
+ ensure # Free resources associated with the statement
1683
+ IBM_DB.free_stmt(stmt) if stmt
1684
+ end
1685
+ else
1686
+ error_msg = IBM_DB.getErrormsg( @connection, IBM_DB::DB_CONN )
1687
+ if error_msg && !error_msg.empty?
1688
+ raise "Failed to retrieve primary key metadata due to error: #{error_msg}"
1689
+ else
1690
+ raise StandardError.new('An unexpected error occurred during primary key retrieval')
1691
+ end
1692
+ end
1693
+ return pk_name
1694
+ end
1695
+
1696
+ # Returns an array of non-primary key indexes for a specified table name
1697
+ def indexes(table_name, name = nil)
1698
+ # to_s required because +table_name+ may be a symbol.
1699
+ table_name = table_name.to_s
1700
+ # Checks if a blank table name has been given.
1701
+ # If so it returns an empty array of columns.
1702
+ return [] if table_name.strip.empty?
1703
+
1704
+ indexes = []
1705
+ pk_index = nil
1706
+ index_schema = []
1707
+
1708
+ #fetch the primary keys of the table using function primary_keys
1709
+ #TABLE_SCHEM:: pk_index[1]
1710
+ #TABLE_NAME:: pk_index[2]
1711
+ #COLUMN_NAME:: pk_index[3]
1712
+ #PK_NAME:: pk_index[5]
1713
+ stmt = IBM_DB.primary_keys( @connection, nil,
1714
+ @servertype.set_case(@schema),
1715
+ @servertype.set_case(table_name))
1716
+ if(stmt)
1717
+ begin
1718
+ while ( pk_index_row = IBM_DB.fetch_array(stmt) )
1719
+ if pk_index_row[5]
1720
+ pk_index_name = pk_index_row[5].downcase
1721
+ pk_index_columns = [pk_index_row[3].downcase] # COLUMN_NAME
1722
+ if pk_index
1723
+ pk_index.columns = pk_index.columns + pk_index_columns
1724
+ else
1725
+ pk_index = IndexDefinition.new(table_name, pk_index_name, true, pk_index_columns)
1726
+ end
1727
+ end
1728
+ end
1729
+ rescue StandardError => fetch_error # Handle driver fetch errors
1730
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
1731
+ if error_msg && !error_msg.empty?
1732
+ raise "Failed to retrieve primarykey metadata during fetch: #{error_msg}"
1733
+ else
1734
+ error_msg = "An unexpected error occurred during retrieval of primary key metadata"
1735
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
1736
+ raise error_msg
1737
+ end
1738
+ ensure # Free resources associated with the statement
1739
+ IBM_DB.free_stmt(stmt) if stmt
1740
+ end
1741
+ else # Handle driver execution errors
1742
+ error_msg = IBM_DB.getErrormsg(@connection, IBM_DB::DB_CONN )
1743
+ if error_msg && !error_msg.empty?
1744
+ raise "Failed to retrieve primary key metadata due to error: #{error_msg}"
1745
+ else
1746
+ raise StandardError.new('An unexpected error occurred during primary key retrieval')
1747
+ end
1748
+ end
1749
+
1750
+ # Query table statistics for all indexes on the table
1751
+ # "TABLE_NAME: #{index_stats[2]}"
1752
+ # "NON_UNIQUE: #{index_stats[3]}"
1753
+ # "INDEX_NAME: #{index_stats[5]}"
1754
+ # "COLUMN_NAME: #{index_stats[8]}"
1755
+ stmt = IBM_DB.statistics( @connection, nil,
1756
+ @servertype.set_case(@schema),
1757
+ @servertype.set_case(table_name), 1 )
1758
+ if(stmt)
1759
+ begin
1760
+ while ( index_stats = IBM_DB.fetch_array(stmt) )
1761
+ is_composite = false
1762
+ if index_stats[5] # INDEX_NAME
1763
+ index_name = index_stats[5].downcase
1764
+ index_unique = (index_stats[3] == 0)
1765
+ index_columns = [index_stats[8].downcase] # COLUMN_NAME
1766
+ index_qualifier = index_stats[4].downcase #Index_Qualifier
1767
+ # Create an IndexDefinition object and add to the indexes array
1768
+ i = 0;
1769
+ indexes.each do |index|
1770
+ if index.name == index_name && index_schema[i] == index_qualifier
1771
+ index.columns = index.columns + index_columns
1772
+ is_composite = true
1773
+ end
1774
+ i = i+1
1775
+ end
1776
+
1777
+ unless is_composite
1778
+ indexes << IndexDefinition.new(table_name, index_name, index_unique, index_columns)
1779
+ index_schema << index_qualifier
1780
+ end
1781
+ end
1782
+ end
1783
+ rescue StandardError => fetch_error # Handle driver fetch errors
1784
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
1785
+ if error_msg && !error_msg.empty?
1786
+ raise "Failed to retrieve index metadata during fetch: #{error_msg}"
1787
+ else
1788
+ error_msg = "An unexpected error occurred during retrieval of index metadata"
1789
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
1790
+ raise error_msg
1791
+ end
1792
+ ensure # Free resources associated with the statement
1793
+ IBM_DB.free_stmt(stmt) if stmt
1794
+ end
1795
+ else # Handle driver execution errors
1796
+ error_msg = IBM_DB.getErrormsg(@connection, IBM_DB::DB_CONN )
1797
+ if error_msg && !error_msg.empty?
1798
+ raise "Failed to retrieve index metadata due to error: #{error_msg}"
1799
+ else
1800
+ raise StandardError.new('An unexpected error occurred during index retrieval')
1801
+ end
1802
+ end
1803
+
1804
+ # remove the primary key index entry.... should not be dumped by the dumper
1805
+
1806
+ i = 0
1807
+ indexes.each do |index|
1808
+ if pk_index && index.columns == pk_index.columns
1809
+ indexes.delete_at(i)
1810
+ end
1811
+ i = i+1
1812
+ end
1813
+ # Returns the indexes array
1814
+ return indexes
1815
+ end
1816
+
1817
+ # Returns an array of Column objects for the table specified by +table_name+
1818
+ def columns(table_name, name = nil)
1819
+ # to_s required because it may be a symbol.
1820
+ table_name = @servertype.set_case(table_name.to_s)
1821
+ # Checks if a blank table name has been given.
1822
+ # If so it returns an empty array
1823
+ return [] if table_name.strip.empty?
1824
+ # +columns+ will contain the resulting array
1825
+ columns = []
1826
+ # Statement required to access all the columns information
1827
+ stmt = IBM_DB.columns( @connection, nil,
1828
+ @servertype.set_case(@schema),
1829
+ @servertype.set_case(table_name) )
1830
+ if(stmt)
1831
+ begin
1832
+ # Fetches all the columns and assigns them to col.
1833
+ # +col+ is an hash with keys/value pairs for a column
1834
+ while col = IBM_DB.fetch_assoc(stmt)
1835
+ column_name = col["column_name"].downcase
1836
+ # Assigns the column default value.
1837
+ column_default_value = col["column_def"]
1838
+ # If there is no default value, it assigns NIL
1839
+ column_default_value = nil if (column_default_value && column_default_value.upcase == 'NULL')
1840
+ # If default value is IDENTITY GENERATED BY DEFAULT (this value is retrieved in case of id columns)
1841
+ column_default_value = nil if (column_default_value && column_default_value.upcase =~ /IDENTITY GENERATED BY DEFAULT/i)
1842
+ # Removes single quotes from the default value
1843
+ column_default_value.gsub!(/^'(.*)'$/, '\1') unless column_default_value.nil?
1844
+ # Assigns the column type
1845
+ column_type = col["type_name"].downcase
1846
+ # Assigns the field length (size) for the column
1847
+ column_length = col["column_size"]
1848
+ column_scale = col["decimal_digits"]
1849
+ # The initializer of the class Column, requires the +column_length+ to be declared
1850
+ # between brackets after the datatype(e.g VARCHAR(50)) for :string and :text types.
1851
+ # If it's a "for bit data" field it does a subsitution in place, if not
1852
+ # it appends the (column_length) string on the supported data types
1853
+ unless column_length.nil? ||
1854
+ column_length == '' ||
1855
+ column_type.sub!(/ \(\) for bit data/i,"(#{column_length}) FOR BIT DATA") ||
1856
+ !column_type =~ /char|lob|graphic/i
1857
+ if column_type =~ /decimal/i
1858
+ column_type << "(#{column_length},#{column_scale})"
1859
+ elsif column_type =~ /smallint|integer|double|date|time|timestamp|xml|bigint/i
1860
+ column_type << "" # override native limits incompatible with table create
1861
+ else
1862
+ column_type << "(#{column_length})"
1863
+ end
1864
+ end
1865
+ # col["NULLABLE"] is 1 if the field is nullable, 0 if not.
1866
+ column_nullable = (col["nullable"] == 1) ? true : false
1867
+ # Make sure the hidden column (db2_generated_rowid_for_lobs) in DB2 z/OS isn't added to the list
1868
+ if !(column_name =~ /db2_generated_rowid_for_lobs/i)
1869
+ # Pushes into the array the *IBM_DBColumn* object, created by passing to the initializer
1870
+ # +column_name+, +default_value+, +column_type+ and +column_nullable+.
1871
+ columns << IBM_DBColumn.new(column_name, column_default_value, column_type, column_nullable)
1872
+ end
1873
+ end
1874
+ rescue StandardError => fetch_error # Handle driver fetch errors
1875
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
1876
+ if error_msg && !error_msg.empty?
1877
+ raise "Failed to retrieve column metadata during fetch: #{error_msg}"
1878
+ else
1879
+ error_msg = "An unexpected error occurred during retrieval of column metadata"
1880
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
1881
+ raise error_msg
1882
+ end
1883
+ ensure # Free resources associated with the statement
1884
+ IBM_DB.free_stmt(stmt) if stmt
1885
+ end
1886
+ else # Handle driver execution errors
1887
+ error_msg = IBM_DB.getErrormsg(@connection, IBM_DB::DB_CONN )
1888
+ if error_msg && !error_msg.empty?
1889
+ raise "Failed to retrieve column metadata due to error: #{error_msg}"
1890
+ else
1891
+ raise StandardError.new('An unexpected error occurred during retrieval of columns metadata')
1892
+ end
1893
+ end
1894
+ # Returns the columns array
1895
+ return columns
1896
+ end
1897
+
1898
+ # Renames a table.
1899
+ # ==== Example
1900
+ # rename_table('octopuses', 'octopi')
1901
+ # Overriden to satisfy IBM data servers syntax
1902
+ def rename_table(name, new_name)
1903
+ # SQL rename table statement
1904
+ rename_table_sql = "RENAME TABLE #{name} TO #{new_name}"
1905
+ stmt = execute(rename_table_sql)
1906
+ # Ensures to free the resources associated with the statement
1907
+ ensure
1908
+ IBM_DB.free_stmt(stmt) if stmt
1909
+ end
1910
+
1911
+ # Renames a column.
1912
+ # ===== Example
1913
+ # rename_column(:suppliers, :description, :name)
1914
+ def rename_column(table_name, column_name, new_column_name)
1915
+ @servertype.rename_column(table_name, column_name, new_column_name)
1916
+ end
1917
+
1918
+ # Removes the column from the table definition.
1919
+ # ===== Examples
1920
+ # remove_column(:suppliers, :qualification)
1921
+ def remove_column(table_name, column_name)
1922
+ @servertype.remove_column(table_name, column_name)
1923
+ end
1924
+
1925
+ # Changes the column's definition according to the new options.
1926
+ # See TableDefinition#column for details of the options you can use.
1927
+ # ===== Examples
1928
+ # change_column(:suppliers, :name, :string, :limit => 80)
1929
+ # change_column(:accounts, :description, :text)
1930
+ def change_column(table_name, column_name, type, options = {})
1931
+ @servertype.change_column(table_name, column_name, type, options)
1932
+ end
1933
+
1934
+ =begin
1935
+ #overrides the abstract adapter method to generate proper sql
1936
+ #specifying the column options, like default value and nullability clause
1937
+ def add_column_options!(sql,options={})
1938
+ #add default null option only if :default option is not specified and
1939
+ #:null option is not specified or is true
1940
+ if (options[:default].nil? && (options[:null].nil? || options[:null] == true))
1941
+ sql << " DEFAULT NULL"
1942
+ else
1943
+ if( !options[:default].nil?)
1944
+ #check, :column option is passed only in case of create_table but not in case of add_column
1945
+ if (!options[:column].nil?)
1946
+ sql << " DEFAULT #{quote(options[:default],options[:column])}"
1947
+ else
1948
+ sql << " DEFAULT #{quote(options[:default])}"
1949
+ end
1950
+ end
1951
+ #append NOT NULL to sql only---
1952
+ #---if options[:null] is not nil and is equal to false
1953
+ unless options[:null] == nil
1954
+ sql << " NOT NULL" if (options[:null] == false)
1955
+ end
1956
+ end
1957
+ end
1958
+ =end
1959
+
1960
+ #Add distinct clause to the sql if there is no order by specified
1961
+ def distinct(columns, order_by)
1962
+ if order_by.nil?
1963
+ "DISTINCT #{columns}"
1964
+ else
1965
+ "#{columns}"
1966
+ end
1967
+ end
1968
+
1969
+ # Sets a new default value for a column. This does not set the default
1970
+ # value to +NULL+, instead, it needs DatabaseStatements#execute which
1971
+ # can execute the appropriate SQL statement for setting the value.
1972
+ # ==== Examples
1973
+ # change_column_default(:suppliers, :qualification, 'new')
1974
+ # change_column_default(:accounts, :authorized, 1)
1975
+ # Method overriden to satisfy IBM data servers syntax.
1976
+ def change_column_default(table_name, column_name, default)
1977
+ @servertype.change_column_default(table_name, column_name, default)
1978
+ end
1979
+
1980
+ #Changes the nullability value of a column
1981
+ def change_column_null(table_name, column_name, null, default = nil)
1982
+ @servertype.change_column_null(table_name, column_name, null, default)
1983
+ end
1984
+
1985
+ # Remove the given index from the table.
1986
+ #
1987
+ # Remove the suppliers_name_index in the suppliers table (legacy support, use the second or third forms).
1988
+ # remove_index :suppliers, :name
1989
+ # Remove the index named accounts_branch_id in the accounts table.
1990
+ # remove_index :accounts, :column => :branch_id
1991
+ # Remove the index named by_branch_party in the accounts table.
1992
+ # remove_index :accounts, :name => :by_branch_party
1993
+ #
1994
+ # You can remove an index on multiple columns by specifying the first column.
1995
+ # add_index :accounts, [:username, :password]
1996
+ # remove_index :accounts, :username
1997
+ # Overriden to use the IBM data servers SQL syntax.
1998
+ def remove_index(table_name, options = {})
1999
+ execute("DROP INDEX #{index_name(table_name, options)}")
2000
+ end
2001
+ end # class IBM_DBAdapter
2002
+
2003
+ # This class contains common code across DB's (DB2 LUW, zOS, i5 and IDS)
2004
+ class IBM_DataServer
2005
+ def initialize(adapter, ar3)
2006
+ @adapter = adapter
2007
+ @isAr3 = ar3
2008
+ end
2009
+
2010
+ def last_generated_id(stmt)
2011
+ end
2012
+
2013
+ def create_index_after_table (table_name,cloumn_name)
2014
+ end
2015
+
2016
+ def setup_for_lob_table ()
2017
+ end
2018
+
2019
+ def reorg_table(table_name)
2020
+ end
2021
+
2022
+ def check_reserved_words(col_name)
2023
+ col_name.to_s
2024
+ end
2025
+
2026
+ # This is supported by the DB2 for Linux, UNIX, Windows data servers
2027
+ # and by the DB2 for i5 data servers
2028
+ def remove_column(table_name, column_name)
2029
+ begin
2030
+ @adapter.execute "ALTER TABLE #{table_name} DROP #{column_name}"
2031
+ reorg_table(table_name)
2032
+ rescue StandardError => exec_err
2033
+ # Provide details on the current XML columns support
2034
+ if exec_err.message.include?('SQLCODE=-1242') && exec_err.message.include?('42997')
2035
+ raise StatementInvalid,
2036
+ "A column that is part of a table containing an XML column cannot be dropped. \
2037
+ To remove the column, the table must be dropped and recreated without the #{column_name} column: #{exec_err}"
2038
+ else
2039
+ raise "#{exec_err}"
2040
+ end
2041
+ end
2042
+ end
2043
+
2044
+ def select(stmt)
2045
+ results = []
2046
+ # Fetches all the results available. IBM_DB.fetch_assoc(stmt) returns
2047
+ # an hash for each single record.
2048
+ # The loop stops when there aren't any more valid records to fetch
2049
+ begin
2050
+ if(@isAr3)
2051
+ while single_hash = IBM_DB.fetch_assoc(stmt)
2052
+ # Add the record to the +results+ array
2053
+ results << single_hash
2054
+ end
2055
+ else
2056
+ while single_hash = IBM_DB.fetch_array(stmt)
2057
+ # Add the record to the +results+ array
2058
+ results << single_hash
2059
+ end
2060
+ end
2061
+ rescue StandardError => fetch_error # Handle driver fetch errors
2062
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2063
+ if error_msg && !error_msg.empty?
2064
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
2065
+ else
2066
+ error_msg = "An unexpected error occurred during data retrieval"
2067
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2068
+ raise error_msg
2069
+ end
2070
+ end
2071
+ return results
2072
+ end
2073
+
2074
+ def select_rows(sql, name, stmt, results)
2075
+ # Fetches all the results available. IBM_DB.fetch_array(stmt) returns
2076
+ # an array representing a row in a result set.
2077
+ # The loop stops when there aren't any more valid records to fetch
2078
+ begin
2079
+ while single_array = IBM_DB.fetch_array(stmt)
2080
+ #Add the array to results array
2081
+ results << single_array
2082
+ end
2083
+ rescue StandardError => fetch_error # Handle driver fetch errors
2084
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2085
+ if error_msg && !error_msg.empty?
2086
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
2087
+ else
2088
+ error_msg = "An unexpected error occurred during data retrieval"
2089
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2090
+ raise error_msg
2091
+ end
2092
+ end
2093
+ return results
2094
+ end
2095
+
2096
+ # Praveen
2097
+ def prepare(sql,name = nil)
2098
+ begin
2099
+ stmt = IBM_DB.prepare(@adapter.connection, sql)
2100
+ if( stmt )
2101
+ stmt
2102
+ else
2103
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2104
+ end
2105
+ rescue StandardError => prep_err
2106
+ if prep_err && !prep_err.message.empty?
2107
+ raise "Failed to prepare sql #{sql} due to: #{prep_err}"
2108
+ else
2109
+ raise
2110
+ end
2111
+ end
2112
+ end
2113
+
2114
+ def execute(sql, name = nil)
2115
+ begin
2116
+ if stmt = IBM_DB.exec(@adapter.connection, sql)
2117
+ stmt # Return the statement object
2118
+ else
2119
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2120
+ end
2121
+ rescue StandardError => exec_err
2122
+ if exec_err && !exec_err.message.empty?
2123
+ raise "Failed to execute statement due to: #{exec_err}"
2124
+ else
2125
+ raise
2126
+ end
2127
+ end
2128
+ end
2129
+
2130
+ def set_schema(schema)
2131
+ @adapter.execute("SET SCHEMA #{schema}")
2132
+ end
2133
+
2134
+ def query_offset_limit(sql, offset, limit)
2135
+ end
2136
+
2137
+ def query_offset_limit!(sql, offset, limit, options)
2138
+ end
2139
+
2140
+ def get_datetime_mapping
2141
+ end
2142
+
2143
+ def get_time_mapping
2144
+ end
2145
+
2146
+ def get_double_mapping
2147
+ end
2148
+
2149
+ def change_column_default(table_name, column_name, default)
2150
+ end
2151
+
2152
+ def change_column_null(table_name, column_name, null, default)
2153
+ end
2154
+
2155
+ def set_binary_default(value)
2156
+ end
2157
+
2158
+ def set_binary_value
2159
+ end
2160
+
2161
+ def set_text_default
2162
+ end
2163
+
2164
+ def set_case(value)
2165
+ end
2166
+
2167
+ def limit_not_supported_types
2168
+ [:integer, :double, :date, :time, :timestamp, :xml, :bigint]
2169
+ end
2170
+ end # class IBM_DataServer
2171
+
2172
+ class IBM_DB2 < IBM_DataServer
2173
+ def initialize(adapter, ar3)
2174
+ super(adapter,ar3)
2175
+ @limit = @offset = nil
2176
+ end
2177
+
2178
+ def rename_column(table_name, column_name, new_column_name)
2179
+ raise NotImplementedError, "rename_column is not implemented yet in the IBM_DB Adapter"
2180
+ end
2181
+
2182
+ def primary_key_definition(start_id)
2183
+ return "INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH #{start_id}) PRIMARY KEY"
2184
+ end
2185
+
2186
+ # Returns the last automatically generated ID.
2187
+ # This method is required by the +insert+ method
2188
+ # The "stmt" parameter is ignored for DB2 but used for IDS
2189
+ def last_generated_id(stmt)
2190
+ # Queries the db to obtain the last ID that was automatically generated
2191
+ sql = "SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1"
2192
+ stmt = IBM_DB.prepare(@adapter.connection, sql)
2193
+ if(stmt)
2194
+ if(IBM_DB.execute(stmt, nil))
2195
+ begin
2196
+ # Fetches the only record available (containing the last id)
2197
+ IBM_DB.fetch_row(stmt)
2198
+ # Retrieves and returns the result of the query with the last id.
2199
+ IBM_DB.result(stmt,0)
2200
+ rescue StandardError => fetch_error # Handle driver fetch errors
2201
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2202
+ if error_msg && !error_msg.empty?
2203
+ raise "Failed to retrieve last generated id: #{error_msg}"
2204
+ else
2205
+ error_msg = "An unexpected error occurred during retrieval of last generated id"
2206
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2207
+ raise error_msg
2208
+ end
2209
+ ensure # Free resources associated with the statement
2210
+ IBM_DB.free_stmt(stmt) if stmt
2211
+ end
2212
+ else
2213
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2214
+ IBM_DB.free_stmt(stmt) if stmt
2215
+ if error_msg && !error_msg.empty?
2216
+ raise "Failed to retrieve last generated id: #{error_msg}"
2217
+ else
2218
+ error_msg = "An unexpected error occurred during retrieval of last generated id"
2219
+ raise error_msg
2220
+ end
2221
+ end
2222
+ else
2223
+ error_msg = IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2224
+ if error_msg && !error_msg.empty?
2225
+ raise "Failed to retrieve last generated id due to error: #{error_msg}"
2226
+ else
2227
+ raise StandardError.new('An unexpected error occurred during retrieval of last generated id')
2228
+ end
2229
+ end
2230
+ end
2231
+
2232
+ def change_column(table_name, column_name, type, options)
2233
+ data_type = @adapter.type_to_sql(type, options[:limit], options[:precision], options[:scale])
2234
+ begin
2235
+ execute "ALTER TABLE #{table_name} ALTER #{column_name} SET DATA TYPE #{data_type}"
2236
+ rescue StandardError => exec_err
2237
+ if exec_err.message.include?('SQLCODE=-190')
2238
+ raise StatementInvalid,
2239
+ "Please consult documentation for compatible data types while changing column datatype. \
2240
+ The column datatype change to [#{data_type}] is not supported by this data server: #{exec_err}"
2241
+ else
2242
+ raise "#{exec_err}"
2243
+ end
2244
+ end
2245
+ reorg_table(table_name)
2246
+ change_column_null(table_name,column_name,options[:null],nil)
2247
+ change_column_default(table_name, column_name, options[:default])
2248
+ reorg_table(table_name)
2249
+ end
2250
+
2251
+ # DB2 specific ALTER TABLE statement to add a default clause
2252
+ def change_column_default(table_name, column_name, default)
2253
+ # SQL statement which alters column's default value
2254
+ change_column_sql = "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} \
2255
+ SET WITH DEFAULT #{@adapter.quote(default)}"
2256
+
2257
+ stmt = execute(change_column_sql)
2258
+ reorg_table(table_name)
2259
+ ensure
2260
+ IBM_DB.free_stmt(stmt) if stmt
2261
+ end
2262
+
2263
+ #DB2 specific ALTER TABLE statement to change the nullability of a column
2264
+ def change_column_null(table_name, column_name, null, default)
2265
+ if !default.nil?
2266
+ change_column_default(table_name, column_name, default)
2267
+ end
2268
+
2269
+ if !null.nil?
2270
+ if null
2271
+ change_column_sql = "ALTER TABLE #{table_name} ALTER #{column_name} DROP NOT NULL"
2272
+ else
2273
+ change_column_sql = "ALTER TABLE #{table_name} ALTER #{column_name} SET NOT NULL"
2274
+ end
2275
+ stmt = execute(change_column_sql)
2276
+ reorg_table(table_name)
2277
+ end
2278
+
2279
+ ensure
2280
+ IBM_DB.free_stmt(stmt) if stmt
2281
+ end
2282
+
2283
+ # This method returns the DB2 SQL type corresponding to the Rails
2284
+ # datetime/timestamp type
2285
+ def get_datetime_mapping
2286
+ return "timestamp"
2287
+ end
2288
+
2289
+ # This method returns the DB2 SQL type corresponding to the Rails
2290
+ # time type
2291
+ def get_time_mapping
2292
+ return "time"
2293
+ end
2294
+
2295
+ #This method returns the DB2 SQL type corresponding to Rails double type
2296
+ def get_double_mapping
2297
+ return "double"
2298
+ end
2299
+ =begin
2300
+ # Commenting this code, as offset handling is now part of sql and we need to handle it in select and also
2301
+ # need not set cursor type during prepare or execute
2302
+ # Fetches all the results available. IBM_DB.fetch_assoc(stmt) returns
2303
+ # an hash for each single record.
2304
+ # The loop stops when there aren't any more valid records to fetch
2305
+ def select(stmt)
2306
+ results = []
2307
+ begin
2308
+ if (!@offset.nil? && @offset >= 0) || (!@limit.nil? && @limit > 0)
2309
+ # We know at this point that there is an offset and/or a limit
2310
+ # Check if the cursor type is set correctly
2311
+ cursor_type = IBM_DB.get_option stmt, IBM_DB::SQL_ATTR_CURSOR_TYPE, 0
2312
+ @offset = 0 if @offset.nil?
2313
+ if (cursor_type == IBM_DB::SQL_CURSOR_STATIC)
2314
+ index = 0
2315
+ # Get @limit rows starting at @offset
2316
+ while (index < @limit)
2317
+ # We increment the offset by 1 because for DB2 the offset of the initial row is 1 instead of 0
2318
+ if single_hash = IBM_DB.fetch_assoc(stmt, @offset + index + 1)
2319
+ # Add the record to the +results+ array
2320
+ results << single_hash
2321
+ index = index + 1
2322
+ else
2323
+ # break from the while loop
2324
+ break
2325
+ end
2326
+ end
2327
+ else # cursor != IBM_DB::SQL_CURSOR_STATIC
2328
+ # If the result set contains a LOB, the cursor type will never be SQL_CURSOR_STATIC
2329
+ # because DB2 does not allow this. We can't use the offset mechanism because the cursor
2330
+ # is not scrollable. In this case, ignore first @offset rows and return rows starting
2331
+ # at @offset to @offset + @limit
2332
+ index = 0
2333
+ while (index < @offset + @limit)
2334
+ if single_hash = IBM_DB.fetch_assoc(stmt)
2335
+ # Add the record to the +results+ array only from row @offset to @offset + @limit
2336
+ if (index >= @offset)
2337
+ results << single_hash
2338
+ end
2339
+ index = index + 1
2340
+ else
2341
+ # break from the while loop
2342
+ break
2343
+ end
2344
+ end
2345
+ end
2346
+ # This is the case where limit is set to zero
2347
+ # Simply return an empty +results+
2348
+ elsif (!@limit.nil? && @limit == 0)
2349
+ results
2350
+ # No limits or offsets specified
2351
+ else
2352
+ while single_hash = IBM_DB.fetch_assoc(stmt)
2353
+ # Add the record to the +results+ array
2354
+ results << single_hash
2355
+ end
2356
+ return results
2357
+ end
2358
+ rescue StandardError => fetch_error # Handle driver fetch errors
2359
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2360
+ if error_msg && !error_msg.empty?
2361
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
2362
+ else
2363
+ error_msg = "An unexpected error occurred during data retrieval"
2364
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2365
+ raise error_msg
2366
+ end
2367
+ ensure
2368
+ # Assign the instance variables to nil. We will not be using them again
2369
+ @offset = nil
2370
+ @limit = nil
2371
+ end
2372
+ end
2373
+
2374
+ # Fetches all the results available. IBM_DB.fetch_array(stmt) returns
2375
+ # an array for each single record.
2376
+ # The loop stops when there aren't any more valid records to fetch
2377
+ def select_rows(sql, name, stmt, results)
2378
+ begin
2379
+ if (!@offset.nil? && @offset >= 0) || (!@limit.nil? && @limit > 0)
2380
+ # We know at this point that there is an offset and/or a limit
2381
+ # Check if the cursor type is set correctly
2382
+ cursor_type = IBM_DB.get_option stmt, IBM_DB::SQL_ATTR_CURSOR_TYPE, 0
2383
+ @offset = 0 if @offset.nil?
2384
+ if (cursor_type == IBM_DB::SQL_CURSOR_STATIC)
2385
+ index = 0
2386
+ # Get @limit rows starting at @offset
2387
+ while (index < @limit)
2388
+ # We increment the offset by 1 because for DB2 the offset of the initial row is 1 instead of 0
2389
+ if single_array = IBM_DB.fetch_array(stmt, @offset + index + 1)
2390
+ # Add the array to the +results+ array
2391
+ results << single_array
2392
+ index = index + 1
2393
+ else
2394
+ # break from the while loop
2395
+ break
2396
+ end
2397
+ end
2398
+ else # cursor != IBM_DB::SQL_CURSOR_STATIC
2399
+ # If the result set contains a LOB, the cursor type will never be SQL_CURSOR_STATIC
2400
+ # because DB2 does not allow this. We can't use the offset mechanism because the cursor
2401
+ # is not scrollable. In this case, ignore first @offset rows and return rows starting
2402
+ # at @offset to @offset + @limit
2403
+ index = 0
2404
+ while (index < @offset + @limit)
2405
+ if single_array = IBM_DB.fetch_array(stmt)
2406
+ # Add the array to the +results+ array only from row @offset to @offset + @limit
2407
+ if (index >= @offset)
2408
+ results << single_array
2409
+ end
2410
+ index = index + 1
2411
+ else
2412
+ # break from the while loop
2413
+ break
2414
+ end
2415
+ end
2416
+ end
2417
+ # This is the case where limit is set to zero
2418
+ # Simply return an empty +results+
2419
+ elsif (!@limit.nil? && @limit == 0)
2420
+ results
2421
+ # No limits or offsets specified
2422
+ else
2423
+ while single_array = IBM_DB.fetch_array(stmt)
2424
+ # Add the array to the +results+ array
2425
+ results << single_array
2426
+ end
2427
+ end
2428
+ rescue StandardError => fetch_error # Handle driver fetch errors
2429
+ error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT )
2430
+ if error_msg && !error_msg.empty?
2431
+ raise StatementInvalid,"Failed to retrieve data: #{error_msg}"
2432
+ else
2433
+ error_msg = "An unexpected error occurred during data retrieval"
2434
+ error_msg = error_msg + ": #{fetch_error.message}" if !fetch_error.message.empty?
2435
+ raise error_msg
2436
+ end
2437
+ ensure
2438
+ # Assign the instance variables to nil. We will not be using them again
2439
+ @offset = nil
2440
+ @limit = nil
2441
+ end
2442
+ return results
2443
+ end
2444
+
2445
+ # Praveen
2446
+ def prepare(sql,name = nil)
2447
+ # Check if there is a limit and/or an offset
2448
+ # If so then make sure and use a static cursor type
2449
+ begin
2450
+ if (!@offset.nil? && @offset >= 0) || (!@limit.nil? && @limit > 0)
2451
+ # Set the cursor type to static so we can later utilize the offset and limit correctly
2452
+ if stmt = IBM_DB.prepare(@adapter.connection, sql,
2453
+ {IBM_DB::SQL_ATTR_CURSOR_TYPE => IBM_DB::SQL_CURSOR_STATIC})
2454
+ stmt # Return the statement object
2455
+ else
2456
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2457
+ end
2458
+ else
2459
+ if stmt = IBM_DB.prepare(@adapter.connection, sql)
2460
+ stmt # Return the statement object
2461
+ else
2462
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2463
+ end
2464
+ end
2465
+ rescue StandardError => prep_err
2466
+ error_msg = "Failed to prepare sql #{sql}"
2467
+ error_msg = error_msg + ": #{prep_err.message}" if !prep_err.message.empty?
2468
+ raise error_msg
2469
+ end
2470
+ end
2471
+
2472
+ # Praveen
2473
+ def execute(sql, name = nil)
2474
+ # Check if there is a limit and/or an offset
2475
+ # If so then make sure and use a static cursor type
2476
+ begin
2477
+ if (!@offset.nil? && @offset >= 0) || (!@limit.nil? && @limit > 0)
2478
+ # Set the cursor type to static so we can later utilize the offset and limit correctly
2479
+ if stmt = IBM_DB.exec(@adapter.connection, sql,
2480
+ {IBM_DB::SQL_ATTR_CURSOR_TYPE => IBM_DB::SQL_CURSOR_STATIC})
2481
+ stmt # Return the statement object
2482
+ else
2483
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2484
+ end
2485
+ else
2486
+ if stmt = IBM_DB.exec(@adapter.connection, sql)
2487
+ stmt # Return the statement object
2488
+ else
2489
+ raise StatementInvalid, IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2490
+ end
2491
+ end
2492
+ rescue StandardError => exec_err
2493
+ error_msg = "Failed to execute statement"
2494
+ error_msg = error_msg + ": #{exec_err.message}" if !exec_err.message.empty?
2495
+ raise error_msg
2496
+ end
2497
+ end
2498
+ =end
2499
+ def query_offset_limit(sql, offset, limit)
2500
+ if(offset.nil? && limit.nil?)
2501
+ return sql
2502
+ end
2503
+
2504
+ if (offset.nil?)
2505
+ return sql << " FETCH FIRST #{limit} ROWS ONLY"
2506
+ end
2507
+
2508
+ if(limit.nil?)
2509
+ sql.sub!(/SELECT/i,"SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_row_num FROM (SELECT")
2510
+ return sql << ") AS I) AS O WHERE sys_row_num > #{offset}"
2511
+ end
2512
+
2513
+ # Defines what will be the last record
2514
+ last_record = offset + limit
2515
+ # Transforms the SELECT query in order to retrieve/fetch only
2516
+ # a number of records after the specified offset.
2517
+ # 'select' or 'SELECT' is replaced with the partial query below that adds the sys_row_num column
2518
+ # to select with the condition of this column being between offset+1 and the offset+limit
2519
+ sql.sub!(/SELECT/i,"SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_row_num FROM (SELECT")
2520
+ # The final part of the query is appended to include a WHERE...BETWEEN...AND condition,
2521
+ # and retrieve only a LIMIT number of records starting from the OFFSET+1
2522
+ sql << ") AS I) AS O WHERE sys_row_num BETWEEN #{offset+1} AND #{last_record}"
2523
+ end
2524
+
2525
+ def query_offset_limit!(sql, offset, limit, options)
2526
+ if(offset.nil? && limit.nil?)
2527
+ options[:paramArray] = []
2528
+ return sql
2529
+ end
2530
+
2531
+ if (offset.nil?)
2532
+ options[:paramArray] = []
2533
+ return sql << " FETCH FIRST #{limit} ROWS ONLY"
2534
+ end
2535
+
2536
+ if(limit.nil?)
2537
+ sql.sub!(/SELECT/i,"SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_row_num FROM (SELECT")
2538
+ sql << ") AS I) AS O WHERE sys_row_num > ?"
2539
+ options[:paramArray] = [offset]
2540
+ return
2541
+ end
2542
+
2543
+ # Defines what will be the last record
2544
+ last_record = offset + limit
2545
+ # Transforms the SELECT query in order to retrieve/fetch only
2546
+ # a number of records after the specified offset.
2547
+ # 'select' or 'SELECT' is replaced with the partial query below that adds the sys_row_num column
2548
+ # to select with the condition of this column being between offset+1 and the offset+limit
2549
+ sql.sub!(/SELECT/i,"SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_row_num FROM (SELECT")
2550
+ # The final part of the query is appended to include a WHERE...BETWEEN...AND condition,
2551
+ # and retrieve only a LIMIT number of records starting from the OFFSET+1
2552
+ sql << ") AS I) AS O WHERE sys_row_num BETWEEN ? AND ?"
2553
+ options[:paramArray] = [offset+1, last_record]
2554
+ end
2555
+
2556
+ # This method generates the default blob value specified for
2557
+ # DB2 Dataservers
2558
+ def set_binary_default(value)
2559
+ "BLOB('#{value}')"
2560
+ end
2561
+
2562
+ # This method generates the blob value specified for DB2 Dataservers
2563
+ def set_binary_value
2564
+ "BLOB('?')"
2565
+ end
2566
+
2567
+ # This method generates the default clob value specified for
2568
+ # DB2 Dataservers
2569
+ def set_text_default(value)
2570
+ "'#{value}'"
2571
+ end
2572
+
2573
+ # For DB2 Dataservers , the arguments to the meta-data functions
2574
+ # need to be in upper-case
2575
+ def set_case(value)
2576
+ value.upcase
2577
+ end
2578
+ end # class IBM_DB2
2579
+
2580
+ class IBM_DB2_LUW < IBM_DB2
2581
+ # Reorganizes the table for column changes
2582
+ def reorg_table(table_name)
2583
+ execute("CALL ADMIN_CMD('REORG TABLE #{table_name}')")
2584
+ end
2585
+ end # class IBM_DB2_LUW
2586
+
2587
+ class IBM_DB2_LUW_COBRA < IBM_DB2_LUW
2588
+ # Cobra supports parameterised timestamp,
2589
+ # hence overriding following method to allow timestamp datatype to be parameterised
2590
+ def limit_not_supported_types
2591
+ [:integer, :double, :date, :time, :xml, :bigint]
2592
+ end
2593
+
2594
+ # Alter table column for renaming a column
2595
+ # This feature is supported for against DB2 V97 and above only
2596
+ def rename_column(table_name, column_name, new_column_name)
2597
+ _table_name = table_name.to_s
2598
+ _column_name = column_name.to_s
2599
+ _new_column_name = new_column_name.to_s
2600
+
2601
+ nil_condition = _table_name.nil? || _column_name.nil? || _new_column_name.nil?
2602
+ empty_condition = _table_name.empty? ||
2603
+ _column_name.empty? ||
2604
+ _new_column_name.empty? unless nil_condition
2605
+
2606
+ if nil_condition || empty_condition
2607
+ raise ArgumentError,"One of the arguments passed to rename_column is empty or nil"
2608
+ end
2609
+
2610
+ begin
2611
+ rename_column_sql = "ALTER TABLE #{_table_name} RENAME COLUMN #{_column_name} \
2612
+ TO #{_new_column_name}"
2613
+
2614
+ unless stmt = execute(rename_column_sql)
2615
+ error_msg = IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2616
+ if error_msg && !error_msg.empty?
2617
+ raise "Rename column failed : #{error_msg}"
2618
+ else
2619
+ raise StandardError.new('An unexpected error occurred during renaming the column')
2620
+ end
2621
+ end
2622
+
2623
+ reorg_table(_table_name)
2624
+
2625
+ ensure
2626
+ IBM_DB.free_stmt(stmt) if stmt
2627
+ end #End of begin
2628
+ end # End of rename_column
2629
+ end #IBM_DB2_LUW_COBRA
2630
+
2631
+ module HostedDataServer
2632
+ require 'pathname'
2633
+ #find DB2-i5-zOS rezerved words file relative path
2634
+ rfile = Pathname.new(File.dirname(__FILE__)).parent + 'vendor' + 'db2-i5-zOS.yaml'
2635
+ if rfile
2636
+ RESERVED_WORDS = open(rfile.to_s) {|f| YAML.load(f) }
2637
+ def check_reserved_words(col_name)
2638
+ if RESERVED_WORDS[col_name]
2639
+ '"' + RESERVED_WORDS[col_name] + '"'
2640
+ else
2641
+ col_name.to_s
2642
+ end
2643
+ end
2644
+ else
2645
+ raise "Failed to locate IBM_DB Adapter dependency: #{rfile}"
2646
+ end
2647
+ end # module HostedDataServer
2648
+
2649
+ class IBM_DB2_ZOS < IBM_DB2
2650
+ # since v9 doesn't need, suggest putting it in HostedDataServer?
2651
+ def create_index_after_table(table_name,column_name)
2652
+ @adapter.add_index(table_name, column_name, :unique => true)
2653
+ end
2654
+
2655
+ def remove_column(table_name, column_name)
2656
+ raise NotImplementedError,
2657
+ "remove_column is not supported by the DB2 for zOS data server"
2658
+ end
2659
+
2660
+ #Alter table column for renaming a column
2661
+ def rename_column(table_name, column_name, new_column_name)
2662
+ _table_name = table_name.to_s
2663
+ _column_name = column_name.to_s
2664
+ _new_column_name = new_column_name.to_s
2665
+
2666
+ nil_condition = _table_name.nil? || _column_name.nil? || _new_column_name.nil?
2667
+ empty_condition = _table_name.empty? ||
2668
+ _column_name.empty? ||
2669
+ _new_column_name.empty? unless nil_condition
2670
+
2671
+ if nil_condition || empty_condition
2672
+ raise ArgumentError,"One of the arguments passed to rename_column is empty or nil"
2673
+ end
2674
+
2675
+ begin
2676
+ rename_column_sql = "ALTER TABLE #{_table_name} RENAME COLUMN #{_column_name} \
2677
+ TO #{_new_column_name}"
2678
+
2679
+ unless stmt = execute(rename_column_sql)
2680
+ error_msg = IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2681
+ if error_msg && !error_msg.empty?
2682
+ raise "Rename column failed : #{error_msg}"
2683
+ else
2684
+ raise StandardError.new('An unexpected error occurred during renaming the column')
2685
+ end
2686
+ end
2687
+
2688
+ reorg_table(_table_name)
2689
+
2690
+ ensure
2691
+ IBM_DB.free_stmt(stmt) if stmt
2692
+ end #End of begin
2693
+ end # End of rename_column
2694
+
2695
+ # DB2 z/OS only allows NULL or "" (empty) string as DEFAULT value for a BLOB column.
2696
+ # For non-empty string and non-NULL values, the server returns error
2697
+ def set_binary_default(value)
2698
+ "#{value}"
2699
+ end
2700
+
2701
+ def change_column_default(table_name, column_name, default)
2702
+ unless default
2703
+ raise NotImplementedError,
2704
+ "DB2 for zOS data server version 9 does not support changing the column default to NULL"
2705
+ else
2706
+ super
2707
+ end
2708
+ end
2709
+
2710
+ def change_column_null(table_name, column_name, null, default)
2711
+ raise NotImplementedError,
2712
+ "DB2 for zOS data server does not support changing the column's nullability"
2713
+ end
2714
+ end # class IBM_DB2_ZOS
2715
+
2716
+ class IBM_DB2_ZOS_8 < IBM_DB2_ZOS
2717
+ include HostedDataServer
2718
+
2719
+ def query_offset_limit(sql, offset, limit)
2720
+ if (!limit.nil?)
2721
+ sql << " FETCH FIRST #{limit} ROWS ONLY"
2722
+ end
2723
+ return sql
2724
+ end
2725
+
2726
+ def query_offset_limit!(sql, offset, limit, options)
2727
+ if (!limit.nil?)
2728
+ sql << " FETCH FIRST #{limit} ROWS ONLY"
2729
+ end
2730
+ options[:paramArray] = []
2731
+ end
2732
+
2733
+ # This call is needed on DB2 z/OS v8 for the creation of tables
2734
+ # with LOBs. When issued, this call does the following:
2735
+ # DB2 creates LOB table spaces, auxiliary tables, and indexes on auxiliary
2736
+ # tables for LOB columns.
2737
+ def setup_for_lob_table()
2738
+ execute "SET CURRENT RULES = 'STD'"
2739
+ end
2740
+
2741
+ def rename_column(table_name, column_name, new_column_name)
2742
+ raise NotImplementedError, "rename_column is not implemented for DB2 on zOS 8"
2743
+ end
2744
+
2745
+ def change_column_default(table_name, column_name, default)
2746
+ raise NotImplementedError,
2747
+ "DB2 for zOS data server version 8 does not support changing the column default"
2748
+ end
2749
+
2750
+ end # class IBM_DB2_ZOS_8
2751
+
2752
+ class IBM_DB2_I5 < IBM_DB2
2753
+ include HostedDataServer
2754
+ end # class IBM_DB2_I5
2755
+
2756
+ class IBM_IDS < IBM_DataServer
2757
+ # IDS does not support the SET SCHEMA syntax
2758
+ def set_schema(schema)
2759
+ end
2760
+
2761
+ # IDS specific ALTER TABLE statement to rename a column
2762
+ def rename_column(table_name, column_name, new_column_name)
2763
+ _table_name = table_name.to_s
2764
+ _column_name = column_name.to_s
2765
+ _new_column_name = new_column_name.to_s
2766
+
2767
+ nil_condition = _table_name.nil? || _column_name.nil? || _new_column_name.nil?
2768
+ empty_condition = _table_name.empty? ||
2769
+ _column_name.empty? ||
2770
+ _new_column_name.empty? unless nil_condition
2771
+
2772
+ if nil_condition || empty_condition
2773
+ raise ArgumentError,"One of the arguments passed to rename_column is empty or nil"
2774
+ end
2775
+
2776
+ begin
2777
+ rename_column_sql = "RENAME COLUMN #{table_name}.#{column_name} TO \
2778
+ #{new_column_name}"
2779
+
2780
+ unless stmt = execute(rename_column_sql)
2781
+ error_msg = IBM_DB.getErrormsg(@adapter.connection, IBM_DB::DB_CONN )
2782
+ if error_msg && !error_msg.empty?
2783
+ raise "Rename column failed : #{error_msg}"
2784
+ else
2785
+ raise StandardError.new('An unexpected error occurred during renaming the column')
2786
+ end
2787
+ end
2788
+
2789
+ reorg_table(_table_name)
2790
+
2791
+ ensure
2792
+ IBM_DB.free_stmt(stmt) if stmt
2793
+ end #End of begin
2794
+ end # End of rename_column
2795
+
2796
+ def primary_key_definition(start_id)
2797
+ return "SERIAL(#{start_id}) PRIMARY KEY"
2798
+ end
2799
+
2800
+ def change_column(table_name, column_name, type, options)
2801
+ if !options[:null].nil? && !options[:null]
2802
+ execute "ALTER TABLE #{table_name} MODIFY #{column_name} #{@adapter.type_to_sql(type, options[:limit], options[:precision], options[:scale])} NOT NULL"
2803
+ else
2804
+ execute "ALTER TABLE #{table_name} MODIFY #{column_name} #{@adapter.type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
2805
+ end
2806
+ if !options[:default].nil?
2807
+ change_column_default(table_name, column_name, options[:default])
2808
+ end
2809
+ reorg_table(table_name)
2810
+ end
2811
+
2812
+ # IDS specific ALTER TABLE statement to add a default clause
2813
+ # IDS requires the data type to be explicitly specified when adding the
2814
+ # DEFAULT clause
2815
+ def change_column_default(table_name, column_name, default)
2816
+ sql_type = nil
2817
+ is_nullable = true
2818
+ @adapter.columns(table_name).select do |col|
2819
+ if (col.name == column_name)
2820
+ sql_type = @adapter.type_to_sql(col.type, col.limit, col.precision, col.scale)
2821
+ is_nullable = col.null
2822
+ end
2823
+ end
2824
+ # SQL statement which alters column's default value
2825
+ change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{sql_type} DEFAULT #{@adapter.quote(default)}"
2826
+ change_column_sql << " NOT NULL" unless is_nullable
2827
+ stmt = execute(change_column_sql)
2828
+ reorg_table(table_name)
2829
+ # Ensures to free the resources associated with the statement
2830
+ ensure
2831
+ IBM_DB.free_stmt(stmt) if stmt
2832
+ end
2833
+
2834
+ # IDS specific ALTER TABLE statement to change the nullability of a column
2835
+ def change_column_null(table_name,column_name,null,default)
2836
+ if !default.nil?
2837
+ change_column_default table_name, column_name, default
2838
+ end
2839
+ sql_type = nil
2840
+ @adapter.columns(table_name).select do |col|
2841
+ if (col.name == column_name)
2842
+ sql_type = @adapter.type_to_sql(col.type, col.limit, col.precision, col.scale)
2843
+ end
2844
+ end
2845
+ if !null.nil?
2846
+ if !null
2847
+ change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{sql_type} NOT NULL"
2848
+ else
2849
+ change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{sql_type}"
2850
+ end
2851
+ stmt = execute(change_column_sql)
2852
+ reorg_table(table_name)
2853
+ end
2854
+
2855
+ ensure
2856
+ IBM_DB.free_stmt(stmt) if stmt
2857
+ end
2858
+
2859
+ # Reorganizes the table for column changes
2860
+ def reorg_table(table_name)
2861
+ execute("UPDATE STATISTICS FOR TABLE #{table_name}")
2862
+ end
2863
+
2864
+ # This method returns the IDS SQL type corresponding to the Rails
2865
+ # datetime/timestamp type
2866
+ def get_datetime_mapping
2867
+ return "datetime year to fraction(5)"
2868
+ end
2869
+
2870
+ # This method returns the IDS SQL type corresponding to the Rails
2871
+ # time type
2872
+ def get_time_mapping
2873
+ return "datetime hour to second"
2874
+ end
2875
+
2876
+ # This method returns the IDS SQL type corresponding to Rails double type
2877
+ def get_double_mapping
2878
+ return "double precision"
2879
+ end
2880
+
2881
+ # Handling offset/limit as per Informix requirements
2882
+ def query_offset_limit(sql, offset, limit)
2883
+ if limit != 0
2884
+ if !offset.nil?
2885
+ # Modifying the SQL to utilize the skip and limit amounts
2886
+ sql.gsub!(/SELECT/i,"SELECT SKIP #{offset} LIMIT #{limit}")
2887
+ else
2888
+ # Modifying the SQL to retrieve only the first #{limit} rows
2889
+ sql = sql.gsub!("SELECT","SELECT FIRST #{limit}")
2890
+ end
2891
+ else
2892
+ # Modifying the SQL to ensure that no rows will be returned
2893
+ sql.gsub!(/SELECT/i,"SELECT * FROM (SELECT")
2894
+ sql << ") WHERE 0 = 1"
2895
+ end
2896
+ end
2897
+
2898
+ # Handling offset/limit as per Informix requirements
2899
+ def query_offset_limit!(sql, offset, limit, options)
2900
+ if limit != 0
2901
+ if !offset.nil?
2902
+ # Modifying the SQL to utilize the skip and limit amounts
2903
+ sql.gsub!(/SELECT/i,"SELECT SKIP #{offset} LIMIT #{limit}")
2904
+ else
2905
+ # Modifying the SQL to retrieve only the first #{limit} rows
2906
+ sql = sql.gsub!("SELECT","SELECT FIRST #{limit}")
2907
+ end
2908
+ else
2909
+ # Modifying the SQL to ensure that no rows will be returned
2910
+ sql.gsub!(/SELECT/i,"SELECT * FROM (SELECT")
2911
+ sql << ") WHERE 0 = 1"
2912
+ end
2913
+ end
2914
+
2915
+ # Method that returns the last automatically generated ID
2916
+ # on the given +@connection+. This method is required by the +insert+
2917
+ # method. IDS returns the last generated serial value in the SQLCA unlike
2918
+ # DB2 where the generated value has to be retrieved using the
2919
+ # IDENTITY_VAL_LOCAL function. We used the "stmt" parameter to identify
2920
+ # the statement resource from which to get the last generated value
2921
+ def last_generated_id(stmt)
2922
+ IBM_DB.get_last_serial_value(stmt)
2923
+ end
2924
+
2925
+ # This method throws an error when trying to create a default value on a
2926
+ # BLOB/CLOB column for IDS. The documentation states: "if the column is a
2927
+ # BLOB or CLOB datatype, NULL is the only valid default value."
2928
+ def set_binary_default(value)
2929
+ unless (value == 'NULL')
2930
+ raise "Informix Dynamic Server only allows NULL as a valid default value for a BLOB data type"
2931
+ end
2932
+ end
2933
+
2934
+ # For Informix Dynamic Server, we treat binary value same as we treat a
2935
+ # text value. We support literals by converting the insert into a dummy
2936
+ # insert and an update (See handle_lobs method above)
2937
+ def set_binary_value
2938
+ "'@@@IBMBINARY@@@'"
2939
+ end
2940
+
2941
+ # This method throws an error when trying to create a default value on a
2942
+ # BLOB/CLOB column for IDS. The documentation states: "if the column is
2943
+ # a BLOB or CLOB datatype, NULL is the only valid default value."
2944
+ def set_text_default(value)
2945
+ unless (value == 'NULL')
2946
+ raise "Informix Dynamic Server only allows NULL as a valid default value for a CLOB data type"
2947
+ end
2948
+ end
2949
+
2950
+ # For Informix Dynamic Server, the arguments to the meta-data functions
2951
+ # need to be in lower-case
2952
+ def set_case(value)
2953
+ value.downcase
2954
+ end
2955
+ end # class IBM_IDS
2956
+ end # module ConnectionAdapters
2957
+ end # module ActiveRecord
2958
+
2959
+ module Arel
2960
+ module Visitors
2961
+ class Visitor #opening and closing the class to ensure backward compatibility
2962
+ end
2963
+
2964
+ class ToSql < Arel::Visitors::Visitor #opening and closing the class to ensure backward compatibility
2965
+ # In case when using Rails-2.3.x there is no arel used due to which the constructor has to be defined explicitly
2966
+ # to ensure the same code works on any version of Rails
2967
+
2968
+ #Check Arel version
2969
+ begin
2970
+ @arelVersion = Arel::VERSION.to_i
2971
+ rescue
2972
+ @arelVersion = 0
2973
+ end
2974
+
2975
+ if(@arelVersion >= 3)
2976
+ def initialize connection
2977
+ @connection = connection
2978
+ @schema_cache = connection.schema_cache if(connection.respond_to?(:schema_cache))
2979
+ @quoted_tables = {}
2980
+ @quoted_columns = {}
2981
+ @last_column = nil
2982
+ end
2983
+ end
2984
+ end
2985
+
2986
+ class IBM_DB < Arel::Visitors::ToSql
2987
+ private
2988
+
2989
+ def visit_Arel_Nodes_Limit o, a=nil
2990
+ visit o.expr
2991
+ end
2992
+
2993
+ def visit_Arel_Nodes_Offset o, a=nil
2994
+ visit o.expr
2995
+ end
2996
+
2997
+ def visit_Arel_Nodes_SelectStatement o, a=nil
2998
+ #Interim fix for backward compatibility [Arel 4.0.0 and below]
2999
+ if self.method(:visit_Arel_Nodes_SelectCore).arity == 1
3000
+ sql = [
3001
+ (visit(o.with) if o.with),
3002
+ o.cores.map { |x| visit_Arel_Nodes_SelectCore x }.join,
3003
+ ("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
3004
+ ].compact.join ' '
3005
+ else
3006
+ sql = [
3007
+ (visit(o.with) if o.with),
3008
+ o.cores.map { |x| visit_Arel_Nodes_SelectCore x,a }.join,
3009
+ ("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
3010
+ ].compact.join ' '
3011
+ end
3012
+
3013
+ if o.limit
3014
+ limit = visit(o.limit)
3015
+ else
3016
+ limit = nil
3017
+ end
3018
+
3019
+ if o.offset
3020
+ offset = visit(o.offset)
3021
+ else
3022
+ offset = nil
3023
+ end
3024
+ @connection.add_limit_offset!(sql, {:limit => limit, :offset => offset})
3025
+ sql << " #{(visit(o.lock) if o.lock)}"
3026
+ return sql
3027
+ end
3028
+
3029
+ end
3030
+ end
3031
+ end