ruby-oci8-master 2.0.7
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/ChangeLog +2321 -0
- data/Makefile +88 -0
- data/NEWS +303 -0
- data/README +76 -0
- data/VERSION +1 -0
- data/dist-files +83 -0
- data/doc/api.en.html +527 -0
- data/doc/api.en.rd +554 -0
- data/doc/api.ja.html +525 -0
- data/doc/api.ja.rd +557 -0
- data/doc/manual.css +35 -0
- data/ext/oci8/.document +18 -0
- data/ext/oci8/MANIFEST +18 -0
- data/ext/oci8/apiwrap.c.tmpl +182 -0
- data/ext/oci8/apiwrap.h.tmpl +61 -0
- data/ext/oci8/apiwrap.rb +91 -0
- data/ext/oci8/apiwrap.yml +1455 -0
- data/ext/oci8/attr.c +105 -0
- data/ext/oci8/bind.c +366 -0
- data/ext/oci8/connection_pool.c +199 -0
- data/ext/oci8/encoding.c +289 -0
- data/ext/oci8/env.c +178 -0
- data/ext/oci8/error.c +378 -0
- data/ext/oci8/extconf.rb +179 -0
- data/ext/oci8/lob.c +805 -0
- data/ext/oci8/metadata.c +232 -0
- data/ext/oci8/object.c +727 -0
- data/ext/oci8/oci8.c +1156 -0
- data/ext/oci8/oci8.h +574 -0
- data/ext/oci8/oci8lib.c +527 -0
- data/ext/oci8/ocidatetime.c +484 -0
- data/ext/oci8/ocihandle.c +751 -0
- data/ext/oci8/ocinumber.c +1612 -0
- data/ext/oci8/oraconf.rb +1119 -0
- data/ext/oci8/oradate.c +611 -0
- data/ext/oci8/oranumber_util.c +352 -0
- data/ext/oci8/oranumber_util.h +24 -0
- data/ext/oci8/post-config.rb +5 -0
- data/ext/oci8/stmt.c +673 -0
- data/ext/oci8/thread_util.c +85 -0
- data/ext/oci8/thread_util.h +30 -0
- data/ext/oci8/win32.c +137 -0
- data/lib/.document +1 -0
- data/lib/dbd/OCI8.rb +591 -0
- data/lib/oci8.rb.in +94 -0
- data/lib/oci8/.document +8 -0
- data/lib/oci8/bindtype.rb +349 -0
- data/lib/oci8/compat.rb +113 -0
- data/lib/oci8/connection_pool.rb +99 -0
- data/lib/oci8/datetime.rb +611 -0
- data/lib/oci8/encoding-init.rb +74 -0
- data/lib/oci8/encoding.yml +537 -0
- data/lib/oci8/metadata.rb +2132 -0
- data/lib/oci8/object.rb +581 -0
- data/lib/oci8/oci8.rb +721 -0
- data/lib/oci8/ocihandle.rb +425 -0
- data/lib/oci8/oracle_version.rb +144 -0
- data/lib/oci8/properties.rb +73 -0
- data/metaconfig +142 -0
- data/pre-distclean.rb +7 -0
- data/ruby-oci8.gemspec +63 -0
- data/setup.rb +1331 -0
- data/test/README +4 -0
- data/test/config.rb +122 -0
- data/test/test_all.rb +51 -0
- data/test/test_appinfo.rb +63 -0
- data/test/test_array_dml.rb +333 -0
- data/test/test_bind_raw.rb +46 -0
- data/test/test_bind_time.rb +178 -0
- data/test/test_break.rb +96 -0
- data/test/test_clob.rb +82 -0
- data/test/test_connstr.rb +81 -0
- data/test/test_datetime.rb +582 -0
- data/test/test_dbi.rb +366 -0
- data/test/test_dbi_clob.rb +53 -0
- data/test/test_encoding.rb +100 -0
- data/test/test_error.rb +88 -0
- data/test/test_metadata.rb +1399 -0
- data/test/test_oci8.rb +434 -0
- data/test/test_oracle_version.rb +70 -0
- data/test/test_oradate.rb +256 -0
- data/test/test_oranumber.rb +746 -0
- data/test/test_rowid.rb +33 -0
- metadata +137 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
/* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */
|
2
|
+
/*
|
3
|
+
* thread_util.c - part of ruby-oci8
|
4
|
+
*
|
5
|
+
* Copyright (C) 2011 KUBO Takehiro <kubo@jiubao.org>
|
6
|
+
*/
|
7
|
+
#include "oci8.h"
|
8
|
+
#include <errno.h>
|
9
|
+
|
10
|
+
#ifdef USE_THREAD_LOCAL_ERRHP
|
11
|
+
|
12
|
+
#ifndef WIN32
|
13
|
+
#include <pthread.h>
|
14
|
+
static pthread_attr_t detached_thread_attr;
|
15
|
+
#endif
|
16
|
+
|
17
|
+
typedef struct {
|
18
|
+
rb_blocking_function_t *func;
|
19
|
+
void *arg;
|
20
|
+
} adapter_arg_t;
|
21
|
+
|
22
|
+
void Init_oci8_thread_util(void)
|
23
|
+
{
|
24
|
+
#ifndef WIN32
|
25
|
+
pthread_attr_init(&detached_thread_attr);
|
26
|
+
pthread_attr_setdetachstate(&detached_thread_attr, PTHREAD_CREATE_DETACHED);
|
27
|
+
#endif
|
28
|
+
}
|
29
|
+
|
30
|
+
#ifdef WIN32
|
31
|
+
|
32
|
+
static void __cdecl adapter(void *arg)
|
33
|
+
{
|
34
|
+
adapter_arg_t *aa = (adapter_arg_t *)arg;
|
35
|
+
aa->func(aa->arg);
|
36
|
+
free(aa);
|
37
|
+
}
|
38
|
+
|
39
|
+
int oci8_run_native_thread(rb_blocking_function_t func, void *arg)
|
40
|
+
{
|
41
|
+
adapter_arg_t *aa = malloc(sizeof(adapter_arg_t));
|
42
|
+
if (aa == NULL) {
|
43
|
+
return ENOMEM;
|
44
|
+
}
|
45
|
+
|
46
|
+
aa->func = func;
|
47
|
+
aa->arg = arg;
|
48
|
+
if (_beginthread(adapter, 0, aa) == (uintptr_t)-1L) {
|
49
|
+
int err = errno;
|
50
|
+
free(aa);
|
51
|
+
return err;
|
52
|
+
}
|
53
|
+
return 0;
|
54
|
+
}
|
55
|
+
|
56
|
+
#else /* WIN32 */
|
57
|
+
|
58
|
+
static void *adapter(void *arg)
|
59
|
+
{
|
60
|
+
adapter_arg_t *aa = (adapter_arg_t *)arg;
|
61
|
+
aa->func(aa->arg);
|
62
|
+
free(aa);
|
63
|
+
return NULL;
|
64
|
+
}
|
65
|
+
|
66
|
+
int oci8_run_native_thread(rb_blocking_function_t func, void *arg)
|
67
|
+
{
|
68
|
+
pthread_t thread;
|
69
|
+
adapter_arg_t *aa = malloc(sizeof(adapter_arg_t));
|
70
|
+
int rv;
|
71
|
+
if (aa == NULL) {
|
72
|
+
return ENOMEM;
|
73
|
+
}
|
74
|
+
|
75
|
+
aa->func = func;
|
76
|
+
aa->arg = arg;
|
77
|
+
rv = pthread_create(&thread, &detached_thread_attr, adapter, aa);
|
78
|
+
if (rv != 0) {
|
79
|
+
free(aa);
|
80
|
+
}
|
81
|
+
return rv;
|
82
|
+
}
|
83
|
+
#endif /* WIN32 */
|
84
|
+
|
85
|
+
#endif /* USE_THREAD_LOCAL_ERRHP */
|
@@ -0,0 +1,30 @@
|
|
1
|
+
/* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */
|
2
|
+
/*
|
3
|
+
* thread_util.h - part of ruby-oci8
|
4
|
+
*
|
5
|
+
* Copyright (C) 2011 KUBO Takehiro <kubo@jiubao.org>
|
6
|
+
*/
|
7
|
+
#ifdef USE_THREAD_LOCAL_ERRHP
|
8
|
+
|
9
|
+
/*
|
10
|
+
* Prepare to execute thread-related functions.
|
11
|
+
*/
|
12
|
+
void Init_oci8_thread_util(void);
|
13
|
+
|
14
|
+
/*
|
15
|
+
* Run the func in a new native thread.
|
16
|
+
* Don't call any ruby functions in the func.
|
17
|
+
* The return value is errno.
|
18
|
+
*/
|
19
|
+
int oci8_run_native_thread(rb_blocking_function_t func, void *arg);
|
20
|
+
|
21
|
+
#else
|
22
|
+
|
23
|
+
/*
|
24
|
+
* For ruby 1.8 configured without --enable-pthread on Unix.
|
25
|
+
*/
|
26
|
+
|
27
|
+
#define Init_oci8_thread_util() do {} while (0)
|
28
|
+
#define oci8_run_native_thread(func, arg) ((func)(arg), 0)
|
29
|
+
|
30
|
+
#endif
|
data/ext/oci8/win32.c
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
/* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */
|
2
|
+
/*
|
3
|
+
win32.c - part of ruby-oci8
|
4
|
+
|
5
|
+
Copyright (C) 2009-2010 KUBO Takehiro <kubo@jiubao.org>
|
6
|
+
*/
|
7
|
+
#include "oci8.h"
|
8
|
+
#ifdef __CYGWIN__
|
9
|
+
/* boolean is defined as a macro in oratypes.h.
|
10
|
+
* It conflicts with the definition in windows.h.
|
11
|
+
*/
|
12
|
+
#undef boolean
|
13
|
+
#endif
|
14
|
+
#include <windows.h>
|
15
|
+
|
16
|
+
NORETURN(static void raise_error(void));
|
17
|
+
|
18
|
+
static void raise_error(void)
|
19
|
+
{
|
20
|
+
char msg[1024];
|
21
|
+
int err = GetLastError();
|
22
|
+
char *p;
|
23
|
+
|
24
|
+
sprintf(msg, "%d: ", err);
|
25
|
+
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
26
|
+
NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
27
|
+
msg + strlen(msg), sizeof(msg) - strlen(msg), NULL);
|
28
|
+
for (p = msg; *p != '\0'; p++) {
|
29
|
+
if (*p == '\n' || *p == '\r') {
|
30
|
+
*p = ' ';
|
31
|
+
}
|
32
|
+
}
|
33
|
+
rb_raise(rb_eRuntimeError, "%s", msg);
|
34
|
+
}
|
35
|
+
|
36
|
+
static VALUE dll_path(VALUE module)
|
37
|
+
{
|
38
|
+
HMODULE hModule;
|
39
|
+
DWORD len;
|
40
|
+
char path[1024];
|
41
|
+
|
42
|
+
hModule = GetModuleHandle("OCI.DLL");
|
43
|
+
if (hModule == NULL) {
|
44
|
+
raise_error();
|
45
|
+
}
|
46
|
+
len = GetModuleFileName(hModule, path, sizeof(path));
|
47
|
+
if (len == 0) {
|
48
|
+
raise_error();
|
49
|
+
}
|
50
|
+
return rb_external_str_new_with_enc(path, len, rb_filesystem_encoding());
|
51
|
+
}
|
52
|
+
|
53
|
+
|
54
|
+
typedef struct {
|
55
|
+
HKEY hKey;
|
56
|
+
HKEY hSubKey;
|
57
|
+
} enum_homes_arg_t;
|
58
|
+
|
59
|
+
static VALUE enum_homes_real(enum_homes_arg_t *arg)
|
60
|
+
{
|
61
|
+
LONG rv;
|
62
|
+
DWORD type;
|
63
|
+
DWORD idx;
|
64
|
+
char name[1024];
|
65
|
+
DWORD name_len;
|
66
|
+
FILETIME ft;
|
67
|
+
|
68
|
+
rv = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\ORACLE", 0, KEY_ENUMERATE_SUB_KEYS, &arg->hKey);
|
69
|
+
if (rv != ERROR_SUCCESS) {
|
70
|
+
return Qnil;
|
71
|
+
}
|
72
|
+
for (idx = 0; ; idx++) {
|
73
|
+
volatile VALUE oracle_home;
|
74
|
+
volatile VALUE nls_lang;
|
75
|
+
|
76
|
+
/* Get subkey name */
|
77
|
+
name_len = sizeof(name);
|
78
|
+
rv = RegEnumKeyEx(arg->hKey, idx, name, &name_len, NULL, NULL, NULL, &ft);
|
79
|
+
if (rv != ERROR_SUCCESS) {
|
80
|
+
break;
|
81
|
+
}
|
82
|
+
/* Open subkey */
|
83
|
+
if (arg->hSubKey != NULL) {
|
84
|
+
RegCloseKey(arg->hSubKey);
|
85
|
+
arg->hSubKey = NULL;
|
86
|
+
}
|
87
|
+
rv = RegOpenKeyEx(arg->hKey, name, 0, KEY_QUERY_VALUE, &arg->hSubKey);
|
88
|
+
if (rv != ERROR_SUCCESS) {
|
89
|
+
continue;
|
90
|
+
}
|
91
|
+
/* Get ORACLE_HOME */
|
92
|
+
name_len = sizeof(name);
|
93
|
+
rv = RegQueryValueEx(arg->hSubKey, "ORACLE_HOME", NULL, &type, TO_ORATEXT(name), &name_len);
|
94
|
+
if (rv != ERROR_SUCCESS || type != REG_SZ) {
|
95
|
+
continue;
|
96
|
+
}
|
97
|
+
oracle_home = rb_locale_str_new_cstr(name);
|
98
|
+
/* Get NLS_LANG */
|
99
|
+
name_len = sizeof(name);
|
100
|
+
rv = RegQueryValueEx(arg->hSubKey, "NLS_LANG", NULL, &type, TO_ORATEXT(name), &name_len);
|
101
|
+
if (rv != ERROR_SUCCESS || type != REG_SZ) {
|
102
|
+
continue;
|
103
|
+
}
|
104
|
+
nls_lang = rb_locale_str_new_cstr(name);
|
105
|
+
rb_yield_values(2, oracle_home, nls_lang);
|
106
|
+
}
|
107
|
+
return Qnil;
|
108
|
+
}
|
109
|
+
|
110
|
+
static VALUE enum_homes_ensure(enum_homes_arg_t *arg)
|
111
|
+
{
|
112
|
+
if (arg->hKey != NULL) {
|
113
|
+
RegCloseKey(arg->hKey);
|
114
|
+
arg->hKey = NULL;
|
115
|
+
}
|
116
|
+
if (arg->hSubKey != NULL) {
|
117
|
+
RegCloseKey(arg->hSubKey);
|
118
|
+
arg->hSubKey = NULL;
|
119
|
+
}
|
120
|
+
return Qnil;
|
121
|
+
}
|
122
|
+
|
123
|
+
static VALUE enum_homes(VALUE module)
|
124
|
+
{
|
125
|
+
enum_homes_arg_t arg;
|
126
|
+
arg.hKey = NULL;
|
127
|
+
arg.hSubKey = NULL;
|
128
|
+
return rb_ensure(enum_homes_real, (VALUE)&arg, enum_homes_ensure, (VALUE)&arg);
|
129
|
+
}
|
130
|
+
|
131
|
+
void Init_oci8_win32(VALUE cOCI8)
|
132
|
+
{
|
133
|
+
VALUE mWin32Util = rb_define_module_under(cOCI8, "Win32Util");
|
134
|
+
|
135
|
+
rb_define_module_function(mWin32Util, "dll_path", dll_path, 0);
|
136
|
+
rb_define_module_function(mWin32Util, "enum_homes", enum_homes, 0);
|
137
|
+
}
|
data/lib/.document
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
oci8
|
data/lib/dbd/OCI8.rb
ADDED
@@ -0,0 +1,591 @@
|
|
1
|
+
#
|
2
|
+
# DBD::OCI8
|
3
|
+
#
|
4
|
+
# Copyright (c) 2002-2007 KUBO Takehiro <kubo@jiubao.org>
|
5
|
+
#
|
6
|
+
# copied some code from DBD::Oracle.
|
7
|
+
# DBD::Oracle's copyright is as follows:
|
8
|
+
# --------------------- begin -------------------
|
9
|
+
#
|
10
|
+
# Copyright (c) 2001, 2002, 2003, 2004 Michael Neumann <mneumann@ntecs.de>
|
11
|
+
#
|
12
|
+
# All rights reserved.
|
13
|
+
#
|
14
|
+
# Redistribution and use in source and binary forms, with or without
|
15
|
+
# modification, are permitted provided that the following conditions
|
16
|
+
# are met:
|
17
|
+
# 1. Redistributions of source code must retain the above copyright
|
18
|
+
# notice, this list of conditions and the following disclaimer.
|
19
|
+
# 2. Redistributions in binary form must reproduce the above copyright
|
20
|
+
# notice, this list of conditions and the following disclaimer in the
|
21
|
+
# documentation and/or other materials provided with the distribution.
|
22
|
+
# 3. The name of the author may not be used to endorse or promote products
|
23
|
+
# derived from this software without specific prior written permission.
|
24
|
+
#
|
25
|
+
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
26
|
+
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
27
|
+
# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
28
|
+
# THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
29
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
30
|
+
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
31
|
+
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
32
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
33
|
+
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
34
|
+
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
35
|
+
#
|
36
|
+
# --------------------- end -------------------
|
37
|
+
|
38
|
+
require 'oci8'
|
39
|
+
|
40
|
+
module DBI # :nodoc:
|
41
|
+
module DBD # :nodoc:
|
42
|
+
module OCI8
|
43
|
+
|
44
|
+
VERSION = "0.1"
|
45
|
+
USED_DBD_VERSION = "0.4"
|
46
|
+
|
47
|
+
def self.driver_name
|
48
|
+
"OCI8"
|
49
|
+
end
|
50
|
+
|
51
|
+
# type converstion handler to bind values. (ruby-dbi 0.4)
|
52
|
+
if DBI.const_defined?(:TypeUtil)
|
53
|
+
DBI::TypeUtil.register_conversion("OCI8") do |obj|
|
54
|
+
case obj
|
55
|
+
when ::TrueClass
|
56
|
+
['1', false]
|
57
|
+
when ::FalseClass
|
58
|
+
['0', false]
|
59
|
+
else
|
60
|
+
[obj, false]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# no type converstion is required for result set. (ruby-dbi 0.4)
|
66
|
+
class NoTypeConversion
|
67
|
+
def self.parse(obj)
|
68
|
+
obj
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
module Util
|
73
|
+
|
74
|
+
ERROR_MAP = {
|
75
|
+
1 => DBI::IntegrityError, # unique constraint violated
|
76
|
+
900 => DBI::ProgrammingError, # invalid SQL statement
|
77
|
+
904 => DBI::ProgrammingError, # invalid identifier
|
78
|
+
905 => DBI::ProgrammingError, # missing keyword
|
79
|
+
923 => DBI::ProgrammingError, # FROM keyword not found where expected
|
80
|
+
936 => DBI::ProgrammingError, # missing expression
|
81
|
+
942 => DBI::ProgrammingError, # table or view does not exist
|
82
|
+
2290 => DBI::IntegrityError, # check constraint violated
|
83
|
+
2291 => DBI::IntegrityError, # parent key not found
|
84
|
+
2292 => DBI::IntegrityError, # child record found
|
85
|
+
2293 => DBI::IntegrityError, # check constraint violated
|
86
|
+
}
|
87
|
+
|
88
|
+
def raise_dbierror(err) # :nodoc:
|
89
|
+
if err.is_a? OCIError
|
90
|
+
exc = ERROR_MAP[err.code] || DBI::DatabaseError
|
91
|
+
raise exc.new(err.message, err.code)
|
92
|
+
else
|
93
|
+
raise DBI::DatabaseError.new(err.message, -1)
|
94
|
+
end
|
95
|
+
rescue DBI::DatabaseError => exc
|
96
|
+
exc.set_backtrace(err.backtrace)
|
97
|
+
raise
|
98
|
+
end
|
99
|
+
|
100
|
+
def column_metadata_to_column_info(col)
|
101
|
+
sql_type, type_name, precision, scale =
|
102
|
+
case col.data_type
|
103
|
+
when :char
|
104
|
+
[SQL_CHAR, col.charset_form == :nchar ? "NCHAR" : "CHAR", col.data_size, nil]
|
105
|
+
when :varchar2
|
106
|
+
[SQL_VARCHAR, col.charset_form == :nchar ? "NVARCHAR2" : "VARCHAR2", col.data_size, nil]
|
107
|
+
when :raw
|
108
|
+
[SQL_VARBINARY, "RAW", col.data_size, nil]
|
109
|
+
when :long
|
110
|
+
[SQL_LONGVARCHAR, "LONG", 4000, nil]
|
111
|
+
when :long_raw
|
112
|
+
[SQL_LONGVARBINARY, "LONG RAW", 4000, nil]
|
113
|
+
when :clob
|
114
|
+
[SQL_CLOB, col.charset_form == :nchar ? "NCLOB" : "CLOB", 4000, nil]
|
115
|
+
when :blob
|
116
|
+
[SQL_BLOB, "BLOB", 4000, nil]
|
117
|
+
when :bfile
|
118
|
+
[SQL_BLOB, "BFILE", 4000, nil]
|
119
|
+
when :number
|
120
|
+
if col.scale == -127 && col.precision != 0
|
121
|
+
# To convert from binary to decimal precision, multiply n by 0.30103.
|
122
|
+
[SQL_FLOAT, "FLOAT", (col.precision * 0.30103).ceil , nil]
|
123
|
+
elsif col.precision == 0
|
124
|
+
# NUMBER or calculated value (eg. col * 1.2).
|
125
|
+
[SQL_NUMERIC, "NUMBER", 38, nil]
|
126
|
+
else
|
127
|
+
[SQL_NUMERIC, "NUMBER", col.precision, col.scale]
|
128
|
+
end
|
129
|
+
when :binary_float
|
130
|
+
# (23 * 0.30103).ceil => 7
|
131
|
+
[SQL_FLOAT, "BINARY_FLOAT", 7, nil]
|
132
|
+
when :binary_double
|
133
|
+
# (52 * 0.30103).ceil => 16
|
134
|
+
[SQL_DOUBLE, "BINARY_DOUBLE", 16, nil]
|
135
|
+
when :date
|
136
|
+
# yyyy-mm-dd hh:mi:ss
|
137
|
+
[SQL_DATE, "DATE", 19, nil]
|
138
|
+
when :timestamp
|
139
|
+
# yyyy-mm-dd hh:mi:ss.SSSS
|
140
|
+
[SQL_TIMESTAMP, "TIMESTAMP", 20 + col.fsprecision, nil]
|
141
|
+
when :timestamp_tz
|
142
|
+
# yyyy-mm-dd hh:mi:ss.SSSS +HH:MM
|
143
|
+
[SQL_TIMESTAMP, "TIMESTAMP WITH TIME ZONE", 27 + col.fsprecision, nil]
|
144
|
+
when :timestamp_ltz
|
145
|
+
# yyyy-mm-dd hh:mi:ss.SSSS
|
146
|
+
[SQL_TIMESTAMP, "TIMESTAMP WITH LOCAL TIME ZONE", 20 + col.fsprecision, nil]
|
147
|
+
when :interval_ym
|
148
|
+
# yyyy-mm
|
149
|
+
[SQL_OTHER, 'INTERVAL YEAR TO MONTH', col.lfprecision + 3, nil]
|
150
|
+
when :interval_ds
|
151
|
+
# dd hh:mi:ss.SSSSS
|
152
|
+
[SQL_OTHER, 'INTERVAL DAY TO SECOND', col.lfprecision + 10 + col.fsprecision, nil]
|
153
|
+
else
|
154
|
+
[SQL_OTHER, col.data_type.to_s, nil, nil]
|
155
|
+
end
|
156
|
+
{'name' => col.name,
|
157
|
+
'sql_type' => sql_type,
|
158
|
+
'type_name' => type_name,
|
159
|
+
'nullable' => col.nullable?,
|
160
|
+
'precision' => precision,
|
161
|
+
'scale' => scale,
|
162
|
+
'dbi_type' => NoTypeConversion,
|
163
|
+
}
|
164
|
+
end
|
165
|
+
private :column_metadata_to_column_info
|
166
|
+
end
|
167
|
+
|
168
|
+
class Driver < DBI::BaseDriver # :nodoc:
|
169
|
+
include Util
|
170
|
+
|
171
|
+
def initialize
|
172
|
+
super(USED_DBD_VERSION)
|
173
|
+
end
|
174
|
+
|
175
|
+
# external OS authentication
|
176
|
+
# (contributed by Dan Fitch)
|
177
|
+
def default_user
|
178
|
+
[nil, nil]
|
179
|
+
end
|
180
|
+
|
181
|
+
def connect( dbname, user, auth, attr )
|
182
|
+
handle = ::OCI8.new(user, auth, dbname, attr['Privilege'])
|
183
|
+
handle.non_blocking = true if attr['NonBlocking']
|
184
|
+
return Database.new(handle, attr)
|
185
|
+
rescue OCIException => err
|
186
|
+
raise_dbierror(err)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class Database < DBI::BaseDatabase
|
191
|
+
include Util
|
192
|
+
|
193
|
+
def disconnect
|
194
|
+
@handle.logoff
|
195
|
+
rescue OCIException => err
|
196
|
+
raise_dbierror(err)
|
197
|
+
end
|
198
|
+
|
199
|
+
def prepare( statement )
|
200
|
+
# convert ?-style parameters to :1, :2 etc.
|
201
|
+
prep_statement = DBI::SQL::PreparedStatement.new(DummyQuoter.new, statement)
|
202
|
+
if prep_statement.unbound.size > 0
|
203
|
+
arr = (1..(prep_statement.unbound.size)).collect{|i| ":#{i}"}
|
204
|
+
statement = prep_statement.bind( arr )
|
205
|
+
end
|
206
|
+
cursor = @handle.parse(statement)
|
207
|
+
Statement.new(cursor)
|
208
|
+
rescue OCIException => err
|
209
|
+
raise_dbierror(err)
|
210
|
+
end
|
211
|
+
|
212
|
+
def ping
|
213
|
+
@handle.exec("BEGIN NULL; END;")
|
214
|
+
true
|
215
|
+
rescue
|
216
|
+
false
|
217
|
+
end
|
218
|
+
|
219
|
+
def commit
|
220
|
+
@handle.commit()
|
221
|
+
rescue OCIException => err
|
222
|
+
raise_dbierror(err)
|
223
|
+
end
|
224
|
+
|
225
|
+
def rollback
|
226
|
+
@handle.rollback()
|
227
|
+
rescue OCIException => err
|
228
|
+
raise_dbierror(err)
|
229
|
+
end
|
230
|
+
|
231
|
+
def tables
|
232
|
+
stmt = execute("SELECT object_name FROM user_objects where object_type in ('TABLE', 'VIEW')")
|
233
|
+
rows = stmt.fetch_all || []
|
234
|
+
stmt.finish
|
235
|
+
rows.collect {|row| row[0]}
|
236
|
+
end
|
237
|
+
|
238
|
+
# SQLs are copied from DBD::Oracle.
|
239
|
+
def columns(table)
|
240
|
+
tab = @handle.describe_table(table)
|
241
|
+
cols = tab.columns
|
242
|
+
cols.collect! do |col|
|
243
|
+
column_metadata_to_column_info(col)
|
244
|
+
end
|
245
|
+
|
246
|
+
dbh = DBI::DatabaseHandle.new(self)
|
247
|
+
|
248
|
+
primaries = {}
|
249
|
+
dbh.select_all(<<EOS, tab.obj_schema, tab.obj_name) do |row|
|
250
|
+
select column_name
|
251
|
+
from all_cons_columns a, all_constraints b
|
252
|
+
where a.owner = b.owner
|
253
|
+
and a.constraint_name = b.constraint_name
|
254
|
+
and a.table_name = b.table_name
|
255
|
+
and b.constraint_type = 'P'
|
256
|
+
and b.owner = :1
|
257
|
+
and b.table_name = :2
|
258
|
+
EOS
|
259
|
+
primaries[row[0]] = true
|
260
|
+
end
|
261
|
+
|
262
|
+
indices = {}
|
263
|
+
uniques = {}
|
264
|
+
dbh.select_all(<<EOS, tab.obj_schema, tab.obj_name) do |row|
|
265
|
+
select a.column_name, a.index_name, b.uniqueness
|
266
|
+
from all_ind_columns a, all_indexes b
|
267
|
+
where a.index_name = b.index_name
|
268
|
+
and a.index_owner = b.owner
|
269
|
+
and a.table_owner = :1
|
270
|
+
and a.table_name = :2
|
271
|
+
EOS
|
272
|
+
col_name, index_name, uniqueness = row
|
273
|
+
indices[col_name] = true
|
274
|
+
uniques[col_name] = true if uniqueness == 'UNIQUE'
|
275
|
+
end
|
276
|
+
|
277
|
+
dbh.select_all(<<EOS, tab.obj_schema, tab.obj_name).collect do |row|
|
278
|
+
select column_id, column_name, data_default
|
279
|
+
from all_tab_columns
|
280
|
+
where owner = :1
|
281
|
+
and table_name = :2
|
282
|
+
EOS
|
283
|
+
col_id, col_name, default = row
|
284
|
+
|
285
|
+
col = cols[col_id.to_i - 1]
|
286
|
+
col_name = col['name']
|
287
|
+
|
288
|
+
if default && default[0] == ?'
|
289
|
+
default = default[1..-2].gsub(/''/, "'")
|
290
|
+
end
|
291
|
+
|
292
|
+
col['indexed'] = indices[col_name] || false
|
293
|
+
col['primary'] = primaries[col_name] || false
|
294
|
+
col['unique'] = uniques[col_name] || false
|
295
|
+
col['default'] = default
|
296
|
+
col
|
297
|
+
end
|
298
|
+
rescue OCIException => err
|
299
|
+
raise_dbierror(err)
|
300
|
+
end
|
301
|
+
|
302
|
+
def [](attr)
|
303
|
+
case attr
|
304
|
+
when 'AutoCommit'
|
305
|
+
@handle.autocommit?
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def []=(attr, value)
|
310
|
+
case attr
|
311
|
+
when 'AutoCommit'
|
312
|
+
@handle.autocommit = value
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
private
|
317
|
+
|
318
|
+
class DummyQuoter # :nodoc:
|
319
|
+
# dummy to substitute ?-style parameter markers by :1 :2 etc.
|
320
|
+
def quote(str)
|
321
|
+
str
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
class Statement < DBI::BaseStatement
|
327
|
+
include Util
|
328
|
+
|
329
|
+
def initialize(cursor)
|
330
|
+
@cursor = cursor
|
331
|
+
end
|
332
|
+
|
333
|
+
def bind_param( param, value, attribs)
|
334
|
+
if attribs.nil? || attribs['type'].nil?
|
335
|
+
if value.nil?
|
336
|
+
@cursor.bind_param(param, nil, String, 1)
|
337
|
+
else
|
338
|
+
@cursor.bind_param(param, value)
|
339
|
+
end
|
340
|
+
else
|
341
|
+
case attribs['type']
|
342
|
+
when SQL_BINARY
|
343
|
+
type = OCI_TYPECODE_RAW
|
344
|
+
else
|
345
|
+
type = attribs['type']
|
346
|
+
end
|
347
|
+
@cursor.bind_param(param, value, type)
|
348
|
+
end
|
349
|
+
rescue OCIException => err
|
350
|
+
raise_dbierror(err)
|
351
|
+
end
|
352
|
+
|
353
|
+
def execute
|
354
|
+
@cursor.exec
|
355
|
+
rescue OCIException => err
|
356
|
+
raise_dbierror(err)
|
357
|
+
end
|
358
|
+
|
359
|
+
def finish
|
360
|
+
@cursor.close
|
361
|
+
rescue OCIException => err
|
362
|
+
raise_dbierror(err)
|
363
|
+
end
|
364
|
+
|
365
|
+
def fetch
|
366
|
+
@cursor.fetch
|
367
|
+
rescue OCIException => err
|
368
|
+
raise_dbierror(err)
|
369
|
+
end
|
370
|
+
|
371
|
+
def column_info
|
372
|
+
# minimum implementation.
|
373
|
+
@cursor.column_metadata.collect do |md|
|
374
|
+
col = column_metadata_to_column_info(md)
|
375
|
+
col['indexed'] = nil
|
376
|
+
col['primary'] = nil
|
377
|
+
col['unique'] = nil
|
378
|
+
col['default'] = nil
|
379
|
+
col
|
380
|
+
end
|
381
|
+
rescue OCIException => err
|
382
|
+
raise_dbierror(err)
|
383
|
+
end
|
384
|
+
|
385
|
+
def rows
|
386
|
+
@cursor.row_count
|
387
|
+
rescue OCIException => err
|
388
|
+
raise_dbierror(err)
|
389
|
+
end
|
390
|
+
|
391
|
+
def __rowid
|
392
|
+
@cursor.rowid
|
393
|
+
end
|
394
|
+
|
395
|
+
def __define(pos, type, length = nil)
|
396
|
+
@cursor.define(pos, type, length)
|
397
|
+
self
|
398
|
+
end
|
399
|
+
|
400
|
+
def __bind_value(param)
|
401
|
+
@cursor[param]
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
# DBI_STMT_NEW_ARGS is DBI::StatementHandle.new's arguments except +handle+.
|
406
|
+
#
|
407
|
+
# FYI: DBI::StatementHandle.new method signatures are follows:
|
408
|
+
# 0.2.2: handle, fetchable=false, prepared=true
|
409
|
+
# 0.4.0: handle, fetchable=false, prepared=true, convert_types=true
|
410
|
+
# 0.4.1: handle, fetchable=false, prepared=true, convert_types=true, executed=false
|
411
|
+
begin
|
412
|
+
DBI::StatementHandle.new(nil, false, true, true, true)
|
413
|
+
# dbi 0.4.1
|
414
|
+
DBI_STMT_NEW_ARGS = [true, true, true, true] # :nodoc:
|
415
|
+
rescue ArgumentError
|
416
|
+
# dbi 0.4.0 or lower
|
417
|
+
DBI_STMT_NEW_ARGS = [true] # :nodoc:
|
418
|
+
end
|
419
|
+
|
420
|
+
if defined? ::OCI8::BindType::Base
|
421
|
+
##
|
422
|
+
## ruby-oci8 2.0 bind classes.
|
423
|
+
##
|
424
|
+
|
425
|
+
module BindType # :nodoc:
|
426
|
+
|
427
|
+
# helper class to define/bind DBI::Date.
|
428
|
+
class DBIDate < ::OCI8::BindType::OraDate
|
429
|
+
def set(val)
|
430
|
+
# convert val to an OraDate,
|
431
|
+
# then set it to the bind handle.
|
432
|
+
super(val && OraDate.new(val.year, val.month, val.day))
|
433
|
+
end
|
434
|
+
def get()
|
435
|
+
# get an Oradate from the bind handle,
|
436
|
+
# then convert it to a DBI::Date.
|
437
|
+
val = super()
|
438
|
+
return nil if val.nil?
|
439
|
+
DBI::Date.new(val.year, val.month, val.day)
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
# helper class to define/bind DBI::Timestamp.
|
444
|
+
#
|
445
|
+
# To fetch all Oracle's DATE columns as DBI::Timestamp:
|
446
|
+
# ::OCI8::BindType::Mapping[OCI8::SQLT_DAT] = ::DBI::DBD::OCI8::BindType::DBITimestamp
|
447
|
+
#
|
448
|
+
class DBITimestamp < ::OCI8::BindType::OraDate
|
449
|
+
def set(val)
|
450
|
+
# convert val to an OraDate,
|
451
|
+
# then set it to the bind handle.
|
452
|
+
super(val && OraDate.new(val.year, val.month, val.day,
|
453
|
+
val.respond_to?(:hour) ? val.hour : 0,
|
454
|
+
val.respond_to?(:min) ? val.min : 0,
|
455
|
+
val.respond_to?(:sec) ? val.sec : 0))
|
456
|
+
end
|
457
|
+
def get()
|
458
|
+
# get an Oradate from the bind handle,
|
459
|
+
# then convert it to a DBI::Timestamp.
|
460
|
+
val = super()
|
461
|
+
return nil if val.nil?
|
462
|
+
DBI::Timestamp.new(val.year, val.month, val.day, val.hour, val.minute, val.second)
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
# helper class to bind ref cursor as DBI::StatementHandle.
|
467
|
+
#
|
468
|
+
# # Create package
|
469
|
+
# dbh.execute(<<EOS)
|
470
|
+
# create or replace package test_pkg is
|
471
|
+
# type ref_cursor is ref cursor;
|
472
|
+
# procedure tab_table(csr out ref_cursor);
|
473
|
+
# end;
|
474
|
+
# EOS
|
475
|
+
#
|
476
|
+
# # Create package body
|
477
|
+
# dbh.execute(<<EOS)
|
478
|
+
# create or replace package body test_pkg is
|
479
|
+
# procedure tab_table(csr out ref_cursor) is
|
480
|
+
# begin
|
481
|
+
# open csr for select * from tab;
|
482
|
+
# end;
|
483
|
+
# end;
|
484
|
+
# EOS
|
485
|
+
#
|
486
|
+
# # Execute test_pkg.tab_table.
|
487
|
+
# # The first parameter is bound as DBI::StatementHandle.
|
488
|
+
# plsql = dbh.execute("begin test_pkg.tab_table(?); end;", DBI::StatementHandle)
|
489
|
+
#
|
490
|
+
# # Get the first parameter, which is a DBI::StatementHandle.
|
491
|
+
# sth = plsql.func(:bind_value, 1)
|
492
|
+
#
|
493
|
+
# # fetch column data.
|
494
|
+
# sth.fetch_all
|
495
|
+
#
|
496
|
+
class DBIStatementHandle < ::OCI8::BindType::Cursor
|
497
|
+
def set(val)
|
498
|
+
if val.is_a? DBI::StatementHandle
|
499
|
+
# get OCI8::Cursor
|
500
|
+
val = val.instance_eval do @handle end
|
501
|
+
val = val.instance_eval do @cursor end
|
502
|
+
end
|
503
|
+
super(val)
|
504
|
+
end
|
505
|
+
def get()
|
506
|
+
val = super
|
507
|
+
return nil if val.nil?
|
508
|
+
stmt = DBI::DBD::OCI8::Statement.new(val)
|
509
|
+
DBI::StatementHandle.new(stmt, *DBI_STMT_NEW_ARGS)
|
510
|
+
end
|
511
|
+
end
|
512
|
+
end # BindType
|
513
|
+
|
514
|
+
else
|
515
|
+
##
|
516
|
+
## ruby-oci8 1.0 bind classes.
|
517
|
+
##
|
518
|
+
|
519
|
+
module BindType # :nodoc:
|
520
|
+
DBIDate = Object.new
|
521
|
+
class << DBIDate
|
522
|
+
def fix_type(env, val, length, precision, scale)
|
523
|
+
# bind as an OraDate
|
524
|
+
[::OCI8::SQLT_DAT, val, nil]
|
525
|
+
end
|
526
|
+
def decorate(b)
|
527
|
+
def b.set(val)
|
528
|
+
# convert val to an OraDate,
|
529
|
+
# then set it to the bind handle.
|
530
|
+
super(val && OraDate.new(val.year, val.month, val.day))
|
531
|
+
end
|
532
|
+
def b.get()
|
533
|
+
# get an Oradate from the bind handle,
|
534
|
+
# then convert it to a DBI::Date.
|
535
|
+
(val = super()) && DBI::Date.new(val.year, val.month, val.day)
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
DBITimestamp = Object.new
|
541
|
+
class << DBITimestamp
|
542
|
+
def fix_type(env, val, length, precision, scale)
|
543
|
+
# bind as an OraDate
|
544
|
+
[::OCI8::SQLT_DAT, val, nil]
|
545
|
+
end
|
546
|
+
def decorate(b)
|
547
|
+
def b.set(val)
|
548
|
+
# convert val to an OraDate,
|
549
|
+
# then set it to the bind handle.
|
550
|
+
super(val && OraDate.new(val.year, val.month, val.day,
|
551
|
+
val.respond_to?(:hour) ? val.hour : 0,
|
552
|
+
val.respond_to?(:min) ? val.min : 0,
|
553
|
+
val.respond_to?(:sec) ? val.sec : 0))
|
554
|
+
end
|
555
|
+
def b.get()
|
556
|
+
# get an Oradate from the bind handle,
|
557
|
+
# then convert it to a DBI::Timestamp.
|
558
|
+
(val = super()) && DBI::Timestamp.new(val.year, val.month, val.day, val.hour, val.minute, val.second)
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
DBIStatementHandle = Object.new
|
564
|
+
class << DBIStatementHandle
|
565
|
+
def fix_type(env, val, length, precision, scale)
|
566
|
+
raise NotImplementedError unless val.nil?
|
567
|
+
[::OCI8::SQLT_RSET, nil, env.alloc(OCIStmt)]
|
568
|
+
end
|
569
|
+
def decorate(b)
|
570
|
+
def b.set(val)
|
571
|
+
raise NotImplementedError
|
572
|
+
end
|
573
|
+
def b.get()
|
574
|
+
val = super
|
575
|
+
return val if val.nil?
|
576
|
+
cur = ::OCI8::Cursor.new(@env, @svc, @ctx, val)
|
577
|
+
stmt = DBI::DBD::OCI8::Statement.new(cur)
|
578
|
+
DBI::StatementHandle.new(stmt, *DBI_STMT_NEW_ARGS)
|
579
|
+
end
|
580
|
+
end
|
581
|
+
end
|
582
|
+
end # BindType
|
583
|
+
end
|
584
|
+
|
585
|
+
::OCI8::BindType::Mapping[DBI::Date] = BindType::DBIDate
|
586
|
+
::OCI8::BindType::Mapping[DBI::Timestamp] = BindType::DBITimestamp
|
587
|
+
::OCI8::BindType::Mapping[DBI::StatementHandle] = BindType::DBIStatementHandle
|
588
|
+
|
589
|
+
end # module OCI8
|
590
|
+
end # module DBD
|
591
|
+
end # module DBI
|