ibm_db 1.1.1-mswin32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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