ibm_db 2.5.26-universal-darwin-14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +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