db2 2.5.6

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