ibm_db 2.5.6-x86-mingw32

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