db2 2.5.10 → 2.6.0

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