ibm_db 1.1.1-mswin32

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