ruby-plsql 0.9.9-java
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.
- checksums.yaml +7 -0
- data/History.txt +282 -0
- data/License.txt +20 -0
- data/README.md +263 -0
- data/VERSION +1 -0
- data/lib/plsql/connection.rb +230 -0
- data/lib/plsql/helpers.rb +7 -0
- data/lib/plsql/jdbc_connection.rb +588 -0
- data/lib/plsql/oci_connection.rb +324 -0
- data/lib/plsql/package.rb +87 -0
- data/lib/plsql/procedure.rb +584 -0
- data/lib/plsql/procedure_call.rb +626 -0
- data/lib/plsql/schema.rb +296 -0
- data/lib/plsql/sequence.rb +46 -0
- data/lib/plsql/sql_statements.rb +85 -0
- data/lib/plsql/table.rb +345 -0
- data/lib/plsql/type.rb +270 -0
- data/lib/plsql/variable.rb +143 -0
- data/lib/plsql/version.rb +3 -0
- data/lib/plsql/view.rb +38 -0
- data/lib/ruby-plsql.rb +1 -0
- data/lib/ruby_plsql.rb +13 -0
- metadata +120 -0
data/lib/plsql/schema.rb
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
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
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(raw_conn = nil, schema = nil, original_schema = nil) # :nodoc:
|
|
19
|
+
self.connection = raw_conn
|
|
20
|
+
@schema_name = schema ? schema.to_s.upcase : nil
|
|
21
|
+
@original_schema = original_schema
|
|
22
|
+
@dbms_output_stream = nil
|
|
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_service_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','current_schema') 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 default_timezone when ActiveRecord connection is used.
|
|
103
|
+
# Prefer the module-level accessor (AR 7.0+; the only one in AR 8.0+) so
|
|
104
|
+
# that AR 7.0/7.1's deprecation warning for ActiveRecord::Base.default_timezone
|
|
105
|
+
# is not emitted. Fall back to the per-class accessor only on pre-7.0 AR,
|
|
106
|
+
# where ActiveRecord.default_timezone does not exist.
|
|
107
|
+
(@connection && @connection.activerecord_class &&
|
|
108
|
+
(ActiveRecord.respond_to?(:default_timezone) ?
|
|
109
|
+
ActiveRecord.default_timezone :
|
|
110
|
+
@connection.activerecord_class.default_timezone)) ||
|
|
111
|
+
# default to local timezone
|
|
112
|
+
:local
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Set default timezone to which database values will be converted - :utc or :local
|
|
117
|
+
def default_timezone=(value)
|
|
118
|
+
if [:local, :utc].include?(value)
|
|
119
|
+
@default_timezone = value
|
|
120
|
+
else
|
|
121
|
+
raise ArgumentError, "default timezone should be :local or :utc"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Same implementation as for ActiveRecord
|
|
126
|
+
# 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
|
|
127
|
+
def local_timezone_offset # :nodoc:
|
|
128
|
+
::Time.local(2007).utc_offset.to_r / 86400
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# DBMS_OUTPUT buffer size (default is 20_000)
|
|
132
|
+
def dbms_output_buffer_size
|
|
133
|
+
if @original_schema
|
|
134
|
+
@original_schema.dbms_output_buffer_size
|
|
135
|
+
else
|
|
136
|
+
@dbms_output_buffer_size || 20_000
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Seet DBMS_OUTPUT buffer size (default is 20_000). Example:
|
|
141
|
+
#
|
|
142
|
+
# plsql.dbms_output_buffer_size = 100_000
|
|
143
|
+
#
|
|
144
|
+
def dbms_output_buffer_size=(value)
|
|
145
|
+
@dbms_output_buffer_size = value
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Maximum line numbers for DBMS_OUTPUT in one PL/SQL call (from DBMSOUTPUT_LINESARRAY type)
|
|
149
|
+
DBMS_OUTPUT_MAX_LINES = 2147483647
|
|
150
|
+
|
|
151
|
+
# Specify IO stream where to log DBMS_OUTPUT from PL/SQL procedures. Example:
|
|
152
|
+
#
|
|
153
|
+
# plsql.dbms_output_stream = STDOUT
|
|
154
|
+
#
|
|
155
|
+
def dbms_output_stream=(stream)
|
|
156
|
+
@dbms_output_stream = stream
|
|
157
|
+
if @dbms_output_stream.nil? && @connection
|
|
158
|
+
sys.dbms_output.disable
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# IO stream where to log DBMS_OUTPUT from PL/SQL procedures.
|
|
163
|
+
def dbms_output_stream
|
|
164
|
+
if @original_schema
|
|
165
|
+
@original_schema.dbms_output_stream
|
|
166
|
+
else
|
|
167
|
+
@dbms_output_stream
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
def reset_instance_variables
|
|
174
|
+
if @connection
|
|
175
|
+
@schema_objects = {}
|
|
176
|
+
else
|
|
177
|
+
@schema_objects = nil
|
|
178
|
+
end
|
|
179
|
+
@schema_name = nil
|
|
180
|
+
@default_timezone = nil
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def method_missing(method, *args, &block)
|
|
184
|
+
raise ArgumentError, "No database connection" unless connection
|
|
185
|
+
# search in database if not in cache at first
|
|
186
|
+
object = (@schema_objects[method] ||= find_database_object(method) || find_other_schema(method) ||
|
|
187
|
+
find_public_synonym(method) || find_standard_procedure(method))
|
|
188
|
+
|
|
189
|
+
raise ArgumentError, "No database object '#{method.to_s.upcase}' found" unless object
|
|
190
|
+
|
|
191
|
+
if object.is_a?(Procedure)
|
|
192
|
+
object.exec(*args, &block)
|
|
193
|
+
elsif object.is_a?(Type) && !args.empty?
|
|
194
|
+
object.new(*args, &block)
|
|
195
|
+
else
|
|
196
|
+
object
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def find_database_object(name, override_schema_name = nil)
|
|
201
|
+
object_schema_name = override_schema_name || schema_name
|
|
202
|
+
object_name = name.to_s.upcase
|
|
203
|
+
if row = select_first(
|
|
204
|
+
"SELECT o.object_type, o.object_id
|
|
205
|
+
FROM all_objects o
|
|
206
|
+
WHERE owner = :owner AND object_name = :object_name
|
|
207
|
+
AND object_type IN ('PROCEDURE','FUNCTION','PACKAGE','TABLE','VIEW','SEQUENCE','TYPE','SYNONYM')",
|
|
208
|
+
object_schema_name, object_name)
|
|
209
|
+
object_type, object_id = row
|
|
210
|
+
case object_type
|
|
211
|
+
when "PROCEDURE", "FUNCTION"
|
|
212
|
+
if (connection.database_version <=> [11, 1, 0, 0]) >= 0
|
|
213
|
+
if row = select_first(
|
|
214
|
+
"SELECT p.object_id FROM all_procedures p
|
|
215
|
+
WHERE p.owner = :owner
|
|
216
|
+
AND p.object_name = :object_name
|
|
217
|
+
AND p.object_type = :object_type",
|
|
218
|
+
object_schema_name, object_name, object_type)
|
|
219
|
+
object_id = row[0]
|
|
220
|
+
else
|
|
221
|
+
raise ArgumentError, "Database object '#{object_schema_name}.#{object_name}' is not in valid status\n#{
|
|
222
|
+
_errors(object_schema_name, object_name, object_type)}"
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
Procedure.new(self, name, nil, override_schema_name, object_id)
|
|
226
|
+
when "PACKAGE"
|
|
227
|
+
Package.new(self, name, override_schema_name)
|
|
228
|
+
when "TABLE"
|
|
229
|
+
Table.new(self, name, override_schema_name)
|
|
230
|
+
when "VIEW"
|
|
231
|
+
View.new(self, name, override_schema_name)
|
|
232
|
+
when "SEQUENCE"
|
|
233
|
+
Sequence.new(self, name, override_schema_name)
|
|
234
|
+
when "TYPE"
|
|
235
|
+
Type.new(self, name, override_schema_name)
|
|
236
|
+
when "SYNONYM"
|
|
237
|
+
target_schema_name, target_object_name = @connection.describe_synonym(object_schema_name, object_name)
|
|
238
|
+
find_database_object(target_object_name, target_schema_name)
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def _errors(object_schema_name, object_name, object_type)
|
|
244
|
+
result = +""
|
|
245
|
+
previous_line = 0
|
|
246
|
+
select_all(
|
|
247
|
+
"SELECT e.line, e.position, e.text error_text, s.text source_text
|
|
248
|
+
FROM all_errors e, all_source s
|
|
249
|
+
WHERE e.owner = :owner AND e.name = :name AND e.type = :type
|
|
250
|
+
AND s.owner = e.owner AND s.name = e.name AND s.type = e.type AND s.line = e.line
|
|
251
|
+
ORDER BY e.sequence",
|
|
252
|
+
object_schema_name, object_name, object_type
|
|
253
|
+
).each do |line, position, error_text, source_text|
|
|
254
|
+
result << "Error on line #{'%4d' % line}: #{source_text}" if line > previous_line
|
|
255
|
+
result << " position #{'%4d' % position}: #{error_text}\n"
|
|
256
|
+
previous_line = line
|
|
257
|
+
end
|
|
258
|
+
result unless result.empty?
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def find_other_schema(name)
|
|
262
|
+
return nil if @original_schema
|
|
263
|
+
if select_first("SELECT username FROM all_users WHERE username = :username", name.to_s.upcase)
|
|
264
|
+
Schema.new(connection, name, self)
|
|
265
|
+
else
|
|
266
|
+
nil
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def find_standard_procedure(name)
|
|
271
|
+
return nil if @original_schema
|
|
272
|
+
Procedure.find(self, name, "STANDARD", "SYS")
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def find_public_synonym(name)
|
|
276
|
+
return nil if @original_schema
|
|
277
|
+
target_schema_name, target_object_name = @connection.describe_synonym("PUBLIC", name)
|
|
278
|
+
find_database_object(target_object_name, target_schema_name) if target_schema_name
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
module Kernel
|
|
284
|
+
# Returns current schema object. You can now chain either database object (packages, procedures, tables, sequences)
|
|
285
|
+
# in current schema or specify different schema name. Examples:
|
|
286
|
+
#
|
|
287
|
+
# plsql.test_function('some parameter')
|
|
288
|
+
# plsql.test_package.test_function('some parameter')
|
|
289
|
+
# plsql.other_schema.test_package.test_function('some parameter')
|
|
290
|
+
# plsql.table_name.all
|
|
291
|
+
# plsql.other_schema.table_name.all
|
|
292
|
+
#
|
|
293
|
+
def plsql(connection_alias = nil)
|
|
294
|
+
PLSQL::Schema.find_or_new(connection_alias)
|
|
295
|
+
end
|
|
296
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module PLSQL
|
|
2
|
+
module SequenceClassMethods # :nodoc:
|
|
3
|
+
def find(schema, sequence)
|
|
4
|
+
if schema.select_first(
|
|
5
|
+
"SELECT sequence_name FROM all_sequences
|
|
6
|
+
WHERE sequence_owner = :owner
|
|
7
|
+
AND sequence_name = :sequence_name",
|
|
8
|
+
schema.schema_name, sequence.to_s.upcase)
|
|
9
|
+
new(schema, sequence)
|
|
10
|
+
# search for synonym
|
|
11
|
+
elsif (row = schema.select_first(
|
|
12
|
+
"SELECT t.sequence_owner, t.sequence_name
|
|
13
|
+
FROM all_synonyms s, all_sequences t
|
|
14
|
+
WHERE s.owner IN (:owner, 'PUBLIC')
|
|
15
|
+
AND s.synonym_name = :synonym_name
|
|
16
|
+
AND t.sequence_owner = s.table_owner
|
|
17
|
+
AND t.sequence_name = s.table_name
|
|
18
|
+
ORDER BY DECODE(s.owner, 'PUBLIC', 1, 0)",
|
|
19
|
+
schema.schema_name, sequence.to_s.upcase))
|
|
20
|
+
new(schema, row[1], row[0])
|
|
21
|
+
else
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class Sequence
|
|
28
|
+
extend SequenceClassMethods
|
|
29
|
+
|
|
30
|
+
def initialize(schema, sequence, override_schema_name = nil) # :nodoc:
|
|
31
|
+
@schema = schema
|
|
32
|
+
@schema_name = override_schema_name || schema.schema_name
|
|
33
|
+
@sequence_name = sequence.to_s.upcase
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Get NEXTVAL of sequence
|
|
37
|
+
def nextval
|
|
38
|
+
@schema.select_one "SELECT \"#{@schema_name}\".\"#{@sequence_name}\".NEXTVAL FROM dual"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Get CURRVAL of sequence (can be called just after nextval)
|
|
42
|
+
def currval
|
|
43
|
+
@schema.select_one "SELECT \"#{@schema_name}\".\"#{@sequence_name}\".CURRVAL FROM dual"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module PLSQL
|
|
2
|
+
module SQLStatements
|
|
3
|
+
# Select first row as array or values (without column names)
|
|
4
|
+
def select_first(sql, *bindvars)
|
|
5
|
+
@connection.select_first(sql, *bindvars)
|
|
6
|
+
end
|
|
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
|
+
|
|
13
|
+
# Select one value (use if only one row with one value is selected)
|
|
14
|
+
def select_one(sql, *bindvars)
|
|
15
|
+
(row = @connection.select_first(sql, *bindvars)) && row[0]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Select :first or :all values. Examples:
|
|
19
|
+
#
|
|
20
|
+
# plsql.select :first, "SELECT * FROM employees WHERE employee_id = :1", 1
|
|
21
|
+
# plsql.select :all, "SELECT * FROM employees ORDER BY employee_id"
|
|
22
|
+
def select(*args)
|
|
23
|
+
case args[0]
|
|
24
|
+
when nil
|
|
25
|
+
raise ArgumentError, "Not enough arguments"
|
|
26
|
+
when :first
|
|
27
|
+
args.shift
|
|
28
|
+
@connection.select_hash_first(*args)
|
|
29
|
+
when :all
|
|
30
|
+
args.shift
|
|
31
|
+
@connection.select_hash_all(*args)
|
|
32
|
+
else
|
|
33
|
+
@connection.select_hash_all(*args)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Execute SQL statement. Example:
|
|
38
|
+
#
|
|
39
|
+
# plsql.execute "DROP TABLE employees"
|
|
40
|
+
def execute(*args)
|
|
41
|
+
@connection.exec(*args)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Execute COMMIT in current database session.
|
|
45
|
+
# Use beforehand
|
|
46
|
+
#
|
|
47
|
+
# plsql.connection.autocommit = false
|
|
48
|
+
#
|
|
49
|
+
# to turn off automatic commits after each statement.
|
|
50
|
+
def commit
|
|
51
|
+
@connection.commit
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Execute ROLLBACK in current database session.
|
|
55
|
+
# Use beforehand
|
|
56
|
+
#
|
|
57
|
+
# plsql.connection.autocommit = false
|
|
58
|
+
#
|
|
59
|
+
# to turn off automatic commits after each statement.
|
|
60
|
+
def rollback
|
|
61
|
+
@connection.rollback
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Create SAVEPOINT with specified name. Later use +rollback_to+ method to roll changes back
|
|
65
|
+
# to specified savepoint.
|
|
66
|
+
# Use beforehand
|
|
67
|
+
#
|
|
68
|
+
# plsql.connection.autocommit = false
|
|
69
|
+
#
|
|
70
|
+
# to turn off automatic commits after each statement.
|
|
71
|
+
def savepoint(name)
|
|
72
|
+
execute "SAVEPOINT #{name}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Roll back changes to specified savepoint (that was created using +savepoint+ method)
|
|
76
|
+
# Use beforehand
|
|
77
|
+
#
|
|
78
|
+
# plsql.connection.autocommit = false
|
|
79
|
+
#
|
|
80
|
+
# to turn off automatic commits after each statement.
|
|
81
|
+
def rollback_to(name)
|
|
82
|
+
execute "ROLLBACK TO #{name}"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|