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