simpleOracleJDBC 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +175 -1
- data/lib/simple_oracle_jdbc.rb +8 -0
- data/lib/simple_oracle_jdbc/bindings.rb +26 -42
- data/lib/simple_oracle_jdbc/db_call.rb +2 -0
- data/lib/simple_oracle_jdbc/ora_array.rb +133 -0
- data/lib/simple_oracle_jdbc/ora_record.rb +139 -0
- data/lib/simple_oracle_jdbc/sql.rb +9 -8
- data/lib/simple_oracle_jdbc/type_map.rb +99 -0
- data/test/README +165 -0
- data/test/binding_test.rb +21 -2
- data/test/helper.rb +3 -2
- data/test/ora_array_test.rb +257 -0
- data/test/ora_record_test.rb +55 -0
- data/test/sql_test.rb +4 -6
- metadata +63 -59
@@ -0,0 +1,139 @@
|
|
1
|
+
module SimpleOracleJDBC
|
2
|
+
|
3
|
+
class OraRecord
|
4
|
+
include TypeMap
|
5
|
+
|
6
|
+
attr_reader :ora_type
|
7
|
+
|
8
|
+
# This must be initialized with the name of the Oracle record type as defined
|
9
|
+
# on the database, ie something like:
|
10
|
+
#
|
11
|
+
# create or replace type t_record as object (
|
12
|
+
# p_varchar varchar2(10),
|
13
|
+
# p_integer integer
|
14
|
+
# );
|
15
|
+
#
|
16
|
+
# Values must be an array of Ruby objects, or nil. The values in the array
|
17
|
+
# will be cast into the appropriate Oracle type depending on the definition
|
18
|
+
# of the record defined in Oracle.
|
19
|
+
def initialize(ora_type, values)
|
20
|
+
@ora_type = ora_type.upcase
|
21
|
+
self.values = values
|
22
|
+
@descriptor = nil
|
23
|
+
@type_attributes = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
# Values must be a Ruby array of objects or nil.
|
27
|
+
#
|
28
|
+
# While the values can be set in upon object initialization, this method
|
29
|
+
# allows them to be changed. The one advantage is that it allows the
|
30
|
+
# object descriptor to be reused across many database calls. As this must
|
31
|
+
# be queried from the database, it requires 1 database round trip for each new object,
|
32
|
+
# but is cached inside the object once it is initialized.
|
33
|
+
def values=(value_array)
|
34
|
+
if value_array and !value_array.is_a? Array
|
35
|
+
raise "The values must be a Ruby array, not #{value_array.class}"
|
36
|
+
end
|
37
|
+
@values = value_array || Array.new
|
38
|
+
end
|
39
|
+
|
40
|
+
# Given a database connection, a prepared statement and a bind index,
|
41
|
+
# this method will bind the array of values (set at object initialization time
|
42
|
+
# or by the values= method) to the statement.
|
43
|
+
def bind_to_call(conn, stmt, index)
|
44
|
+
set_descriptor(conn)
|
45
|
+
|
46
|
+
# Avoid binding an empty record for OUT parameters
|
47
|
+
if @values.length == 0
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
ora_array = convert_to_oracle_struct(conn, @values)
|
52
|
+
stmt.set_object(index, ora_array)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Given a ruby array, convert it into the Oracle STRUCT required
|
56
|
+
# when binding it to a procedure call
|
57
|
+
def convert_to_oracle_struct(conn, input)
|
58
|
+
set_descriptor(conn)
|
59
|
+
|
60
|
+
if @type_attributes.length != input.length
|
61
|
+
raise "Not enough values (#{input.length}) for Oracle record (#{@type_attributes.length})"
|
62
|
+
end
|
63
|
+
|
64
|
+
temp_array = Array.new
|
65
|
+
@type_attributes.each_with_index do |t, i|
|
66
|
+
if t == 'VARCHAR' or t == 'CHAR'
|
67
|
+
temp_array.push input[i]
|
68
|
+
elsif t == 'RAW'
|
69
|
+
temp_array.push ruby_raw_string_as_jdbc_raw(input[i])
|
70
|
+
elsif t == 'NUMBER' or t == 'INTEGER'
|
71
|
+
temp_array.push ruby_number_as_jdbc_number(input[i])
|
72
|
+
elsif t == 'DATE' or t == 'TIMESTAMP'
|
73
|
+
temp_array.push ruby_any_date_as_jdbc_date(input[i])
|
74
|
+
else
|
75
|
+
raise "#{base_type}: Unimplemented Record Type"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
ora_array = STRUCT.new(@descriptor, conn, temp_array.to_java)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Given a database connection, a prepared statement and a bind index,
|
82
|
+
# register the bind at that index as an out or inout parameter.
|
83
|
+
def register_as_out_parameter(conn, stmt, index)
|
84
|
+
set_descriptor(conn)
|
85
|
+
stmt.register_out_parameter(index, OracleTypes::STRUCT, @ora_type)
|
86
|
+
end
|
87
|
+
|
88
|
+
# After executing a statement, retrieve the resultant array from Oracle
|
89
|
+
# returning a Ruby array of Ruby objects.
|
90
|
+
def retrieve_out_value(conn, stmt, index)
|
91
|
+
set_descriptor(conn)
|
92
|
+
convert_struct_to_ruby(conn, stmt.get_struct(index))
|
93
|
+
end
|
94
|
+
|
95
|
+
def convert_struct_to_ruby(conn, struct)
|
96
|
+
set_descriptor(conn)
|
97
|
+
final_array = Array.new
|
98
|
+
|
99
|
+
ora_array = struct.get_attributes.to_a
|
100
|
+
ora_array.each_with_index do |v,i|
|
101
|
+
base_type = @type_attributes[i]
|
102
|
+
if base_type == 'VARCHAR' or base_type == 'CHAR'
|
103
|
+
final_array.push v
|
104
|
+
elsif base_type == 'RAW'
|
105
|
+
# RAW is a bit different as the default returned type is a byte array
|
106
|
+
# By extracting the results as 'oracle_attributes' you get a to_string
|
107
|
+
# method on the RAW type to turn it into a hex string.
|
108
|
+
temp_array = struct.get_oracle_attributes.to_a
|
109
|
+
final_array.push oracle_raw_as_string(temp_array[i])
|
110
|
+
elsif base_type == 'NUMBER' or base_type == 'INTEGER'
|
111
|
+
final_array.push java_number_as_float(v)
|
112
|
+
elsif base_type == 'DATE' or base_type == 'TIMESTAMP'
|
113
|
+
final_array.push java_date_as_time(v)
|
114
|
+
else
|
115
|
+
raise "#{base_type}: Unimplemented Record Type"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
final_array
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def set_descriptor(conn)
|
124
|
+
@descriptor ||= StructDescriptor.createDescriptor(@ora_type, conn);
|
125
|
+
set_type_attributes
|
126
|
+
end
|
127
|
+
|
128
|
+
def set_type_attributes
|
129
|
+
unless @type_attributes
|
130
|
+
@type_attributes = []
|
131
|
+
meta = @descriptor.get_meta_data
|
132
|
+
1.upto(meta.get_column_count) do |i|
|
133
|
+
@type_attributes.push(meta.get_column_type_name(i))
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
@@ -24,29 +24,30 @@ module SimpleOracleJDBC
|
|
24
24
|
include Binding
|
25
25
|
include ResultSet
|
26
26
|
|
27
|
-
attr_reader :statement, :sql, :result_set
|
27
|
+
attr_reader :connection, :statement, :sql, :result_set
|
28
28
|
|
29
29
|
attr_writer :result_set
|
30
30
|
|
31
31
|
# Creates a new instance of this class. Not intended to be used directly. Use the factory
|
32
32
|
# class methods prepare or execute instead.
|
33
|
-
def initialize
|
33
|
+
def initialize(connection)
|
34
|
+
@connection = connection
|
34
35
|
@auto_statement_close = true
|
35
36
|
end
|
36
37
|
|
37
38
|
# Takes a JDBC connection object and an SQL statement and returns a SimpleOracleJDBC::Sql
|
38
39
|
# object with the prepared statement.
|
39
40
|
def self.prepare(connection, sql)
|
40
|
-
sql_object = self.new
|
41
|
+
sql_object = self.new(connection)
|
41
42
|
sql_object.disable_auto_statement_close
|
42
|
-
sql_object.prepare(
|
43
|
+
sql_object.prepare(sql)
|
43
44
|
end
|
44
45
|
|
45
46
|
# Takes a JDBC connection object, an SQL statement and an optional list of bind variables and
|
46
47
|
# will prepare and execute the sql statement, returning an SimpleOracle::Sql object.
|
47
48
|
def self.execute(connection, sql, *binds)
|
48
|
-
sql_object = self.new
|
49
|
-
sql_object.prepare(
|
49
|
+
sql_object = self.new(connection)
|
50
|
+
sql_object.prepare(sql)
|
50
51
|
sql_object.execute(*binds)
|
51
52
|
end
|
52
53
|
|
@@ -54,9 +55,9 @@ module SimpleOracleJDBC
|
|
54
55
|
# and a JDBC prepared statement will be stored in @statement.
|
55
56
|
#
|
56
57
|
# This method returns self to allow calls to be chained.
|
57
|
-
def prepare(
|
58
|
+
def prepare(sql)
|
58
59
|
@sql = sql
|
59
|
-
@statement = connection.prepare_statement(@sql)
|
60
|
+
@statement = @connection.prepare_statement(@sql)
|
60
61
|
self
|
61
62
|
end
|
62
63
|
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module SimpleOracleJDBC
|
2
|
+
module TypeMap
|
3
|
+
|
4
|
+
def java_date_as_date(v)
|
5
|
+
if v
|
6
|
+
Date.new(v.get_year+1900, v.get_month+1, v.get_date)
|
7
|
+
else
|
8
|
+
nil
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def java_date_as_time(v)
|
13
|
+
if v
|
14
|
+
Time.at(v.get_time.to_f / 1000)
|
15
|
+
else
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def java_number_as_float(v)
|
21
|
+
if v
|
22
|
+
v.double_value
|
23
|
+
else
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def java_integer_as_integer(v)
|
29
|
+
# JRuby automatically converts INT to INT
|
30
|
+
v
|
31
|
+
end
|
32
|
+
|
33
|
+
def java_string_as_string(v)
|
34
|
+
# JRubyt automatically converts to a Ruby string
|
35
|
+
v
|
36
|
+
end
|
37
|
+
|
38
|
+
def oracle_raw_as_string(v)
|
39
|
+
if v
|
40
|
+
v.string_value
|
41
|
+
else
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def ruby_date_as_jdbc_date(v)
|
47
|
+
if v
|
48
|
+
jdbc_date = Java::JavaSql::Date.new(v.strftime("%s").to_f * 1000)
|
49
|
+
else
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def ruby_time_as_jdbc_timestamp(v)
|
55
|
+
if v
|
56
|
+
TIMESTAMP.new(Java::JavaSql::Timestamp.new(v.to_f * 1000))
|
57
|
+
else
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def ruby_any_date_as_jdbc_date(v)
|
63
|
+
if v
|
64
|
+
if v.is_a? Date
|
65
|
+
ruby_date_as_jdbc_date(v)
|
66
|
+
elsif v.is_a? Time
|
67
|
+
ruby_time_as_jdbc_timestamp(v)
|
68
|
+
else
|
69
|
+
raise "#{v.class}: unimplemented Ruby date type for arrays. Use Date or Time"
|
70
|
+
end
|
71
|
+
else
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def ruby_number_as_jdbc_number(v)
|
77
|
+
if v
|
78
|
+
# Avoid warning that appeared in JRuby 1.7.3. There are many signatures of
|
79
|
+
# Java::OracleSql::NUMBER and it has to pick one. This causes a warning. This
|
80
|
+
# technique works around the warning and forces it to the the signiture with a
|
81
|
+
# double input - see https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby
|
82
|
+
# under the Constructors section.
|
83
|
+
construct = Java::OracleSql::NUMBER.java_class.constructor(Java::double)
|
84
|
+
construct.new_instance(v)
|
85
|
+
else
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def ruby_raw_string_as_jdbc_raw(v)
|
91
|
+
if v
|
92
|
+
Java::OracleSql::RAW.new(v)
|
93
|
+
else
|
94
|
+
v
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
data/test/README
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
For the test suit to run the Oracle objects below must be created upfront.
|
2
|
+
|
3
|
+
** The test harness will not create them - maybe in the future ... **
|
4
|
+
|
5
|
+
Right now, these objects are only needed to test the array functionality.
|
6
|
+
|
7
|
+
|
8
|
+
create or replace type t_varchar2_tab is table of varchar2(100);
|
9
|
+
/
|
10
|
+
|
11
|
+
create or replace type t_char_tab is table of char(100);
|
12
|
+
/
|
13
|
+
|
14
|
+
create or replace type t_integer_tab is table of integer;
|
15
|
+
/
|
16
|
+
|
17
|
+
create or replace type t_number_tab is table of number;
|
18
|
+
/
|
19
|
+
|
20
|
+
create or replace type t_date_tab is table of date;
|
21
|
+
/
|
22
|
+
|
23
|
+
create or replace type t_timestamp_tab is table of timestamp;
|
24
|
+
/
|
25
|
+
|
26
|
+
create or replace type t_raw_tab is table of raw(100);
|
27
|
+
/
|
28
|
+
|
29
|
+
create or replace type t_record as object (
|
30
|
+
p_varchar varchar2(10),
|
31
|
+
p_integer integer,
|
32
|
+
p_number number,
|
33
|
+
p_char char(10),
|
34
|
+
p_date date,
|
35
|
+
p_timestamp timestamp,
|
36
|
+
p_raw raw(10)
|
37
|
+
);
|
38
|
+
/
|
39
|
+
|
40
|
+
create or replace type t_record_tab as table of t_record;
|
41
|
+
/
|
42
|
+
|
43
|
+
create or replace function test_record(i_record t_record)
|
44
|
+
return t_record
|
45
|
+
is
|
46
|
+
begin
|
47
|
+
return i_record;
|
48
|
+
end;
|
49
|
+
/
|
50
|
+
|
51
|
+
create or replace function test_array_of_records(i_array t_record_tab)
|
52
|
+
return t_record_tab
|
53
|
+
is
|
54
|
+
v_return_value t_record_tab;
|
55
|
+
begin
|
56
|
+
v_return_value := t_record_tab();
|
57
|
+
for i in 1..i_array.count loop
|
58
|
+
v_return_value.extend(1);
|
59
|
+
v_return_value(v_return_value.count) := i_array(i);
|
60
|
+
end loop;
|
61
|
+
return v_return_value;
|
62
|
+
end;
|
63
|
+
/
|
64
|
+
|
65
|
+
|
66
|
+
create or replace function test_array_varchar(i_array t_varchar2_tab)
|
67
|
+
return t_varchar2_tab
|
68
|
+
is
|
69
|
+
v_return_value t_varchar2_tab;
|
70
|
+
begin
|
71
|
+
v_return_value := t_varchar2_tab();
|
72
|
+
for i in 1..i_array.count loop
|
73
|
+
v_return_value.extend(1);
|
74
|
+
v_return_value(v_return_value.count) := i_array(i);
|
75
|
+
end loop;
|
76
|
+
return v_return_value;
|
77
|
+
end;
|
78
|
+
/
|
79
|
+
|
80
|
+
create or replace function test_array_char(i_array t_char_tab)
|
81
|
+
return t_char_tab
|
82
|
+
is
|
83
|
+
v_return_value t_char_tab;
|
84
|
+
begin
|
85
|
+
v_return_value := t_char_tab();
|
86
|
+
for i in 1..i_array.count loop
|
87
|
+
v_return_value.extend(1);
|
88
|
+
v_return_value(v_return_value.count) := i_array(i);
|
89
|
+
end loop;
|
90
|
+
return v_return_value;
|
91
|
+
end;
|
92
|
+
/
|
93
|
+
|
94
|
+
|
95
|
+
create or replace function test_array_integer(i_array t_integer_tab)
|
96
|
+
return t_integer_tab
|
97
|
+
is
|
98
|
+
v_return_value t_integer_tab;
|
99
|
+
begin
|
100
|
+
v_return_value := t_integer_tab();
|
101
|
+
for i in 1..i_array.count loop
|
102
|
+
v_return_value.extend(1);
|
103
|
+
v_return_value(v_return_value.count) := i_array(i);
|
104
|
+
end loop;
|
105
|
+
return v_return_value;
|
106
|
+
end;
|
107
|
+
/
|
108
|
+
|
109
|
+
|
110
|
+
create or replace function test_array_number(i_array t_number_tab)
|
111
|
+
return t_number_tab
|
112
|
+
is
|
113
|
+
v_return_value t_number_tab;
|
114
|
+
begin
|
115
|
+
v_return_value := t_number_tab();
|
116
|
+
for i in 1..i_array.count loop
|
117
|
+
v_return_value.extend(1);
|
118
|
+
v_return_value(v_return_value.count) := i_array(i);
|
119
|
+
end loop;
|
120
|
+
return v_return_value;
|
121
|
+
end;
|
122
|
+
/
|
123
|
+
|
124
|
+
|
125
|
+
create or replace function test_array_date(i_array t_date_tab)
|
126
|
+
return t_date_tab
|
127
|
+
is
|
128
|
+
v_return_value t_date_tab;
|
129
|
+
begin
|
130
|
+
v_return_value := t_date_tab();
|
131
|
+
for i in 1..i_array.count loop
|
132
|
+
v_return_value.extend(1);
|
133
|
+
v_return_value(v_return_value.count) := i_array(i);
|
134
|
+
end loop;
|
135
|
+
return v_return_value;
|
136
|
+
end;
|
137
|
+
/
|
138
|
+
|
139
|
+
create or replace function test_array_timestamp(i_array t_timestamp_tab)
|
140
|
+
return t_timestamp_tab
|
141
|
+
is
|
142
|
+
v_return_value t_timestamp_tab;
|
143
|
+
begin
|
144
|
+
v_return_value := t_timestamp_tab();
|
145
|
+
for i in 1..i_array.count loop
|
146
|
+
v_return_value.extend(1);
|
147
|
+
v_return_value(v_return_value.count) := i_array(i);
|
148
|
+
end loop;
|
149
|
+
return v_return_value;
|
150
|
+
end;
|
151
|
+
/
|
152
|
+
|
153
|
+
create or replace function test_array_raw(i_array t_raw_tab)
|
154
|
+
return t_raw_tab
|
155
|
+
is
|
156
|
+
v_return_value t_raw_tab;
|
157
|
+
begin
|
158
|
+
v_return_value := t_raw_tab();
|
159
|
+
for i in 1..i_array.count loop
|
160
|
+
v_return_value.extend(1);
|
161
|
+
v_return_value(v_return_value.count) := i_array(i);
|
162
|
+
end loop;
|
163
|
+
return v_return_value;
|
164
|
+
end;
|
165
|
+
/
|