flash-gordons-ruby-plsql 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,124 @@
1
+ module PLSQL
2
+ class ProcedureCall < SubprogramCall #:nodoc:
3
+ attr_reader :output_stream
4
+
5
+ def initialize(procedure, args = [], options = {})
6
+ @output_stream = procedure.schema.dbms_output_stream
7
+ super
8
+ end
9
+
10
+ def exec
11
+ # puts "DEBUG: sql = #{@sql.gsub("\n","<br/>\n")}"
12
+ @cursor = @schema.connection.parse(@sql)
13
+
14
+ @binds[:values].each do |arg, value|
15
+ @cursor.bind_param(":#{arg}", value, @binds[:metadata][arg])
16
+ end
17
+
18
+ @return[:variables].each do |var|
19
+ @cursor.bind_param(":#{var}", nil, @return[:metadata][var])
20
+ end
21
+
22
+ @cursor.exec
23
+
24
+ dbms_output_log
25
+
26
+ if block_given?
27
+ yield get_return_value
28
+ nil
29
+ else
30
+ get_return_value
31
+ end
32
+ ensure
33
+ @cursor.close if @cursor
34
+ end
35
+
36
+ private
37
+
38
+ def construct_sql(arguments)
39
+ prepare_sql_construction
40
+ call_sql = ""
41
+ call_sql << add_return if return_metadata
42
+
43
+ # construct procedure call if procedure name is available
44
+ # otherwise will get surrounding call_sql from @procedure (used for table statements)
45
+ if subprogram_name
46
+ call_sql << "#{full_subprogram_name}(#{add_arguments(arguments)});\n"
47
+ else
48
+ call_sql << add_arguments(arguments)
49
+ call_sql = @subprogram.call_sql(call_sql)
50
+ end
51
+
52
+ add_out_variables
53
+
54
+ dbms_output_enable_sql, dbms_output_get_sql = dbms_output_sql
55
+
56
+ @sql = <<-SQL
57
+ DECLARE
58
+ #{@declare_sql}
59
+ BEGIN
60
+ #{@assignment_sql}
61
+ #{dbms_output_enable_sql}
62
+ #{call_sql}
63
+ #{dbms_output_get_sql}
64
+ #{@return[:sql]}
65
+ END;
66
+ SQL
67
+ end
68
+
69
+ def get_return_value
70
+ # create output hash if there are any out variables
71
+ output = out_list.inject({}) {|res, k| res[k] = out_variable_value(k); res} if out_list.size > 0
72
+ # if function with output parameters
73
+ if return_metadata && out_list.size > 0
74
+ [function_return_value, output]
75
+ # if function without output parameters
76
+ elsif return_metadata
77
+ function_return_value
78
+ # if procedure with output parameters
79
+ elsif out_list.size > 0
80
+ output
81
+ end
82
+ # nil if procedure without output parameters
83
+ end
84
+
85
+ def dbms_output_sql
86
+ return ["", ""] unless output_stream
87
+
88
+ dbms_output_enable_sql = "DBMS_OUTPUT.ENABLE(#{@schema.dbms_output_buffer_size});\n"
89
+ # if database version is at least 10.2 then use DBMS_OUTPUT.GET_LINES with SYS.DBMSOUTPUT_LINESARRAY
90
+ if (@schema.connection.database_version <=> [10, 2, 0, 0]) >= 0
91
+ add_variable_declaration('dbms_output_numlines', 'integer', :value => Schema::DBMS_OUTPUT_MAX_LINES)
92
+ dbms_output_get_sql = "DBMS_OUTPUT.GET_LINES(:dbms_output_lines, l_dbms_output_numlines);\n"
93
+ bind_value(:dbms_output_lines, nil,
94
+ :data_type => 'TABLE', :data_length => nil,
95
+ :sql_type_name => "SYS.DBMSOUTPUT_LINESARRAY", :in_out => 'OUT')
96
+ # if database version is less than 10.2 then use individual DBMS_OUTPUT.GET_LINE calls
97
+ else
98
+ dbms_output_get_sql = ""
99
+ end
100
+ [dbms_output_enable_sql, dbms_output_get_sql]
101
+ end
102
+
103
+ def dbms_output_log
104
+ return unless output_stream
105
+
106
+ # if database version is at least 10.2 then :dbms_output_lines output bind variable has dbms_output lines
107
+ if @binds[:metadata][:dbms_output_lines]
108
+ @cursor[':dbms_output_lines'].each {|line| output_stream.puts("DBMS_OUTPUT: #{line}") if line}
109
+ # if database version is less than 10.2 then use individual DBMS_OUTPUT.GET_LINE calls
110
+ else
111
+ cursor = @schema.connection.parse("BEGIN sys.dbms_output.get_line(:line, :status); END;")
112
+ while true do
113
+ cursor.bind_param(':line', nil, :data_type => 'VARCHAR2', :in_out => 'OUT')
114
+ cursor.bind_param(':status', nil, :data_type => 'NUMBER', :in_out => 'OUT')
115
+ cursor.exec
116
+ break unless cursor[':status'] == 0
117
+ output_stream.puts "DBMS_OUTPUT: #{cursor[':line']}"
118
+ end
119
+ cursor.close
120
+ end
121
+ output_stream.flush
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,309 @@
1
+ module PLSQL
2
+ class Schema
3
+ include SQLStatements
4
+
5
+ @@schemas = {}
6
+
7
+ class <<self
8
+ def find_or_new(connection_alias) #:nodoc:
9
+ connection_alias ||= :default
10
+ if @@schemas[connection_alias]
11
+ @@schemas[connection_alias]
12
+ else
13
+ @@schemas[connection_alias] = self.new
14
+ end
15
+ end
16
+
17
+ end
18
+
19
+ def initialize(raw_conn = nil, schema = nil, original_schema = nil) #:nodoc:
20
+ self.connection = raw_conn
21
+ @schema_name = schema ? schema.to_s.upcase : nil
22
+ @original_schema = original_schema
23
+ end
24
+
25
+ # Returns connection wrapper object (this is not raw OCI8 or JDBC connection!)
26
+ attr_reader :connection
27
+
28
+ def root_schema #:nodoc:
29
+ @original_schema || self
30
+ end
31
+
32
+ def raw_connection=(raw_conn) #:nodoc:
33
+ @connection = raw_conn ? Connection.create(raw_conn) : nil
34
+ reset_instance_variables
35
+ end
36
+
37
+ # Set connection to OCI8 or JDBC connection:
38
+ #
39
+ # plsql.connection = OCI8.new(database_user, database_password, database_name)
40
+ #
41
+ # or
42
+ #
43
+ # plsql.connection = java.sql.DriverManager.getConnection(
44
+ # "jdbc:oracle:thin:@#{database_host}:#{database_port}:#{database_name}",
45
+ # database_user, database_password)
46
+ #
47
+ def connection=(conn)
48
+ if conn.is_a?(Connection)
49
+ @connection = conn
50
+ reset_instance_variables
51
+ else
52
+ self.raw_connection = conn
53
+ end
54
+ conn
55
+ end
56
+
57
+ # Create new OCI8 or JDBC connection using one of the following ways:
58
+ #
59
+ # plsql.connect! username, password, database_tns_alias
60
+ # plsql.connect! username, password, :host => host, :port => port, :database => database
61
+ # plsql.connect! :username => username, :password => password, :database => database_tns_alias
62
+ # plsql.connect! :username => username, :password => password, :host => host, :port => port, :database => database
63
+ #
64
+ def connect!(*args)
65
+ params = {}
66
+ params[:username] = args.shift if args[0].is_a?(String)
67
+ params[:password] = args.shift if args[0].is_a?(String)
68
+ params[:database] = args.shift if args[0].is_a?(String)
69
+ params.merge!(args.shift) if args[0].is_a?(Hash)
70
+ raise ArgumentError, "Wrong number of arguments" unless args.empty?
71
+ self.connection = Connection.create_new(params)
72
+ end
73
+
74
+ # Set connection to current ActiveRecord connection (use in initializer file):
75
+ #
76
+ # plsql.activerecord_class = ActiveRecord::Base
77
+ #
78
+ def activerecord_class=(ar_class)
79
+ @connection = ar_class ? Connection.create(nil, ar_class) : nil
80
+ reset_instance_variables
81
+ ar_class
82
+ end
83
+
84
+ # Disconnect from Oracle
85
+ def logoff
86
+ @connection.logoff
87
+ self.connection = nil
88
+ end
89
+
90
+ # Current Oracle schema name
91
+ def schema_name
92
+ return nil unless connection
93
+ @schema_name ||= select_first("SELECT SYS_CONTEXT('userenv','session_user') FROM dual")[0]
94
+ end
95
+
96
+ # Default timezone to which database values will be converted - :utc or :local
97
+ def default_timezone
98
+ if @original_schema
99
+ @original_schema.default_timezone
100
+ else
101
+ @default_timezone ||
102
+ # Use ActiveRecord class default_timezone when ActiveRecord connection is used
103
+ (@connection && (ar_class = @connection.activerecord_class) && ar_class.default_timezone) ||
104
+ # default to local timezone
105
+ :local
106
+ end
107
+ end
108
+
109
+ # Set default timezone to which database values will be converted - :utc or :local
110
+ def default_timezone=(value)
111
+ if [:local, :utc].include?(value)
112
+ @default_timezone = value
113
+ else
114
+ raise ArgumentError, "default timezone should be :local or :utc"
115
+ end
116
+ end
117
+
118
+ # Same implementation as for ActiveRecord
119
+ # DateTimes aren't aware of DST rules, so use a consistent non-DST offset when creating a DateTime with an offset in the local zone
120
+ def local_timezone_offset #:nodoc:
121
+ ::Time.local(2007).utc_offset.to_r / 86400
122
+ end
123
+
124
+ # DBMS_OUTPUT buffer size (default is 20_000)
125
+ def dbms_output_buffer_size
126
+ if @original_schema
127
+ @original_schema.dbms_output_buffer_size
128
+ else
129
+ @dbms_output_buffer_size || 20_000
130
+ end
131
+ end
132
+
133
+ # Seet DBMS_OUTPUT buffer size (default is 20_000). Example:
134
+ #
135
+ # plsql.dbms_output_buffer_size = 100_000
136
+ #
137
+ def dbms_output_buffer_size=(value)
138
+ @dbms_output_buffer_size = value
139
+ end
140
+
141
+ # Maximum line numbers for DBMS_OUTPUT in one PL/SQL call (from DBMSOUTPUT_LINESARRAY type)
142
+ DBMS_OUTPUT_MAX_LINES = 2147483647
143
+
144
+ # Specify IO stream where to log DBMS_OUTPUT from PL/SQL procedures. Example:
145
+ #
146
+ # plsql.dbms_output_stream = STDOUT
147
+ #
148
+ def dbms_output_stream=(stream)
149
+ @dbms_output_stream = stream
150
+ if @dbms_output_stream.nil? && @connection
151
+ sys.dbms_output.disable
152
+ end
153
+ end
154
+
155
+ # IO stream where to log DBMS_OUTPUT from PL/SQL procedures.
156
+ def dbms_output_stream
157
+ if @original_schema
158
+ @original_schema.dbms_output_stream
159
+ else
160
+ @dbms_output_stream
161
+ end
162
+ end
163
+
164
+ private
165
+
166
+ def reset_instance_variables
167
+ if @connection
168
+ @schema_objects = {}
169
+ else
170
+ @schema_objects = nil
171
+ end
172
+ @schema_name = nil
173
+ @default_timezone = nil
174
+ end
175
+
176
+ def method_missing(method, *args, &block)
177
+ raise ArgumentError, "No database connection" unless connection
178
+ # search in database if not in cache at first
179
+ object = (@schema_objects[method] ||= find_database_object(method) || find_other_schema(method) ||
180
+ find_public_synonym(method) || find_standard_procedure(method))
181
+
182
+ raise ArgumentError, "No database object '#{method.to_s.upcase}' found" unless object
183
+
184
+ if object.is_a?(Procedure)
185
+ object.exec(*args, &block)
186
+ elsif object.is_a?(Type) && !args.empty?
187
+ object.new(*args, &block)
188
+ else
189
+ object
190
+ end
191
+ end
192
+
193
+ def find_database_object(name, override_schema_name = nil)
194
+ object_schema_name = override_schema_name || schema_name
195
+ object_name = name.to_s.upcase
196
+ if (row = select_first(<<-SQL, object_schema_name, object_name))
197
+ SELECT o.object_type,
198
+ o.object_id,
199
+ o.status,
200
+ (CASE
201
+ WHEN o.object_type = 'PACKAGE' THEN
202
+ (SELECT ob.status
203
+ FROM all_objects ob
204
+ WHERE ob.owner = o.owner
205
+ AND ob.object_name = o.object_name
206
+ AND ob.object_type = 'PACKAGE BODY')
207
+ ELSE NULL
208
+ END) body_status,
209
+ (CASE
210
+ WHEN o.object_type = 'FUNCTION' THEN
211
+ (SELECT p.pipelined
212
+ FROM all_procedures p
213
+ WHERE p.owner = o.owner
214
+ AND p.object_name = o.object_name
215
+ AND p.object_type = 'FUNCTION')
216
+ ELSE NULL
217
+ END) pipelined
218
+ FROM all_objects o
219
+ WHERE owner = :owner
220
+ AND object_name = :object_name
221
+ AND object_type IN ('PROCEDURE','FUNCTION','PACKAGE','TABLE','VIEW','SEQUENCE','TYPE','SYNONYM')
222
+ SQL
223
+
224
+ object_type, object_id, status, body_status, pipelined = row
225
+ raise ArgumentError, "Database object '#{object_schema_name}.#{object_name}' is not in valid status\n#{
226
+ _errors(object_schema_name, object_name, object_type)}" if status == 'INVALID'
227
+ raise ArgumentError, "Package '#{object_schema_name}.#{object_name}' body is not in valid status\n#{
228
+ _errors(object_schema_name, object_name, 'PACKAGE BODY')}" if body_status == 'INVALID'
229
+ case object_type
230
+ when 'PROCEDURE'
231
+ Procedure.new(self, name, nil, override_schema_name, object_id)
232
+ when 'FUNCTION'
233
+ if pipelined == 'NO'
234
+ Procedure.new(self, name, nil, override_schema_name, object_id)
235
+ else
236
+ PipelinedFunction.new(self, name, nil, override_schema_name, object_id)
237
+ end
238
+ when 'PACKAGE'
239
+ Package.new(self, name, override_schema_name)
240
+ when 'TABLE'
241
+ Table.new(self, name, override_schema_name)
242
+ when 'VIEW'
243
+ View.new(self, name, override_schema_name)
244
+ when 'SEQUENCE'
245
+ Sequence.new(self, name, override_schema_name)
246
+ when 'TYPE'
247
+ Type.new(self, name, override_schema_name)
248
+ when 'SYNONYM'
249
+ target_schema_name, target_object_name = @connection.describe_synonym(object_schema_name, object_name)
250
+ find_database_object(target_object_name, target_schema_name)
251
+ end
252
+ end
253
+ end
254
+
255
+ def _errors(object_schema_name, object_name, object_type)
256
+ result = ""
257
+ previous_line = 0
258
+ select_all(
259
+ "SELECT e.line, e.position, e.text error_text, s.text source_text
260
+ FROM all_errors e, all_source s
261
+ WHERE e.owner = :owner AND e.name = :name AND e.type = :type
262
+ AND s.owner = e.owner AND s.name = e.name AND s.type = e.type AND s.line = e.line
263
+ ORDER BY e.sequence",
264
+ object_schema_name, object_name, object_type
265
+ ).each do |line, position, error_text, source_text|
266
+ result << "Error on line #{'%4d' % line}: #{source_text}" if line > previous_line
267
+ result << " position #{'%4d' % position}: #{error_text}\n"
268
+ previous_line = line
269
+ end
270
+ result unless result.empty?
271
+ end
272
+
273
+ def find_other_schema(name)
274
+ return nil if @original_schema
275
+ if select_first("SELECT username FROM all_users WHERE username = :username", name.to_s.upcase)
276
+ Schema.new(connection, name, self)
277
+ else
278
+ nil
279
+ end
280
+ end
281
+
282
+ def find_standard_procedure(name)
283
+ return nil if @original_schema
284
+ Procedure.find(self, name, 'STANDARD', 'SYS')
285
+ end
286
+
287
+ def find_public_synonym(name)
288
+ return nil if @original_schema
289
+ target_schema_name, target_object_name = @connection.describe_synonym('PUBLIC', name)
290
+ find_database_object(target_object_name, target_schema_name) if target_schema_name
291
+ end
292
+
293
+ end
294
+ end
295
+
296
+ module Kernel
297
+ # Returns current schema object. You can now chain either database object (packages, procedures, tables, sequences)
298
+ # in current schema or specify different schema name. Examples:
299
+ #
300
+ # plsql.test_function('some parameter')
301
+ # plsql.test_package.test_function('some parameter')
302
+ # plsql.other_schema.test_package.test_function('some parameter')
303
+ # plsql.table_name.all
304
+ # plsql.other_schema.table_name.all
305
+ #
306
+ def plsql(connection_alias = nil)
307
+ PLSQL::Schema.find_or_new(connection_alias)
308
+ end
309
+ end
@@ -0,0 +1,49 @@
1
+ module PLSQL
2
+
3
+ module SequenceClassMethods #:nodoc:
4
+ def find(schema, sequence)
5
+ if schema.select_first(
6
+ "SELECT sequence_name FROM all_sequences
7
+ WHERE sequence_owner = :owner
8
+ AND sequence_name = :sequence_name",
9
+ schema.schema_name, sequence.to_s.upcase)
10
+ new(schema, sequence)
11
+ # search for synonym
12
+ elsif (row = schema.select_first(
13
+ "SELECT t.sequence_owner, t.sequence_name
14
+ FROM all_synonyms s, all_sequences t
15
+ WHERE s.owner IN (:owner, 'PUBLIC')
16
+ AND s.synonym_name = :synonym_name
17
+ AND t.sequence_owner = s.table_owner
18
+ AND t.sequence_name = s.table_name
19
+ ORDER BY DECODE(s.owner, 'PUBLIC', 1, 0)",
20
+ schema.schema_name, sequence.to_s.upcase))
21
+ new(schema, row[1], row[0])
22
+ else
23
+ nil
24
+ end
25
+ end
26
+ end
27
+
28
+ class Sequence
29
+ extend SequenceClassMethods
30
+
31
+ def initialize(schema, sequence, override_schema_name = nil) #:nodoc:
32
+ @schema = schema
33
+ @schema_name = override_schema_name || schema.schema_name
34
+ @sequence_name = sequence.to_s.upcase
35
+ end
36
+
37
+ # Get NEXTVAL of sequence
38
+ def nextval
39
+ @schema.select_one "SELECT \"#{@schema_name}\".\"#{@sequence_name}\".NEXTVAL FROM dual"
40
+ end
41
+
42
+ # Get CURRVAL of sequence (can be called just after nextval)
43
+ def currval
44
+ @schema.select_one "SELECT \"#{@schema_name}\".\"#{@sequence_name}\".CURRVAL FROM dual"
45
+ end
46
+
47
+ end
48
+
49
+ end