do_oracle 0.10.1-x86-mingw32
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.markdown +3 -0
- data/INSTALL.markdown +5 -0
- data/LICENSE +20 -0
- data/README.markdown +106 -0
- data/Rakefile +65 -0
- data/ext/do_oracle/do_oracle.c +903 -0
- data/ext/do_oracle/extconf.rb +36 -0
- data/lib/do_oracle.rb +133 -0
- data/lib/do_oracle/1.8/do_oracle.so +0 -0
- data/lib/do_oracle/1.9/do_oracle.so +0 -0
- data/lib/do_oracle/transaction.rb +36 -0
- data/lib/do_oracle/version.rb +5 -0
- data/spec/command_spec.rb +53 -0
- data/spec/connection_spec.rb +21 -0
- data/spec/encoding_spec.rb +12 -0
- data/spec/reader_spec.rb +8 -0
- data/spec/result_spec.rb +95 -0
- data/spec/spec_helper.rb +190 -0
- data/spec/typecast/array_spec.rb +8 -0
- data/spec/typecast/bigdecimal_spec.rb +9 -0
- data/spec/typecast/boolean_spec.rb +9 -0
- data/spec/typecast/byte_array_spec.rb +89 -0
- data/spec/typecast/class_spec.rb +64 -0
- data/spec/typecast/date_spec.rb +11 -0
- data/spec/typecast/datetime_spec.rb +9 -0
- data/spec/typecast/float_spec.rb +51 -0
- data/spec/typecast/integer_spec.rb +8 -0
- data/spec/typecast/nil_spec.rb +10 -0
- data/spec/typecast/other_spec.rb +8 -0
- data/spec/typecast/range_spec.rb +8 -0
- data/spec/typecast/string_spec.rb +170 -0
- data/spec/typecast/time_spec.rb +85 -0
- data/tasks/compile.rake +42 -0
- data/tasks/release.rake +16 -0
- data/tasks/retrieve.rake +67 -0
- data/tasks/spec.rake +23 -0
- metadata +151 -0
data/ChangeLog.markdown
ADDED
data/INSTALL.markdown
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 - 2010 Yehuda Katz, Dirkjan Bussink, Raimonds Simanovskis
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# do_oracle
|
2
|
+
|
3
|
+
* <http://dataobjects.info>
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
An Oracle driver for DataObjects.
|
8
|
+
|
9
|
+
## Features/Problems
|
10
|
+
|
11
|
+
This driver implements the DataObjects API for the Oracle relational database.
|
12
|
+
|
13
|
+
## Synopsis
|
14
|
+
|
15
|
+
An example of usage:
|
16
|
+
|
17
|
+
@connection = DataObjects::Connection.new("oracle://employees")
|
18
|
+
@reader = @connection.create_command('SELECT * FROM users').execute_reader
|
19
|
+
@reader.next!
|
20
|
+
|
21
|
+
In the future, the `Connection` constructor will be able to be passed either a
|
22
|
+
DataObjects-style URL or JDBC style URL, when using do\_oracle on JRuby. However,
|
23
|
+
this feature is not currently working reliably and is a known issue.
|
24
|
+
|
25
|
+
## Requirements
|
26
|
+
|
27
|
+
This driver is provided for the following platforms:
|
28
|
+
* Ruby MRI (1.8.6/7), 1.9: tested on Linux, Mac OS X and Windows platforms.
|
29
|
+
* JRuby 1.3.1 + (1.4+ recommended).
|
30
|
+
* Rubinius (experimental).
|
31
|
+
|
32
|
+
Additionally you should have the following prerequisites:
|
33
|
+
* `data_objects` gem
|
34
|
+
* `do_jdbc` gem (shared library), if running on JRuby.
|
35
|
+
|
36
|
+
## Install
|
37
|
+
|
38
|
+
To install the gem:
|
39
|
+
|
40
|
+
gem install do_oracle
|
41
|
+
|
42
|
+
To compile and install from source:
|
43
|
+
|
44
|
+
* For MRI/Rubinius extensions:
|
45
|
+
* Install the `gcc` compiler. On OS X, you should install XCode tools. On
|
46
|
+
Ubuntu, run `apt-get install build-essential`.
|
47
|
+
* THESE INSTRUCTIONS ARE CURRENTLY INCOMPLETE!
|
48
|
+
|
49
|
+
* For JRuby extensions:
|
50
|
+
* Install the Java Development Kit (provided if you are
|
51
|
+
on a recent version of Mac OS X) from <http://java.sun.com>.
|
52
|
+
* Install a recent version of JRuby. Ensure `jruby` is in your `PATH` and/or
|
53
|
+
you have configured the `JRUBY_HOME` environment variable to point to your
|
54
|
+
JRuby installation.
|
55
|
+
* Install `data_objects` and `do_jdbc` with `jruby -S rake install`.
|
56
|
+
|
57
|
+
* Then, install this driver with `(jruby -S) rake install`.
|
58
|
+
|
59
|
+
For more information, see the Oracle driver wiki page:
|
60
|
+
<http://wiki.github.com/datamapper/do/oracle>.
|
61
|
+
|
62
|
+
## Developers
|
63
|
+
|
64
|
+
Follow the above installation instructions. Additionally, you'll need:
|
65
|
+
* `bacon` gem for running specs.
|
66
|
+
* `YARD` gem for generating documentation.
|
67
|
+
|
68
|
+
See the DataObjects wiki for more comprehensive information on installing and
|
69
|
+
contributing to the JRuby-variant of this driver:
|
70
|
+
<http://wiki.github.com/datamapper/do/jruby>.
|
71
|
+
|
72
|
+
### install oracle jdbc driver in maven
|
73
|
+
|
74
|
+
$ mvn install
|
75
|
+
will produce an error and give you message like (maybe with a different version). please follow these instructions to install the
|
76
|
+
|
77
|
+
Try downloading the file manually from:
|
78
|
+
http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/index.html
|
79
|
+
|
80
|
+
Then, install it using the command:
|
81
|
+
mvn install:install-file -DgroupId=com.oracle -DartifactId=ojdbc14 -Dversion=10.2.0.3.0 -Dpackaging=jar -Dfile=/path/to/file
|
82
|
+
|
83
|
+
Alternatively, if you host your own repository you can deploy the file there:
|
84
|
+
mvn deploy:deploy-file -DgroupId=com.oracle -DartifactId=ojdbc14 -Dversion=10.2.0.3.0 -Dpackaging=jar -Dfile=/path/to/file -Durl=[url] -DrepositoryId=[id]
|
85
|
+
|
86
|
+
### Specs
|
87
|
+
|
88
|
+
To run specs:
|
89
|
+
|
90
|
+
rake spec
|
91
|
+
|
92
|
+
To run specs without compiling extensions first:
|
93
|
+
|
94
|
+
rake spec_no_compile
|
95
|
+
|
96
|
+
To run individual specs:
|
97
|
+
|
98
|
+
rake spec TEST=spec/connection_spec.rb
|
99
|
+
|
100
|
+
(Note that the `rake` task uses a `TEST` parameter, not `SPEC`. This is because
|
101
|
+
the `Rake::TestTask` is used for executing the Bacon specs).
|
102
|
+
|
103
|
+
## License
|
104
|
+
|
105
|
+
This code is licensed under an **MIT (X11) License**. Please see the
|
106
|
+
accompanying `LICENSE` file.
|
data/Rakefile
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/clean'
|
5
|
+
|
6
|
+
ROOT = Pathname(__FILE__).dirname.expand_path
|
7
|
+
|
8
|
+
require ROOT + 'lib/do_oracle/version'
|
9
|
+
|
10
|
+
JRUBY = RUBY_PLATFORM =~ /java/
|
11
|
+
IRONRUBY = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ironruby'
|
12
|
+
WINDOWS = Gem.win_platform? || (JRUBY && ENV_JAVA['os.name'] =~ /windows/i)
|
13
|
+
SUDO = WINDOWS ? '' : ('sudo' unless ENV['SUDOLESS'])
|
14
|
+
BINARY_VERSION = '10.2.0.4.0'
|
15
|
+
|
16
|
+
CLEAN.include(%w[ {tmp,pkg}/ **/*.{o,so,bundle,jar,log,a,gem,dSYM,obj,pdb,exp,DS_Store,rbc,db} ext/do_oracle/Makefile ext-java/target ])
|
17
|
+
|
18
|
+
begin
|
19
|
+
gem 'jeweler', '~> 1.4'
|
20
|
+
require 'jeweler'
|
21
|
+
|
22
|
+
Jeweler::Tasks.new do |gem|
|
23
|
+
gem.name = 'do_oracle'
|
24
|
+
gem.version = DataObjects::Oracle::VERSION
|
25
|
+
gem.summary = 'DataObjects Oracle Driver'
|
26
|
+
gem.description = 'Implements the DataObjects API for Oracle'
|
27
|
+
gem.platform = Gem::Platform::RUBY
|
28
|
+
gem.files = Dir['lib/**/*.rb', 'spec/**/*.rb', 'tasks/**/*.rake',
|
29
|
+
'ext/**/*.{rb,c,h}', 'LICENSE', 'Rakefile',
|
30
|
+
'*.{markdown,rdoc,txt,yml}']
|
31
|
+
gem.extra_rdoc_files = FileList['README*', 'ChangeLog*', 'INSTALL.markdown',
|
32
|
+
'LICENSE']
|
33
|
+
gem.test_files = FileList['spec/**/*.rb']
|
34
|
+
|
35
|
+
# rake-compiler should generate gemspecs for other platforms (e.g. 'java')
|
36
|
+
# and modify dependencies and extensions appropriately
|
37
|
+
gem.extensions << 'ext/do_oracle/extconf.rb'
|
38
|
+
|
39
|
+
gem.add_dependency 'data_objects', DataObjects::Oracle::VERSION
|
40
|
+
gem.add_dependency 'ruby-oci8', '~>2.0'
|
41
|
+
|
42
|
+
gem.add_development_dependency 'bacon', '~>1.1'
|
43
|
+
gem.add_development_dependency 'rake-compiler', '~>0.7'
|
44
|
+
|
45
|
+
gem.has_rdoc = false
|
46
|
+
gem.rubyforge_project = 'dorb'
|
47
|
+
gem.authors = [ 'Raimonds Simanovskis' ]
|
48
|
+
gem.email = 'raimonds.simanovskis@gmail.com'
|
49
|
+
end
|
50
|
+
|
51
|
+
if JRUBY
|
52
|
+
Rake::Task['build'].clear_actions if Rake::Task.task_defined?('build')
|
53
|
+
Rake::Task['install'].clear_actions if Rake::Task.task_defined?('install')
|
54
|
+
task :build => [ :java, :gem ]
|
55
|
+
task :install do
|
56
|
+
sh "#{Config::CONFIG['RUBY_INSTALL_NAME']} -S gem install pkg/do_oracle-#{DataObjects::Oracle::VERSION}-java.gem"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
Jeweler::GemcutterTasks.new
|
61
|
+
|
62
|
+
FileList['tasks/**/*.rake'].each { |task| import task }
|
63
|
+
rescue LoadError
|
64
|
+
puts 'Jeweler (or a dependency) not available. Install it with: gem install jeweler'
|
65
|
+
end
|
@@ -0,0 +1,903 @@
|
|
1
|
+
#ifdef _WIN32
|
2
|
+
#define do_int64 signed __int64
|
3
|
+
#else
|
4
|
+
#define do_int64 signed long long int
|
5
|
+
#endif
|
6
|
+
|
7
|
+
#include <ruby.h>
|
8
|
+
#include <string.h>
|
9
|
+
#include <math.h>
|
10
|
+
#include <ctype.h>
|
11
|
+
#include <time.h>
|
12
|
+
|
13
|
+
#define ID_CONST_GET rb_intern("const_get")
|
14
|
+
|
15
|
+
#define RUBY_STRING(char_ptr) rb_str_new2(char_ptr)
|
16
|
+
#define TAINTED_STRING(name, length) rb_tainted_str_new(name, length)
|
17
|
+
#define CONST_GET(scope, constant) (rb_funcall(scope, ID_CONST_GET, 1, rb_str_new2(constant)))
|
18
|
+
#define ORACLE_CLASS(klass, parent) (rb_define_class_under(mOracle, klass, parent))
|
19
|
+
#define DEBUG(value) data_objects_debug(value)
|
20
|
+
#define RUBY_CLASS(name) rb_const_get(rb_cObject, rb_intern(name))
|
21
|
+
|
22
|
+
#ifndef RSTRING_PTR
|
23
|
+
#define RSTRING_PTR(s) (RSTRING(s)->ptr)
|
24
|
+
#endif
|
25
|
+
|
26
|
+
#ifndef RSTRING_LEN
|
27
|
+
#define RSTRING_LEN(s) (RSTRING(s)->len)
|
28
|
+
#endif
|
29
|
+
|
30
|
+
#ifndef RARRAY_LEN
|
31
|
+
#define RARRAY_LEN(a) RARRAY(a)->len
|
32
|
+
#endif
|
33
|
+
|
34
|
+
|
35
|
+
// To store rb_intern values
|
36
|
+
static ID ID_NEW;
|
37
|
+
static ID ID_NEW_DATE;
|
38
|
+
static ID ID_LOGGER;
|
39
|
+
static ID ID_DEBUG;
|
40
|
+
static ID ID_LEVEL;
|
41
|
+
static ID ID_TO_S;
|
42
|
+
static ID ID_RATIONAL;
|
43
|
+
|
44
|
+
static ID ID_NAME;
|
45
|
+
|
46
|
+
static ID ID_NUMBER;
|
47
|
+
static ID ID_VARCHAR2;
|
48
|
+
static ID ID_CHAR;
|
49
|
+
static ID ID_DATE;
|
50
|
+
static ID ID_TIMESTAMP;
|
51
|
+
static ID ID_TIMESTAMP_TZ;
|
52
|
+
static ID ID_TIMESTAMP_LTZ;
|
53
|
+
static ID ID_CLOB;
|
54
|
+
static ID ID_BLOB;
|
55
|
+
static ID ID_LONG;
|
56
|
+
static ID ID_RAW;
|
57
|
+
static ID ID_LONG_RAW;
|
58
|
+
static ID ID_BFILE;
|
59
|
+
static ID ID_BINARY_FLOAT;
|
60
|
+
static ID ID_BINARY_DOUBLE;
|
61
|
+
|
62
|
+
static ID ID_TO_A;
|
63
|
+
static ID ID_TO_I;
|
64
|
+
static ID ID_TO_S;
|
65
|
+
static ID ID_TO_F;
|
66
|
+
|
67
|
+
static ID ID_UTC_OFFSET;
|
68
|
+
static ID ID_FULL_CONST_GET;
|
69
|
+
|
70
|
+
static ID ID_PARSE;
|
71
|
+
static ID ID_FETCH;
|
72
|
+
static ID ID_TYPE;
|
73
|
+
static ID ID_EXECUTE;
|
74
|
+
static ID ID_EXEC;
|
75
|
+
|
76
|
+
static ID ID_SELECT_STMT;
|
77
|
+
static ID ID_COLUMN_METADATA;
|
78
|
+
static ID ID_PRECISION;
|
79
|
+
static ID ID_SCALE;
|
80
|
+
static ID ID_BIND_PARAM;
|
81
|
+
static ID ID_ELEM;
|
82
|
+
static ID ID_READ;
|
83
|
+
|
84
|
+
static ID ID_CLOSE;
|
85
|
+
static ID ID_LOGOFF;
|
86
|
+
|
87
|
+
static VALUE mExtlib;
|
88
|
+
static VALUE mDO;
|
89
|
+
static VALUE cDO_Quoting;
|
90
|
+
static VALUE cDO_Connection;
|
91
|
+
static VALUE cDO_Command;
|
92
|
+
static VALUE cDO_Result;
|
93
|
+
static VALUE cDO_Reader;
|
94
|
+
|
95
|
+
static VALUE rb_cDate;
|
96
|
+
static VALUE rb_cDateTime;
|
97
|
+
static VALUE rb_cBigDecimal;
|
98
|
+
static VALUE rb_cByteArray;
|
99
|
+
|
100
|
+
static VALUE cOCI8;
|
101
|
+
static VALUE cOCI8_Cursor;
|
102
|
+
static VALUE cOCI8_BLOB;
|
103
|
+
static VALUE cOCI8_CLOB;
|
104
|
+
|
105
|
+
static VALUE mOracle;
|
106
|
+
static VALUE cConnection;
|
107
|
+
static VALUE cCommand;
|
108
|
+
static VALUE cResult;
|
109
|
+
static VALUE cReader;
|
110
|
+
|
111
|
+
static VALUE eArgumentError;
|
112
|
+
static VALUE eSQLError;
|
113
|
+
static VALUE eConnectionError;
|
114
|
+
static VALUE eDataError;
|
115
|
+
|
116
|
+
static void data_objects_debug(VALUE string, struct timeval* start) {
|
117
|
+
struct timeval stop;
|
118
|
+
char *message;
|
119
|
+
|
120
|
+
char *query = RSTRING_PTR(string);
|
121
|
+
int length = RSTRING_LEN(string);
|
122
|
+
char total_time[32];
|
123
|
+
do_int64 duration = 0;
|
124
|
+
|
125
|
+
VALUE logger = rb_funcall(mOracle, ID_LOGGER, 0);
|
126
|
+
int log_level = NUM2INT(rb_funcall(logger, ID_LEVEL, 0));
|
127
|
+
|
128
|
+
if (0 == log_level) {
|
129
|
+
gettimeofday(&stop, NULL);
|
130
|
+
|
131
|
+
duration = (stop.tv_sec - start->tv_sec) * 1000000 + stop.tv_usec - start->tv_usec;
|
132
|
+
|
133
|
+
snprintf(total_time, 32, "%.6f", duration / 1000000.0);
|
134
|
+
message = (char *)calloc(length + strlen(total_time) + 4, sizeof(char));
|
135
|
+
snprintf(message, length + strlen(total_time) + 4, "(%s) %s", total_time, query);
|
136
|
+
rb_funcall(logger, ID_DEBUG, 1, rb_str_new(message, length + strlen(total_time) + 3));
|
137
|
+
free(message);
|
138
|
+
}
|
139
|
+
}
|
140
|
+
|
141
|
+
static char * get_uri_option(VALUE query_hash, char * key) {
|
142
|
+
VALUE query_value;
|
143
|
+
char * value = NULL;
|
144
|
+
|
145
|
+
if(!rb_obj_is_kind_of(query_hash, rb_cHash)) { return NULL; }
|
146
|
+
|
147
|
+
query_value = rb_hash_aref(query_hash, RUBY_STRING(key));
|
148
|
+
|
149
|
+
if (Qnil != query_value) {
|
150
|
+
value = StringValuePtr(query_value);
|
151
|
+
}
|
152
|
+
|
153
|
+
return value;
|
154
|
+
}
|
155
|
+
|
156
|
+
/* ====== Time/Date Parsing Helper Functions ====== */
|
157
|
+
static void reduce( do_int64 *numerator, do_int64 *denominator ) {
|
158
|
+
do_int64 a, b, c;
|
159
|
+
a = *numerator;
|
160
|
+
b = *denominator;
|
161
|
+
while ( a != 0 ) {
|
162
|
+
c = a; a = b % a; b = c;
|
163
|
+
}
|
164
|
+
*numerator = *numerator / b;
|
165
|
+
*denominator = *denominator / b;
|
166
|
+
}
|
167
|
+
|
168
|
+
// Generate the date integer which Date.civil_to_jd returns
|
169
|
+
static int jd_from_date(int year, int month, int day) {
|
170
|
+
int a, b;
|
171
|
+
if ( month <= 2 ) {
|
172
|
+
year -= 1;
|
173
|
+
month += 12;
|
174
|
+
}
|
175
|
+
a = year / 100;
|
176
|
+
b = 2 - a + (a / 4);
|
177
|
+
return floor(365.25 * (year + 4716)) + floor(30.6001 * (month + 1)) + day + b - 1524;
|
178
|
+
}
|
179
|
+
|
180
|
+
// Creates a Rational for use as a Timezone offset to be passed to DateTime.new!
|
181
|
+
static VALUE seconds_to_offset(do_int64 num) {
|
182
|
+
do_int64 den = 86400;
|
183
|
+
reduce(&num, &den);
|
184
|
+
return rb_funcall(rb_mKernel, ID_RATIONAL, 2, rb_ll2inum(num), rb_ll2inum(den));
|
185
|
+
}
|
186
|
+
|
187
|
+
static VALUE timezone_to_offset(int hour_offset, int minute_offset) {
|
188
|
+
do_int64 seconds = 0;
|
189
|
+
|
190
|
+
seconds += hour_offset * 3600;
|
191
|
+
seconds += minute_offset * 60;
|
192
|
+
|
193
|
+
return seconds_to_offset(seconds);
|
194
|
+
}
|
195
|
+
|
196
|
+
// Implementation using C functions
|
197
|
+
|
198
|
+
static VALUE parse_date(VALUE r_value) {
|
199
|
+
VALUE time_array;
|
200
|
+
int year, month, day;
|
201
|
+
int jd, ajd;
|
202
|
+
VALUE rational;
|
203
|
+
|
204
|
+
if (rb_obj_class(r_value) == rb_cTime) {
|
205
|
+
time_array = rb_funcall(r_value, ID_TO_A, 0);
|
206
|
+
year = NUM2INT(rb_ary_entry(time_array, 5));
|
207
|
+
month = NUM2INT(rb_ary_entry(time_array, 4));
|
208
|
+
day = NUM2INT(rb_ary_entry(time_array, 3));
|
209
|
+
|
210
|
+
jd = jd_from_date(year, month, day);
|
211
|
+
|
212
|
+
// Math from Date.jd_to_ajd
|
213
|
+
ajd = jd * 2 - 1;
|
214
|
+
rational = rb_funcall(rb_mKernel, ID_RATIONAL, 2, INT2NUM(ajd), INT2NUM(2));
|
215
|
+
|
216
|
+
return rb_funcall(rb_cDate, ID_NEW_DATE, 3, rational, INT2NUM(0), INT2NUM(2299161));
|
217
|
+
|
218
|
+
} else if (rb_obj_class(r_value) == rb_cDate) {
|
219
|
+
return r_value;
|
220
|
+
|
221
|
+
} else if (rb_obj_class(r_value) == rb_cDateTime) {
|
222
|
+
rational = rb_iv_get(r_value, "@ajd");
|
223
|
+
return rb_funcall(rb_cDate, ID_NEW_DATE, 3, rational, INT2NUM(0), INT2NUM(2299161));
|
224
|
+
|
225
|
+
} else {
|
226
|
+
// Something went terribly wrong
|
227
|
+
rb_raise(eDataError, "Couldn't parse date from class %s object", rb_obj_classname(r_value));
|
228
|
+
}
|
229
|
+
}
|
230
|
+
|
231
|
+
// Implementation using C functions
|
232
|
+
|
233
|
+
static VALUE parse_date_time(VALUE r_value) {
|
234
|
+
VALUE ajd, offset;
|
235
|
+
|
236
|
+
VALUE time_array;
|
237
|
+
int year, month, day, hour, min, sec, hour_offset, minute_offset;
|
238
|
+
// int usec;
|
239
|
+
int jd;
|
240
|
+
do_int64 num, den;
|
241
|
+
|
242
|
+
long int gmt_offset;
|
243
|
+
int is_dst;
|
244
|
+
|
245
|
+
// time_t rawtime;
|
246
|
+
// struct tm * timeinfo;
|
247
|
+
|
248
|
+
// int tokens_read, max_tokens;
|
249
|
+
|
250
|
+
if (rb_obj_class(r_value) == rb_cDateTime) {
|
251
|
+
return r_value;
|
252
|
+
} else if (rb_obj_class(r_value) == rb_cTime) {
|
253
|
+
time_array = rb_funcall(r_value, ID_TO_A, 0);
|
254
|
+
year = NUM2INT(rb_ary_entry(time_array, 5));
|
255
|
+
month = NUM2INT(rb_ary_entry(time_array, 4));
|
256
|
+
day = NUM2INT(rb_ary_entry(time_array, 3));
|
257
|
+
hour = NUM2INT(rb_ary_entry(time_array, 2));
|
258
|
+
min = NUM2INT(rb_ary_entry(time_array, 1));
|
259
|
+
sec = NUM2INT(rb_ary_entry(time_array, 0));
|
260
|
+
|
261
|
+
is_dst = rb_ary_entry(time_array, 8) == Qtrue ? 3600 : 0;
|
262
|
+
gmt_offset = NUM2INT(rb_funcall(r_value, ID_UTC_OFFSET, 0 ));
|
263
|
+
|
264
|
+
if ( is_dst > 0 )
|
265
|
+
gmt_offset -= is_dst;
|
266
|
+
|
267
|
+
hour_offset = -(gmt_offset / 3600);
|
268
|
+
minute_offset = -(gmt_offset % 3600 / 60);
|
269
|
+
|
270
|
+
jd = jd_from_date(year, month, day);
|
271
|
+
|
272
|
+
// Generate ajd with fractional days for the time
|
273
|
+
// Extracted from Date#jd_to_ajd, Date#day_fraction_to_time, and Rational#+ and #-
|
274
|
+
num = (hour * 1440) + (min * 24);
|
275
|
+
|
276
|
+
// Modify the numerator so when we apply the timezone everything works out
|
277
|
+
num -= (hour_offset * 1440) + (minute_offset * 24);
|
278
|
+
|
279
|
+
den = (24 * 1440);
|
280
|
+
reduce(&num, &den);
|
281
|
+
|
282
|
+
num = (num * 86400) + (sec * den);
|
283
|
+
den = den * 86400;
|
284
|
+
reduce(&num, &den);
|
285
|
+
|
286
|
+
num = (jd * den) + num;
|
287
|
+
|
288
|
+
num = num * 2;
|
289
|
+
num = num - den;
|
290
|
+
den = den * 2;
|
291
|
+
|
292
|
+
reduce(&num, &den);
|
293
|
+
|
294
|
+
ajd = rb_funcall(rb_mKernel, ID_RATIONAL, 2, rb_ull2inum(num), rb_ull2inum(den));
|
295
|
+
offset = timezone_to_offset(hour_offset, minute_offset);
|
296
|
+
|
297
|
+
return rb_funcall(rb_cDateTime, ID_NEW_DATE, 3, ajd, offset, INT2NUM(2299161));
|
298
|
+
} else {
|
299
|
+
// Something went terribly wrong
|
300
|
+
rb_raise(eDataError, "Couldn't parse datetime from class %s object", rb_obj_classname(r_value));
|
301
|
+
}
|
302
|
+
|
303
|
+
}
|
304
|
+
|
305
|
+
static VALUE parse_time(VALUE r_value) {
|
306
|
+
if (rb_obj_class(r_value) == rb_cTime) {
|
307
|
+
return r_value;
|
308
|
+
} else {
|
309
|
+
// Something went terribly wrong
|
310
|
+
rb_raise(eDataError, "Couldn't parse time from class %s object", rb_obj_classname(r_value));
|
311
|
+
}
|
312
|
+
}
|
313
|
+
|
314
|
+
static VALUE parse_boolean(VALUE r_value) {
|
315
|
+
if (TYPE(r_value) == T_FIXNUM || TYPE(r_value) == T_BIGNUM) {
|
316
|
+
return NUM2INT(r_value) >= 1 ? Qtrue : Qfalse;
|
317
|
+
} else if (TYPE(r_value) == T_STRING) {
|
318
|
+
char value = NIL_P(r_value) || RSTRING_LEN(r_value) == 0 ? '\0' : *(RSTRING_PTR(r_value));
|
319
|
+
return value == 'Y' || value == 'y' || value == 'T' || value == 't' ? Qtrue : Qfalse;
|
320
|
+
} else {
|
321
|
+
// Something went terribly wrong
|
322
|
+
rb_raise(eDataError, "Couldn't parse boolean from class %s object", rb_obj_classname(r_value));
|
323
|
+
}
|
324
|
+
}
|
325
|
+
|
326
|
+
/* ===== Typecasting Functions ===== */
|
327
|
+
|
328
|
+
static VALUE infer_ruby_type(VALUE type, VALUE precision, VALUE scale) {
|
329
|
+
ID type_id = SYM2ID(type);
|
330
|
+
|
331
|
+
if (type_id == ID_NUMBER)
|
332
|
+
return scale != Qnil && NUM2INT(scale) == 0 ?
|
333
|
+
(NUM2INT(precision) == 1 ? rb_cTrueClass : rb_cInteger) : rb_cBigDecimal;
|
334
|
+
else if (type_id == ID_VARCHAR2 || type_id == ID_CHAR || type_id == ID_CLOB || type_id == ID_LONG)
|
335
|
+
return rb_cString;
|
336
|
+
else if (type_id == ID_DATE)
|
337
|
+
// return rb_cDateTime;
|
338
|
+
// by default map DATE type to Time class as it is much faster than DateTime class
|
339
|
+
return rb_cTime;
|
340
|
+
else if (type_id == ID_TIMESTAMP || type_id == ID_TIMESTAMP_TZ || type_id == ID_TIMESTAMP_LTZ)
|
341
|
+
// return rb_cDateTime;
|
342
|
+
// by default map TIMESTAMP type to Time class as it is much faster than DateTime class
|
343
|
+
return rb_cTime;
|
344
|
+
else if (type_id == ID_BLOB || type_id == ID_RAW || type_id == ID_LONG_RAW || type_id == ID_BFILE)
|
345
|
+
return rb_cByteArray;
|
346
|
+
else if (type_id == ID_BINARY_FLOAT || type_id == ID_BINARY_DOUBLE)
|
347
|
+
return rb_cFloat;
|
348
|
+
else
|
349
|
+
return rb_cString;
|
350
|
+
}
|
351
|
+
|
352
|
+
static VALUE typecast(VALUE r_value, const VALUE type) {
|
353
|
+
VALUE r_data;
|
354
|
+
|
355
|
+
if (type == rb_cInteger) {
|
356
|
+
return TYPE(r_value) == T_FIXNUM || TYPE(r_value) == T_BIGNUM ? r_value : rb_funcall(r_value, ID_TO_I, 0);
|
357
|
+
|
358
|
+
} else if (type == rb_cString) {
|
359
|
+
if (TYPE(r_value) == T_STRING)
|
360
|
+
return r_value;
|
361
|
+
else if (rb_obj_class(r_value) == cOCI8_CLOB)
|
362
|
+
return rb_funcall(r_value, ID_READ, 0);
|
363
|
+
else
|
364
|
+
return rb_funcall(r_value, ID_TO_S, 0);
|
365
|
+
|
366
|
+
} else if (type == rb_cFloat) {
|
367
|
+
return TYPE(r_value) == T_FLOAT ? r_value : rb_funcall(r_value, ID_TO_F, 0);
|
368
|
+
|
369
|
+
} else if (type == rb_cBigDecimal) {
|
370
|
+
VALUE r_string = TYPE(r_value) == T_STRING ? r_value : rb_funcall(r_value, ID_TO_S, 0);
|
371
|
+
return rb_funcall(rb_cBigDecimal, ID_NEW, 1, r_string);
|
372
|
+
|
373
|
+
} else if (type == rb_cDate) {
|
374
|
+
return parse_date(r_value);
|
375
|
+
|
376
|
+
} else if (type == rb_cDateTime) {
|
377
|
+
return parse_date_time(r_value);
|
378
|
+
|
379
|
+
} else if (type == rb_cTime) {
|
380
|
+
return parse_time(r_value);
|
381
|
+
|
382
|
+
} else if (type == rb_cTrueClass) {
|
383
|
+
return parse_boolean(r_value);
|
384
|
+
|
385
|
+
} else if (type == rb_cByteArray) {
|
386
|
+
if (rb_obj_class(r_value) == cOCI8_BLOB)
|
387
|
+
r_data = rb_funcall(r_value, ID_READ, 0);
|
388
|
+
else
|
389
|
+
r_data = r_value;
|
390
|
+
return rb_funcall(rb_cByteArray, ID_NEW, 1, r_data);
|
391
|
+
|
392
|
+
} else if (type == rb_cClass) {
|
393
|
+
return rb_funcall(mDO, ID_FULL_CONST_GET, 1, r_value);
|
394
|
+
|
395
|
+
// TODO: where are tests for this mapping?
|
396
|
+
} else if (type == rb_cObject) {
|
397
|
+
if (rb_obj_class(r_value) == cOCI8_CLOB)
|
398
|
+
return rb_marshal_load(rb_funcall(r_value, ID_READ, 0));
|
399
|
+
else
|
400
|
+
return rb_marshal_load(r_value);
|
401
|
+
|
402
|
+
} else if (type == rb_cNilClass) {
|
403
|
+
return Qnil;
|
404
|
+
|
405
|
+
} else {
|
406
|
+
if (rb_obj_class(r_value) == cOCI8_CLOB)
|
407
|
+
return rb_funcall(r_value, ID_READ, 0);
|
408
|
+
else
|
409
|
+
return r_value;
|
410
|
+
}
|
411
|
+
|
412
|
+
}
|
413
|
+
|
414
|
+
static VALUE typecast_bind_value(VALUE oci8_conn, VALUE r_value) {
|
415
|
+
VALUE r_class = rb_obj_class(r_value);
|
416
|
+
// replace nil value with '' as otherwise OCI8 cannot get bind variable type
|
417
|
+
// '' will be inserted as NULL by Oracle
|
418
|
+
if (NIL_P(r_value))
|
419
|
+
return RUBY_STRING("");
|
420
|
+
else if (r_class == rb_cString)
|
421
|
+
// if string is longer than 4000 characters then convert to CLOB
|
422
|
+
return RSTRING_LEN(r_value) <= 4000 ? r_value : rb_funcall(cOCI8_CLOB, ID_NEW, 2, oci8_conn, r_value);
|
423
|
+
else if (r_class == rb_cBigDecimal)
|
424
|
+
return rb_funcall(r_value, ID_TO_S, 1, RUBY_STRING("F"));
|
425
|
+
else if (r_class == rb_cTrueClass)
|
426
|
+
return INT2NUM(1);
|
427
|
+
else if (r_class == rb_cFalseClass)
|
428
|
+
return INT2NUM(0);
|
429
|
+
else if (r_class == rb_cByteArray)
|
430
|
+
return rb_funcall(cOCI8_BLOB, ID_NEW, 2, oci8_conn, r_value);
|
431
|
+
else if (r_class == rb_cClass)
|
432
|
+
return rb_funcall(r_value, ID_TO_S, 0);
|
433
|
+
else
|
434
|
+
return r_value;
|
435
|
+
}
|
436
|
+
|
437
|
+
/* ====== Public API ======= */
|
438
|
+
static VALUE cConnection_dispose(VALUE self) {
|
439
|
+
VALUE oci8_conn = rb_iv_get(self, "@connection");
|
440
|
+
|
441
|
+
if (Qnil == oci8_conn)
|
442
|
+
return Qfalse;
|
443
|
+
|
444
|
+
rb_funcall(oci8_conn, ID_LOGOFF, 0);
|
445
|
+
|
446
|
+
rb_iv_set(self, "@connection", Qnil);
|
447
|
+
|
448
|
+
return Qtrue;
|
449
|
+
}
|
450
|
+
|
451
|
+
static VALUE cCommand_set_types(int argc, VALUE *argv, VALUE self) {
|
452
|
+
VALUE type_strings = rb_ary_new();
|
453
|
+
VALUE array = rb_ary_new();
|
454
|
+
|
455
|
+
int i, j;
|
456
|
+
|
457
|
+
for ( i = 0; i < argc; i++) {
|
458
|
+
rb_ary_push(array, argv[i]);
|
459
|
+
}
|
460
|
+
|
461
|
+
for (i = 0; i < RARRAY_LEN(array); i++) {
|
462
|
+
VALUE entry = rb_ary_entry(array, i);
|
463
|
+
if(TYPE(entry) == T_CLASS) {
|
464
|
+
rb_ary_push(type_strings, entry);
|
465
|
+
} else if (TYPE(entry) == T_ARRAY) {
|
466
|
+
for (j = 0; j < RARRAY_LEN(entry); j++) {
|
467
|
+
VALUE sub_entry = rb_ary_entry(entry, j);
|
468
|
+
if(TYPE(sub_entry) == T_CLASS) {
|
469
|
+
rb_ary_push(type_strings, sub_entry);
|
470
|
+
} else {
|
471
|
+
rb_raise(eArgumentError, "Invalid type given");
|
472
|
+
}
|
473
|
+
}
|
474
|
+
} else {
|
475
|
+
rb_raise(eArgumentError, "Invalid type given");
|
476
|
+
}
|
477
|
+
}
|
478
|
+
|
479
|
+
rb_iv_set(self, "@field_types", type_strings);
|
480
|
+
|
481
|
+
return array;
|
482
|
+
}
|
483
|
+
|
484
|
+
typedef struct {
|
485
|
+
VALUE self;
|
486
|
+
VALUE oci8_conn;
|
487
|
+
VALUE cursor;
|
488
|
+
VALUE statement_type;
|
489
|
+
VALUE args;
|
490
|
+
VALUE sql;
|
491
|
+
struct timeval start;
|
492
|
+
} cCommand_execute_try_t;
|
493
|
+
|
494
|
+
static VALUE cCommand_execute_try(cCommand_execute_try_t *arg);
|
495
|
+
static VALUE cCommand_execute_ensure(cCommand_execute_try_t *arg);
|
496
|
+
|
497
|
+
// called by Command#execute that is written in Ruby
|
498
|
+
static VALUE cCommand_execute_internal(VALUE self, VALUE oci8_conn, VALUE sql, VALUE args) {
|
499
|
+
cCommand_execute_try_t arg;
|
500
|
+
arg.self = self;
|
501
|
+
arg.oci8_conn = oci8_conn;
|
502
|
+
arg.sql = sql;
|
503
|
+
// store start time before SQL parsing
|
504
|
+
gettimeofday(&arg.start, NULL);
|
505
|
+
arg.cursor = rb_funcall(oci8_conn, ID_PARSE, 1, sql);
|
506
|
+
arg.statement_type = rb_funcall(arg.cursor, ID_TYPE, 0);
|
507
|
+
arg.args = args;
|
508
|
+
|
509
|
+
return rb_ensure(cCommand_execute_try, (VALUE)&arg, cCommand_execute_ensure, (VALUE)&arg);
|
510
|
+
}
|
511
|
+
|
512
|
+
// wrapper for simple SQL calls without arguments
|
513
|
+
static VALUE execute_sql(VALUE oci8_conn, VALUE sql) {
|
514
|
+
return cCommand_execute_internal(Qnil, oci8_conn, sql, Qnil);
|
515
|
+
}
|
516
|
+
|
517
|
+
static VALUE cCommand_execute_try(cCommand_execute_try_t *arg) {
|
518
|
+
VALUE result = Qnil;
|
519
|
+
int insert_id_present;
|
520
|
+
|
521
|
+
// no arguments given
|
522
|
+
if NIL_P(arg->args) {
|
523
|
+
result = rb_funcall(arg->cursor, ID_EXEC, 0);
|
524
|
+
// arguments given - need to typecast
|
525
|
+
} else {
|
526
|
+
insert_id_present = (!NIL_P(arg->self) && rb_iv_get(arg->self, "@insert_id_present") == Qtrue);
|
527
|
+
|
528
|
+
if (insert_id_present)
|
529
|
+
rb_funcall(arg->cursor, ID_BIND_PARAM, 2, RUBY_STRING(":insert_id"), INT2NUM(0));
|
530
|
+
|
531
|
+
int i;
|
532
|
+
VALUE r_orig_value, r_new_value;
|
533
|
+
for (i = 0; i < RARRAY_LEN(arg->args); i++) {
|
534
|
+
r_orig_value = rb_ary_entry(arg->args, i);
|
535
|
+
r_new_value = typecast_bind_value(arg->oci8_conn, r_orig_value);
|
536
|
+
if (r_orig_value != r_new_value)
|
537
|
+
rb_ary_store(arg->args, i, r_new_value);
|
538
|
+
}
|
539
|
+
|
540
|
+
result = rb_apply(arg->cursor, ID_EXEC, arg->args);
|
541
|
+
|
542
|
+
if (insert_id_present) {
|
543
|
+
VALUE insert_id = rb_funcall(arg->cursor, ID_ELEM, 1, RUBY_STRING(":insert_id"));
|
544
|
+
rb_iv_set(arg->self, "@insert_id", insert_id);
|
545
|
+
}
|
546
|
+
}
|
547
|
+
|
548
|
+
if (SYM2ID(arg->statement_type) == ID_SELECT_STMT)
|
549
|
+
return arg->cursor;
|
550
|
+
else {
|
551
|
+
return result;
|
552
|
+
}
|
553
|
+
|
554
|
+
}
|
555
|
+
|
556
|
+
static VALUE cCommand_execute_ensure(cCommand_execute_try_t *arg) {
|
557
|
+
if (SYM2ID(arg->statement_type) != ID_SELECT_STMT)
|
558
|
+
rb_funcall(arg->cursor, ID_CLOSE, 0);
|
559
|
+
// Log SQL and execution time
|
560
|
+
data_objects_debug(arg->sql, &(arg->start));
|
561
|
+
return Qnil;
|
562
|
+
}
|
563
|
+
|
564
|
+
static VALUE cConnection_initialize(VALUE self, VALUE uri) {
|
565
|
+
VALUE r_host, r_port, r_path, r_user, r_password;
|
566
|
+
VALUE r_query, r_time_zone;
|
567
|
+
char *non_blocking = NULL;
|
568
|
+
char *time_zone = NULL;
|
569
|
+
char set_time_zone_command[80];
|
570
|
+
|
571
|
+
char *host = "localhost", *port = "1521", *path = NULL;
|
572
|
+
char *connect_string;
|
573
|
+
int connect_string_length;
|
574
|
+
VALUE oci8_conn;
|
575
|
+
|
576
|
+
r_user = rb_funcall(uri, rb_intern("user"), 0);
|
577
|
+
r_password = rb_funcall(uri, rb_intern("password"), 0);
|
578
|
+
|
579
|
+
r_host = rb_funcall(uri, rb_intern("host"), 0);
|
580
|
+
if ( Qnil != r_host && RSTRING_LEN(r_host) > 0) {
|
581
|
+
host = StringValuePtr(r_host);
|
582
|
+
}
|
583
|
+
|
584
|
+
r_port = rb_funcall(uri, rb_intern("port"), 0);
|
585
|
+
if ( Qnil != r_port ) {
|
586
|
+
r_port = rb_funcall(r_port, ID_TO_S, 0);
|
587
|
+
port = StringValuePtr(r_port);
|
588
|
+
}
|
589
|
+
|
590
|
+
r_path = rb_funcall(uri, rb_intern("path"), 0);
|
591
|
+
path = StringValuePtr(r_path);
|
592
|
+
|
593
|
+
// If just host name is specified then use it as TNS names alias
|
594
|
+
if ((r_host != Qnil && RSTRING_LEN(r_host) > 0) &&
|
595
|
+
(r_port == Qnil) &&
|
596
|
+
(r_path == Qnil || RSTRING_LEN(r_path) == 0)) {
|
597
|
+
connect_string = host;
|
598
|
+
// If database name is specified in path (in format "/database")
|
599
|
+
} else if (strlen(path) > 1) {
|
600
|
+
connect_string_length = strlen(host) + strlen(port) + strlen(path) + 4;
|
601
|
+
connect_string = (char *)calloc(connect_string_length, sizeof(char));
|
602
|
+
snprintf(connect_string, connect_string_length, "//%s:%s%s", host, port, path);
|
603
|
+
} else {
|
604
|
+
rb_raise(eConnectionError, "Database must be specified");
|
605
|
+
}
|
606
|
+
|
607
|
+
// oci8_conn = rb_funcall(cOCI8, ID_NEW, 3, r_user, r_password, RUBY_STRING(connect_string));
|
608
|
+
oci8_conn = rb_funcall(cConnection, rb_intern("oci8_new"), 3, r_user, r_password, RUBY_STRING(connect_string));
|
609
|
+
|
610
|
+
// Pull the querystring off the URI
|
611
|
+
r_query = rb_funcall(uri, rb_intern("query"), 0);
|
612
|
+
|
613
|
+
non_blocking = get_uri_option(r_query, "non_blocking");
|
614
|
+
// Enable non-blocking mode
|
615
|
+
if (non_blocking != NULL && strcmp(non_blocking, "true") == 0)
|
616
|
+
rb_funcall(oci8_conn, rb_intern("non_blocking="), 1, Qtrue);
|
617
|
+
// By default enable auto-commit mode
|
618
|
+
rb_funcall(oci8_conn, rb_intern("autocommit="), 1, Qtrue);
|
619
|
+
// Set prefetch rows to 100 to increase fetching performance SELECTs with many rows
|
620
|
+
rb_funcall(oci8_conn, rb_intern("prefetch_rows="), 1, INT2NUM(100));
|
621
|
+
|
622
|
+
// Set session time zone
|
623
|
+
// at first look for option in connection string
|
624
|
+
time_zone = get_uri_option(r_query, "time_zone");
|
625
|
+
// if no option specified then look in ENV['TZ']
|
626
|
+
if (time_zone == NULL) {
|
627
|
+
r_time_zone = rb_funcall(cConnection, rb_intern("ruby_time_zone"), 0);
|
628
|
+
if (!NIL_P(r_time_zone))
|
629
|
+
time_zone = StringValuePtr(r_time_zone);
|
630
|
+
}
|
631
|
+
if (time_zone) {
|
632
|
+
snprintf(set_time_zone_command, 80, "alter session set time_zone = '%s'", time_zone);
|
633
|
+
execute_sql(oci8_conn, RUBY_STRING(set_time_zone_command));
|
634
|
+
}
|
635
|
+
|
636
|
+
execute_sql(oci8_conn, RUBY_STRING("alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'"));
|
637
|
+
execute_sql(oci8_conn, RUBY_STRING("alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS.FF'"));
|
638
|
+
execute_sql(oci8_conn, RUBY_STRING("alter session set nls_timestamp_tz_format = 'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM'"));
|
639
|
+
|
640
|
+
rb_iv_set(self, "@uri", uri);
|
641
|
+
rb_iv_set(self, "@connection", oci8_conn);
|
642
|
+
|
643
|
+
return Qtrue;
|
644
|
+
}
|
645
|
+
|
646
|
+
static VALUE cCommand_execute_non_query(int argc, VALUE *argv[], VALUE self) {
|
647
|
+
VALUE affected_rows = rb_funcall2(self, ID_EXECUTE, argc, (VALUE *)argv);
|
648
|
+
if (affected_rows == Qtrue)
|
649
|
+
affected_rows = INT2NUM(0);
|
650
|
+
|
651
|
+
VALUE insert_id = rb_iv_get(self, "@insert_id");
|
652
|
+
|
653
|
+
return rb_funcall(cResult, ID_NEW, 3, self, affected_rows, insert_id);
|
654
|
+
}
|
655
|
+
|
656
|
+
static VALUE cCommand_execute_reader(int argc, VALUE *argv[], VALUE self) {
|
657
|
+
VALUE reader, query;
|
658
|
+
VALUE field_names, field_types;
|
659
|
+
VALUE column_metadata, column, column_name;
|
660
|
+
|
661
|
+
int i;
|
662
|
+
int field_count;
|
663
|
+
int infer_types = 0;
|
664
|
+
|
665
|
+
VALUE cursor = rb_funcall2(self, ID_EXECUTE, argc, (VALUE *)argv);
|
666
|
+
|
667
|
+
if (rb_obj_class(cursor) != cOCI8_Cursor) {
|
668
|
+
rb_raise(eArgumentError, "\"%s\" is invalid SELECT query", StringValuePtr(query));
|
669
|
+
}
|
670
|
+
|
671
|
+
column_metadata = rb_funcall(cursor, ID_COLUMN_METADATA, 0);
|
672
|
+
field_count = RARRAY_LEN(column_metadata);
|
673
|
+
// reduce field_count by 1 if RAW_RNUM_ is present as last column
|
674
|
+
// (generated by DataMapper to simulate LIMIT and OFFSET)
|
675
|
+
column = rb_ary_entry(column_metadata, field_count-1);
|
676
|
+
column_name = rb_funcall(column, ID_NAME, 0);
|
677
|
+
if (strncmp(RSTRING_PTR(column_name), "RAW_RNUM_", RSTRING_LEN(column_name)) == 0)
|
678
|
+
field_count--;
|
679
|
+
|
680
|
+
reader = rb_funcall(cReader, ID_NEW, 0);
|
681
|
+
rb_iv_set(reader, "@reader", cursor);
|
682
|
+
rb_iv_set(reader, "@field_count", INT2NUM(field_count));
|
683
|
+
|
684
|
+
field_names = rb_ary_new();
|
685
|
+
field_types = rb_iv_get(self, "@field_types");
|
686
|
+
|
687
|
+
if ( field_types == Qnil || 0 == RARRAY_LEN(field_types) ) {
|
688
|
+
field_types = rb_ary_new();
|
689
|
+
infer_types = 1;
|
690
|
+
} else if (RARRAY_LEN(field_types) != field_count) {
|
691
|
+
// Whoops... wrong number of types passed to set_types. Close the reader and raise
|
692
|
+
// and error
|
693
|
+
rb_funcall(reader, ID_CLOSE, 0);
|
694
|
+
rb_raise(eArgumentError, "Field-count mismatch. Expected %ld fields, but the query yielded %d", RARRAY_LEN(field_types), field_count);
|
695
|
+
}
|
696
|
+
|
697
|
+
for ( i = 0; i < field_count; i++ ) {
|
698
|
+
column = rb_ary_entry(column_metadata, i);
|
699
|
+
column_name = rb_funcall(column, ID_NAME, 0);
|
700
|
+
rb_ary_push(field_names, column_name);
|
701
|
+
if ( infer_types == 1 ) {
|
702
|
+
rb_ary_push(field_types,
|
703
|
+
infer_ruby_type(rb_iv_get(column, "@data_type"),
|
704
|
+
rb_funcall(column, ID_PRECISION, 0),
|
705
|
+
rb_funcall(column, ID_SCALE, 0))
|
706
|
+
);
|
707
|
+
}
|
708
|
+
}
|
709
|
+
|
710
|
+
rb_iv_set(reader, "@position", INT2NUM(0));
|
711
|
+
rb_iv_set(reader, "@fields", field_names);
|
712
|
+
rb_iv_set(reader, "@field_types", field_types);
|
713
|
+
|
714
|
+
rb_iv_set(reader, "@last_row", Qfalse);
|
715
|
+
|
716
|
+
return reader;
|
717
|
+
}
|
718
|
+
|
719
|
+
static VALUE cReader_close(VALUE self) {
|
720
|
+
VALUE cursor = rb_iv_get(self, "@reader");
|
721
|
+
|
722
|
+
if (Qnil == cursor)
|
723
|
+
return Qfalse;
|
724
|
+
|
725
|
+
rb_funcall(cursor, ID_CLOSE, 0);
|
726
|
+
|
727
|
+
rb_iv_set(self, "@reader", Qnil);
|
728
|
+
return Qtrue;
|
729
|
+
}
|
730
|
+
|
731
|
+
static VALUE cReader_next(VALUE self) {
|
732
|
+
VALUE cursor = rb_iv_get(self, "@reader");
|
733
|
+
|
734
|
+
int field_count;
|
735
|
+
int i;
|
736
|
+
|
737
|
+
if (Qnil == cursor || Qtrue == rb_iv_get(self, "@last_row"))
|
738
|
+
return Qfalse;
|
739
|
+
|
740
|
+
VALUE row = rb_ary_new();
|
741
|
+
VALUE field_types, field_type;
|
742
|
+
VALUE value;
|
743
|
+
|
744
|
+
VALUE fetch_result = rb_funcall(cursor, ID_FETCH, 0);
|
745
|
+
|
746
|
+
if (Qnil == fetch_result) {
|
747
|
+
rb_iv_set(self, "@values", Qnil);
|
748
|
+
rb_iv_set(self, "@last_row", Qtrue);
|
749
|
+
return Qfalse;
|
750
|
+
}
|
751
|
+
|
752
|
+
field_count = NUM2INT(rb_iv_get(self, "@field_count"));
|
753
|
+
field_types = rb_iv_get(self, "@field_types");
|
754
|
+
|
755
|
+
for ( i = 0; i < field_count; i++ ) {
|
756
|
+
field_type = rb_ary_entry(field_types, i);
|
757
|
+
value = rb_ary_entry(fetch_result, i);
|
758
|
+
// Always return nil if the value returned from Oracle is null
|
759
|
+
if (Qnil != value) {
|
760
|
+
value = typecast(value, field_type);
|
761
|
+
}
|
762
|
+
|
763
|
+
rb_ary_push(row, value);
|
764
|
+
}
|
765
|
+
|
766
|
+
rb_iv_set(self, "@values", row);
|
767
|
+
return Qtrue;
|
768
|
+
}
|
769
|
+
|
770
|
+
static VALUE cReader_values(VALUE self) {
|
771
|
+
|
772
|
+
VALUE values = rb_iv_get(self, "@values");
|
773
|
+
if(values == Qnil) {
|
774
|
+
rb_raise(eDataError, "Reader not initialized");
|
775
|
+
return Qnil;
|
776
|
+
} else {
|
777
|
+
return values;
|
778
|
+
}
|
779
|
+
}
|
780
|
+
|
781
|
+
static VALUE cReader_fields(VALUE self) {
|
782
|
+
return rb_iv_get(self, "@fields");
|
783
|
+
}
|
784
|
+
|
785
|
+
static VALUE cReader_field_count(VALUE self) {
|
786
|
+
return rb_iv_get(self, "@field_count");
|
787
|
+
}
|
788
|
+
|
789
|
+
|
790
|
+
void Init_do_oracle() {
|
791
|
+
// rb_require("oci8");
|
792
|
+
rb_require("date");
|
793
|
+
rb_require("bigdecimal");
|
794
|
+
|
795
|
+
// Get references classes needed for Date/Time parsing
|
796
|
+
rb_cDate = CONST_GET(rb_mKernel, "Date");
|
797
|
+
rb_cDateTime = CONST_GET(rb_mKernel, "DateTime");
|
798
|
+
rb_cBigDecimal = CONST_GET(rb_mKernel, "BigDecimal");
|
799
|
+
|
800
|
+
rb_funcall(rb_mKernel, rb_intern("require"), 1, rb_str_new2("data_objects"));
|
801
|
+
|
802
|
+
ID_NEW = rb_intern("new");
|
803
|
+
#ifdef RUBY_LESS_THAN_186
|
804
|
+
ID_NEW_DATE = rb_intern("new0");
|
805
|
+
#else
|
806
|
+
ID_NEW_DATE = rb_intern("new!");
|
807
|
+
#endif
|
808
|
+
ID_LOGGER = rb_intern("logger");
|
809
|
+
ID_DEBUG = rb_intern("debug");
|
810
|
+
ID_LEVEL = rb_intern("level");
|
811
|
+
ID_TO_S = rb_intern("to_s");
|
812
|
+
ID_RATIONAL = rb_intern("Rational");
|
813
|
+
|
814
|
+
ID_NAME = rb_intern("name");
|
815
|
+
|
816
|
+
ID_NUMBER = rb_intern("number");
|
817
|
+
ID_VARCHAR2 = rb_intern("varchar2");
|
818
|
+
ID_CHAR = rb_intern("char");
|
819
|
+
ID_DATE = rb_intern("date");
|
820
|
+
ID_TIMESTAMP = rb_intern("timestamp");
|
821
|
+
ID_TIMESTAMP_TZ = rb_intern("timestamp_tz");
|
822
|
+
ID_TIMESTAMP_LTZ = rb_intern("timestamp_ltz");
|
823
|
+
ID_CLOB = rb_intern("clob");
|
824
|
+
ID_BLOB = rb_intern("blob");
|
825
|
+
ID_LONG = rb_intern("long");
|
826
|
+
ID_RAW = rb_intern("raw");
|
827
|
+
ID_LONG_RAW = rb_intern("long_raw");
|
828
|
+
ID_BFILE = rb_intern("bfile");
|
829
|
+
ID_BINARY_FLOAT = rb_intern("binary_float");
|
830
|
+
ID_BINARY_DOUBLE = rb_intern("binary_double");
|
831
|
+
|
832
|
+
ID_TO_A = rb_intern("to_a");
|
833
|
+
ID_TO_I = rb_intern("to_i");
|
834
|
+
ID_TO_S = rb_intern("to_s");
|
835
|
+
ID_TO_F = rb_intern("to_f");
|
836
|
+
|
837
|
+
ID_UTC_OFFSET = rb_intern("utc_offset");
|
838
|
+
ID_FULL_CONST_GET = rb_intern("full_const_get");
|
839
|
+
|
840
|
+
ID_PARSE = rb_intern("parse");
|
841
|
+
ID_FETCH = rb_intern("fetch");
|
842
|
+
ID_TYPE = rb_intern("type");
|
843
|
+
ID_EXECUTE = rb_intern("execute");
|
844
|
+
ID_EXEC = rb_intern("exec");
|
845
|
+
|
846
|
+
ID_SELECT_STMT = rb_intern("select_stmt");
|
847
|
+
ID_COLUMN_METADATA = rb_intern("column_metadata");
|
848
|
+
ID_PRECISION = rb_intern("precision");
|
849
|
+
ID_SCALE = rb_intern("scale");
|
850
|
+
ID_BIND_PARAM = rb_intern("bind_param");
|
851
|
+
ID_ELEM = rb_intern("[]");
|
852
|
+
ID_READ = rb_intern("read");
|
853
|
+
|
854
|
+
ID_CLOSE = rb_intern("close");
|
855
|
+
ID_LOGOFF = rb_intern("logoff");
|
856
|
+
|
857
|
+
// Get references to the Extlib module
|
858
|
+
mExtlib = CONST_GET(rb_mKernel, "Extlib");
|
859
|
+
rb_cByteArray = CONST_GET(mExtlib, "ByteArray");
|
860
|
+
|
861
|
+
// Get reference to OCI8 class
|
862
|
+
cOCI8 = CONST_GET(rb_mKernel, "OCI8");
|
863
|
+
cOCI8_Cursor = CONST_GET(cOCI8, "Cursor");
|
864
|
+
cOCI8_BLOB = CONST_GET(cOCI8, "BLOB");
|
865
|
+
cOCI8_CLOB = CONST_GET(cOCI8, "CLOB");
|
866
|
+
|
867
|
+
// Get references to the DataObjects module and its classes
|
868
|
+
mDO = CONST_GET(rb_mKernel, "DataObjects");
|
869
|
+
cDO_Quoting = CONST_GET(mDO, "Quoting");
|
870
|
+
cDO_Connection = CONST_GET(mDO, "Connection");
|
871
|
+
cDO_Command = CONST_GET(mDO, "Command");
|
872
|
+
cDO_Result = CONST_GET(mDO, "Result");
|
873
|
+
cDO_Reader = CONST_GET(mDO, "Reader");
|
874
|
+
|
875
|
+
// Top Level Module that all the classes live under
|
876
|
+
mOracle = rb_define_module_under(mDO, "Oracle");
|
877
|
+
|
878
|
+
eArgumentError = CONST_GET(rb_mKernel, "ArgumentError");
|
879
|
+
eSQLError = CONST_GET(mDO, "SQLError");
|
880
|
+
eConnectionError = CONST_GET(mDO, "ConnectionError");
|
881
|
+
eDataError = CONST_GET(mDO, "DataError");
|
882
|
+
// eOracleError = rb_define_class("OracleError", rb_eStandardError);
|
883
|
+
|
884
|
+
cConnection = ORACLE_CLASS("Connection", cDO_Connection);
|
885
|
+
rb_define_method(cConnection, "initialize", cConnection_initialize, 1);
|
886
|
+
rb_define_method(cConnection, "dispose", cConnection_dispose, 0);
|
887
|
+
|
888
|
+
cCommand = ORACLE_CLASS("Command", cDO_Command);
|
889
|
+
rb_define_method(cCommand, "set_types", cCommand_set_types, -1);
|
890
|
+
rb_define_method(cCommand, "execute_non_query", cCommand_execute_non_query, -1);
|
891
|
+
rb_define_method(cCommand, "execute_reader", cCommand_execute_reader, -1);
|
892
|
+
rb_define_method(cCommand, "execute_internal", cCommand_execute_internal, 3);
|
893
|
+
|
894
|
+
cResult = ORACLE_CLASS("Result", cDO_Result);
|
895
|
+
|
896
|
+
cReader = ORACLE_CLASS("Reader", cDO_Reader);
|
897
|
+
rb_define_method(cReader, "close", cReader_close, 0);
|
898
|
+
rb_define_method(cReader, "next!", cReader_next, 0);
|
899
|
+
rb_define_method(cReader, "values", cReader_values, 0);
|
900
|
+
rb_define_method(cReader, "fields", cReader_fields, 0);
|
901
|
+
rb_define_method(cReader, "field_count", cReader_field_count, 0);
|
902
|
+
|
903
|
+
}
|