ibm_db 2.5.26-universal-darwin-14

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