ruby-plsql 0.3.1 → 0.4.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.
- data/.gitignore +10 -0
- data/History.txt +7 -0
- data/README.rdoc +97 -29
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/lib/plsql/connection.rb +85 -11
- data/lib/plsql/jdbc_connection.rb +317 -165
- data/lib/plsql/oci_connection.rb +158 -78
- data/lib/plsql/package.rb +5 -5
- data/lib/plsql/procedure.rb +69 -134
- data/lib/plsql/procedure_call.rb +345 -0
- data/lib/plsql/schema.rb +58 -40
- data/lib/plsql/sequence.rb +49 -0
- data/lib/plsql/sql_statements.rb +61 -0
- data/lib/plsql/table.rb +285 -0
- data/lib/plsql/version.rb +3 -0
- data/lib/ruby-plsql.rb +1 -0
- data/lib/ruby_plsql.rb +7 -40
- data/spec/plsql/connection_spec.rb +40 -24
- data/spec/plsql/procedure_spec.rb +1145 -453
- data/spec/plsql/schema_spec.rb +9 -2
- data/spec/plsql/sequence_spec.rb +67 -0
- data/spec/plsql/sql_statements_spec.rb +109 -0
- data/spec/plsql/table_spec.rb +269 -0
- data/spec/spec_helper.rb +20 -10
- metadata +35 -34
- data/lib/ruby_plsql/version.rb +0 -3
@@ -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 CURRTVAL 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
|
@@ -0,0 +1,61 @@
|
|
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 one value (use if only one row with one value is selected)
|
9
|
+
def select_one(sql, *bindvars)
|
10
|
+
(row = @connection.select_first(sql, *bindvars)) && row[0]
|
11
|
+
end
|
12
|
+
|
13
|
+
# Select :first or :all values. Examples:
|
14
|
+
#
|
15
|
+
# plsql.select :first, "SELECT * FROM employees WHERE employee_id = :1", 1
|
16
|
+
# plsql.select :all, "SELECT * FROM employees ORDER BY employee_id"
|
17
|
+
def select(*args)
|
18
|
+
case args[0]
|
19
|
+
when nil
|
20
|
+
raise ArgumentError, "Not enough arguments"
|
21
|
+
when :first
|
22
|
+
args.shift
|
23
|
+
@connection.select_hash_first(*args)
|
24
|
+
when :all
|
25
|
+
args.shift
|
26
|
+
@connection.select_hash_all(*args)
|
27
|
+
else
|
28
|
+
@connection.select_hash_all(*args)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Execute SQL statement. Example:
|
33
|
+
#
|
34
|
+
# plsql.execute "DROP TABLE employees"
|
35
|
+
def execute(*args)
|
36
|
+
@connection.exec(*args)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Execute COMMIT in current database session.
|
40
|
+
# Use beforehand
|
41
|
+
#
|
42
|
+
# plsql.connection.autocommit = false
|
43
|
+
#
|
44
|
+
# to turn off automatic commits after each statement.
|
45
|
+
def commit
|
46
|
+
connection.commit
|
47
|
+
end
|
48
|
+
|
49
|
+
# Execute ROLLBACK in current database session.
|
50
|
+
# Use beforehand
|
51
|
+
#
|
52
|
+
# plsql.connection.autocommit = false
|
53
|
+
#
|
54
|
+
# to turn off automatic commits after each statement.
|
55
|
+
def rollback
|
56
|
+
connection.rollback
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
data/lib/plsql/table.rb
ADDED
@@ -0,0 +1,285 @@
|
|
1
|
+
module PLSQL
|
2
|
+
|
3
|
+
module TableClassMethods #:nodoc:
|
4
|
+
def find(schema, table)
|
5
|
+
if schema.select_first(
|
6
|
+
"SELECT table_name FROM all_tables
|
7
|
+
WHERE owner = :owner
|
8
|
+
AND table_name = :table_name",
|
9
|
+
schema.schema_name, table.to_s.upcase)
|
10
|
+
new(schema, table)
|
11
|
+
# search for synonym
|
12
|
+
elsif (row = schema.select_first(
|
13
|
+
"SELECT t.owner, t.table_name
|
14
|
+
FROM all_synonyms s, all_tables t
|
15
|
+
WHERE s.owner IN (:owner, 'PUBLIC')
|
16
|
+
AND s.synonym_name = :synonym_name
|
17
|
+
AND t.owner = s.table_owner
|
18
|
+
AND t.table_name = s.table_name
|
19
|
+
ORDER BY DECODE(s.owner, 'PUBLIC', 1, 0)",
|
20
|
+
schema.schema_name, table.to_s.upcase))
|
21
|
+
new(schema, row[1], row[0])
|
22
|
+
else
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Table
|
29
|
+
extend TableClassMethods
|
30
|
+
|
31
|
+
attr_reader :columns, :schema_name, :table_name #:nodoc:
|
32
|
+
|
33
|
+
def initialize(schema, table, override_schema_name = nil) #:nodoc:
|
34
|
+
@schema = schema
|
35
|
+
@schema_name = override_schema_name || schema.schema_name
|
36
|
+
@table_name = table.to_s.upcase
|
37
|
+
@columns = {}
|
38
|
+
|
39
|
+
@schema.connection.select_all("
|
40
|
+
SELECT c.column_name, c.column_id position,
|
41
|
+
c.data_type, c.data_length, c.data_precision, c.data_scale, c.char_used,
|
42
|
+
c.data_type_owner, c.data_type_mod, t.typecode
|
43
|
+
FROM all_tab_columns c, all_types t
|
44
|
+
WHERE c.owner = :owner
|
45
|
+
AND c.table_name = :table_name
|
46
|
+
AND t.owner(+) = c.data_type_owner
|
47
|
+
AND t.type_name(+) = c.data_type
|
48
|
+
ORDER BY c.column_id",
|
49
|
+
@schema_name, @table_name
|
50
|
+
) do |r|
|
51
|
+
column_name, position,
|
52
|
+
data_type, data_length, data_precision, data_scale, char_used,
|
53
|
+
data_type_owner, data_type_mod, typecode = r
|
54
|
+
@columns[column_name.downcase.to_sym] = {
|
55
|
+
:position => position && position.to_i,
|
56
|
+
:data_type => data_type_owner && (typecode == 'COLLECTION' ? 'TABLE' : 'OBJECT' ) || data_type,
|
57
|
+
:data_length => data_type_owner ? nil : data_length && data_length.to_i,
|
58
|
+
:data_precision => data_precision && data_precision.to_i,
|
59
|
+
:data_scale => data_scale && data_scale.to_i,
|
60
|
+
:char_used => char_used,
|
61
|
+
:type_owner => data_type_owner,
|
62
|
+
:type_name => data_type_owner && data_type,
|
63
|
+
:sql_type_name => data_type_owner && "#{data_type_owner}.#{data_type}"
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# General select method with :first, :all or :count as first parameter.
|
69
|
+
# It is recommended to use #first, #all or #count method instead of this one.
|
70
|
+
def select(first_or_all, sql_params='', *bindvars)
|
71
|
+
case first_or_all
|
72
|
+
when :first, :all
|
73
|
+
select_sql = "SELECT * "
|
74
|
+
when :count
|
75
|
+
select_sql = "SELECT COUNT(*) "
|
76
|
+
else
|
77
|
+
raise ArgumentError, "Only :first, :all or :count are supported"
|
78
|
+
end
|
79
|
+
select_sql << "FROM \"#{@schema_name}\".\"#{@table_name}\" "
|
80
|
+
case sql_params
|
81
|
+
when String
|
82
|
+
select_sql << sql_params
|
83
|
+
when Hash
|
84
|
+
raise ArgumentError, "Cannot specify bind variables when passing WHERE conditions as Hash" unless bindvars.empty?
|
85
|
+
where_sqls = []
|
86
|
+
order_by_sql = nil
|
87
|
+
sql_params.each do |k,v|
|
88
|
+
if k == :order_by
|
89
|
+
order_by_sql = "ORDER BY #{v} "
|
90
|
+
else
|
91
|
+
where_sqls << "#{k} = :#{k}"
|
92
|
+
bindvars << v
|
93
|
+
end
|
94
|
+
end
|
95
|
+
select_sql << "WHERE " << where_sqls.join(' AND ') unless where_sqls.empty?
|
96
|
+
select_sql << order_by_sql if order_by_sql
|
97
|
+
else
|
98
|
+
raise ArgumentError, "Only String or Hash can be provided as SQL condition argument"
|
99
|
+
end
|
100
|
+
if first_or_all == :count
|
101
|
+
@schema.select_one(select_sql, *bindvars)
|
102
|
+
else
|
103
|
+
@schema.select(first_or_all, select_sql, *bindvars)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Select all table records using optional conditions. Examples:
|
108
|
+
#
|
109
|
+
# plsql.employees.all
|
110
|
+
# plsql.employees.all(:order_by => :employee_id)
|
111
|
+
# plsql.employees.all("WHERE employee_id > :employee_id", 5)
|
112
|
+
#
|
113
|
+
def all(sql='', *bindvars)
|
114
|
+
select(:all, sql, *bindvars)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Select first table record using optional conditions. Examples:
|
118
|
+
#
|
119
|
+
# plsql.employees.first
|
120
|
+
# plsql.employees.first(:employee_id => 1)
|
121
|
+
# plsql.employees.first("WHERE employee_id = 1")
|
122
|
+
# plsql.employees.first("WHERE employee_id = :employee_id", 1)
|
123
|
+
#
|
124
|
+
def first(sql='', *bindvars)
|
125
|
+
select(:first, sql, *bindvars)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Count table records using optional conditions. Examples:
|
129
|
+
#
|
130
|
+
# plsql.employees.count
|
131
|
+
# plsql.employees.count("WHERE employee_id > :employee_id", 5)
|
132
|
+
#
|
133
|
+
def count(sql='', *bindvars)
|
134
|
+
select(:count, sql, *bindvars)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Insert record or records in table. Examples:
|
138
|
+
#
|
139
|
+
# employee = { :employee_id => 1, :first_name => 'First', :last_name => 'Last', :hire_date => Time.local(2000,01,31) }
|
140
|
+
# plsql.employees.insert employee
|
141
|
+
# # => INSERT INTO employees VALUES (1, 'First', 'Last', ...)
|
142
|
+
#
|
143
|
+
# employees = [employee1, employee2, ... ] # array of many Hashes
|
144
|
+
# plsql.employees.insert employees
|
145
|
+
#
|
146
|
+
def insert(record)
|
147
|
+
# if Array of records is passed then insert each individually
|
148
|
+
if record.is_a?(Array)
|
149
|
+
record.each {|r| insert(r)}
|
150
|
+
return nil
|
151
|
+
end
|
152
|
+
|
153
|
+
call = ProcedureCall.new(TableProcedure.new(@schema, self, :insert), [record])
|
154
|
+
call.exec
|
155
|
+
end
|
156
|
+
|
157
|
+
# Update table records using optional conditions. Example:
|
158
|
+
#
|
159
|
+
# plsql.employees.update(:first_name => 'Second', :where => {:employee_id => 1})
|
160
|
+
# # => UPDATE employees SET first_name = 'Second' WHERE employee_id = 1
|
161
|
+
#
|
162
|
+
def update(params)
|
163
|
+
raise ArgumentError, "Only Hash parameter can be passed to table update method" unless params.is_a?(Hash)
|
164
|
+
where = params.delete(:where)
|
165
|
+
|
166
|
+
table_proc = TableProcedure.new(@schema, self, :update)
|
167
|
+
table_proc.add_set_arguments(params)
|
168
|
+
table_proc.add_where_arguments(where) if where
|
169
|
+
call = ProcedureCall.new(table_proc, table_proc.argument_values)
|
170
|
+
call.exec
|
171
|
+
end
|
172
|
+
|
173
|
+
# Delete table records using optional conditions. Example:
|
174
|
+
#
|
175
|
+
# plsql.employees.delete(:employee_id => 1)
|
176
|
+
# # => DELETE FROM employees WHERE employee_id = 1
|
177
|
+
#
|
178
|
+
def delete(sql_params='', *bindvars)
|
179
|
+
delete_sql = "DELETE FROM \"#{@schema_name}\".\"#{@table_name}\" "
|
180
|
+
case sql_params
|
181
|
+
when String
|
182
|
+
delete_sql << sql_params
|
183
|
+
when Hash
|
184
|
+
raise ArgumentError, "Cannot specify bind variables when passing WHERE conditions as Hash" unless bindvars.empty?
|
185
|
+
where_sqls = []
|
186
|
+
sql_params.each do |k,v|
|
187
|
+
where_sqls << "#{k} = :#{k}"
|
188
|
+
bindvars << v
|
189
|
+
end
|
190
|
+
delete_sql << "WHERE " << where_sqls.join(' AND ') unless where_sqls.empty?
|
191
|
+
else
|
192
|
+
raise ArgumentError, "Only String or Hash can be provided as SQL condition argument"
|
193
|
+
end
|
194
|
+
@schema.execute(delete_sql, *bindvars)
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
def get_typecode(owner, type_name)
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
# wrapper class to simulate Procedure class for ProcedureClass#exec
|
204
|
+
class TableProcedure #:nodoc:
|
205
|
+
attr_reader :arguments, :argument_list, :return, :out_list, :schema
|
206
|
+
|
207
|
+
def initialize(schema, table, operation)
|
208
|
+
@schema = schema
|
209
|
+
@table = table
|
210
|
+
@operation = operation
|
211
|
+
|
212
|
+
@return = [nil]
|
213
|
+
@out_list = [[]]
|
214
|
+
|
215
|
+
case @operation
|
216
|
+
when :insert
|
217
|
+
@argument_list = [[:p_record]]
|
218
|
+
@arguments = [{:p_record => {
|
219
|
+
:data_type => 'PL/SQL RECORD',
|
220
|
+
:fields => @table.columns
|
221
|
+
}}]
|
222
|
+
when :update
|
223
|
+
@argument_list = [[]]
|
224
|
+
@arguments = [{}]
|
225
|
+
@set_sqls = []
|
226
|
+
@set_values = []
|
227
|
+
@where_sqls = []
|
228
|
+
@where_values = []
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def overloaded?
|
233
|
+
false
|
234
|
+
end
|
235
|
+
|
236
|
+
def procedure
|
237
|
+
nil
|
238
|
+
end
|
239
|
+
|
240
|
+
def add_set_arguments(params)
|
241
|
+
params.each do |k,v|
|
242
|
+
raise ArgumentError, "Invalid column name #{k.inspect} specified as argument" unless (column_metadata = @table.columns[k])
|
243
|
+
@argument_list[0] << k
|
244
|
+
@arguments[0][k] = column_metadata
|
245
|
+
@set_sqls << "#{k}=:#{k}"
|
246
|
+
@set_values << v
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def add_where_arguments(params)
|
251
|
+
case params
|
252
|
+
when Hash
|
253
|
+
params.each do |k,v|
|
254
|
+
raise ArgumentError, "Invalid column name #{k.inspect} specified as argument" unless (column_metadata = @table.columns[k])
|
255
|
+
@argument_list[0] << :"w_#{k}"
|
256
|
+
@arguments[0][:"w_#{k}"] = column_metadata
|
257
|
+
@where_sqls << "#{k}=:w_#{k}"
|
258
|
+
@where_values << v
|
259
|
+
end
|
260
|
+
when String
|
261
|
+
@where_sqls << params
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def argument_values
|
266
|
+
@set_values + @where_values
|
267
|
+
end
|
268
|
+
|
269
|
+
def call_sql(params_string)
|
270
|
+
case @operation
|
271
|
+
when :insert
|
272
|
+
"INSERT INTO \"#{@table.schema_name}\".\"#{@table.table_name}\" VALUES #{params_string};\n"
|
273
|
+
when :update
|
274
|
+
update_sql = "UPDATE \"#{@table.schema_name}\".\"#{@table.table_name}\" SET #{@set_sqls.join(', ')}"
|
275
|
+
update_sql << " WHERE #{@where_sqls.join(' AND ')}" unless @where_sqls.empty?
|
276
|
+
update_sql << ";\n"
|
277
|
+
update_sql
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
data/lib/ruby-plsql.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "ruby_plsql"
|
data/lib/ruby_plsql.rb
CHANGED
@@ -1,46 +1,13 @@
|
|
1
|
-
|
1
|
+
require "time"
|
2
|
+
require "date"
|
3
|
+
require "bigdecimal"
|
2
4
|
|
3
|
-
|
5
|
+
%w(connection sql_statements schema procedure procedure_call package table sequence).each do |file|
|
6
|
+
require "plsql/#{file}"
|
4
7
|
end
|
5
8
|
|
6
9
|
unless defined?(JRUBY_VERSION)
|
7
|
-
|
8
|
-
require "oci8"
|
9
|
-
rescue LoadError
|
10
|
-
puts <<-EOS
|
11
|
-
To use ruby_plsql you must install ruby-oci8 library.
|
12
|
-
EOS
|
13
|
-
end
|
10
|
+
require "plsql/oci_connection"
|
14
11
|
else
|
15
|
-
|
16
|
-
require "java"
|
17
|
-
require "jruby"
|
18
|
-
# Adds JRuby classloader to current thread classloader - as a result ojdbc14.jar should not be in $JRUBY_HOME/lib
|
19
|
-
java.lang.Thread.currentThread.setContextClassLoader(JRuby.runtime.jruby_class_loader)
|
20
|
-
|
21
|
-
ojdbc_jar = "ojdbc14.jar"
|
22
|
-
if ojdbc_jar_path = ENV["PATH"].split(/[:;]/).find{|d| File.exists?(File.join(d,ojdbc_jar))}
|
23
|
-
require File.join(ojdbc_jar_path,ojdbc_jar)
|
24
|
-
else
|
25
|
-
require ojdbc_jar
|
26
|
-
end
|
27
|
-
# import java.sql.Statement
|
28
|
-
# import java.sql.Connection
|
29
|
-
# import java.sql.SQLException
|
30
|
-
# import java.sql.Types
|
31
|
-
# import java.sql.DriverManager
|
32
|
-
java.sql.DriverManager.registerDriver Java::oracle.jdbc.driver.OracleDriver.new
|
33
|
-
rescue LoadError
|
34
|
-
puts <<-EOS
|
35
|
-
To use ruby_plsql you must have Oracle JDBC driver installed.
|
36
|
-
EOS
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
require "time"
|
41
|
-
require "date"
|
42
|
-
require "bigdecimal"
|
43
|
-
|
44
|
-
%w(connection oci_connection jdbc_connection schema procedure package).each do |file|
|
45
|
-
require File.dirname(__FILE__) + "/plsql/#{file}"
|
12
|
+
require "plsql/jdbc_connection"
|
46
13
|
end
|