ruby-plsql 0.9.9-java
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.
- checksums.yaml +7 -0
- data/History.txt +282 -0
- data/License.txt +20 -0
- data/README.md +263 -0
- data/VERSION +1 -0
- data/lib/plsql/connection.rb +230 -0
- data/lib/plsql/helpers.rb +7 -0
- data/lib/plsql/jdbc_connection.rb +588 -0
- data/lib/plsql/oci_connection.rb +324 -0
- data/lib/plsql/package.rb +87 -0
- data/lib/plsql/procedure.rb +584 -0
- data/lib/plsql/procedure_call.rb +626 -0
- data/lib/plsql/schema.rb +296 -0
- data/lib/plsql/sequence.rb +46 -0
- data/lib/plsql/sql_statements.rb +85 -0
- data/lib/plsql/table.rb +345 -0
- data/lib/plsql/type.rb +270 -0
- data/lib/plsql/variable.rb +143 -0
- data/lib/plsql/version.rb +3 -0
- data/lib/plsql/view.rb +38 -0
- data/lib/ruby-plsql.rb +1 -0
- data/lib/ruby_plsql.rb +13 -0
- metadata +120 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require "oci8"
|
|
3
|
+
rescue LoadError
|
|
4
|
+
# OCI8 driver is unavailable.
|
|
5
|
+
msg = $!.to_s
|
|
6
|
+
if /-- oci8$/ =~ msg
|
|
7
|
+
# ruby-oci8 is not installed.
|
|
8
|
+
# MRI <= 1.9.2, Rubinius, JRuby:
|
|
9
|
+
# no such file to load -- oci8
|
|
10
|
+
# MRI >= 1.9.3:
|
|
11
|
+
# cannot load such file -- oci8
|
|
12
|
+
msg = "Please install ruby-oci8 gem."
|
|
13
|
+
end
|
|
14
|
+
raise LoadError, "ERROR: ruby-plsql could not load ruby-oci8 library. #{msg}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# check ruby-oci8 version
|
|
18
|
+
if Gem::Version.new(OCI8::VERSION) < Gem::Version.new("2.1.0")
|
|
19
|
+
raise LoadError, "ERROR: ruby-oci8 version #{OCI8::VERSION} is too old. Please install ruby-oci8 version 2.1.0 or later."
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module PLSQL
|
|
23
|
+
class OCIConnection < Connection # :nodoc:
|
|
24
|
+
def self.create_raw(params)
|
|
25
|
+
connection_string = if params[:host]
|
|
26
|
+
"//#{params[:host]}:#{params[:port] || 1521}/#{params[:database]}"
|
|
27
|
+
else
|
|
28
|
+
params[:database]
|
|
29
|
+
end
|
|
30
|
+
new(OCI8.new(params[:username], params[:password], connection_string))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def logoff
|
|
34
|
+
super
|
|
35
|
+
raw_connection.logoff
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def commit
|
|
39
|
+
raw_connection.commit
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def rollback
|
|
43
|
+
raw_connection.rollback
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def autocommit?
|
|
47
|
+
raw_connection.autocommit?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def autocommit=(value)
|
|
51
|
+
raw_connection.autocommit = value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def prefetch_rows=(value)
|
|
55
|
+
raw_connection.prefetch_rows = value
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def exec(sql, *bindvars)
|
|
59
|
+
raw_connection.exec(sql, *bindvars)
|
|
60
|
+
true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class Cursor # :nodoc:
|
|
64
|
+
include Connection::CursorCommon
|
|
65
|
+
|
|
66
|
+
attr_reader :raw_cursor
|
|
67
|
+
|
|
68
|
+
# stack of open cursors per thread
|
|
69
|
+
def self.open_cursors
|
|
70
|
+
Thread.current[:plsql_oci_cursor_stack] ||= []
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def initialize(conn, raw_cursor)
|
|
74
|
+
@connection = conn
|
|
75
|
+
@raw_cursor = raw_cursor
|
|
76
|
+
self.class.open_cursors.push self
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.new_from_parse(conn, sql)
|
|
80
|
+
raw_cursor = conn.raw_connection.parse(sql)
|
|
81
|
+
self.new(conn, raw_cursor)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.new_from_query(conn, sql, bindvars = [], options = {})
|
|
85
|
+
cursor = new_from_parse(conn, sql)
|
|
86
|
+
if prefetch_rows = options[:prefetch_rows]
|
|
87
|
+
cursor.prefetch_rows = prefetch_rows
|
|
88
|
+
end
|
|
89
|
+
cursor.exec(*bindvars)
|
|
90
|
+
cursor
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def prefetch_rows=(value)
|
|
94
|
+
@raw_cursor.prefetch_rows = value
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def bind_param(arg, value, metadata)
|
|
98
|
+
type, length = @connection.plsql_to_ruby_data_type(metadata)
|
|
99
|
+
ora_value = @connection.ruby_value_to_ora_value(value, type)
|
|
100
|
+
@raw_cursor.bind_param(arg, ora_value, type, length)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def exec(*bindvars)
|
|
104
|
+
@raw_cursor.exec(*bindvars)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def [](key)
|
|
108
|
+
@connection.ora_value_to_ruby_value(@raw_cursor[key])
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def fetch
|
|
112
|
+
row = @raw_cursor.fetch
|
|
113
|
+
row && row.map { |v| @connection.ora_value_to_ruby_value(v) }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def fields
|
|
117
|
+
@fields ||= @raw_cursor.get_col_names.map { |c| c.downcase.to_sym }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def close_raw_cursor
|
|
121
|
+
@raw_cursor.close
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def close
|
|
125
|
+
# close all cursors that were created after this one
|
|
126
|
+
while (open_cursor = self.class.open_cursors.pop) && !open_cursor.equal?(self)
|
|
127
|
+
open_cursor.close_raw_cursor
|
|
128
|
+
end
|
|
129
|
+
close_raw_cursor
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def parse(sql)
|
|
134
|
+
Cursor.new_from_parse(self, sql)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def cursor_from_query(sql, bindvars = [], options = {})
|
|
138
|
+
Cursor.new_from_query(self, sql, bindvars, options)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def plsql_to_ruby_data_type(metadata)
|
|
142
|
+
data_type, data_length = metadata[:data_type], metadata[:data_length]
|
|
143
|
+
case data_type
|
|
144
|
+
when "VARCHAR", "VARCHAR2", "CHAR", "NVARCHAR2", "NCHAR"
|
|
145
|
+
[String, data_length || 32767]
|
|
146
|
+
when "CLOB", "NCLOB"
|
|
147
|
+
[OCI8::CLOB, nil]
|
|
148
|
+
when "BLOB"
|
|
149
|
+
[OCI8::BLOB, nil]
|
|
150
|
+
when "NUMBER", "NATURAL", "NATURALN", "POSITIVE", "POSITIVEN", "SIGNTYPE", "SIMPLE_INTEGER", "PLS_INTEGER", "BINARY_INTEGER"
|
|
151
|
+
[OraNumber, nil]
|
|
152
|
+
when "DATE"
|
|
153
|
+
[DateTime, nil]
|
|
154
|
+
when "TIMESTAMP", "TIMESTAMP WITH TIME ZONE", "TIMESTAMP WITH LOCAL TIME ZONE"
|
|
155
|
+
[Time, nil]
|
|
156
|
+
when "TABLE", "VARRAY", "OBJECT", "XMLTYPE", "OPAQUE/XMLTYPE"
|
|
157
|
+
# create Ruby class for collection
|
|
158
|
+
klass = OCI8::Object::Base.get_class_by_typename(metadata[:sql_type_name])
|
|
159
|
+
unless klass
|
|
160
|
+
klass = Class.new(OCI8::Object::Base)
|
|
161
|
+
klass.set_typename metadata[:sql_type_name]
|
|
162
|
+
end
|
|
163
|
+
[klass, nil]
|
|
164
|
+
when "REF CURSOR"
|
|
165
|
+
[OCI8::Cursor]
|
|
166
|
+
else
|
|
167
|
+
[String, 32767]
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def ruby_value_to_ora_value(value, type = nil)
|
|
172
|
+
type ||= value.class
|
|
173
|
+
case type.to_s.to_sym
|
|
174
|
+
when :Integer, :BigDecimal, :String
|
|
175
|
+
value
|
|
176
|
+
when :OraNumber
|
|
177
|
+
# pass parameters as OraNumber to avoid rounding errors
|
|
178
|
+
case value
|
|
179
|
+
when BigDecimal
|
|
180
|
+
OraNumber.new(value.to_s("F"))
|
|
181
|
+
when TrueClass
|
|
182
|
+
OraNumber.new(1)
|
|
183
|
+
when FalseClass
|
|
184
|
+
OraNumber.new(0)
|
|
185
|
+
else
|
|
186
|
+
value
|
|
187
|
+
end
|
|
188
|
+
when :DateTime
|
|
189
|
+
case value
|
|
190
|
+
when Time
|
|
191
|
+
::DateTime.civil(value.year, value.month, value.day, value.hour, value.min, value.sec, Rational(value.utc_offset, 86400))
|
|
192
|
+
when DateTime
|
|
193
|
+
value
|
|
194
|
+
when Date
|
|
195
|
+
::DateTime.civil(value.year, value.month, value.day, 0, 0, 0, 0)
|
|
196
|
+
else
|
|
197
|
+
value
|
|
198
|
+
end
|
|
199
|
+
when :"OCI8::CLOB", :"OCI8::BLOB"
|
|
200
|
+
# ruby-oci8 cannot create CLOB/BLOB from ''
|
|
201
|
+
value.to_s.length > 0 ? type.new(raw_oci_connection, value) : nil
|
|
202
|
+
when :"OCI8::Cursor"
|
|
203
|
+
value && value.raw_cursor
|
|
204
|
+
else
|
|
205
|
+
# collections and object types
|
|
206
|
+
if type.superclass == OCI8::Object::Base
|
|
207
|
+
return nil if value.nil?
|
|
208
|
+
tdo = raw_oci_connection.get_tdo_by_class(type)
|
|
209
|
+
if tdo.is_collection?
|
|
210
|
+
raise ArgumentError, "You should pass Array value for collection type parameter" unless value.is_a?(Array)
|
|
211
|
+
elem_list = value.map do |elem|
|
|
212
|
+
if (attr_tdo = tdo.coll_attr.typeinfo)
|
|
213
|
+
attr_type, _ = plsql_to_ruby_data_type(data_type: "OBJECT", sql_type_name: attr_tdo.typename)
|
|
214
|
+
else
|
|
215
|
+
attr_type = elem.class
|
|
216
|
+
end
|
|
217
|
+
ruby_value_to_ora_value(elem, attr_type)
|
|
218
|
+
end
|
|
219
|
+
# construct collection value
|
|
220
|
+
# TODO: change setting instance variable to appropriate ruby-oci8 method call when available
|
|
221
|
+
collection = type.new(raw_oci_connection)
|
|
222
|
+
collection.instance_variable_set("@attributes", elem_list)
|
|
223
|
+
collection
|
|
224
|
+
else # object type
|
|
225
|
+
raise ArgumentError, "You should pass Hash value for object type parameter" unless value.is_a?(Hash)
|
|
226
|
+
object_attrs = value.dup
|
|
227
|
+
object_attrs.keys.each do |key|
|
|
228
|
+
raise ArgumentError, "Wrong object type field passed to PL/SQL procedure" unless (attr = tdo.attr_getters[key])
|
|
229
|
+
case attr.datatype
|
|
230
|
+
when OCI8::TDO::ATTR_NAMED_TYPE, OCI8::TDO::ATTR_NAMED_COLLECTION
|
|
231
|
+
# nested object type or collection
|
|
232
|
+
attr_type, _ = plsql_to_ruby_data_type(data_type: "OBJECT", sql_type_name: attr.typeinfo.typename)
|
|
233
|
+
object_attrs[key] = ruby_value_to_ora_value(object_attrs[key], attr_type)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
type.new(raw_oci_connection, object_attrs)
|
|
237
|
+
end
|
|
238
|
+
# all other cases
|
|
239
|
+
else
|
|
240
|
+
value
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def ora_value_to_ruby_value(value)
|
|
246
|
+
case value
|
|
247
|
+
when Float, OraNumber, BigDecimal
|
|
248
|
+
ora_number_to_ruby_number(value)
|
|
249
|
+
when DateTime, OraDate
|
|
250
|
+
ora_date_to_ruby_date(value)
|
|
251
|
+
when OCI8::LOB
|
|
252
|
+
if value.available?
|
|
253
|
+
value.rewind
|
|
254
|
+
value.read
|
|
255
|
+
else
|
|
256
|
+
nil
|
|
257
|
+
end
|
|
258
|
+
when OCI8::Object::Base
|
|
259
|
+
tdo = raw_oci_connection.get_tdo_by_class(value.class)
|
|
260
|
+
if tdo.is_collection?
|
|
261
|
+
value.to_ary.map { |e| ora_value_to_ruby_value(e) }
|
|
262
|
+
else # object type
|
|
263
|
+
tdo.attributes.inject({}) do |hash, attr|
|
|
264
|
+
hash[attr.name] = ora_value_to_ruby_value(value.instance_variable_get(:@attributes)[attr.name])
|
|
265
|
+
hash
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
when OCI8::Cursor
|
|
269
|
+
Cursor.new(self, value)
|
|
270
|
+
else
|
|
271
|
+
value
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def database_version
|
|
276
|
+
@database_version ||= (version = raw_connection.oracle_server_version) &&
|
|
277
|
+
[version.major, version.minor, version.update, version.patch]
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
private
|
|
281
|
+
|
|
282
|
+
def raw_oci_connection
|
|
283
|
+
if raw_connection.is_a? OCI8
|
|
284
|
+
raw_connection
|
|
285
|
+
# ActiveRecord Oracle enhanced adapter puts OCI8EnhancedAutoRecover wrapper around OCI8
|
|
286
|
+
# in this case we need to pass original OCI8 connection
|
|
287
|
+
else
|
|
288
|
+
if raw_connection.instance_variable_defined?(:@raw_connection)
|
|
289
|
+
raw_connection.instance_variable_get(:@raw_connection)
|
|
290
|
+
else
|
|
291
|
+
raw_connection.instance_variable_get(:@connection)
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def ora_number_to_ruby_number(num)
|
|
297
|
+
# return BigDecimal instead of Float to avoid rounding errors
|
|
298
|
+
num == (num_to_i = num.to_i) ? num_to_i : (num.is_a?(BigDecimal) ? num : BigDecimal(num.to_s))
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def ora_date_to_ruby_date(val)
|
|
302
|
+
case val
|
|
303
|
+
when DateTime
|
|
304
|
+
# similar implementation as in oracle_enhanced adapter
|
|
305
|
+
begin
|
|
306
|
+
Time.send(plsql.default_timezone, val.year, val.month, val.day, val.hour, val.min, val.sec)
|
|
307
|
+
rescue
|
|
308
|
+
offset = plsql.default_timezone.to_sym == :local ? plsql.local_timezone_offset : 0
|
|
309
|
+
DateTime.civil(val.year, val.month, val.day, val.hour, val.min, val.sec, offset)
|
|
310
|
+
end
|
|
311
|
+
when OraDate
|
|
312
|
+
# similar implementation as in oracle_enhanced adapter
|
|
313
|
+
begin
|
|
314
|
+
Time.send(plsql.default_timezone, val.year, val.month, val.day, val.hour, val.minute, val.second)
|
|
315
|
+
rescue
|
|
316
|
+
offset = plsql.default_timezone.to_sym == :local ? plsql.local_timezone_offset : 0
|
|
317
|
+
DateTime.civil(val.year, val.month, val.day, val.hour, val.minute, val.second, offset)
|
|
318
|
+
end
|
|
319
|
+
else
|
|
320
|
+
val
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module PLSQL
|
|
2
|
+
module PackageClassMethods # :nodoc:
|
|
3
|
+
def find(schema, package)
|
|
4
|
+
package_name = package.to_s.upcase
|
|
5
|
+
find_in_schema(schema, package_name) || find_by_synonym(schema, package_name)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def find_in_schema(schema, package_name)
|
|
9
|
+
row = schema.select_first(<<-SQL, schema.schema_name, package_name)
|
|
10
|
+
SELECT object_name
|
|
11
|
+
FROM all_objects
|
|
12
|
+
WHERE owner = :owner
|
|
13
|
+
AND object_name = :package
|
|
14
|
+
AND object_type = 'PACKAGE'
|
|
15
|
+
SQL
|
|
16
|
+
new(schema, package_name) if row
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def find_by_synonym(schema, package_name)
|
|
20
|
+
row = schema.select_first(<<-SQL, schema.schema_name, package_name)
|
|
21
|
+
SELECT o.object_name, o.owner
|
|
22
|
+
FROM all_synonyms s,
|
|
23
|
+
all_objects o
|
|
24
|
+
WHERE s.owner IN (:owner, 'PUBLIC')
|
|
25
|
+
AND s.synonym_name = :synonym_name
|
|
26
|
+
AND o.owner = s.table_owner
|
|
27
|
+
AND o.object_name = s.table_name
|
|
28
|
+
AND o.object_type = 'PACKAGE'
|
|
29
|
+
ORDER BY DECODE(s.owner, 'PUBLIC', 1, 0)
|
|
30
|
+
SQL
|
|
31
|
+
new(schema, row[0], row[1]) if row
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class Package # :nodoc:
|
|
36
|
+
extend PackageClassMethods
|
|
37
|
+
|
|
38
|
+
def initialize(schema, package, override_schema_name = nil)
|
|
39
|
+
@schema = schema
|
|
40
|
+
@override_schema_name = override_schema_name
|
|
41
|
+
@package = package.to_s.upcase
|
|
42
|
+
@package_objects = {}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def procedure_defined?(name)
|
|
46
|
+
PLSQL::Procedure.find(@schema, name, @package) ? true : false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def [](object_name)
|
|
50
|
+
object_name = object_name.to_s.downcase
|
|
51
|
+
@package_objects[object_name] ||= [Procedure, Variable].inject(nil) do |res, object_type|
|
|
52
|
+
res || object_type.find(@schema, object_name, @package, @override_schema_name)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def method_missing(method, *args, &block)
|
|
59
|
+
method = +method.to_s
|
|
60
|
+
method.chop! if (assignment = method[/=$/])
|
|
61
|
+
|
|
62
|
+
case (object = self[method])
|
|
63
|
+
when Procedure
|
|
64
|
+
if assignment
|
|
65
|
+
raise ArgumentError, "Cannot assign value to package procedure '#{method.upcase}'"
|
|
66
|
+
end
|
|
67
|
+
object.exec(*args, &block)
|
|
68
|
+
when Variable
|
|
69
|
+
if assignment
|
|
70
|
+
unless args.size == 1 && block.nil?
|
|
71
|
+
raise ArgumentError, "Just one value can be assigned " \
|
|
72
|
+
"to package variable '#{method.upcase}'"
|
|
73
|
+
end
|
|
74
|
+
object.value = args[0]
|
|
75
|
+
else
|
|
76
|
+
unless args.size == 0 && block.nil?
|
|
77
|
+
raise ArgumentError, "Cannot pass arguments when getting " \
|
|
78
|
+
"package variable '#{method.upcase}' value"
|
|
79
|
+
end
|
|
80
|
+
object.value
|
|
81
|
+
end
|
|
82
|
+
else
|
|
83
|
+
raise ArgumentError, "No PL/SQL procedure or variable '#{method.upcase}' found"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|