db2 2.5.10 → 2.6.0

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