ruby-plsql 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,16 +3,16 @@ module PLSQL
3
3
  module ProcedureClassMethods #:nodoc:
4
4
  def find(schema, procedure, package = nil, override_schema_name = nil)
5
5
  if package.nil?
6
- if schema.select_first("
7
- SELECT object_name FROM all_objects
6
+ if (row = schema.select_first(
7
+ "SELECT object_id FROM all_objects
8
8
  WHERE owner = :owner
9
9
  AND object_name = :object_name
10
10
  AND object_type IN ('PROCEDURE','FUNCTION')",
11
- schema.schema_name, procedure.to_s.upcase)
12
- new(schema, procedure)
11
+ schema.schema_name, procedure.to_s.upcase))
12
+ new(schema, procedure, nil, nil, row[0])
13
13
  # search for synonym
14
- elsif (row = schema.select_first("
15
- SELECT o.owner, o.object_name
14
+ elsif (row = schema.select_first(
15
+ "SELECT o.owner, o.object_name, o.object_id
16
16
  FROM all_synonyms s, all_objects o
17
17
  WHERE s.owner IN (:owner, 'PUBLIC')
18
18
  AND s.synonym_name = :synonym_name
@@ -21,17 +21,21 @@ module PLSQL
21
21
  AND o.object_type IN ('PROCEDURE','FUNCTION')
22
22
  ORDER BY DECODE(s.owner, 'PUBLIC', 1, 0)",
23
23
  schema.schema_name, procedure.to_s.upcase))
24
- new(schema, row[1], nil, row[0])
24
+ new(schema, row[1], nil, row[0], row[2])
25
25
  else
26
26
  nil
27
27
  end
28
- elsif package && schema.select_first("
29
- SELECT object_name FROM all_procedures
30
- WHERE owner = :owner
31
- AND object_name = :object_name
32
- AND procedure_name = :procedure_name
33
- ", override_schema_name || schema.schema_name, package, procedure.to_s.upcase)
34
- new(schema, procedure, package, override_schema_name)
28
+ elsif package && (row = schema.select_first(
29
+ # older Oracle versions do not have object_id column in all_procedures
30
+ "SELECT o.object_id FROM all_procedures p, all_objects o
31
+ WHERE p.owner = :owner
32
+ AND p.object_name = :object_name
33
+ AND p.procedure_name = :procedure_name
34
+ AND o.owner = p.owner
35
+ AND o.object_name = p.object_name
36
+ AND o.object_type = 'PACKAGE'",
37
+ override_schema_name || schema.schema_name, package, procedure.to_s.upcase))
38
+ new(schema, procedure, package, override_schema_name, row[0])
35
39
  else
36
40
  nil
37
41
  end
@@ -44,7 +48,7 @@ module PLSQL
44
48
  attr_reader :arguments, :argument_list, :out_list, :return
45
49
  attr_reader :schema, :schema_name, :package, :procedure
46
50
 
47
- def initialize(schema, procedure, package = nil, override_schema_name = nil)
51
+ def initialize(schema, procedure, package, override_schema_name, object_id)
48
52
  @schema = schema
49
53
  @schema_name = override_schema_name || schema.schema_name
50
54
  @procedure = procedure.to_s.upcase
@@ -58,18 +62,8 @@ module PLSQL
58
62
  # store reference to previous level record or collection metadata
59
63
  previous_level_argument_metadata = {}
60
64
 
61
- # RSI: due to 10gR2 all_arguments performance issue SELECT split into two statements
62
- # added condition to ensure that if object is package then package specification not body is selected
63
- object_id = @schema.connection.select_first("
64
- SELECT o.object_id
65
- FROM all_objects o
66
- WHERE o.owner = :owner
67
- AND o.object_name = :object_name
68
- AND o.object_type <> 'PACKAGE BODY'
69
- ", @schema_name, @package ? @package : @procedure
70
- )[0] rescue nil
71
- num_rows = @schema.connection.select_all("
72
- SELECT overload, argument_name, position, data_level,
65
+ @schema.select_all(
66
+ "SELECT overload, argument_name, position, data_level,
73
67
  data_type, in_out, data_length, data_precision, data_scale, char_used,
74
68
  type_owner, type_name, type_subname
75
69
  FROM all_arguments
@@ -77,8 +71,8 @@ module PLSQL
77
71
  AND owner = :owner
78
72
  AND object_name = :procedure_name
79
73
  AND NVL(package_name,'nil') = :package
80
- ORDER BY overload, sequence
81
- ", object_id, @schema_name, @procedure, @package ? @package : 'nil'
74
+ ORDER BY overload, sequence",
75
+ object_id, @schema_name, @procedure, @package ? @package : 'nil'
82
76
  ) do |r|
83
77
 
84
78
  overload, argument_name, position, data_level,
@@ -3,12 +3,14 @@ module PLSQL
3
3
 
4
4
  def initialize(procedure, args = [])
5
5
  @procedure = procedure
6
+ @schema = @procedure.schema
7
+ @dbms_output_stream = @schema.dbms_output_stream
6
8
  @overload = get_overload_from_arguments_list(args)
7
9
  construct_sql(args)
8
10
  end
9
11
 
10
12
  def exec
11
- @cursor = @procedure.schema.connection.parse(@sql)
13
+ @cursor = @schema.connection.parse(@sql)
12
14
 
13
15
  @bind_values.each do |arg, value|
14
16
  @cursor.bind_param(":#{arg}", value, @bind_metadata[arg])
@@ -20,6 +22,8 @@ module PLSQL
20
22
 
21
23
  @cursor.exec
22
24
 
25
+ dbms_output_log
26
+
23
27
  if block_given?
24
28
  yield get_return_value
25
29
  nil
@@ -66,10 +70,10 @@ module PLSQL
66
70
  @return_vars = []
67
71
  @return_vars_metadata = {}
68
72
 
73
+ @call_sql << add_return if return_metadata
69
74
  # construct procedure call if procedure name is available
70
75
  # otherwise will get surrounding call_sql from @procedure (used for table statements)
71
76
  if procedure_name
72
- @call_sql << add_return if return_metadata
73
77
  @call_sql << "#{schema_name}." if schema_name
74
78
  @call_sql << "#{package_name}." if package_name
75
79
  @call_sql << "#{procedure_name}("
@@ -122,8 +126,53 @@ module PLSQL
122
126
  @call_sql = @procedure.call_sql(@call_sql)
123
127
  end
124
128
  add_out_vars
125
- @sql = "" << @declare_sql << @assignment_sql << @call_sql << @return_sql << "END;\n"
126
- # puts "DEBUG: sql = #{@sql.gsub "\n", "<br/>\n"}"
129
+
130
+ dbms_output_enable_sql, dbms_output_get_sql = dbms_output_sql
131
+
132
+ @sql = "" << @declare_sql << @assignment_sql << dbms_output_enable_sql << @call_sql << dbms_output_get_sql << @return_sql << "END;\n"
133
+ end
134
+
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
127
176
  end
128
177
 
129
178
  def add_argument(argument, value)
data/lib/plsql/schema.rb CHANGED
@@ -3,7 +3,7 @@ module PLSQL
3
3
  include SQLStatements
4
4
 
5
5
  @@schemas = {}
6
-
6
+
7
7
  class <<self
8
8
  def find_or_new(connection_alias) #:nodoc:
9
9
  connection_alias ||= :default
@@ -15,18 +15,16 @@ module PLSQL
15
15
  end
16
16
 
17
17
  end
18
-
19
- def initialize(raw_conn = nil, schema = nil, first = true) #:nodoc:
18
+
19
+ def initialize(raw_conn = nil, schema = nil, original_schema = nil) #:nodoc:
20
20
  self.connection = raw_conn
21
21
  @schema_name = schema ? schema.to_s.upcase : nil
22
- @first = first
22
+ @original_schema = original_schema
23
23
  end
24
-
24
+
25
25
  # Returns connection wrapper object (this is not raw OCI8 or JDBC connection!)
26
- def connection
27
- @connection
28
- end
29
-
26
+ attr_reader :connection
27
+
30
28
  def raw_connection=(raw_conn) #:nodoc:
31
29
  @connection = raw_conn ? Connection.create(raw_conn) : nil
32
30
  reset_instance_variables
@@ -49,6 +47,7 @@ module PLSQL
49
47
  else
50
48
  self.raw_connection = conn
51
49
  end
50
+ conn
52
51
  end
53
52
 
54
53
  # Set connection to current ActiveRecord connection (use in initializer file):
@@ -58,6 +57,7 @@ module PLSQL
58
57
  def activerecord_class=(ar_class)
59
58
  @connection = ar_class ? Connection.create(nil, ar_class) : nil
60
59
  reset_instance_variables
60
+ ar_class
61
61
  end
62
62
 
63
63
  # Disconnect from Oracle
@@ -72,22 +72,23 @@ module PLSQL
72
72
  @schema_name ||= select_first("SELECT SYS_CONTEXT('userenv','session_user') FROM dual")[0]
73
73
  end
74
74
 
75
- # Set to :local or :utc
76
- @@default_timezone = nil
77
-
78
75
  # Default timezone to which database values will be converted - :utc or :local
79
76
  def default_timezone
80
- @@default_timezone ||
81
- # Use ActiveRecord class default_timezone when ActiveRecord connection is used
82
- (@connection && (ar_class = @connection.activerecord_class) && ar_class.default_timezone) ||
83
- # default to local timezone
84
- :local
77
+ if @original_schema
78
+ @original_schema.default_timezone
79
+ else
80
+ @default_timezone ||
81
+ # Use ActiveRecord class default_timezone when ActiveRecord connection is used
82
+ (@connection && (ar_class = @connection.activerecord_class) && ar_class.default_timezone) ||
83
+ # default to local timezone
84
+ :local
85
+ end
85
86
  end
86
87
 
87
88
  # Set default timezone to which database values will be converted - :utc or :local
88
89
  def default_timezone=(value)
89
90
  if [:local, :utc].include?(value)
90
- @@default_timezone = value
91
+ @default_timezone = value
91
92
  else
92
93
  raise ArgumentError, "default timezone should be :local or :utc"
93
94
  end
@@ -98,7 +99,47 @@ module PLSQL
98
99
  def local_timezone_offset #:nodoc:
99
100
  ::Time.local(2007).utc_offset.to_r / 86400
100
101
  end
101
-
102
+
103
+ # DBMS_OUTPUT buffer size (default is 20_000)
104
+ def dbms_output_buffer_size
105
+ if @original_schema
106
+ @original_schema.dbms_output_buffer_size
107
+ else
108
+ @dbms_output_buffer_size || 20_000
109
+ end
110
+ end
111
+
112
+ # Seet DBMS_OUTPUT buffer size (default is 20_000). Example:
113
+ #
114
+ # plsql.dbms_output_buffer_size = 100_000
115
+ #
116
+ def dbms_output_buffer_size=(value)
117
+ @dbms_output_buffer_size = value
118
+ end
119
+
120
+ # Maximum line numbers for DBMS_OUTPUT in one PL/SQL call (from DBMSOUTPUT_LINESARRAY type)
121
+ DBMS_OUTPUT_MAX_LINES = 2147483647
122
+
123
+ # Specify IO stream where to log DBMS_OUTPUT from PL/SQL procedures. Example:
124
+ #
125
+ # plsql.dbms_output_stream = STDOUT
126
+ #
127
+ def dbms_output_stream=(stream)
128
+ @dbms_output_stream = stream
129
+ if @dbms_output_stream.nil? && @connection
130
+ sys.dbms_output.disable
131
+ end
132
+ end
133
+
134
+ # IO stream where to log DBMS_OUTPUT from PL/SQL procedures.
135
+ def dbms_output_stream
136
+ if @original_schema
137
+ @original_schema.dbms_output_stream
138
+ else
139
+ @dbms_output_stream
140
+ end
141
+ end
142
+
102
143
  private
103
144
 
104
145
  def reset_instance_variables
@@ -108,44 +149,72 @@ module PLSQL
108
149
  @schema_objects = nil
109
150
  end
110
151
  @schema_name = nil
111
- @@default_timezone = nil
152
+ @default_timezone = nil
112
153
  end
113
-
154
+
114
155
  def method_missing(method, *args, &block)
115
- raise ArgumentError, "No PL/SQL connection" unless connection
116
- # look in cache at first
117
- if schema_object = @schema_objects[method]
118
- if schema_object.is_a?(Procedure)
119
- schema_object.exec(*args, &block)
120
- else
121
- schema_object
122
- end
123
- # search in database
124
- elsif procedure = Procedure.find(self, method)
125
- @schema_objects[method] = procedure
126
- procedure.exec(*args, &block)
127
- elsif package = Package.find(self, method)
128
- @schema_objects[method] = package
129
- elsif table = Table.find(self, method)
130
- @schema_objects[method] = table
131
- elsif sequence = Sequence.find(self, method)
132
- @schema_objects[method] = sequence
133
- elsif schema = find_other_schema(method)
134
- @schema_objects[method] = schema
156
+ raise ArgumentError, "No database connection" unless connection
157
+ # search in database if not in cache at first
158
+ object = (@schema_objects[method] ||= find_database_object(method) || find_other_schema(method) ||
159
+ find_public_synonym(method)) || find_standard_procedure(method)
160
+
161
+ raise ArgumentError, "No database object '#{method.to_s.upcase}' found" unless object
162
+
163
+ if object.is_a?(Procedure)
164
+ object.exec(*args, &block)
135
165
  else
136
- raise ArgumentError, "No PL/SQL procedure found"
166
+ object
167
+ end
168
+ end
169
+
170
+ def find_database_object(name, override_schema_name = nil)
171
+ object_schema_name = override_schema_name || schema_name
172
+ object_name = name.to_s.upcase
173
+ if row = select_first(
174
+ "SELECT object_type, object_id FROM all_objects
175
+ WHERE owner = :owner AND object_name = :object_name
176
+ AND object_type IN ('PROCEDURE','FUNCTION','PACKAGE','TABLE','VIEW','SEQUENCE','TYPE','SYNONYM')",
177
+ object_schema_name, object_name)
178
+ case row[0]
179
+ when 'PROCEDURE', 'FUNCTION'
180
+ Procedure.new(self, name, nil, override_schema_name, row[1])
181
+ when 'PACKAGE', 'PACKAGE BODY'
182
+ Package.new(self, name, override_schema_name)
183
+ when 'TABLE'
184
+ Table.new(self, name, override_schema_name)
185
+ when 'VIEW'
186
+ View.new(self, name, override_schema_name)
187
+ when 'SEQUENCE'
188
+ Sequence.new(self, name, override_schema_name)
189
+ when 'TYPE'
190
+ Type.new(self, name, override_schema_name)
191
+ when 'SYNONYM'
192
+ target_schema_name, target_object_name = @connection.describe_synonym(object_schema_name, object_name)
193
+ find_database_object(target_object_name, target_schema_name)
194
+ end
137
195
  end
138
196
  end
139
197
 
140
198
  def find_other_schema(name)
141
- return nil unless @first && connection
199
+ return nil if @original_schema
142
200
  if select_first("SELECT username FROM all_users WHERE username = :username", name.to_s.upcase)
143
- Schema.new(connection, name, false)
201
+ Schema.new(connection, name, self)
144
202
  else
145
203
  nil
146
204
  end
147
205
  end
148
-
206
+
207
+ def find_standard_procedure(name)
208
+ return nil if @original_schema
209
+ Procedure.find(self, name, 'STANDARD', 'SYS')
210
+ end
211
+
212
+ def find_public_synonym(name)
213
+ return nil if @original_schema
214
+ target_schema_name, target_object_name = @connection.describe_synonym('PUBLIC', name)
215
+ find_database_object(target_object_name, target_schema_name) if target_schema_name
216
+ end
217
+
149
218
  end
150
219
  end
151
220
 
@@ -39,7 +39,7 @@ module PLSQL
39
39
  @schema.select_one "SELECT \"#{@schema_name}\".\"#{@sequence_name}\".NEXTVAL FROM dual"
40
40
  end
41
41
 
42
- # Get CURRTVAL of sequence (can be called just after nextval)
42
+ # Get CURRVAL of sequence (can be called just after nextval)
43
43
  def currval
44
44
  @schema.select_one "SELECT \"#{@schema_name}\".\"#{@sequence_name}\".CURRVAL FROM dual"
45
45
  end
@@ -5,6 +5,11 @@ module PLSQL
5
5
  @connection.select_first(sql, *bindvars)
6
6
  end
7
7
 
8
+ # Select all rows as array or values (without column names)
9
+ def select_all(sql, *bindvars, &block)
10
+ @connection.select_all(sql, *bindvars, &block)
11
+ end
12
+
8
13
  # Select one value (use if only one row with one value is selected)
9
14
  def select_one(sql, *bindvars)
10
15
  (row = @connection.select_first(sql, *bindvars)) && row[0]
@@ -43,7 +48,7 @@ module PLSQL
43
48
  #
44
49
  # to turn off automatic commits after each statement.
45
50
  def commit
46
- connection.commit
51
+ @connection.commit
47
52
  end
48
53
 
49
54
  # Execute ROLLBACK in current database session.
@@ -53,7 +58,7 @@ module PLSQL
53
58
  #
54
59
  # to turn off automatic commits after each statement.
55
60
  def rollback
56
- connection.rollback
61
+ @connection.rollback
57
62
  end
58
63
 
59
64
  end