ruby-plsql 0.4.1 → 0.4.2
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/History.txt +18 -0
- data/Rakefile +2 -0
- data/VERSION +1 -1
- data/lib/plsql/connection.rb +60 -10
- data/lib/plsql/jdbc_connection.rb +14 -2
- data/lib/plsql/oci_connection.rb +11 -1
- data/lib/plsql/procedure.rb +120 -25
- data/lib/plsql/procedure_call.rb +349 -165
- data/lib/plsql/schema.rb +59 -8
- data/lib/plsql/sql_statements.rb +21 -0
- data/lib/plsql/table.rb +10 -4
- data/lib/plsql/type.rb +194 -6
- data/lib/plsql/variable.rb +3 -3
- data/spec/plsql/connection_spec.rb +76 -1
- data/spec/plsql/procedure_spec.rb +336 -12
- data/spec/plsql/schema_spec.rb +80 -9
- data/spec/plsql/table_spec.rb +48 -28
- data/spec/plsql/type_spec.rb +173 -2
- data/spec/plsql/variable_spec.rb +21 -0
- data/spec/plsql/view_spec.rb +16 -11
- data/spec/spec_helper.rb +19 -5
- metadata +22 -2
data/History.txt
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
== 0.4.2 2010-02-26
|
2
|
+
|
3
|
+
* New features
|
4
|
+
* Support default and custom constructors of object types, support member and static method calls on PL/SQL objects
|
5
|
+
* Support for PL/SQL record types defined inside packages
|
6
|
+
* Support for PL/SQL table and index-by table of records types defined inside packages
|
7
|
+
* plsql.savepoint and plsql.rollback_to methods
|
8
|
+
* plsql.connect! method for establishing new connection
|
9
|
+
* Improvements
|
10
|
+
* Better support for detecting matching overloaded implementation of procedure by sequential argument types
|
11
|
+
* Check if database object is valid and raise exception with compilation error if not valid
|
12
|
+
* Store :nullable and :data_default in table and view columns metadata
|
13
|
+
* Bug fixes
|
14
|
+
* accessing package variables with schema prefixed object types
|
15
|
+
* insert of TIMESTAMP values in table
|
16
|
+
* support package variables with VARCHAR2(n CHAR) and VARCHAR2(n BYTE) types
|
17
|
+
* table select :order_by option
|
18
|
+
|
1
19
|
== 0.4.1 2010-01-04
|
2
20
|
|
3
21
|
* New features
|
data/Rakefile
CHANGED
@@ -15,6 +15,8 @@ EOS
|
|
15
15
|
gem.homepage = "http://github.com/rsim/ruby-plsql"
|
16
16
|
gem.authors = ["Raimonds Simanovskis"]
|
17
17
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
18
|
+
gem.add_development_dependency "activerecord", "= 2.3.5"
|
19
|
+
gem.add_development_dependency "activerecord-oracle_enhanced-adapter", ">= 1.2.4"
|
18
20
|
gem.extra_rdoc_files = ['README.rdoc']
|
19
21
|
end
|
20
22
|
Jeweler::GemcutterTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
1
|
+
0.4.2
|
data/lib/plsql/connection.rb
CHANGED
@@ -3,27 +3,49 @@ module PLSQL
|
|
3
3
|
attr_reader :raw_driver
|
4
4
|
attr_reader :activerecord_class
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
@raw_driver =
|
6
|
+
def initialize(raw_conn, ar_class = nil) #:nodoc:
|
7
|
+
@raw_driver = self.class.driver_type
|
8
8
|
@raw_connection = raw_conn
|
9
9
|
@activerecord_class = ar_class
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
def self.create(raw_conn, ar_class = nil) #:nodoc:
|
13
13
|
if ar_class && !(defined?(::ActiveRecord) && [ar_class, ar_class.superclass].include?(::ActiveRecord::Base))
|
14
14
|
raise ArgumentError, "Wrong ActiveRecord class"
|
15
15
|
end
|
16
|
+
case driver_type
|
17
|
+
when :oci
|
18
|
+
OCIConnection.new(raw_conn, ar_class)
|
19
|
+
when :jdbc
|
20
|
+
JDBCConnection.new(raw_conn, ar_class)
|
21
|
+
else
|
22
|
+
raise ArgumentError, "Unknown raw driver"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.create_new(params) #:nodoc:
|
27
|
+
case driver_type
|
28
|
+
when :oci
|
29
|
+
OCIConnection.create_raw(params)
|
30
|
+
when :jdbc
|
31
|
+
JDBCConnection.create_raw(params)
|
32
|
+
else
|
33
|
+
raise ArgumentError, "Unknown raw driver"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.driver_type #:nodoc:
|
16
38
|
# MRI 1.8.6 or YARV 1.9.1
|
17
|
-
if (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby") && defined?(OCI8)
|
18
|
-
|
39
|
+
@driver_type ||= if (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby") && defined?(OCI8)
|
40
|
+
:oci
|
19
41
|
# JRuby
|
20
42
|
elsif (defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby")
|
21
|
-
|
43
|
+
:jdbc
|
22
44
|
else
|
23
|
-
|
45
|
+
nil
|
24
46
|
end
|
25
47
|
end
|
26
|
-
|
48
|
+
|
27
49
|
# Returns OCI8 or JDBC connection
|
28
50
|
def raw_connection
|
29
51
|
if @activerecord_class
|
@@ -32,7 +54,7 @@ module PLSQL
|
|
32
54
|
@raw_connection
|
33
55
|
end
|
34
56
|
end
|
35
|
-
|
57
|
+
|
36
58
|
# Is it OCI8 connection
|
37
59
|
def oci?
|
38
60
|
@raw_driver == :oci
|
@@ -44,7 +66,10 @@ module PLSQL
|
|
44
66
|
end
|
45
67
|
|
46
68
|
def logoff #:nodoc:
|
47
|
-
|
69
|
+
# Rollback any uncommited transactions
|
70
|
+
rollback
|
71
|
+
# Common cleanup activities before logoff, should be called from particular driver method
|
72
|
+
drop_session_ruby_temporary_tables
|
48
73
|
end
|
49
74
|
|
50
75
|
def commit #:nodoc:
|
@@ -165,6 +190,31 @@ module PLSQL
|
|
165
190
|
raise NoMethodError, "Not implemented for this raw driver"
|
166
191
|
end
|
167
192
|
|
193
|
+
# Returns session ID
|
194
|
+
def session_id
|
195
|
+
@session_id ||= select_first("SELECT TO_NUMBER(USERENV('SESSIONID')) FROM dual")[0]
|
196
|
+
end
|
197
|
+
|
198
|
+
RUBY_TEMP_TABLE_PREFIX = 'ruby_'
|
199
|
+
|
200
|
+
# Drop all ruby temporary tables that are used for calling packages with table parameter types defined in packages
|
201
|
+
def drop_all_ruby_temporary_tables
|
202
|
+
select_all("SELECT table_name FROM user_tables WHERE temporary='Y' AND table_name LIKE :table_name",
|
203
|
+
RUBY_TEMP_TABLE_PREFIX.upcase+'%').each do |row|
|
204
|
+
exec "TRUNCATE TABLE #{row[0]}"
|
205
|
+
exec "DROP TABLE #{row[0]}"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Drop ruby temporary tables created in current session that are used for calling packages with table parameter types defined in packages
|
210
|
+
def drop_session_ruby_temporary_tables
|
211
|
+
select_all("SELECT table_name FROM user_tables WHERE temporary='Y' AND table_name LIKE :table_name",
|
212
|
+
RUBY_TEMP_TABLE_PREFIX.upcase+"#{session_id}_%").each do |row|
|
213
|
+
exec "TRUNCATE TABLE #{row[0]}"
|
214
|
+
exec "DROP TABLE #{row[0]}"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
168
218
|
end
|
169
219
|
|
170
220
|
end
|
@@ -31,7 +31,18 @@ end
|
|
31
31
|
|
32
32
|
module PLSQL
|
33
33
|
class JDBCConnection < Connection #:nodoc:
|
34
|
+
|
35
|
+
def self.create_raw(params)
|
36
|
+
url = if ENV['TNS_ADMIN'] && params[:database] && !params[:host] && !params[:url]
|
37
|
+
"jdbc:oracle:thin:@#{params[:database]}"
|
38
|
+
else
|
39
|
+
params[:url] || "jdbc:oracle:thin:@#{params[:host] || 'localhost'}:#{params[:port] || 1521}:#{params[:database]}"
|
40
|
+
end
|
41
|
+
new(java.sql.DriverManager.getConnection(url, params[:username], params[:password]))
|
42
|
+
end
|
43
|
+
|
34
44
|
def logoff
|
45
|
+
super
|
35
46
|
raw_connection.close
|
36
47
|
true
|
37
48
|
rescue
|
@@ -84,7 +95,7 @@ module PLSQL
|
|
84
95
|
if metadata[:in_out] =~ /OUT/
|
85
96
|
@out_types[arg] = type || ora_value.class
|
86
97
|
@out_index[arg] = bind_param_index(arg)
|
87
|
-
if ['TABLE','VARRAY','OBJECT'
|
98
|
+
if ['TABLE','VARRAY','OBJECT'].include?(metadata[:data_type])
|
88
99
|
@statement.registerOutParameter(@out_index[arg], @connection.get_java_sql_type(ora_value,type),
|
89
100
|
metadata[:sql_type_name])
|
90
101
|
else
|
@@ -209,10 +220,11 @@ module PLSQL
|
|
209
220
|
Hash => Java::oracle.jdbc.OracleTypes::STRUCT,
|
210
221
|
java.sql.ResultSet => Java::oracle.jdbc.OracleTypes::CURSOR,
|
211
222
|
}
|
212
|
-
|
223
|
+
|
213
224
|
SQL_TYPE_TO_RUBY_CLASS = {
|
214
225
|
java.sql.Types::CHAR => String,
|
215
226
|
java.sql.Types::VARCHAR => String,
|
227
|
+
java.sql.Types::LONGVARCHAR => String,
|
216
228
|
java.sql.Types::NUMERIC => BigDecimal,
|
217
229
|
java.sql.Types::INTEGER => Fixnum,
|
218
230
|
java.sql.Types::DATE => Time,
|
data/lib/plsql/oci_connection.rb
CHANGED
@@ -16,8 +16,18 @@ end
|
|
16
16
|
|
17
17
|
module PLSQL
|
18
18
|
class OCIConnection < Connection #:nodoc:
|
19
|
-
|
19
|
+
|
20
|
+
def self.create_raw(params)
|
21
|
+
connection_string = if params[:host]
|
22
|
+
"//#{params[:host]}:#{params[:port]||1521}/#{params[:database]}"
|
23
|
+
else
|
24
|
+
params[:database]
|
25
|
+
end
|
26
|
+
new(OCI8.new(params[:username], params[:password], connection_string))
|
27
|
+
end
|
28
|
+
|
20
29
|
def logoff
|
30
|
+
super
|
21
31
|
raw_connection.logoff
|
22
32
|
end
|
23
33
|
|
data/lib/plsql/procedure.rb
CHANGED
@@ -42,17 +42,31 @@ module PLSQL
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
|
46
|
-
extend ProcedureClassMethods
|
47
|
-
|
45
|
+
module ProcedureCommon #:nodoc:
|
48
46
|
attr_reader :arguments, :argument_list, :out_list, :return
|
49
47
|
attr_reader :schema, :schema_name, :package, :procedure
|
50
48
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
49
|
+
# return type string from metadata that can be used in DECLARE block or table definition
|
50
|
+
def self.type_to_sql(metadata) #:nodoc:
|
51
|
+
case metadata[:data_type]
|
52
|
+
when 'NUMBER'
|
53
|
+
precision, scale = metadata[:data_precision], metadata[:data_scale]
|
54
|
+
"NUMBER#{precision ? "(#{precision}#{scale ? ",#{scale}": ""})" : ""}"
|
55
|
+
when 'VARCHAR2', 'CHAR', 'NVARCHAR2', 'NCHAR'
|
56
|
+
length = metadata[:data_length]
|
57
|
+
if length && (char_used = metadata[:char_used])
|
58
|
+
length = "#{length} #{char_used == 'C' ? 'CHAR' : 'BYTE'}"
|
59
|
+
end
|
60
|
+
"#{metadata[:data_type]}#{length ? "(#{length})": ""}"
|
61
|
+
when 'PL/SQL TABLE', 'TABLE', 'VARRAY', 'OBJECT'
|
62
|
+
metadata[:sql_type_name]
|
63
|
+
else
|
64
|
+
metadata[:data_type]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# get procedure argument metadata from data dictionary
|
69
|
+
def get_argument_metadata #:nodoc:
|
56
70
|
@arguments = {}
|
57
71
|
@argument_list = {}
|
58
72
|
@out_list = {}
|
@@ -62,20 +76,27 @@ module PLSQL
|
|
62
76
|
# store reference to previous level record or collection metadata
|
63
77
|
previous_level_argument_metadata = {}
|
64
78
|
|
79
|
+
# store tmp tables for each overload for table parameters with types defined inside packages
|
80
|
+
@tmp_table_names = {}
|
81
|
+
# store if tmp tables are created for specific overload
|
82
|
+
@tmp_tables_created = {}
|
83
|
+
|
84
|
+
# subprogram_id column is available just from version 10g
|
85
|
+
subprogram_id_column = (@schema.connection.database_version <=> [10, 2]) >= 0 ? 'subprogram_id' : 'NULL'
|
86
|
+
|
65
87
|
@schema.select_all(
|
66
|
-
"SELECT overload, argument_name, position, data_level,
|
88
|
+
"SELECT #{subprogram_id_column}, object_name, TO_NUMBER(overload), argument_name, position, data_level,
|
67
89
|
data_type, in_out, data_length, data_precision, data_scale, char_used,
|
68
90
|
type_owner, type_name, type_subname
|
69
91
|
FROM all_arguments
|
70
92
|
WHERE object_id = :object_id
|
71
93
|
AND owner = :owner
|
72
94
|
AND object_name = :procedure_name
|
73
|
-
AND NVL(package_name,'nil') = :package
|
74
95
|
ORDER BY overload, sequence",
|
75
|
-
object_id, @schema_name, @procedure
|
96
|
+
@object_id, @schema_name, @procedure
|
76
97
|
) do |r|
|
77
98
|
|
78
|
-
overload, argument_name, position, data_level,
|
99
|
+
subprogram_id, object_name, overload, argument_name, position, data_level,
|
79
100
|
data_type, in_out, data_length, data_precision, data_scale, char_used,
|
80
101
|
type_owner, type_name, type_subname = r
|
81
102
|
|
@@ -84,8 +105,26 @@ module PLSQL
|
|
84
105
|
overload ||= 0
|
85
106
|
@arguments[overload] ||= {}
|
86
107
|
@return[overload] ||= nil
|
87
|
-
|
88
|
-
|
108
|
+
@tmp_table_names[overload] ||= []
|
109
|
+
|
110
|
+
sql_type_name = type_owner && "#{type_owner == 'PUBLIC' ? nil : "#{type_owner}."}#{type_name}#{type_subname ? ".#{type_subname}" : nil}"
|
111
|
+
|
112
|
+
tmp_table_name = nil
|
113
|
+
# type defined inside package
|
114
|
+
if type_subname
|
115
|
+
if collection_type?(data_type)
|
116
|
+
raise ArgumentError, "#{data_type} type #{sql_type_name} definition inside package is not supported as part of other type definition," <<
|
117
|
+
" use CREATE TYPE outside package" if data_level > 0
|
118
|
+
# if subprogram_id was not supported by all_arguments view
|
119
|
+
# then generate unique ID from object_name and overload
|
120
|
+
subprogram_id ||= "#{object_name.hash % 10000}#{overload}"
|
121
|
+
tmp_table_name = "#{Connection::RUBY_TEMP_TABLE_PREFIX}#{@schema.connection.session_id}_#{@object_id}_#{subprogram_id}_#{position}"
|
122
|
+
elsif data_type != 'PL/SQL RECORD'
|
123
|
+
# raise exception only when there are no overloaded procedure definitions
|
124
|
+
# (as probably this overload will not be used at all)
|
125
|
+
raise ArgumentError, "Parameter type #{sql_type_name} definition inside package is not supported, use CREATE TYPE outside package" if overload == 0
|
126
|
+
end
|
127
|
+
end
|
89
128
|
|
90
129
|
argument_metadata = {
|
91
130
|
:position => position && position.to_i,
|
@@ -98,8 +137,12 @@ module PLSQL
|
|
98
137
|
:type_owner => type_owner,
|
99
138
|
:type_name => type_name,
|
100
139
|
:type_subname => type_subname,
|
101
|
-
:sql_type_name =>
|
140
|
+
:sql_type_name => sql_type_name
|
102
141
|
}
|
142
|
+
if tmp_table_name
|
143
|
+
@tmp_table_names[overload] << [(argument_metadata[:tmp_table_name] = tmp_table_name), argument_metadata]
|
144
|
+
end
|
145
|
+
|
103
146
|
if composite_type?(data_type)
|
104
147
|
case data_type
|
105
148
|
when 'PL/SQL RECORD'
|
@@ -108,28 +151,33 @@ module PLSQL
|
|
108
151
|
previous_level_argument_metadata[data_level] = argument_metadata
|
109
152
|
end
|
110
153
|
|
154
|
+
# if function has return value
|
155
|
+
if argument_name.nil? && data_level == 0 && in_out == 'OUT'
|
156
|
+
@return[overload] = argument_metadata
|
111
157
|
# if parameter
|
112
|
-
|
158
|
+
else
|
113
159
|
# top level parameter
|
114
160
|
if data_level == 0
|
115
|
-
|
161
|
+
# sometime there are empty IN arguments in all_arguments view for procedures without arguments (e.g. for DBMS_OUTPUT.DISABLE)
|
162
|
+
@arguments[overload][argument_name.downcase.to_sym] = argument_metadata if argument_name
|
116
163
|
# or lower level part of composite type
|
117
164
|
else
|
118
165
|
case previous_level_argument_metadata[data_level - 1][:data_type]
|
119
166
|
when 'PL/SQL RECORD'
|
120
167
|
previous_level_argument_metadata[data_level - 1][:fields][argument_name.downcase.to_sym] = argument_metadata
|
121
|
-
when 'TABLE', 'VARRAY'
|
168
|
+
when 'PL/SQL TABLE', 'TABLE', 'VARRAY'
|
122
169
|
previous_level_argument_metadata[data_level - 1][:element] = argument_metadata
|
123
170
|
end
|
124
171
|
end
|
125
|
-
# if function has return value
|
126
|
-
elsif argument_name.nil? && data_level == 0 && in_out == 'OUT'
|
127
|
-
@return[overload] = argument_metadata
|
128
172
|
end
|
129
173
|
end
|
130
174
|
# if procedure is without arguments then create default empty argument list for default overload
|
131
175
|
@arguments[0] = {} if @arguments.keys.empty?
|
132
|
-
|
176
|
+
|
177
|
+
construct_argument_list_for_overloads
|
178
|
+
end
|
179
|
+
|
180
|
+
def construct_argument_list_for_overloads #:nodoc:
|
133
181
|
@overloads = @arguments.keys.sort
|
134
182
|
@overloads.each do |overload|
|
135
183
|
@argument_list[overload] = @arguments[overload].keys.sort {|k1, k2| @arguments[overload][k1][:position] <=> @arguments[overload][k2][:position]}
|
@@ -137,14 +185,61 @@ module PLSQL
|
|
137
185
|
end
|
138
186
|
end
|
139
187
|
|
140
|
-
|
141
|
-
|
188
|
+
def ensure_tmp_tables_created(overload) #:nodoc:
|
189
|
+
return if @tmp_tables_created.nil? || @tmp_tables_created[overload]
|
190
|
+
@tmp_table_names[overload] && @tmp_table_names[overload].each do |table_name, argument_metadata|
|
191
|
+
sql = "CREATE GLOBAL TEMPORARY TABLE #{table_name} (\n"
|
192
|
+
element_metadata = argument_metadata[:element]
|
193
|
+
case element_metadata[:data_type]
|
194
|
+
when 'PL/SQL RECORD'
|
195
|
+
fields_metadata = element_metadata[:fields]
|
196
|
+
fields_sorted_by_position = fields_metadata.keys.sort_by{|k| fields_metadata[k][:position]}
|
197
|
+
sql << fields_sorted_by_position.map do |field|
|
198
|
+
metadata = fields_metadata[field]
|
199
|
+
"#{field} #{ProcedureCommon.type_to_sql(metadata)}"
|
200
|
+
end.join(",\n")
|
201
|
+
else
|
202
|
+
sql << "element #{ProcedureCommon.type_to_sql(element_metadata)}"
|
203
|
+
end
|
204
|
+
sql << ",\ni__ NUMBER(38)\n"
|
205
|
+
sql << ") ON COMMIT PRESERVE ROWS\n"
|
206
|
+
sql_block = "DECLARE\nPRAGMA AUTONOMOUS_TRANSACTION;\nBEGIN\nEXECUTE IMMEDIATE :sql;\nEND;\n"
|
207
|
+
@schema.execute sql_block, sql
|
208
|
+
end
|
209
|
+
@tmp_tables_created[overload] = true
|
210
|
+
end
|
211
|
+
|
212
|
+
PLSQL_COMPOSITE_TYPES = ['PL/SQL RECORD', 'PL/SQL TABLE', 'TABLE', 'VARRAY'].freeze
|
213
|
+
def composite_type?(data_type) #:nodoc:
|
142
214
|
PLSQL_COMPOSITE_TYPES.include? data_type
|
143
215
|
end
|
144
216
|
|
145
|
-
|
217
|
+
PLSQL_COLLECTION_TYPES = ['PL/SQL TABLE', 'TABLE', 'VARRAY'].freeze
|
218
|
+
def collection_type?(data_type) #:nodoc:
|
219
|
+
PLSQL_COLLECTION_TYPES.include? data_type
|
220
|
+
end
|
221
|
+
|
222
|
+
def overloaded? #:nodoc:
|
146
223
|
@overloaded
|
147
224
|
end
|
225
|
+
end
|
226
|
+
|
227
|
+
class Procedure #:nodoc:
|
228
|
+
extend ProcedureClassMethods
|
229
|
+
include ProcedureCommon
|
230
|
+
|
231
|
+
attr_reader :arguments, :argument_list, :out_list, :return
|
232
|
+
attr_reader :schema, :schema_name, :package, :procedure
|
233
|
+
|
234
|
+
def initialize(schema, procedure, package, override_schema_name, object_id)
|
235
|
+
@schema = schema
|
236
|
+
@schema_name = override_schema_name || schema.schema_name
|
237
|
+
@procedure = procedure.to_s.upcase
|
238
|
+
@package = package
|
239
|
+
@object_id = object_id
|
240
|
+
|
241
|
+
get_argument_metadata
|
242
|
+
end
|
148
243
|
|
149
244
|
def exec(*args, &block)
|
150
245
|
call = ProcedureCall.new(self, args)
|
data/lib/plsql/procedure_call.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
1
|
module PLSQL
|
2
2
|
class ProcedureCall #:nodoc:
|
3
3
|
|
4
|
-
def initialize(procedure, args = [])
|
4
|
+
def initialize(procedure, args = [], options = {})
|
5
5
|
@procedure = procedure
|
6
6
|
@schema = @procedure.schema
|
7
7
|
@dbms_output_stream = @schema.dbms_output_stream
|
8
|
+
@skip_self = options[:skip_self]
|
9
|
+
@self = options[:self]
|
8
10
|
@overload = get_overload_from_arguments_list(args)
|
11
|
+
@procedure.ensure_tmp_tables_created(@overload) if @procedure.respond_to?(:ensure_tmp_tables_created)
|
9
12
|
construct_sql(args)
|
10
13
|
end
|
11
14
|
|
12
15
|
def exec
|
16
|
+
# puts "DEBUG: sql = #{@sql.gsub("\n","<br/>\n")}"
|
13
17
|
@cursor = @schema.connection.parse(@sql)
|
14
18
|
|
15
19
|
@bind_values.each do |arg, value|
|
@@ -37,34 +41,106 @@ module PLSQL
|
|
37
41
|
private
|
38
42
|
|
39
43
|
def get_overload_from_arguments_list(args)
|
40
|
-
#
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
number_of_args = args.size
|
54
|
-
overload = overload_argument_list.keys.detect do |ov|
|
55
|
-
overload_argument_list[ov].size == number_of_args
|
56
|
-
end
|
44
|
+
# if not overloaded then overload index 0 is used
|
45
|
+
return 0 unless @procedure.overloaded?
|
46
|
+
# If named arguments are used then
|
47
|
+
# there should be just one Hash argument with symbol keys
|
48
|
+
if args.size == 1 && args[0].is_a?(Hash) && args[0].keys.all?{|k| k.is_a?(Symbol)}
|
49
|
+
args_keys = args[0].keys
|
50
|
+
# implicit SELF argument for object instance procedures
|
51
|
+
args_keys << :self if @self && !args_keys.include?(:self)
|
52
|
+
args_keys = args_keys.sort_by{|k| k.to_s}
|
53
|
+
number_of_args = args_keys.size
|
54
|
+
overload = overload_argument_list.keys.detect do |ov|
|
55
|
+
overload_argument_list[ov].size == number_of_args &&
|
56
|
+
overload_arguments[ov].keys.sort_by{|k| k.to_s} == args_keys
|
57
57
|
end
|
58
|
-
|
59
|
-
overload
|
58
|
+
# otherwise try matching by sequential arguments count and types
|
60
59
|
else
|
61
|
-
|
60
|
+
number_of_args = args.size
|
61
|
+
matching_types = []
|
62
|
+
# if implicit SELF argument for object instance procedures should be passed
|
63
|
+
# then it should be added as first argument to find matches
|
64
|
+
if @self
|
65
|
+
number_of_args += 1
|
66
|
+
matching_types << ['OBJECT']
|
67
|
+
end
|
68
|
+
args.each do |arg|
|
69
|
+
matching_types << matching_oracle_types_for_ruby_value(arg)
|
70
|
+
end
|
71
|
+
exact_overloads = [] # overloads with exact number of matching arguments
|
72
|
+
smaller_overloads = [] # overloads with exact number of matching arguments
|
73
|
+
# overload = overload_argument_list.keys.detect do |ov|
|
74
|
+
# overload_argument_list[ov].size == number_of_args
|
75
|
+
# end
|
76
|
+
overload_argument_list.keys.each do |ov|
|
77
|
+
score = 0 # lower score is better match
|
78
|
+
ov_arg_list_size = overload_argument_list[ov].size
|
79
|
+
if (number_of_args <= ov_arg_list_size &&
|
80
|
+
(0..(number_of_args-1)).all? do |i|
|
81
|
+
ov_arg = overload_argument_list[ov][i]
|
82
|
+
matching_types[i] == :all || # either value matches any type
|
83
|
+
(ind = matching_types[i].index(overload_arguments[ov][ov_arg][:data_type])) &&
|
84
|
+
(score += ind) # or add index of matched type
|
85
|
+
end)
|
86
|
+
if number_of_args == ov_arg_list_size
|
87
|
+
exact_overloads << [ov, score]
|
88
|
+
else
|
89
|
+
smaller_overloads << [ov, score]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
# pick either first exact matching overload of first matching with smaller argument count
|
94
|
+
# (hoping that missing arguments will be defaulted - cannot find default value from all_arguments)
|
95
|
+
overload = if !exact_overloads.empty?
|
96
|
+
exact_overloads.sort_by{|ov, score| score}[0][0]
|
97
|
+
elsif !smaller_overloads.empty?
|
98
|
+
smaller_overloads.sort_by{|ov, score| score}[0][0]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
raise ArgumentError, "Wrong number or types of arguments passed to overloaded PL/SQL procedure" unless overload
|
102
|
+
overload
|
103
|
+
end
|
104
|
+
|
105
|
+
MATCHING_TYPES = {
|
106
|
+
:integer => ['NUMBER', 'PLS_INTEGER', 'BINARY_INTEGER'],
|
107
|
+
:decimal => ['NUMBER', 'BINARY_FLOAT', 'BINARY_DOUBLE'],
|
108
|
+
:string => ['VARCHAR2', 'NVARCHAR2', 'CHAR', 'NCHAR', 'CLOB', 'BLOB'],
|
109
|
+
:date => ['DATE'],
|
110
|
+
:time => ['DATE', 'TIMESTAMP', 'TIMESTAMP WITH TIME ZONE', 'TIMESTAMP WITH LOCAL TIME ZONE'],
|
111
|
+
:boolean => ['PL/SQL BOOLEAN'],
|
112
|
+
:hash => ['PL/SQL RECORD', 'OBJECT', 'PL/SQL TABLE'],
|
113
|
+
:array => ['TABLE', 'VARRAY'],
|
114
|
+
:cursor => ['REF CURSOR']
|
115
|
+
}
|
116
|
+
def matching_oracle_types_for_ruby_value(value)
|
117
|
+
case value
|
118
|
+
when NilClass
|
119
|
+
:all
|
120
|
+
when Fixnum, Bignum
|
121
|
+
MATCHING_TYPES[:integer]
|
122
|
+
when BigDecimal, Float
|
123
|
+
MATCHING_TYPES[:decimal]
|
124
|
+
when String
|
125
|
+
MATCHING_TYPES[:string]
|
126
|
+
when Date
|
127
|
+
MATCHING_TYPES[:date]
|
128
|
+
when Time
|
129
|
+
MATCHING_TYPES[:time]
|
130
|
+
when TrueClass, FalseClass
|
131
|
+
MATCHING_TYPES[:boolean]
|
132
|
+
when Hash
|
133
|
+
MATCHING_TYPES[:hash]
|
134
|
+
when Array
|
135
|
+
MATCHING_TYPES[:array]
|
136
|
+
when CursorCommon
|
137
|
+
MATCHING_TYPES[:cursor]
|
62
138
|
end
|
63
139
|
end
|
64
140
|
|
65
141
|
def construct_sql(args)
|
66
|
-
@declare_sql = "
|
67
|
-
@assignment_sql = "
|
142
|
+
@declare_sql = ""
|
143
|
+
@assignment_sql = ""
|
68
144
|
@call_sql = ""
|
69
145
|
@return_sql = ""
|
70
146
|
@return_vars = []
|
@@ -83,12 +159,13 @@ module PLSQL
|
|
83
159
|
@bind_metadata = {}
|
84
160
|
|
85
161
|
# Named arguments
|
86
|
-
|
87
|
-
|
162
|
+
# there should be just one Hash argument with symbol keys
|
163
|
+
if args.size == 1 && args[0].is_a?(Hash) && args[0].keys.all?{|k| k.is_a?(Symbol)} &&
|
164
|
+
# do not use named arguments if procedure has just one PL/SQL record PL/SQL table or object type argument -
|
88
165
|
# in that case passed Hash should be used as value for this PL/SQL record argument
|
89
166
|
# (which will be processed in sequential arguments bracnh)
|
90
167
|
!(argument_list.size == 1 &&
|
91
|
-
['PL/SQL RECORD','OBJECT'].include?(arguments[(only_argument=argument_list[0])][:data_type]) &&
|
168
|
+
['PL/SQL RECORD','PL/SQL TABLE','OBJECT'].include?(arguments[(only_argument=argument_list[0])][:data_type]) &&
|
92
169
|
args[0].keys != [only_argument])
|
93
170
|
# Add missing output arguments with nil value
|
94
171
|
arguments.each do |arg, metadata|
|
@@ -96,6 +173,8 @@ module PLSQL
|
|
96
173
|
args[0][arg] = nil
|
97
174
|
end
|
98
175
|
end
|
176
|
+
# Add SELF argument if provided
|
177
|
+
args[0][:self] = @self if @self
|
99
178
|
# Add passed parameters to procedure call with parameter names
|
100
179
|
@call_sql << args[0].map do |arg, value|
|
101
180
|
"#{arg} => " << add_argument(arg, value)
|
@@ -103,6 +182,8 @@ module PLSQL
|
|
103
182
|
|
104
183
|
# Sequential arguments
|
105
184
|
else
|
185
|
+
# add SELF as first argument if provided
|
186
|
+
args.unshift(@self) if @self
|
106
187
|
argument_count = argument_list.size
|
107
188
|
raise ArgumentError, "Too many arguments passed to PL/SQL procedure" if args.size > argument_count
|
108
189
|
# Add missing output arguments with nil value
|
@@ -125,62 +206,20 @@ module PLSQL
|
|
125
206
|
else
|
126
207
|
@call_sql = @procedure.call_sql(@call_sql)
|
127
208
|
end
|
128
|
-
|
209
|
+
add_out_variables
|
129
210
|
|
130
211
|
dbms_output_enable_sql, dbms_output_get_sql = dbms_output_sql
|
131
212
|
|
132
|
-
@sql =
|
213
|
+
@sql = @declare_sql.empty? ? "" : "DECLARE\n" << @declare_sql
|
214
|
+
@sql << "BEGIN\n" << @assignment_sql << dbms_output_enable_sql << @call_sql << dbms_output_get_sql << @return_sql << "END;\n"
|
133
215
|
end
|
134
216
|
|
135
|
-
def
|
136
|
-
|
137
|
-
dbms_output_enable_sql = "DBMS_OUTPUT.ENABLE(#{@schema.dbms_output_buffer_size});\n"
|
138
|
-
# if database version is at least 10.2 then use DBMS_OUTPUT.GET_LINES with SYS.DBMSOUTPUT_LINESARRAY
|
139
|
-
if (@schema.connection.database_version <=> [10, 2]) >= 0
|
140
|
-
@declare_sql << "l_dbms_output_numlines INTEGER := #{Schema::DBMS_OUTPUT_MAX_LINES};\n"
|
141
|
-
dbms_output_get_sql = "DBMS_OUTPUT.GET_LINES(:dbms_output_lines, l_dbms_output_numlines);\n"
|
142
|
-
@bind_values[:dbms_output_lines] = nil
|
143
|
-
@bind_metadata[:dbms_output_lines] = {:data_type => 'TABLE', :data_length => nil,
|
144
|
-
:sql_type_name => "SYS.DBMSOUTPUT_LINESARRAY", :in_out => 'OUT'}
|
145
|
-
# if database version is less than 10.2 then use individual DBMS_OUTPUT.GET_LINE calls
|
146
|
-
else
|
147
|
-
dbms_output_get_sql = ""
|
148
|
-
end
|
149
|
-
[dbms_output_enable_sql, dbms_output_get_sql]
|
150
|
-
else
|
151
|
-
["", ""]
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
def dbms_output_log
|
156
|
-
if @dbms_output_stream
|
157
|
-
# if database version is at least 10.2 then :dbms_output_lines output bind variable has dbms_output lines
|
158
|
-
if @bind_metadata[:dbms_output_lines]
|
159
|
-
@cursor[':dbms_output_lines'].each do |line|
|
160
|
-
@dbms_output_stream.puts "DBMS_OUTPUT: #{line}" if line
|
161
|
-
end
|
162
|
-
# if database version is less than 10.2 then use individual DBMS_OUTPUT.GET_LINE calls
|
163
|
-
else
|
164
|
-
cursor = @schema.connection.parse("BEGIN sys.dbms_output.get_line(:line, :status); END;")
|
165
|
-
while true do
|
166
|
-
cursor.bind_param(':line', nil, :data_type => 'VARCHAR2', :in_out => 'OUT')
|
167
|
-
cursor.bind_param(':status', nil, :data_type => 'NUMBER', :in_out => 'OUT')
|
168
|
-
cursor.exec
|
169
|
-
break unless cursor[':status'] == 0
|
170
|
-
@dbms_output_stream.puts "DBMS_OUTPUT: #{cursor[':line']}"
|
171
|
-
end
|
172
|
-
cursor.close
|
173
|
-
end
|
174
|
-
@dbms_output_stream.flush
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
def add_argument(argument, value)
|
179
|
-
argument_metadata = arguments[argument]
|
217
|
+
def add_argument(argument, value, argument_metadata=nil)
|
218
|
+
argument_metadata ||= arguments[argument]
|
180
219
|
raise ArgumentError, "Wrong argument #{argument.inspect} passed to PL/SQL procedure" unless argument_metadata
|
181
220
|
case argument_metadata[:data_type]
|
182
221
|
when 'PL/SQL RECORD'
|
183
|
-
|
222
|
+
add_record_declaration(argument, argument_metadata)
|
184
223
|
record_assignment_sql, record_bind_values, record_bind_metadata =
|
185
224
|
record_assignment_sql_values_metadata(argument, argument_metadata, value)
|
186
225
|
@assignment_sql << record_assignment_sql
|
@@ -194,22 +233,94 @@ module PLSQL
|
|
194
233
|
@bind_metadata[argument] = argument_metadata.merge(:data_type => "NUMBER", :data_precision => 1)
|
195
234
|
"l_#{argument}"
|
196
235
|
else
|
197
|
-
|
198
|
-
|
199
|
-
|
236
|
+
# TABLE or PL/SQL TABLE type defined inside package
|
237
|
+
if argument_metadata[:tmp_table_name]
|
238
|
+
add_table_declaration_and_assignment(argument, argument_metadata)
|
239
|
+
insert_values_into_tmp_table(argument, argument_metadata, value)
|
240
|
+
"l_#{argument}"
|
241
|
+
else
|
242
|
+
@bind_values[argument] = value
|
243
|
+
@bind_metadata[argument] = argument_metadata
|
244
|
+
":#{argument}"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def add_table_declaration_and_assignment(argument, argument_metadata)
|
250
|
+
is_index_by_table = argument_metadata[:data_type] == 'PL/SQL TABLE'
|
251
|
+
@declare_sql << "l_#{argument} #{argument_metadata[:sql_type_name]}#{is_index_by_table ? nil : " := #{argument_metadata[:sql_type_name]}()"};\n"
|
252
|
+
@assignment_sql << "FOR r_#{argument} IN c_#{argument} LOOP\n"
|
253
|
+
@assignment_sql << "l_#{argument}.EXTEND;\n" unless is_index_by_table
|
254
|
+
case argument_metadata[:element][:data_type]
|
255
|
+
when 'PL/SQL RECORD'
|
256
|
+
fields = record_fields_sorted_by_position(argument_metadata[:element][:fields])
|
257
|
+
fields_string = is_index_by_table ? "*" : fields.join(', ')
|
258
|
+
@declare_sql << "CURSOR c_#{argument} IS SELECT #{fields_string} FROM #{argument_metadata[:tmp_table_name]} ORDER BY i__;\n"
|
259
|
+
if is_index_by_table
|
260
|
+
fields.each do |field|
|
261
|
+
@assignment_sql << "l_#{argument}(r_#{argument}.i__).#{field} := r_#{argument}.#{field};\n"
|
262
|
+
end
|
263
|
+
else
|
264
|
+
@assignment_sql << "l_#{argument}(l_#{argument}.COUNT) := r_#{argument};\n"
|
265
|
+
end
|
266
|
+
else
|
267
|
+
@declare_sql << "CURSOR c_#{argument} IS SELECT * FROM #{argument_metadata[:tmp_table_name]} ORDER BY i__;\n"
|
268
|
+
@assignment_sql << "l_#{argument}(r_#{argument}.i__) := r_#{argument}.element;\n"
|
269
|
+
end
|
270
|
+
@assignment_sql << "END LOOP;\n"
|
271
|
+
@assignment_sql << "DELETE FROM #{argument_metadata[:tmp_table_name]};\n"
|
272
|
+
end
|
273
|
+
|
274
|
+
def insert_values_into_tmp_table(argument, argument_metadata, values)
|
275
|
+
return unless values && !values.empty?
|
276
|
+
is_index_by_table = argument_metadata[:data_type] == 'PL/SQL TABLE'
|
277
|
+
if is_index_by_table
|
278
|
+
raise ArgumentError, "Hash value should be passed for #{argument.inspect} argument" unless values.is_a?(Hash)
|
279
|
+
else
|
280
|
+
raise ArgumentError, "Array value should be passed for #{argument.inspect} argument" unless values.is_a?(Array)
|
281
|
+
end
|
282
|
+
tmp_table = @schema.root_schema.send(argument_metadata[:tmp_table_name])
|
283
|
+
# insert values without autocommit
|
284
|
+
old_autocommit = @schema.connection.autocommit?
|
285
|
+
@schema.connection.autocommit = false if old_autocommit
|
286
|
+
case argument_metadata[:element][:data_type]
|
287
|
+
when 'PL/SQL RECORD'
|
288
|
+
values_with_index = []
|
289
|
+
if is_index_by_table
|
290
|
+
values.each{|i,v| values_with_index << v.merge(:i__ => i)}
|
291
|
+
else
|
292
|
+
values.each_with_index{|v,i| values_with_index << v.merge(:i__ => i+1)}
|
293
|
+
end
|
294
|
+
tmp_table.insert values_with_index
|
295
|
+
else
|
296
|
+
values_with_index = []
|
297
|
+
if is_index_by_table
|
298
|
+
values.each{|i,v| values_with_index << [v, i]}
|
299
|
+
else
|
300
|
+
values.each_with_index{|v,i| values_with_index << [v, i+1]}
|
301
|
+
end
|
302
|
+
tmp_table.insert_values [:element, :i__], *values_with_index
|
303
|
+
end
|
304
|
+
@schema.connection.autocommit = true if old_autocommit
|
305
|
+
end
|
306
|
+
|
307
|
+
def add_record_declaration(argument, argument_metadata)
|
308
|
+
@declare_sql << if argument_metadata[:type_subname]
|
309
|
+
"l_#{argument} #{argument_metadata[:sql_type_name]};\n"
|
310
|
+
else
|
311
|
+
fields_metadata = argument_metadata[:fields]
|
312
|
+
sql = "TYPE t_#{argument} IS RECORD (\n"
|
313
|
+
sql << record_fields_sorted_by_position(fields_metadata).map do |field|
|
314
|
+
metadata = fields_metadata[field]
|
315
|
+
"#{field} #{type_to_sql(metadata)}"
|
316
|
+
end.join(",\n")
|
317
|
+
sql << ");\n"
|
318
|
+
sql << "l_#{argument} t_#{argument};\n"
|
200
319
|
end
|
201
320
|
end
|
202
321
|
|
203
|
-
def
|
204
|
-
fields_metadata
|
205
|
-
sql = "TYPE t_#{argument} IS RECORD (\n"
|
206
|
-
fields_sorted_by_position = fields_metadata.keys.sort_by{|k| fields_metadata[k][:position]}
|
207
|
-
sql << fields_sorted_by_position.map do |field|
|
208
|
-
metadata = fields_metadata[field]
|
209
|
-
"#{field} #{type_to_sql(metadata)}"
|
210
|
-
end.join(",\n")
|
211
|
-
sql << ");\n"
|
212
|
-
sql << "l_#{argument} t_#{argument};\n"
|
322
|
+
def record_fields_sorted_by_position(fields_metadata)
|
323
|
+
fields_metadata.keys.sort_by{|k| fields_metadata[k][:position]}
|
213
324
|
end
|
214
325
|
|
215
326
|
def record_assignment_sql_values_metadata(argument, argument_metadata, record_value)
|
@@ -229,136 +340,165 @@ module PLSQL
|
|
229
340
|
end
|
230
341
|
|
231
342
|
def add_return
|
232
|
-
|
343
|
+
add_return_variable(:return, return_metadata, true)
|
344
|
+
end
|
345
|
+
|
346
|
+
def add_out_variables
|
347
|
+
out_list.each do |argument|
|
348
|
+
add_return_variable(argument, arguments[argument])
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def add_return_variable(argument, argument_metadata, is_return_value=false)
|
353
|
+
case argument_metadata[:data_type]
|
233
354
|
when 'PL/SQL RECORD'
|
234
|
-
|
235
|
-
|
236
|
-
|
355
|
+
add_record_declaration(argument, argument_metadata) if is_return_value
|
356
|
+
argument_metadata[:fields].each do |field, metadata|
|
357
|
+
# should use different output bind variable as JDBC does not support
|
358
|
+
# if output bind variable appears in several places
|
359
|
+
bind_variable = :"#{argument}_o#{metadata[:position]}"
|
237
360
|
@return_vars << bind_variable
|
238
361
|
@return_vars_metadata[bind_variable] = metadata
|
239
|
-
@return_sql << ":#{bind_variable} :=
|
362
|
+
@return_sql << ":#{bind_variable} := l_#{argument}.#{field};\n"
|
240
363
|
end
|
241
|
-
"
|
364
|
+
"l_#{argument} := " if is_return_value
|
242
365
|
when 'PL/SQL BOOLEAN'
|
243
|
-
@declare_sql << "
|
244
|
-
@declare_sql << "
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
"
|
366
|
+
@declare_sql << "l_#{argument} BOOLEAN;\n" if is_return_value
|
367
|
+
@declare_sql << "o_#{argument} NUMBER(1);\n"
|
368
|
+
# should use different output bind variable as JDBC does not support
|
369
|
+
# if output bind variable appears in several places
|
370
|
+
bind_variable = :"o_#{argument}"
|
371
|
+
@return_vars << bind_variable
|
372
|
+
@return_vars_metadata[bind_variable] = argument_metadata.merge(:data_type => "NUMBER", :data_precision => 1)
|
373
|
+
@return_sql << "IF l_#{argument} IS NULL THEN\no_#{argument} := NULL;\n" <<
|
374
|
+
"ELSIF l_#{argument} THEN\no_#{argument} := 1;\nELSE\no_#{argument} := 0;\nEND IF;\n" <<
|
375
|
+
":#{bind_variable} := o_#{argument};\n"
|
376
|
+
"l_#{argument} := " if is_return_value
|
250
377
|
else
|
251
|
-
|
252
|
-
|
253
|
-
|
378
|
+
if argument_metadata[:tmp_table_name]
|
379
|
+
add_return_table(argument, argument_metadata, is_return_value)
|
380
|
+
elsif is_return_value
|
381
|
+
@return_vars << argument
|
382
|
+
@return_vars_metadata[argument] = argument_metadata
|
383
|
+
":#{argument} := "
|
384
|
+
end
|
254
385
|
end
|
255
386
|
end
|
256
387
|
|
257
|
-
def
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
388
|
+
def add_return_table(argument, argument_metadata, is_return_value=false)
|
389
|
+
is_index_by_table = argument_metadata[:data_type] == 'PL/SQL TABLE'
|
390
|
+
declare_i__
|
391
|
+
@declare_sql << "l_return #{return_metadata[:sql_type_name]};\n" if is_return_value
|
392
|
+
@return_vars << argument
|
393
|
+
@return_vars_metadata[argument] = argument_metadata.merge(:data_type => "REF CURSOR")
|
394
|
+
@return_sql << if is_index_by_table
|
395
|
+
"i__ := l_#{argument}.FIRST;\nLOOP\nEXIT WHEN i__ IS NULL;\n"
|
396
|
+
else
|
397
|
+
"FOR i__ IN l_#{argument}.FIRST..l_#{argument}.LAST LOOP\n"
|
398
|
+
end
|
399
|
+
case argument_metadata[:element][:data_type]
|
400
|
+
when 'PL/SQL RECORD'
|
401
|
+
field_names = record_fields_sorted_by_position(argument_metadata[:element][:fields])
|
402
|
+
values_string = field_names.map{|f| "l_return(i__).#{f}"}.join(', ')
|
403
|
+
@return_sql << "INSERT INTO #{argument_metadata[:tmp_table_name]} VALUES (#{values_string}, i__);\n"
|
404
|
+
return_fields_string = is_index_by_table ? '*' : field_names.join(', ')
|
405
|
+
else
|
406
|
+
@return_sql << "INSERT INTO #{argument_metadata[:tmp_table_name]} VALUES (l_#{argument}(i__), i__);\n"
|
407
|
+
return_fields_string = '*'
|
277
408
|
end
|
409
|
+
@return_sql << "i__ := l_#{argument}.NEXT(i__);\n" if is_index_by_table
|
410
|
+
@return_sql << "END LOOP;\n"
|
411
|
+
@return_sql << "OPEN :#{argument} FOR SELECT #{return_fields_string} FROM #{argument_metadata[:tmp_table_name]} ORDER BY i__;\n"
|
412
|
+
@return_sql << "DELETE FROM #{argument_metadata[:tmp_table_name]};\n"
|
413
|
+
"l_#{argument} := " if is_return_value
|
278
414
|
end
|
279
415
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
when 'VARCHAR2', 'CHAR', 'NVARCHAR2', 'NCHAR'
|
286
|
-
length = metadata[:data_length]
|
287
|
-
if length && (char_used = metadata[:char_used])
|
288
|
-
length = "#{length} #{char_used == 'C' ? 'CHAR' : 'BYTE'}"
|
289
|
-
end
|
290
|
-
"#{metadata[:data_type]}#{length ? "(#{length})": ""}"
|
291
|
-
when 'TABLE', 'VARRAY', 'OBJECT'
|
292
|
-
metadata[:sql_type_name]
|
293
|
-
else
|
294
|
-
metadata[:data_type]
|
416
|
+
# declare once temp variable i__ that is used as itertor
|
417
|
+
def declare_i__
|
418
|
+
unless @declared_i__
|
419
|
+
@declare_sql << "i__ PLS_INTEGER;\n"
|
420
|
+
@declared_i__ = true
|
295
421
|
end
|
296
422
|
end
|
297
423
|
|
424
|
+
def type_to_sql(metadata)
|
425
|
+
ProcedureCommon.type_to_sql(metadata)
|
426
|
+
end
|
427
|
+
|
298
428
|
def get_return_value
|
299
429
|
# if function with output parameters
|
300
430
|
if return_metadata && out_list.size > 0
|
301
431
|
result = [function_return_value, {}]
|
302
432
|
out_list.each do |k|
|
303
|
-
result[1][k] =
|
433
|
+
result[1][k] = out_variable_value(k)
|
304
434
|
end
|
435
|
+
result
|
305
436
|
# if function without output parameters
|
306
437
|
elsif return_metadata
|
307
|
-
|
438
|
+
function_return_value
|
308
439
|
# if procedure with output parameters
|
309
440
|
elsif out_list.size > 0
|
310
441
|
result = {}
|
311
442
|
out_list.each do |k|
|
312
|
-
result[k] =
|
443
|
+
result[k] = out_variable_value(k)
|
313
444
|
end
|
445
|
+
result
|
314
446
|
# if procedure without output parameters
|
315
447
|
else
|
316
|
-
|
448
|
+
nil
|
317
449
|
end
|
318
|
-
result
|
319
450
|
end
|
320
451
|
|
321
452
|
def function_return_value
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
return_value[field] = @cursor[":#{bind_variable}"]
|
328
|
-
end
|
329
|
-
return_value
|
330
|
-
when 'PL/SQL BOOLEAN'
|
331
|
-
numeric_value = @cursor[':return']
|
332
|
-
numeric_value.nil? ? nil : numeric_value == 1
|
333
|
-
else
|
334
|
-
@cursor[':return']
|
335
|
-
end
|
453
|
+
return_variable_value(:return, return_metadata)
|
454
|
+
end
|
455
|
+
|
456
|
+
def out_variable_value(argument)
|
457
|
+
return_variable_value(argument, arguments[argument])
|
336
458
|
end
|
337
459
|
|
338
|
-
def
|
339
|
-
argument_metadata = arguments[argument]
|
460
|
+
def return_variable_value(argument, argument_metadata)
|
340
461
|
case argument_metadata[:data_type]
|
341
462
|
when 'PL/SQL RECORD'
|
342
463
|
return_value = {}
|
343
464
|
argument_metadata[:fields].each do |field, metadata|
|
344
|
-
|
345
|
-
return_value[field] = @cursor[":#{bind_variable}"]
|
465
|
+
return_value[field] = @cursor[":#{argument}_o#{metadata[:position]}"]
|
346
466
|
end
|
347
467
|
return_value
|
348
468
|
when 'PL/SQL BOOLEAN'
|
349
469
|
numeric_value = @cursor[":o_#{argument}"]
|
350
470
|
numeric_value.nil? ? nil : numeric_value == 1
|
351
471
|
else
|
352
|
-
|
472
|
+
if argument_metadata[:tmp_table_name]
|
473
|
+
is_index_by_table = argument_metadata[:data_type] == 'PL/SQL TABLE'
|
474
|
+
case argument_metadata[:element][:data_type]
|
475
|
+
when 'PL/SQL RECORD'
|
476
|
+
if is_index_by_table
|
477
|
+
Hash[*@cursor[":#{argument}"].fetch_hash_all.map{|row| [row.delete(:i__), row]}.flatten]
|
478
|
+
else
|
479
|
+
@cursor[":#{argument}"].fetch_hash_all
|
480
|
+
end
|
481
|
+
else
|
482
|
+
if is_index_by_table
|
483
|
+
Hash[*@cursor[":#{argument}"].fetch_all.map{|row| [row[1], row[0]]}.flatten]
|
484
|
+
else
|
485
|
+
@cursor[":#{argument}"].fetch_all.map{|row| row[0]}
|
486
|
+
end
|
487
|
+
end
|
488
|
+
else
|
489
|
+
@cursor[":#{argument}"]
|
490
|
+
end
|
353
491
|
end
|
354
492
|
end
|
355
493
|
|
356
494
|
def overload_argument_list
|
357
|
-
@overload_argument_list ||=
|
495
|
+
@overload_argument_list ||=
|
496
|
+
@skip_self ? @procedure.argument_list_without_self : @procedure.argument_list
|
358
497
|
end
|
359
498
|
|
360
499
|
def overload_arguments
|
361
|
-
@overload_arguments ||=
|
500
|
+
@overload_arguments ||=
|
501
|
+
@skip_self ? @procedure.arguments_without_self : @procedure.arguments
|
362
502
|
end
|
363
503
|
|
364
504
|
def argument_list
|
@@ -374,7 +514,8 @@ module PLSQL
|
|
374
514
|
end
|
375
515
|
|
376
516
|
def out_list
|
377
|
-
@out_list ||=
|
517
|
+
@out_list ||=
|
518
|
+
@skip_self ? @procedure.out_list_without_self[@overload] : @procedure.out_list[@overload]
|
378
519
|
end
|
379
520
|
|
380
521
|
def schema_name
|
@@ -389,6 +530,49 @@ module PLSQL
|
|
389
530
|
@procedure_name ||= @procedure.procedure
|
390
531
|
end
|
391
532
|
|
533
|
+
def dbms_output_sql
|
534
|
+
if @dbms_output_stream
|
535
|
+
dbms_output_enable_sql = "DBMS_OUTPUT.ENABLE(#{@schema.dbms_output_buffer_size});\n"
|
536
|
+
# if database version is at least 10.2 then use DBMS_OUTPUT.GET_LINES with SYS.DBMSOUTPUT_LINESARRAY
|
537
|
+
if (@schema.connection.database_version <=> [10, 2]) >= 0
|
538
|
+
@declare_sql << "l_dbms_output_numlines INTEGER := #{Schema::DBMS_OUTPUT_MAX_LINES};\n"
|
539
|
+
dbms_output_get_sql = "DBMS_OUTPUT.GET_LINES(:dbms_output_lines, l_dbms_output_numlines);\n"
|
540
|
+
@bind_values[:dbms_output_lines] = nil
|
541
|
+
@bind_metadata[:dbms_output_lines] = {:data_type => 'TABLE', :data_length => nil,
|
542
|
+
:sql_type_name => "SYS.DBMSOUTPUT_LINESARRAY", :in_out => 'OUT'}
|
543
|
+
# if database version is less than 10.2 then use individual DBMS_OUTPUT.GET_LINE calls
|
544
|
+
else
|
545
|
+
dbms_output_get_sql = ""
|
546
|
+
end
|
547
|
+
[dbms_output_enable_sql, dbms_output_get_sql]
|
548
|
+
else
|
549
|
+
["", ""]
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
def dbms_output_log
|
554
|
+
if @dbms_output_stream
|
555
|
+
# if database version is at least 10.2 then :dbms_output_lines output bind variable has dbms_output lines
|
556
|
+
if @bind_metadata[:dbms_output_lines]
|
557
|
+
@cursor[':dbms_output_lines'].each do |line|
|
558
|
+
@dbms_output_stream.puts "DBMS_OUTPUT: #{line}" if line
|
559
|
+
end
|
560
|
+
# if database version is less than 10.2 then use individual DBMS_OUTPUT.GET_LINE calls
|
561
|
+
else
|
562
|
+
cursor = @schema.connection.parse("BEGIN sys.dbms_output.get_line(:line, :status); END;")
|
563
|
+
while true do
|
564
|
+
cursor.bind_param(':line', nil, :data_type => 'VARCHAR2', :in_out => 'OUT')
|
565
|
+
cursor.bind_param(':status', nil, :data_type => 'NUMBER', :in_out => 'OUT')
|
566
|
+
cursor.exec
|
567
|
+
break unless cursor[':status'] == 0
|
568
|
+
@dbms_output_stream.puts "DBMS_OUTPUT: #{cursor[':line']}"
|
569
|
+
end
|
570
|
+
cursor.close
|
571
|
+
end
|
572
|
+
@dbms_output_stream.flush
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
392
576
|
end
|
393
577
|
|
394
578
|
end
|