ibm_db 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ext/ruby_ibm_db.h ADDED
@@ -0,0 +1,164 @@
1
+ /*
2
+ +----------------------------------------------------------------------+
3
+ | Licensed Materials - Property of IBM |
4
+ | |
5
+ | (C) Copyright IBM Corporation 2007. |
6
+ +----------------------------------------------------------------------+
7
+ | Authors: Sushant Koduru, Lynh Nguyen, Kanchana Padmanabhan, |
8
+ | Dan Scott, Helmut Tessarek, Kellen Bombardier, Sam Ruby |
9
+ +----------------------------------------------------------------------+
10
+ */
11
+
12
+ #ifndef RUBY_IBM_DB_H
13
+ #define RUBY_IBM_DB_H
14
+
15
+ #include <stdio.h>
16
+ #include <string.h>
17
+ #include <stdlib.h>
18
+ #include <sqlcli1.h>
19
+
20
+ #ifndef SQL_XML
21
+ #define SQL_XML -370
22
+ #endif
23
+
24
+ #ifndef SQL_ATTR_REPLACE_QUOTED_LITERALS
25
+ #define SQL_ATTR_REPLACE_QUOTED_LITERALS 116
26
+ #endif
27
+
28
+ #ifdef _WIN32
29
+ #define RUBY_IBM_DB_API __declspec(dllexport)
30
+ #else
31
+ #define RUBY_IBM_DB_API
32
+ #endif
33
+
34
+ /* strlen(" SQLCODE=") added in */
35
+ #define DB2_MAX_ERR_MSG_LEN (SQL_MAX_MESSAGE_LENGTH + SQL_SQLSTATE_SIZE + 10)
36
+
37
+ /* Used in _ruby_parse_options */
38
+ #define DB2_ERRMSG 1
39
+ #define DB2_ERR 2
40
+
41
+ /* DB2 instance environment variable */
42
+ #define DB2_VAR_INSTANCE "DB2INSTANCE="
43
+
44
+ /******** Makes code compatible with the options used by the user */
45
+ #define BINARY 1
46
+ #define CONVERT 2
47
+ #define PASSTHRU 3
48
+ #define PARAM_FILE 11
49
+
50
+ #ifdef PASE
51
+ #define SQL_IS_INTEGER 0
52
+ #define SQL_BEST_ROWID 0
53
+ #define SQLLEN long
54
+ #define SQLFLOAT double
55
+ #endif
56
+
57
+ /*fetch*/
58
+ #define FETCH_INDEX 0x01
59
+ #define FETCH_ASSOC 0x02
60
+ #define FETCH_BOTH 0x03
61
+
62
+ /* Change column case */
63
+ #define ATTR_CASE 3271982
64
+ #define CASE_NATURAL 0
65
+ #define CASE_LOWER 1
66
+ #define CASE_UPPER 2
67
+
68
+ /* maximum sizes */
69
+ #define USERID_LEN 16
70
+ #define ACCTSTR_LEN 200
71
+ #define APPLNAME_LEN 32
72
+ #define WRKSTNNAME_LEN 18
73
+
74
+ void Init_ibm_db();
75
+
76
+ VALUE ibm_db_connect(int argc, VALUE *argv, VALUE self);
77
+ VALUE ibm_db_commit(int argc, VALUE *argv, VALUE self);
78
+ VALUE ibm_db_pconnect(int argc, VALUE *argv, VALUE self);
79
+ VALUE ibm_db_autocommit(int argc, VALUE *argv, VALUE self);
80
+ VALUE ibm_db_bind_param(int argc, VALUE *argv, VALUE self);
81
+ VALUE ibm_db_close(int argc, VALUE *argv, VALUE self);
82
+ VALUE ibm_db_columnprivileges(int argc, VALUE *argv, VALUE self);
83
+ VALUE ibm_db_column_privileges(int argc, VALUE *argv, VALUE self);
84
+ VALUE ibm_db_columns(int argc, VALUE *argv, VALUE self);
85
+ VALUE ibm_db_foreignkeys(int argc, VALUE *argv, VALUE self);
86
+ VALUE ibm_db_foreign_keys(int argc, VALUE *argv, VALUE self);
87
+ VALUE ibm_db_primarykeys(int argc, VALUE *argv, VALUE self);
88
+ VALUE ibm_db_primary_keys(int argc, VALUE *argv, VALUE self);
89
+ VALUE ibm_db_procedure_columns(int argc, VALUE *argv, VALUE self);
90
+ VALUE ibm_db_procedures(int argc, VALUE *argv, VALUE self);
91
+ VALUE ibm_db_specialcolumns(int argc, VALUE *argv, VALUE self);
92
+ VALUE ibm_db_special_columns(int argc, VALUE *argv, VALUE self);
93
+ VALUE ibm_db_statistics(int argc, VALUE *argv, VALUE self);
94
+ VALUE ibm_db_tableprivileges(int argc, VALUE *argv, VALUE self);
95
+ VALUE ibm_db_table_privileges(int argc, VALUE *argv, VALUE self);
96
+ VALUE ibm_db_tables(int argc, VALUE *argv, VALUE self);
97
+ VALUE ibm_db_commit(int argc, VALUE *argv, VALUE self);
98
+ VALUE ibm_db_exec(int argc, VALUE *argv, VALUE self);
99
+ VALUE ibm_db_prepare(int argc, VALUE *argv, VALUE self);
100
+ VALUE ibm_db_execute(int argc, VALUE *argv, VALUE self);
101
+ VALUE ibm_db_conn_errormsg(int argc, VALUE *argv, VALUE self);
102
+ VALUE ibm_db_stmt_errormsg(int argc, VALUE *argv, VALUE self);
103
+ VALUE ibm_db_conn_error(int argc, VALUE *argv, VALUE self);
104
+ VALUE ibm_db_stmt_error(int argc, VALUE *argv, VALUE self);
105
+ VALUE ibm_db_next_result(int argc, VALUE *argv, VALUE self);
106
+ VALUE ibm_db_num_fields(int argc, VALUE *argv, VALUE self);
107
+ VALUE ibm_db_num_rows(int argc, VALUE *argv, VALUE self);
108
+ VALUE ibm_db_field_name(int argc, VALUE *argv, VALUE self);
109
+ VALUE ibm_db_field_display_size(int argc, VALUE *argv, VALUE self);
110
+ VALUE ibm_db_field_num(int argc, VALUE *argv, VALUE self);
111
+ VALUE ibm_db_field_precision(int argc, VALUE *argv, VALUE self);
112
+ VALUE ibm_db_field_scale(int argc, VALUE *argv, VALUE self);
113
+ VALUE ibm_db_field_type(int argc, VALUE *argv, VALUE self);
114
+ VALUE ibm_db_field_width(int argc, VALUE *argv, VALUE self);
115
+ VALUE ibm_db_cursor_type(int argc, VALUE *argv, VALUE self);
116
+ VALUE ibm_db_rollback(int argc, VALUE *argv, VALUE self);
117
+ VALUE ibm_db_free_stmt(int argc, VALUE *argv, VALUE self);
118
+ VALUE ibm_db_result(int argc, VALUE *argv, VALUE self);
119
+ VALUE ibm_db_fetch_row(int argc, VALUE *argv, VALUE self);
120
+ VALUE ibm_db_fetch_assoc(int argc, VALUE *argv, VALUE self);
121
+ VALUE ibm_db_fetch_array(int argc, VALUE *argv, VALUE self);
122
+ VALUE ibm_db_fetch_both(int argc, VALUE *argv, VALUE self);
123
+ VALUE ibm_db_result_all(int argc, VALUE *argv, VALUE self);
124
+ VALUE ibm_db_free_result(int argc, VALUE *argv, VALUE self);
125
+ VALUE ibm_db_set_option(int argc, VALUE *argv, VALUE self);
126
+ VALUE ibm_db_setoption(int argc, VALUE *argv, VALUE self);
127
+ VALUE ibm_db_get_option(int argc, VALUE *argv, VALUE self);
128
+ VALUE ibm_db_getoption(int argc, VALUE *argv, VALUE self);
129
+ VALUE ibm_db_fetch_object(int argc, VALUE *argv, VALUE self);
130
+ VALUE ibm_db_server_info(int argc, VALUE *argv, VALUE self);
131
+ VALUE ibm_db_client_info(int argc, VALUE *argv, VALUE self);
132
+ VALUE ibm_db_active(int argc, VALUE *argv, VALUE self);
133
+
134
+ /*
135
+ Declare any global variables you may need between the BEGIN
136
+ and END macros here:
137
+ */
138
+ struct _ibm_db_globals {
139
+ int bin_mode;
140
+ char __ruby_conn_err_msg[DB2_MAX_ERR_MSG_LEN];
141
+ char __ruby_conn_err_state[SQL_SQLSTATE_SIZE + 1];
142
+ char __ruby_stmt_err_msg[DB2_MAX_ERR_MSG_LEN];
143
+ char __ruby_stmt_err_state[SQL_SQLSTATE_SIZE + 1];
144
+ #ifdef PASE /* i5/OS ease of use turn off commit */
145
+ long i5_allow_commit;
146
+ #endif /* PASE */
147
+ };
148
+
149
+ /*
150
+ TODO: make this threadsafe
151
+ */
152
+
153
+ #define IBM_DB_G(v) (ibm_db_globals->v)
154
+
155
+ #endif /* RUBY_IBM_DB_H */
156
+
157
+
158
+ /*
159
+ * Local variables:
160
+ * tab-width: 4
161
+ * c-basic-offset: 4
162
+ * indent-tabs-mode: t
163
+ * End:
164
+ */
data/init.rb ADDED
@@ -0,0 +1,42 @@
1
+ # +----------------------------------------------------------------------+
2
+ # | Licensed Materials - Property of IBM |
3
+ # | |
4
+ # | (C) Copyright IBM Corporation 2006, 2007. |
5
+ # +----------------------------------------------------------------------+
6
+
7
+ require 'pathname'
8
+
9
+ begin
10
+ puts ".. Attempt to load IBM_DB Ruby driver for IBM Data Servers for this platform: #{RUBY_PLATFORM}"
11
+ unless defined? IBM_DB
12
+ # find IBM_DB driver path relative init.rb
13
+ drv_path = Pathname.new(File.dirname(__FILE__)) + 'lib'
14
+ drv_path += (RUBY_PLATFORM =~ /mswin32/) ? 'mswin32' : 'linux32'
15
+ puts ".. Locate IBM_DB Ruby driver path: #{drv_path}"
16
+ drv_lib = drv_path + 'ibm_db.so'
17
+ require "#{drv_lib.to_s}"
18
+ puts ".. Successfuly loaded IBM_DB Ruby driver: #{drv_lib}"
19
+ end
20
+ rescue
21
+ raise LoadError, "Failed to load IBM_DB Driver !?"
22
+ end
23
+
24
+ # Include IBM_DB in the list of supported adapters
25
+ RAILS_CONNECTION_ADAPTERS << 'ibm_db'
26
+ # load IBM_DB Adapter provided by the plugin
27
+ require 'active_record/connection_adapters/ibm_db_adapter'
28
+
29
+ # Override the frameworks initialization to re-enable ActiveRecord after being
30
+ # disabled during plugin install (i.e. config.frameworks -= [ :active_record ])
31
+ [:load_environment,\
32
+ :initialize_database,\
33
+ :initialize_logger,\
34
+ :initialize_framework_logging,\
35
+ :initialize_framework_settings,\
36
+ :initialize_framework_views,\
37
+ :initialize_dependency_mechanism,\
38
+ :load_environment ].each do |routine|
39
+ Rails::Initializer.run(routine) do |config|
40
+ config.frameworks = [:active_record]
41
+ end
42
+ end
data/lib/IBM_DB.rb ADDED
@@ -0,0 +1,2 @@
1
+ require (RUBY_PLATFORM =~ /mswin32/) ? 'mswin32/ibm_db.so' : 'ibm_db.so'
2
+ require 'active_record/connection_adapters/ibm_db_adapter'
@@ -0,0 +1,1053 @@
1
+ # +----------------------------------------------------------------------+
2
+ # | Licensed Materials - Property of IBM |
3
+ # | |
4
+ # | (C) Copyright IBM Corporation 2006, 2007. |
5
+ # +----------------------------------------------------------------------+
6
+ # | Author: Antonio Cangiano <cangiano@ca.ibm.com> |
7
+ # +----------------------------------------------------------------------+
8
+
9
+ require 'active_record/connection_adapters/abstract_adapter'
10
+
11
+ module ActiveRecord
12
+ class Base
13
+ # Method required to handle LOBs and XML fields. An after save callback checks if a marker
14
+ # has been inserted by the calling insert or update method, and if so it proceeds to update that record
15
+ # with the real large object through query binding.
16
+ after_save :handle_lobs
17
+ def handle_lobs()
18
+ if connection.kind_of?(ConnectionAdapters::IBM_DBAdapter)
19
+ # Checks that the insert or update had at least a BLOB, CLOB or XML field
20
+ if connection.sql =~ /BLOB\('(.*)'\)/i || connection.sql =~ /@@@IBMTEXT@@@/i || connection.sql =~ /@@@IBMXML@@@/i
21
+ update_query = "UPDATE #{self.class.table_name} SET ("
22
+ counter = 0
23
+ values = []
24
+ params = []
25
+ # Selects only binary, text and xml columns
26
+ self.class.columns.select{|col| col.type == :binary || col.type == :text || col.type == :xml}.each do |col|
27
+ # Adds the selected columns to the query
28
+ if counter == 0
29
+ update_query << "#{col.name}"
30
+ else
31
+ update_query << ",#{col.name}"
32
+ end
33
+
34
+ # Add a '?' for the parameter or a NULL if the value is nil or empty (except for a CLOB field where '' can be a value)
35
+ if self[col.name].nil? || self[col.name] == {} || (self[col.name] == '' && col.type != :text)
36
+ params << 'NULL'
37
+ else
38
+ values << self[col.name]
39
+ params << '?'
40
+ end
41
+ counter += 1
42
+ end
43
+
44
+ update_query << ") = "
45
+ # IBM DB accepts 'SET (column) = NULL' but not (NULL),
46
+ # therefore the sql needs to be changed for a single NULL field.
47
+ if params.size==1 && params[0] == 'NULL'
48
+ update_query << "NULL"
49
+ else
50
+ update_query << "(" + params.join(',') + ")"
51
+ end
52
+ update_query << " WHERE #{self.class.primary_key} = #{id}"
53
+
54
+ stmt = IBM_DB::prepare(connection.connection, update_query)
55
+ connection.log_query(update_query,'update of LOB/XML field(s)in handle_lobs')
56
+
57
+ # rollback any failed LOB/XML field updates (and remove associated marker)
58
+ unless IBM_DB::execute(stmt, values)
59
+ connection.execute("ROLLBACK")
60
+ raise "Failed to insert/update LOB/XML field(s) due to: #{IBM_DB::stmt_errormsg(stmt)}"
61
+ end
62
+ end # if connection.sql
63
+ end # if connection.kind_of?
64
+ end # handle_lobs
65
+ private :handle_lobs
66
+
67
+ # Establishes a connection to a specified database using the credentials provided
68
+ # with the +config+ argument. All the ActiveRecord objects will use this connection
69
+ def self.ibm_db_connection(config)
70
+ # Attempts to load the Ruby driver IBM databases
71
+ # while not already loaded or raises LoadError in case of failure.
72
+ begin
73
+ require 'ibm_db' unless defined? IBM_DB
74
+ rescue LoadError
75
+ raise LoadError, "Failed to load IBM DB Ruby driver."
76
+ end
77
+
78
+ # Converts all +config+ keys to symbols
79
+ config = config.symbolize_keys
80
+
81
+ # Retrieves the database alias (local catalog name) or remote name
82
+ # (for remote TCP/IP connections) from the +config+ hash
83
+ # or raises ArgumentError in case of failure.
84
+ if config.has_key?(:database)
85
+ database = config[:database].to_s
86
+ else
87
+ raise ArgumentError, "Missing argument: a database name needs to be specified."
88
+ end
89
+
90
+ # Retrieves database user credentials from the +config+ hash
91
+ # or raises ArgumentError in case of failure.
92
+ if !config.has_key?(:username) || !config.has_key?(:password)
93
+ raise ArgumentError, "Missing argument(s): Database configuration #{config} requires credentials: username and password"
94
+ else
95
+ username = config[:username].to_s
96
+ password = config[:password].to_s
97
+ end
98
+
99
+ # Providing default schema (username) when not specified
100
+ config[:schema] = config.has_key?(:schema) ? config[:schema].to_s : config[:username].to_s
101
+
102
+ # Extract connection options from the database configuration
103
+ # (in support to formatting, audit and billing purposes):
104
+ # Retrieve database objects fields in lowercase
105
+ conn_options = {IBM_DB::ATTR_CASE => IBM_DB::CASE_LOWER}
106
+ # Set connection's user info
107
+ conn_options[:user] = config[:user].to_s if config.has_key?(:user)
108
+ # Set connection's account info
109
+ conn_options[:account] = config[:account].to_s if config.has_key?(:account)
110
+ # Set connection's application info
111
+ conn_options[:application] = config[:application].to_s if config.has_key?(:application)
112
+ # Set connection's workstation info
113
+ conn_options[:workstation] = config[:workstation].to_s if config.has_key?(:workstation)
114
+
115
+ # Checks if a host name or address has been specified. If so, this implies a TCP/IP connection
116
+ # Returns IBM_DB::Connection object upon succesful DB connection to the database
117
+ # If otherwise the connection fails, +false+ is returned
118
+ if config.has_key?(:host)
119
+ # Retrieves the host address/name
120
+ host = config[:host]
121
+ # A net address connection requires a port. If no port has been specified, 50000 is used by default
122
+ port = config[:port] || 50000
123
+ # Connects to the database using the database, host, port, username and password specified
124
+ connection = IBM_DB::connect "DRIVER={IBM DB2 ODBC DRIVER};DATABASE=#{database};HOSTNAME=#{host};\
125
+ PORT=#{port};PROTOCOL=TCPIP;UID=#{username};PWD=#{password};", '', '', conn_options
126
+
127
+ else
128
+ # No host implies a local catalog-based connection: +database+ represents catalog alias
129
+ connection = IBM_DB::connect( database, username, password, conn_options )
130
+ end
131
+
132
+ # Verifies that the connection was succesfull
133
+ if connection
134
+ # Creates an instance of *IBM_DBAdapter* based on the +connection+
135
+ # and credentials provided in +config+
136
+ ConnectionAdapters::IBM_DBAdapter.new(connection, logger, config, conn_options)
137
+ else
138
+ # If the connection failed, it raises a Runtime error
139
+ raise "Failed to connect to the #{database} due to: #{IBM_DB::conn_errormsg}"
140
+ end
141
+ end # method self.ibm_db_connection
142
+ end # class Base
143
+
144
+ module ConnectionAdapters
145
+ class IBM_DBColumn < Column
146
+
147
+ # Casts value (which is a String) to an appropriate instance
148
+ def type_cast(value)
149
+ # Casts the database NULL value to nil
150
+ return nil if value == 'NULL'
151
+ # Invokes parent's method for default casts
152
+ super
153
+ end
154
+
155
+ # Used to convert from BLOBs to Strings
156
+ def self.binary_to_string(value)
157
+ # Returns a string removing the eventual BLOB scalar function
158
+ value.to_s.gsub(/"SYSIBM"."BLOB"\('(.*)'\)/i,'\1')
159
+ end
160
+
161
+ private
162
+ # Mapping IBM DB SQL datatypes to Ruby data types
163
+ def simplified_type(field_type)
164
+ case field_type
165
+ # if +field_type+ contains 'for bit data' handle it as a binary
166
+ when /for bit data/i
167
+ :binary
168
+ when /smallint/i
169
+ :boolean
170
+ when /int/i
171
+ :integer
172
+ when /float|double|real/i
173
+ :float
174
+ when /decimal|numeric/i
175
+ :decimal
176
+ when /timestamp/i
177
+ :timestamp
178
+ when /time/i
179
+ :time
180
+ when /date/i
181
+ :date
182
+ when /clob|text|graphic/i
183
+ :text
184
+ when /xml/i
185
+ :xml
186
+ when /blob|binary/i
187
+ :binary
188
+ when /char/i
189
+ :string
190
+ when /boolean/i
191
+ :boolean
192
+ end
193
+
194
+ end # method simplified_type
195
+ end #class IBM_DBColumn
196
+
197
+
198
+ # The IBM_DB Adapter requires the native Ruby Driver for IBM Databases (ibm_db.so).
199
+ # +config+ is a hash passed as an argument to the class initializer.
200
+ # Connection parameters can be specified in 2 different ways:
201
+ # Local connection (through a database catalog alias):
202
+ # ==== Example
203
+ # adapter: ibm_db
204
+ # database: SAMPLE
205
+ # username: user
206
+ # password: pass
207
+ # schema: myschema
208
+ #
209
+ # Remote TCP/IP connection:
210
+ # ==== Example
211
+ # adapter: ibm_db
212
+ # database: SAMPLE
213
+ # username: user
214
+ # password: pass
215
+ # schema: myschema
216
+ # host: my_hostname_or_address
217
+ # port: 50000
218
+ #
219
+ # 'SAMPLE' is the database alias as it appears in the local IBM DB catalog
220
+ # Once you pick the type of connection you want to use, you need to fill-in all the fields above, as shown in the example.
221
+ # The current version allows you to set only one schema. If you don't specify a schema, the username is used as a schema.
222
+ class IBM_DBAdapter < AbstractAdapter
223
+ attr_reader :connection
224
+ attr_accessor :sql
225
+ attr_reader :schema, :user, :account, :application, :workstation
226
+
227
+ # Name of the adapter
228
+ def adapter_name
229
+ 'IBM_DB'
230
+ end
231
+
232
+ def initialize(connection, logger, config, conn_options)
233
+ # Caching database connection configuration (+connect+ or +reconnect+ support)
234
+ @database = config[:database]
235
+ @username = config[:username]
236
+ @password = config[:password]
237
+ if config.has_key?(:host)
238
+ @host = config[:host]
239
+ @port = config[:port] || 50000 # default port
240
+ end
241
+ @schema = config[:schema]
242
+
243
+ # Caching database connection options (auditing and billing support)
244
+ @user = conn_options[:user] if conn_options.has_key?(:user)
245
+ @account = conn_options[:account] if conn_options.has_key?(:account)
246
+ @application = conn_options[:application] if conn_options.has_key?(:application)
247
+ @workstation = conn_options[:workstation] if conn_options.has_key?(:workstation)
248
+
249
+ # Calls the parent class +ConnectionAdapters+' initializer
250
+ # which sets @connection, @logger, @runtime and @last_verification
251
+ super(connection, logger)
252
+
253
+ if @connection
254
+ server_info = IBM_DB::server_info( @connection )
255
+ case server_info.DBMS_NAME
256
+ # DB2 on Linux, Unix and Windows (LUW)
257
+ when /DB2\//i
258
+ @servertype = IBM_DB2_LUW.new(@connection, self)
259
+ # DB2 on zOS
260
+ #
261
+ when /DB2/i
262
+ case server_info.DBMS_VER
263
+ when /09/
264
+ @servertype = IBM_DB2_ZOS.new(@connection, self)
265
+ when /08/
266
+ @servertype = IBM_DB2_ZOS_8.new(@connection, self)
267
+ when /07/
268
+ @servertype = IBM_DB2_ZOS_7.new(@connection, self)
269
+ else
270
+ raise "Only DB2 z/OS version 7 and above are currently supported"
271
+ end
272
+ # DB2 on i5
273
+ when /AS/i
274
+ @servertype = IBM_DB2_I5.new(@connection, self)
275
+ end
276
+ end
277
+
278
+ # Executes the +set schema+ statement using the schema identifier provided
279
+ stmt = execute("SET SCHEMA #{@schema}") if @schema && @schema != @username
280
+ end
281
+
282
+ def schema=(name)
283
+ unless name == @schema
284
+ @schema = name
285
+ stmt = execute("SET SCHEMA #{@schema}")
286
+ end
287
+ end
288
+
289
+ def user=(name)
290
+ unless name == @user
291
+ option = {IBM_DB::SQL_ATTR_INFO_USERID => "#{name}"}
292
+ if IBM_DB::set_option( @connection, option, 1 )
293
+ @user = IBM_DB::get_option( @connection, IBM_DB::SQL_ATTR_INFO_USERID )
294
+ end
295
+ end
296
+ end
297
+
298
+ def account=(name)
299
+ unless name == @account
300
+ option = {IBM_DB::SQL_ATTR_INFO_ACCTSTR => "#{name}"}
301
+ if IBM_DB::set_option( @connection, option, 1 )
302
+ @account = IBM_DB::get_option( @connection, IBM_DB::SQL_ATTR_INFO_ACCTSTR )
303
+ end
304
+ end
305
+ end
306
+
307
+ def application=(name)
308
+ unless name == @application
309
+ option = {IBM_DB::SQL_ATTR_INFO_APPLNAME => "#{name}"}
310
+ if IBM_DB::set_option( @connection, option, 1 )
311
+ @application = IBM_DB::get_option( @connection, IBM_DB::SQL_ATTR_INFO_APPLNAME )
312
+ end
313
+ end
314
+ end
315
+
316
+ def workstation=(name)
317
+ unless name == @workstation
318
+ option = {IBM_DB::SQL_ATTR_INFO_WRKSTNNAME => "#{name}"}
319
+ if IBM_DB::set_option( @connection, option, 1 )
320
+ @workstation = IBM_DB::get_option( @connection, IBM_DB::SQL_ATTR_INFO_WRKSTNNAME )
321
+ end
322
+ end
323
+ end
324
+
325
+ # This adapter supports migrations
326
+ # Some limits applies, for example +remove_column+ and +change_column+
327
+ # are not currently implemented
328
+ def supports_migrations?
329
+ true
330
+ end
331
+
332
+ def log_query(sql, name) #:nodoc:
333
+ # Used by handle_lobs
334
+ log(sql,name){}
335
+ end
336
+
337
+ #==============================================
338
+ # CONNECTION MANAGEMENT
339
+ #==============================================
340
+
341
+ # Tests the connection status
342
+ def active?
343
+ IBM_DB::active @connection
344
+ end
345
+
346
+ # Private method used by +reconnect!+.
347
+ # It connects to the database with the initially provided credentials
348
+ def connect
349
+ # If the type of connection is net based
350
+ if @host
351
+ # Connects and assigns the resulting IBM_DB::Connection to the +@connection+ instance variable
352
+ @connection = IBM_DB::connect "DRIVER={IBM DB2 ODBC DRIVER};DATABASE=#{@database};HOSTNAME=#{@host};\
353
+ PORT=#{@port};PROTOCOL=TCPIP;UID=#{@username};PWD=#{@password};", '', '', @conn_options
354
+ else
355
+ # Connects to the database using the local alias (@database)
356
+ # and assigns the connection object (IBM_DB::Connection) to @connection
357
+ @connection = IBM_DB::connect @database, @username, @password, @conn_options
358
+ end
359
+ # Sets the schema if different from default (username)
360
+ if @schema && @schema != @username
361
+ execute("SET SCHEMA #{@schema}")
362
+ end
363
+ end
364
+ private :connect
365
+
366
+ # Closes the current connection and opens a new one
367
+ def reconnect!
368
+ disconnect!
369
+ connect
370
+ end
371
+
372
+ # Closes the current connection
373
+ def disconnect!
374
+ # Attempts to close the connection. The methods will return:
375
+ # * true if succesfull
376
+ # * false if the connection is already closed
377
+ # * nil if an error is raised
378
+ IBM_DB::close(@connection) rescue nil
379
+ end
380
+
381
+ #==============================================
382
+ # DATABASE STATEMENTS
383
+ #==============================================
384
+
385
+ def create_table(name, options = {})
386
+ @servertype.setup_for_lob_table
387
+ super
388
+ @servertype.create_index_after_table(name, self)
389
+ end
390
+
391
+ # DB2 throws an error SQL0214N An expression in the ORDER BY
392
+ # clause in the following position, or starting with "UPPER..."
393
+ # in the "ORDER BY" clause is not valid. Reason code = "2".
394
+ # SQLSTATE=42822
395
+ # If the order_by columns do not match the select columns, we change
396
+ # the select columns.
397
+ def distinct(columns, order_by)
398
+ if !order_by.nil? && !order_by.empty?
399
+ order_by_columns = order_by.upcase.split(',').collect! { |e| (e.split).first }
400
+ distinct_sql = (columns.upcase.split(',') + order_by_columns).uniq
401
+ distinct_sql.collect! {|e| e + ", " }
402
+ return "DISTINCT #{distinct_sql.to_s.strip.chop}"
403
+ else
404
+ return "DISTINCT #{columns}"
405
+ end
406
+ end
407
+
408
+ # Returns an array of hashes with the column names as keys and
409
+ # column values as values. +sql+ is the select query, and +name+ is an optional description for logging
410
+ def select_all(sql, name = nil)
411
+ # Replaces {"= NULL" with "IS NULL"} OR {"IN (NULL)" with "IS NULL"}
412
+ sql.gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, "IS NULL" )
413
+
414
+ results = []
415
+ # Invokes the method +execute+ in order to log and execute the sql instructions
416
+ # and get a IBM_DB::Statement from which is possible to fetch the results
417
+ if stmt = execute(sql, name)
418
+ begin
419
+ # Fetches all the results available. IBM_DB::fetch_assoc(stmt) returns
420
+ # an hash for each single record.
421
+ # The loop stops when there aren't any more valid records to fetch
422
+ while single_hash = IBM_DB::fetch_assoc(stmt)
423
+ # Add the record to the +results+ array
424
+ results << single_hash
425
+ end
426
+ ensure
427
+ # Ensures to free the resources associated with the statement
428
+ IBM_DB::free_result stmt
429
+ end
430
+ end
431
+ # The array of record hashes is returned
432
+ results
433
+ end
434
+
435
+ # Returns a record hash with the column names as keys and column values
436
+ # as values.
437
+ def select_one(sql, name = nil)
438
+ # Gets the first hash from the array of hashes returned by
439
+ # select_all
440
+ select_all(sql,name).first
441
+ end
442
+
443
+ # Perform an insert and returns the last ID generated.
444
+ # This can be the ID passed to the method or the one auto-generated by the database,
445
+ # and retrieved by the +last_generated_id+ method.
446
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
447
+ if stmt = execute(sql, name)
448
+ begin
449
+ @sql = sql
450
+ return id_value || @servertype.last_generated_id
451
+ # Ensures to free the resources associated with the statement
452
+ ensure
453
+ IBM_DB::free_result(stmt)
454
+ end
455
+ end
456
+ end
457
+
458
+ # Executes and logs a +sql+ commands and
459
+ # returns a +IBM_DB::Statement+ object.
460
+ def execute(sql, name = nil)
461
+ # Logs and execute the sql instructions.
462
+ # The +log+ method is defined in the parent class +AbstractAdapter+
463
+ log(sql, name) do
464
+ if stmt = IBM_DB::exec(@connection, sql)
465
+ stmt # Return the statement object
466
+ else
467
+ raise StatementInvalid, IBM_DB::stmt_errormsg
468
+ end
469
+ end
470
+ end
471
+
472
+ def update(sql, name = nil)
473
+ # Make sure the WHERE clause handles NULL's correctly
474
+ sqlarray = sql.split(/\s*WHERE\s*/)
475
+ if !sqlarray[1].nil?
476
+ sqlarray[1].gsub!( /(=\s*NULL|IN\s*\(NULL\))/i, "IS NULL" )
477
+ sql = sqlarray[0] + " WHERE " + sqlarray[1]
478
+ end
479
+
480
+ # Logs and execute the given sql query.
481
+ if stmt = execute(sql, name)
482
+ begin
483
+ @sql = sql
484
+ # Retrieves the number of affected rows
485
+ IBM_DB::num_rows(stmt)
486
+ # Ensures to free the resources associated with the statement
487
+ ensure
488
+ IBM_DB::free_result(stmt)
489
+ end
490
+ end
491
+ end
492
+
493
+ # The delete method executes the delete
494
+ # statement and returns the number of affected rows.
495
+ # The method is an alias for +update+
496
+ alias_method :delete, :update
497
+
498
+ # Begins the transaction (and turns off auto-committing)
499
+ def begin_db_transaction
500
+ # Turns off the auto-commit
501
+ IBM_DB::autocommit(@connection, IBM_DB::SQL_AUTOCOMMIT_OFF)
502
+ end
503
+
504
+ # Commits the transaction (and turns on auto-committing)
505
+ def commit_db_transaction
506
+ # Commits the transaction
507
+ IBM_DB::commit @connection rescue nil
508
+ # Turns on auto-committing
509
+ IBM_DB::autocommit @connection, IBM_DB::SQL_AUTOCOMMIT_ON
510
+ end
511
+
512
+ # Rolls back the transaction (and turns on auto-committing). Must be
513
+ # done if the transaction block raises an exception or returns false
514
+ def rollback_db_transaction
515
+ # ROLLBACK the transaction
516
+ IBM_DB::rollback(@connection) rescue nil
517
+ # Turns on auto-committing
518
+ IBM_DB::autocommit @connection, IBM_DB::SQL_AUTOCOMMIT_ON
519
+ end
520
+
521
+
522
+ # Modifies a sql statement in order to implement a LIMIT and an OFFSET.
523
+ # A LIMIT defines the number of rows that should be fetched, while
524
+ # an OFFSET defines from what row the records must be fetched.
525
+ # IBM DB SQL implements a LIMIT with the instructions:
526
+ # FETCH FIRST n ROWS ONLY, where n is the number of rows required.
527
+ # The implementation of OFFSET is more elaborate, and require the usage of
528
+ # subqueries and the ROW_NUMBER() command in order to add row numbering
529
+ # as an additional column to a copy of the existing table.
530
+ # ==== Examples
531
+ # add_limit_offset!('SELECT * FROM staff', {:limit => 10})
532
+ # generates: "SELECT * FROM staff FETCH FIRST 10 ROWS ONLY"
533
+ #
534
+ # add_limit_offset!('SELECT * FROM staff', {:limit => 10, :offset => 30})
535
+ # generates "SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_rownum
536
+ # FROM (SELECT * FROM staff) AS I) AS O WHERE sys_row_num BETWEEN 31 AND 40"
537
+ def add_limit_offset!(sql, options)
538
+ # If there is a limit
539
+ if limit = options[:limit]
540
+ # if the limit is zero
541
+ if limit == 0
542
+ # Returns a query that will always generate zero records
543
+ # (e.g. WHERE sys_row_num BETWEEN 1 and 0)
544
+ sql = query_offset_limit(sql, 0, limit)
545
+ # If there is a non-zero limit
546
+ else
547
+ offset = options[:offset]
548
+ # If an offset is specified builds the query with offset and limit, oterwise retrieves only the first +limit+ rows
549
+ offset ? sql = query_offset_limit(sql, offset, limit) : sql << " FETCH FIRST #{limit} ROWS ONLY"
550
+ end
551
+ end
552
+ # Returns the sql query in any case
553
+ sql
554
+ end # method add_limit_offset!
555
+
556
+ # Private method used by +add_limit_offset!+ to create a
557
+ # sql query given an offset and a limit
558
+ def query_offset_limit(sql, offset, limit)
559
+ # Defines what will be the last record
560
+ last_record = offset + limit
561
+ # Transforms the SELECT query in order to retrieve/fetch only
562
+ # a number of records after the specified offset.
563
+ # 'select' or 'SELECT' is replaced with the partial query below that adds the sys_row_num column
564
+ # to select with the condition of this column being between offset+1 and the offset+limit
565
+ sql.gsub!(/SELECT/i,"SELECT O.* FROM (SELECT I.*, ROW_NUMBER() OVER () sys_row_num FROM (SELECT")
566
+ # The final part of the query is appended to include a WHERE...BETWEEN...AND condition,
567
+ # and retrieve only a LIMIT number of records starting from the OFFSET+1
568
+ sql << ") AS I) AS O WHERE sys_row_num BETWEEN #{offset+1} AND #{last_record}"
569
+ end
570
+ private :query_offset_limit
571
+
572
+ def default_sequence_name(table, column) # :nodoc:
573
+ "#{table}_#{column}_seq"
574
+ end
575
+
576
+
577
+ #==============================================
578
+ # QUOTING
579
+ #==============================================
580
+
581
+ # Properly quotes the various data types.
582
+ # +value+ contains the data, +column+ is optional and contains info on the field
583
+ def quote(value, column = nil)
584
+ case value
585
+ # If it's a numeric value and the column type is not a string, it shouldn't be quoted
586
+ # (IBM DB doesn't accept quotes on numeric types)
587
+ when Numeric
588
+ # If the column type is text or string, return the quote value
589
+ if column && column.type == :text || column && column.type == :string
590
+ "'#{value}'"
591
+ else
592
+ # value is Numeric, column.type is not a string,
593
+ # therefore it converts the number to string without quoting it
594
+ value.to_s
595
+ end
596
+ when String, ActiveSupport::Multibyte::Chars
597
+ if column && column.type == :binary
598
+ # If quoting is required for the insert/update of a BLOB
599
+ unless caller[0] =~ /add_column_options/i
600
+ # Invokes a convertion from string to binary
601
+ "BLOB('?')"
602
+ else
603
+ # Quoting required for the default value of a column
604
+ "BLOB('#{value}')"
605
+ end
606
+ elsif column && column.type == :text
607
+ unless caller[0] =~ /add_column_options/i
608
+ "'@@@IBMTEXT@@@'"
609
+ else
610
+ "#{value}"
611
+ end
612
+ elsif column && column.type == :xml
613
+ unless caller[0] =~ /add_column_options/i
614
+ "'<ibm>@@@IBMXML@@@</ibm>'"
615
+ else
616
+ "#{value}"
617
+ end
618
+ else
619
+ "'#{quote_string(value)}'"
620
+ end
621
+ when TrueClass then quoted_true # return '1' for true
622
+ when FalseClass then quoted_false # return '0' for false
623
+ when NilClass
624
+ if column && column.instance_of?(IBM_DBColumn) && !column.primary && !column.null
625
+ "''" # allow empty inserts if not nullable or identity
626
+ else # in order to support default ActiveRecord constructors
627
+ "NULL"
628
+ end
629
+ else super # rely on superclass handling
630
+ end
631
+ end
632
+
633
+ # Quotes a given string, escaping single quote (') characters.
634
+ def quote_string(string)
635
+ string.gsub(/'/, "''")
636
+ end
637
+
638
+ # *true* is represented by a smallint 1, *false*
639
+ # by 0, as no native boolean type exists in DB2.
640
+ # Numerics are not quoted in DB2.
641
+ def quoted_true
642
+ "1"
643
+ end
644
+
645
+ def quoted_false
646
+ "0"
647
+ end
648
+
649
+ def quote_column_name(name)
650
+ @servertype.check_reserved_words(name)
651
+ end
652
+
653
+ #==============================================
654
+ # SCHEMA STATEMENTS
655
+ #==============================================
656
+
657
+ # Returns a Hash of mappings from the abstract data types to the native
658
+ # database types
659
+ def native_database_types
660
+ {
661
+ :primary_key => @servertype.primary_key,
662
+ :string => { :name => "varchar", :limit => 255 },
663
+ :text => { :name => "clob" },
664
+ :integer => { :name => "integer" },
665
+ :float => { :name => "float" },
666
+ :datetime => { :name => "timestamp" },
667
+ :timestamp => { :name => "timestamp" },
668
+ :time => { :name => "time" },
669
+ :date => { :name => "date" },
670
+ :binary => { :name => "blob" },
671
+
672
+ # IBM DB doesn't have a native boolean type.
673
+ # A boolean can be represented by a smallint,
674
+ # adopting the convention that False is 0 and True is 1
675
+ :boolean => { :name => "smallint"},
676
+ :xml => { :name => "xml"},
677
+ :decimal => { :name => "decimal" }
678
+ }
679
+ end
680
+
681
+ # DB2 does not support limits on the integer and double data types
682
+ # unlike MySQL. It does support limits on float and decimal/numeric
683
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
684
+ if type == :integer && (!limit.nil? && limit > 0)
685
+ return 'integer'
686
+ elsif type == :double && (!limit.nil? && limit > 0)
687
+ return 'double'
688
+ else
689
+ return super
690
+ end
691
+ end
692
+
693
+ # Returns the maximum length a table alias identifier can be.
694
+ # IBM DB (cross-platform) table limit is 128 characters
695
+ def table_alias_length
696
+ 128
697
+ end
698
+
699
+ # Returns an array of tables within the given shema
700
+ def tables(name = nil)
701
+ # Initializes the tables array
702
+ tables = []
703
+ # Returns a IBM_DB::Statement used to retrieve the tables
704
+ if stmt = IBM_DB::tables(@connection, nil, @schema.upcase)
705
+ # Fetches all the records available
706
+ while tab = IBM_DB::fetch_assoc(stmt)
707
+ # Adds the lowercase table name to the array
708
+ tables << tab["table_name"].downcase
709
+ end
710
+ end
711
+ # Returns the tables array
712
+ tables
713
+ end
714
+
715
+ # Returns an array of non-primary key indexes for the given table
716
+ def indexes(table_name, name = nil)
717
+ # to_s required because +table_name+ may be a symbol.
718
+ table_name = table_name.to_s
719
+ # Checks if a blank table name has been given.
720
+ # If so it returns an empty array of columns.
721
+ return [] if table_name.strip.empty?
722
+
723
+ # +indexes+ will contain the resulting array
724
+ indexes = []
725
+ # Query table statistics for all indexes on the table
726
+ # "TABLE_NAME: #{index_stats[2]}"
727
+ # "NON_UNIQUE: #{index_stats[3]}"
728
+ # "INDEX_NAME: #{index_stats[5]}"
729
+ # "COLUMN_NAME: #{index_stats[8]}"
730
+ stmt = IBM_DB::statistics( @connection, nil, @schema.upcase, table_name.upcase, 1)
731
+ while ( index_stats = IBM_DB::fetch_array(stmt) )
732
+ if index_stats[5] # INDEX_NAME
733
+ index_name = index_stats[5].downcase
734
+ # Non-unique index type (not the primary key)
735
+ unless index_stats[3] == 0 # NON_UNIQUE
736
+ index_unique = (index_stats[3] == 0)
737
+ index_columns = index_stats[8].map{|c| c.downcase} # COLUMN_NAME
738
+ # Create an IndexDefinition object and add to the indexes array
739
+ indexes << IndexDefinition.new(table_name, index_name, index_unique, index_columns)
740
+ end
741
+ end
742
+ end
743
+ # Returns the indexes array
744
+ return indexes
745
+ # Ensures to free the resources associated with the statement
746
+ ensure
747
+ IBM_DB::free_result(stmt) if stmt
748
+ end
749
+
750
+ # Returns an array of Column objects for the table specified by +table_name+
751
+ def columns(table_name, name = nil)
752
+ # to_s required because it may be a symbol.
753
+ table_name = table_name.to_s.upcase
754
+ # Checks if a blank table name has been given.
755
+ # If so it returns an empty array
756
+ return [] if table_name.strip.empty?
757
+ # +columns+ will contain the resulting array
758
+ columns = []
759
+ # Statement required to access all the columns information
760
+ if stmt = IBM_DB::columns(@connection, nil, @schema.upcase, table_name.upcase)
761
+ begin
762
+ # Fetches all the columns and assigns them to col.
763
+ # +col+ is an hash with keys/value pairs for a column
764
+ while col = IBM_DB::fetch_assoc(stmt)
765
+ column_name = col["column_name"].downcase
766
+ # Assigns the column default value.
767
+ column_default_value = col["column_def"]
768
+ # If there is no default value, it assigns NIL
769
+ column_default_value = nil if (column_default_value && column_default_value.upcase == 'NULL')
770
+ # Removes single quotes from the default value
771
+ column_default_value.gsub!(/^'(.*)'$/, '\1') unless column_default_value.nil?
772
+ # Assigns the column type
773
+ column_type = col["type_name"].downcase
774
+ # Assigns the field length (size) for the column
775
+ column_length = col["column_size"]
776
+ column_scale = col["decimal_digits"]
777
+ # The initializer of the class Column, requires the +column_length+ to be declared between brackets after
778
+ # the datatype(e.g VARCHAR(50)) for :string and :text types. If it's a "for bit data" field it does a subsitution in place, if not
779
+ # it appends the (column_length) string on the supported data types
780
+ unless column_length.nil? || column_length == '' || column_type.sub!(/ \(\) for bit data/i,"(#{column_length}) FOR BIT DATA") || !column_type =~ /char|lob|graphic/i
781
+ if column_type =~ /decimal/i
782
+ column_type << "(#{column_length},#{column_scale})"
783
+ else
784
+ column_type << "(#{column_length})"
785
+ end
786
+ end
787
+ # col["NULLABLE"] is 1 if the field is nullable, 0 if not.
788
+ column_nullable = (col["nullable"] == 1) ? true : false
789
+ # Make sure the hidden column (db2_generated_rowid_for_lobs) in DB2 z/OS isn't added to the list
790
+ if !(column_name =~ /db2_generated_rowid_for_lobs/i)
791
+ # Pushes into the array the *IBM_DBColumn* object, created by passing to the initializer
792
+ # +column_name+, +default_value+, +column_type+ and +column_nullable+.
793
+ columns << IBM_DBColumn.new(column_name, column_default_value, column_type, column_nullable)
794
+ end
795
+ end
796
+ rescue
797
+ raise "Failed to retrieve column metadata due to error: #{IBM_DB::conn_errormsg} ... #{IBM_DB::stmt_errormsg}"
798
+ end
799
+ end
800
+ # Returns the columns array
801
+ columns
802
+ end
803
+
804
+ # Renames a table.
805
+ # ==== Example
806
+ # rename_table('octopuses', 'octopi')
807
+ # Overriden to satisfy IBM DB syntax
808
+ def rename_table(name, new_name)
809
+ # SQL rename table statement
810
+ rename_table_sql = "RENAME TABLE #{name} TO #{new_name}"
811
+ stmt = execute(rename_table_sql)
812
+ # Ensures to free the resources associated with the statement
813
+ ensure
814
+ IBM_DB::free_result stmt if stmt
815
+ end
816
+
817
+ # Renames a column.
818
+ # ===== Example
819
+ # rename_column(:suppliers, :description, :name)
820
+ def rename_column(table_name, column_name, new_column_name)
821
+ @servertype.rename_column(table_name, column_name, new_column_name)
822
+ end
823
+
824
+ # Removes the column from the table definition.
825
+ # ===== Examples
826
+ # remove_column(:suppliers, :qualification)
827
+ def remove_column(table_name, column_name)
828
+ @servertype.remove_column(table_name, column_name)
829
+ end
830
+
831
+ # Changes the column's definition according to the new options.
832
+ # See TableDefinition#column for details of the options you can use.
833
+ # ===== Examples
834
+ # change_column(:suppliers, :name, :string, :limit => 80)
835
+ # change_column(:accounts, :description, :text)
836
+ def change_column(table_name, column_name, type, options = {})
837
+ @servertype.change_column(table_name, column_name, type, options)
838
+ end
839
+
840
+ # Sets a new default value for a column. You can't set the default
841
+ # value to +NULL+, In that case, you need to
842
+ # DatabaseStatements#execute the apppropriate SQL statement.
843
+ # ==== Examples
844
+ # change_column_default(:suppliers, :qualification, 'new')
845
+ # change_column_default(:accounts, :authorized, 1)
846
+ # Method overriden to satisfy IBM DB syntax.
847
+ def change_column_default(table_name, column_name, default)
848
+ # SQL statement which alters column's default value
849
+ change_column_sql = "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET WITH DEFAULT #{quote(default)}"
850
+ stmt = execute(change_column_sql)
851
+ # Ensures to free the resources associated with the statement
852
+ ensure
853
+ IBM_DB::free_result stmt if stmt
854
+ end
855
+
856
+ # Adds a new index to the table. +column_name+ can be a single Symbol, or
857
+ # an Array of Symbols.
858
+ #
859
+ # The index will be named after the table and the first column names,
860
+ # unless you pass +:name+ as an option.
861
+ #
862
+ # When creating an index on multiple columns, the first column is used as a name
863
+ # for the index. For example, when you specify an index on two columns
864
+ # [+:first+, +:last+], the DBMS creates an index for both columns as well as an
865
+ # index for the first colum +:first+. Using just the first name for this index
866
+ # makes sense, because you will never have to create a singular index with this
867
+ # name.
868
+ #
869
+ # ===== Examples
870
+ # ====== Creating a simple index
871
+ # add_index(:suppliers, :name)
872
+ # generates
873
+ # CREATE INDEX suppliers_name_index ON suppliers(name)
874
+ # ====== Creating a unique index
875
+ # add_index(:accounts, [:branch_id, :party_id], :unique => true)
876
+ # generates
877
+ # CREATE UNIQUE INDEX accounts_branch_id_index ON accounts(branch_id, party_id)
878
+ # ====== Creating a named index
879
+ # add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
880
+ # generates
881
+ # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
882
+ # Overidden to comply to the 18 characters cross-platform limit on index identifiers.
883
+ def add_index(table_name, column_name, options = {})
884
+ index_name = index_name(table_name, options)
885
+ if Hash === options # legacy support, since this param was a string
886
+ index_type = options[:unique] ? "UNIQUE" : ""
887
+ else
888
+ index_type = options
889
+ end
890
+ execute("CREATE #{index_type} INDEX #{index_name} ON #{table_name} (#{Array(column_name).join(", ")})")
891
+ end
892
+
893
+ # Remove the given index from the table.
894
+ #
895
+ # Remove the suppliers_name_index in the suppliers table (legacy support, use the second or third forms).
896
+ # remove_index :suppliers, :name
897
+ # Remove the index named accounts_branch_id in the accounts table.
898
+ # remove_index :accounts, :column => :branch_id
899
+ # Remove the index named by_branch_party in the accounts table.
900
+ # remove_index :accounts, :name => :by_branch_party
901
+ #
902
+ # You can remove an index on multiple columns by specifying the first column.
903
+ # add_index :accounts, [:username, :password]
904
+ # remove_index :accounts, :username
905
+ # Overriden to use the IBM DB SQL syntax.
906
+ def remove_index(table_name, options = {})
907
+ execute("DROP INDEX #{index_name(table_name, options)}")
908
+ end
909
+
910
+ # Builds an index name from a table_name and column. If an index name has been passed,
911
+ # the method returns it. Overrides the default method to respect the IBM DB (cross-platform)
912
+ # limit on indexes (max of 18 characters)
913
+ def index_name(table_name, options)
914
+ if Hash === options and options[:name]
915
+ # legacy support
916
+ # If a name has been specified, this is returned
917
+ options[:name]
918
+ else
919
+ # We reverse the table name to reduce the chance of hitting duplicate
920
+ # index name errors. For e.g. indexes on table names like accounts,
921
+ # accounts_favorites
922
+ "ror_#{table_name.to_s.reverse[0,10]}_idx"
923
+ end
924
+ end
925
+ private :index_name
926
+
927
+ end # class IBM_DBAdapter
928
+
929
+ # This class contains common code across DB's (DB2 LUW, zOS and i5)
930
+ class IBM_DataServer
931
+ def initialize(connection, caller)
932
+ @connection = connection
933
+ @caller = caller
934
+ end
935
+
936
+ # Implemented by concrete DataServer if applicable
937
+ def last_generated_id
938
+ end
939
+
940
+ # Implemented by concrete DataServer if applicable
941
+ def create_index_after_table (table_name, caller)
942
+ end
943
+
944
+ # Implemented by concrete DataServer if applicable
945
+ def setup_for_lob_table ()
946
+ end
947
+
948
+ # Implemented by concrete DataServer if applicable
949
+ def reorg_table(table_name)
950
+ end
951
+
952
+ # Implemented by concrete DataServer if applicable
953
+ def check_reserved_words(col_name)
954
+ col_name
955
+ end
956
+
957
+ # This is supported by LUW and i5
958
+ def remove_column(table_name, column_name)
959
+ @caller.execute "ALTER TABLE #{table_name} DROP #{column_name}"
960
+ reorg_table(table_name)
961
+ end
962
+
963
+ end # class IBM_DataServer
964
+
965
+ class IBM_DB2 < IBM_DataServer
966
+ def rename_column(table_name, column_name, new_column_name)
967
+ raise NotImplementedError, "rename_column is not implemented yet in the IBM DB Adapter"
968
+ end
969
+
970
+ def primary_key
971
+ return "INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 100) PRIMARY KEY"
972
+ end
973
+
974
+ # Method that returns the last automatically generated ID
975
+ # on the given +@connection+. This method is required by the +insert+
976
+ # method
977
+ def last_generated_id
978
+ # Queries the db to obtain the last ID that was automatically generated
979
+ sql = "SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1"
980
+ stmt = IBM_DB::exec(@connection, sql)
981
+ # Fetches the only record available (containing the last id)
982
+ IBM_DB::fetch_row(stmt)
983
+ # Retrieves and returns the result of the query with the last id.
984
+ IBM_DB::result(stmt,0)
985
+ end
986
+
987
+ def change_column(table_name, column_name, type, options)
988
+ @caller.execute "ALTER TABLE #{table_name} ALTER #{column_name} SET DATA TYPE #{@caller.type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
989
+ reorg_table(table_name)
990
+ if !options[:default].nil?
991
+ @caller.change_column_default(table_name, column_name, options[:default])
992
+ end
993
+ end
994
+ end # class IBM_DB2
995
+
996
+ class IBM_DB2_LUW < IBM_DB2
997
+ # Reorganizes the table for column changes
998
+ def reorg_table(table_name)
999
+ @caller.execute("CALL ADMIN_CMD('REORG TABLE #{table_name}')")
1000
+ end
1001
+ end # class IBM_DB2_LUW
1002
+
1003
+ module HostedDataServer
1004
+ require 'pathname'
1005
+ #find DB2-i5-zOS rezerved words file relative path
1006
+ rfile = Pathname.new(File.dirname(__FILE__)).parent + 'vendor' + 'db2-i5-zOS.yaml'
1007
+ if rfile
1008
+ RESERVED_WORDS = open(rfile.to_s) {|f| YAML.load(f) }
1009
+ def check_reserved_words(col_name)
1010
+ if RESERVED_WORDS[col_name]
1011
+ '"' + RESERVED_WORDS[col_name] + '"'
1012
+ else
1013
+ col_name
1014
+ end
1015
+ end
1016
+ else
1017
+ raise "Failed to locate IBM_DB Adapter dependency: #{rfile}"
1018
+ end
1019
+ end # module HostedDataServer
1020
+
1021
+ class IBM_DB2_ZOS < IBM_DB2
1022
+ # since v9 doesn't need, suggest putting it in HostedDataServer?
1023
+ def create_index_after_table(table_name, caller)
1024
+ caller.add_index(table_name, "id", :unique => true)
1025
+ end
1026
+
1027
+ # This call is needed on DB2 z/OS v8 and earlier for the creation of tables
1028
+ # with LOBs. When issued, this call does the following:
1029
+ # DB2 creates LOB table spaces, auxiliary tables, and indexes on auxiliary
1030
+ # tables for LOB columns.
1031
+ def setup_for_lob_table()
1032
+ @caller.execute "SET CURRENT RULES = 'STD'"
1033
+ end
1034
+
1035
+ def remove_column(table_name, column_name)
1036
+ raise NotImplementedError, "remove_column is not supported for DB2/zOS server"
1037
+ end
1038
+ end # class IBM_DB2_ZOS
1039
+
1040
+ class IBM_DB2_ZOS_8 < IBM_DB2_ZOS
1041
+ include HostedDataServer
1042
+ end # class IBM_DB2_ZOS_8
1043
+
1044
+ class IBM_DB2_ZOS_7 < IBM_DB2_ZOS
1045
+ include HostedDataServer
1046
+ end # class IBM_DB2_ZOS_7
1047
+
1048
+ class IBM_DB2_I5 < IBM_DB2
1049
+ include HostedDataServer
1050
+ end # class IBM_DB2_I5
1051
+
1052
+ end # module ConnectionAdapters
1053
+ end # module ActiveRecord