flash-gordons-ruby-plsql 0.5.0

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.
@@ -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