ibm_db 2.5.6-x86-mswin32-60

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