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.
- data/CHANGES +155 -0
- data/LICENSE +18 -0
- data/MANIFEST +14 -0
- data/README +274 -0
- data/ext/Makefile.nt32 +181 -0
- data/ext/extconf.rb +58 -0
- data/ext/ibm_db.c +6553 -0
- data/ext/ruby_ibm_db.h +214 -0
- data/init.rb +42 -0
- data/lib/IBM_DB.rb +2 -0
- data/lib/active_record/connection_adapters/ibm_db_adapter.rb +2218 -0
- data/lib/active_record/vendor/db2-i5-zOS.yaml +328 -0
- data/lib/mswin32/ibm_db.so +0 -0
- data/test/cases/adapter_test.rb +180 -0
- data/test/cases/associations/cascaded_eager_loading_test.rb +133 -0
- data/test/cases/associations/eager_test.rb +842 -0
- data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +874 -0
- data/test/cases/associations/has_many_through_associations_test.rb +281 -0
- data/test/cases/associations/join_model_test.rb +801 -0
- data/test/cases/attribute_methods_test.rb +312 -0
- data/test/cases/base_test.rb +2114 -0
- data/test/cases/calculations_test.rb +346 -0
- data/test/cases/finder_test.rb +1092 -0
- data/test/cases/fixtures_test.rb +660 -0
- data/test/cases/migration_test.rb +1618 -0
- data/test/cases/schema_dumper_test.rb +197 -0
- data/test/cases/validations_test.rb +1604 -0
- data/test/connections/native_ibm_db/connection.rb +40 -0
- data/test/ibm_db_test.rb +25 -0
- data/test/models/warehouse_thing.rb +5 -0
- data/test/schema/i5/ibm_db_specific_schema.rb +134 -0
- data/test/schema/ids/ibm_db_specific_schema.rb +137 -0
- data/test/schema/luw/ibm_db_specific_schema.rb +134 -0
- data/test/schema/schema.rb +499 -0
- data/test/schema/zOS/ibm_db_specific_schema.rb +205 -0
- 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,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
|