ruby-plsql 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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