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 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
1
+ 0.4.2
@@ -3,27 +3,49 @@ module PLSQL
3
3
  attr_reader :raw_driver
4
4
  attr_reader :activerecord_class
5
5
 
6
- def initialize(raw_drv, raw_conn, ar_class = nil) #:nodoc:
7
- @raw_driver = raw_drv
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
- OCIConnection.new(:oci, raw_conn, ar_class)
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
- JDBCConnection.new(:jdbc, raw_conn, ar_class)
43
+ :jdbc
22
44
  else
23
- raise ArgumentError, "Unknown raw driver"
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
- raise NoMethodError, "Not implemented for this raw driver"
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','REF CURSOR'].include?(metadata[:data_type])
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,
@@ -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
 
@@ -42,17 +42,31 @@ module PLSQL
42
42
  end
43
43
  end
44
44
 
45
- class Procedure #:nodoc:
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
- def initialize(schema, procedure, package, override_schema_name, object_id)
52
- @schema = schema
53
- @schema_name = override_schema_name || schema.schema_name
54
- @procedure = procedure.to_s.upcase
55
- @package = package
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, @package ? @package : 'nil'
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
- raise ArgumentError, "Parameter type definition inside package is not supported, use CREATE TYPE outside package" if type_subname
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 => "#{type_owner}.#{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
- if argument_name
158
+ else
113
159
  # top level parameter
114
160
  if data_level == 0
115
- @arguments[overload][argument_name.downcase.to_sym] = argument_metadata
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
- PLSQL_COMPOSITE_TYPES = ['PL/SQL RECORD', 'TABLE', 'VARRAY'].freeze
141
- def composite_type?(data_type)
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
- def overloaded?
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)
@@ -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
- # find which overloaded definition to use
41
- # if definition is overloaded then match by number of arguments
42
- if @procedure.overloaded?
43
- # named arguments
44
- if args.size == 1 && args[0].is_a?(Hash)
45
- number_of_args = args[0].keys.size
46
- overload = overload_argument_list.keys.detect do |ov|
47
- overload_argument_list[ov].size == number_of_args &&
48
- overload_arguments[ov].keys.sort_by{|k| k.to_s} == args[0].keys.sort_by{|k| k.to_s}
49
- end
50
- # sequential arguments
51
- # TODO: should try to implement matching by types of arguments
52
- else
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
- raise ArgumentError, "Wrong number of arguments passed to overloaded PL/SQL procedure" unless overload
59
- overload
58
+ # otherwise try matching by sequential arguments count and types
60
59
  else
61
- 0
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 = "DECLARE\n"
67
- @assignment_sql = "BEGIN\n"
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
- if args.size == 1 && args[0].is_a?(Hash) &&
87
- # do not use named arguments if procedure has just one PL/SQL record or object type argument -
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
- add_out_vars
209
+ add_out_variables
129
210
 
130
211
  dbms_output_enable_sql, dbms_output_get_sql = dbms_output_sql
131
212
 
132
- @sql = "" << @declare_sql << @assignment_sql << dbms_output_enable_sql << @call_sql << dbms_output_get_sql << @return_sql << "END;\n"
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 dbms_output_sql
136
- if @dbms_output_stream
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
- @declare_sql << record_declaration_sql(argument, argument_metadata)
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
- @bind_values[argument] = value
198
- @bind_metadata[argument] = argument_metadata
199
- ":#{argument}"
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 record_declaration_sql(argument, argument_metadata)
204
- fields_metadata = argument_metadata[:fields]
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
- case return_metadata[:data_type]
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
- @declare_sql << record_declaration_sql('return', return_metadata)
235
- return_metadata[:fields].each do |field, metadata|
236
- bind_variable = :"return_f#{metadata[:position]}"
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} := l_return.#{field};\n"
362
+ @return_sql << ":#{bind_variable} := l_#{argument}.#{field};\n"
240
363
  end
241
- "l_return := "
364
+ "l_#{argument} := " if is_return_value
242
365
  when 'PL/SQL BOOLEAN'
243
- @declare_sql << "l_return BOOLEAN;\n"
244
- @declare_sql << "x_return NUMBER(1);\n"
245
- @return_vars << :return
246
- @return_vars_metadata[:return] = return_metadata.merge(:data_type => "NUMBER", :data_precision => 1)
247
- @return_sql << "IF l_return IS NULL THEN\nx_return := NULL;\nELSIF l_return THEN\nx_return := 1;\nELSE\nx_return := 0;\nEND IF;\n" <<
248
- ":return := x_return;\n"
249
- "l_return := "
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
- @return_vars << :return
252
- @return_vars_metadata[:return] = return_metadata
253
- ':return := '
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 add_out_vars
258
- out_list.each do |argument|
259
- argument_metadata = arguments[argument]
260
- case argument_metadata[:data_type]
261
- when 'PL/SQL RECORD'
262
- argument_metadata[:fields].each do |field, metadata|
263
- bind_variable = :"#{argument}_o#{metadata[:position]}"
264
- @return_vars << bind_variable
265
- @return_vars_metadata[bind_variable] = metadata
266
- @return_sql << ":#{bind_variable} := l_#{argument}.#{field};\n"
267
- end
268
- when 'PL/SQL BOOLEAN'
269
- @declare_sql << "x_#{argument} NUMBER(1);\n"
270
- bind_variable = :"o_#{argument}"
271
- @return_vars << bind_variable
272
- @return_vars_metadata[bind_variable] = argument_metadata.merge(:data_type => "NUMBER", :data_precision => 1)
273
- @return_sql << "IF l_#{argument} IS NULL THEN\nx_#{argument} := NULL;\n" <<
274
- "ELSIF l_#{argument} THEN\nx_#{argument} := 1;\nELSE\nx_#{argument} := 0;\nEND IF;\n" <<
275
- ":#{bind_variable} := x_#{argument};\n"
276
- end
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
- def type_to_sql(metadata)
281
- case metadata[:data_type]
282
- when 'NUMBER'
283
- precision, scale = metadata[:data_precision], metadata[:data_scale]
284
- "NUMBER#{precision ? "(#{precision}#{scale ? ",#{scale}": ""})" : ""}"
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] = out_var_value(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
- result = function_return_value
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] = out_var_value(k)
443
+ result[k] = out_variable_value(k)
313
444
  end
445
+ result
314
446
  # if procedure without output parameters
315
447
  else
316
- result = nil
448
+ nil
317
449
  end
318
- result
319
450
  end
320
451
 
321
452
  def function_return_value
322
- case return_metadata[:data_type]
323
- when 'PL/SQL RECORD'
324
- return_value = {}
325
- return_metadata[:fields].each do |field, metadata|
326
- bind_variable = :"return_f#{metadata[:position]}"
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 out_var_value(argument)
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
- bind_variable = :"#{argument}_o#{metadata[:position]}"
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
- @cursor[":#{argument}"]
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 ||= @procedure.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 ||= @procedure.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 ||= @procedure.out_list[@overload]
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