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.
- data/History.txt +19 -0
- data/License.txt +1 -1
- data/README.rdoc +9 -4
- data/VERSION +1 -1
- data/lib/plsql/connection.rb +24 -9
- data/lib/plsql/helpers.rb +9 -0
- data/lib/plsql/jdbc_connection.rb +38 -9
- data/lib/plsql/oci8_patches.rb +25 -0
- data/lib/plsql/oci_connection.rb +46 -12
- data/lib/plsql/package.rb +25 -12
- data/lib/plsql/procedure.rb +23 -29
- data/lib/plsql/procedure_call.rb +53 -4
- data/lib/plsql/schema.rb +114 -45
- data/lib/plsql/sequence.rb +1 -1
- data/lib/plsql/sql_statements.rb +7 -2
- data/lib/plsql/table.rb +80 -25
- data/lib/plsql/type.rb +87 -0
- data/lib/plsql/variable.rb +146 -0
- data/lib/plsql/version.rb +1 -1
- data/lib/plsql/view.rb +41 -0
- data/lib/ruby_plsql.rb +1 -1
- data/spec/plsql/connection_spec.rb +112 -50
- data/spec/plsql/package_spec.rb +19 -10
- data/spec/plsql/procedure_spec.rb +159 -126
- data/spec/plsql/schema_spec.rb +109 -1
- data/spec/plsql/sql_statements_spec.rb +14 -32
- data/spec/plsql/table_spec.rb +75 -9
- data/spec/plsql/type_spec.rb +133 -0
- data/spec/plsql/variable_spec.rb +458 -0
- data/spec/plsql/version_spec.rb +8 -0
- data/spec/plsql/view_spec.rb +259 -0
- data/spec/spec_helper.rb +1 -1
- metadata +15 -2
data/lib/plsql/procedure.rb
CHANGED
@@ -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
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
AND
|
33
|
-
|
34
|
-
|
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
|
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
|
-
|
62
|
-
|
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
|
-
|
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,
|
data/lib/plsql/procedure_call.rb
CHANGED
@@ -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 = @
|
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
|
-
|
126
|
-
|
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,
|
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
|
-
@
|
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
|
-
|
27
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
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
|
-
|
152
|
+
@default_timezone = nil
|
112
153
|
end
|
113
|
-
|
154
|
+
|
114
155
|
def method_missing(method, *args, &block)
|
115
|
-
raise ArgumentError, "No
|
116
|
-
#
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
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
|
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,
|
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
|
|
data/lib/plsql/sequence.rb
CHANGED
@@ -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
|
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
|
data/lib/plsql/sql_statements.rb
CHANGED
@@ -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
|