ruby-plsql 0.4.1 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|