simpleOracleJDBC 0.2.0 → 0.3.0
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/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
|
+
/
|