ibm_db 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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