ruby-plsql 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,6 @@
1
+ == 0.1.0 2008-03-15
2
+
3
+ * Initial release
4
+ * Known limitations
5
+ * Currently just NUMBER, VARCHAR2, DATE, TIMESTAMP argument types are supported for PL/SQL procedures
6
+
data/License.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Raimonds Simanovskis
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,31 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ config/hoe.rb
7
+ config/requirements.rb
8
+ lib/plsql/package.rb
9
+ lib/plsql/procedure.rb
10
+ lib/plsql/schema.rb
11
+ lib/ruby_plsql.rb
12
+ lib/ruby_plsql/version.rb
13
+ log/debug.log
14
+ script/destroy
15
+ script/generate
16
+ script/txt2html
17
+ setup.rb
18
+ spec/plsql/package_spec.rb
19
+ spec/plsql/procedure_spec.rb
20
+ spec/plsql/schema_spec.rb
21
+ spec/spec.opts
22
+ spec/spec_helper.rb
23
+ tasks/deployment.rake
24
+ tasks/environment.rake
25
+ tasks/rspec.rake
26
+ tasks/website.rake
27
+ website/index.html
28
+ website/index.txt
29
+ website/javascripts/rounded_corners_lite.inc.js
30
+ website/stylesheets/screen.css
31
+ website/template.rhtml
data/README.txt ADDED
@@ -0,0 +1,66 @@
1
+ = ruby-plsql
2
+
3
+ * http://rubyforge.org/projects/ruby-plsql/
4
+
5
+ == DESCRIPTION:
6
+
7
+ ruby-plsql gem provides simple Ruby API for calling Oracle PL/SQL procedures. Currently ruby-plsql requires ruby-oci8 for connection to Oracle database, it is planned to add JRuby/JDBC support in the future.
8
+
9
+ See http://blog.rayapps.com for more information.
10
+
11
+ Look ar RSpec tests under spec directory for usage examples.
12
+
13
+ == FEATURES/PROBLEMS:
14
+
15
+ * Currently just NUMBER, VARCHAR2, DATE, TIMESTAMP argument types are supported for PL/SQL procedures
16
+
17
+ == SYNOPSIS:
18
+
19
+ Usage examples:
20
+
21
+ require "ruby_plsql"
22
+
23
+ plsql.connection = OCI8.new("hr","hr","xe")
24
+
25
+ plsql.test_uppercase('xxx') # => "XXX"
26
+ plsql.test_uppercase(:p_string => 'xxx') # => "XXX"
27
+ plsql.test_copy("abc", nil, nil) # => { :p_to => "abc", :p_to_double => "abcabc" }
28
+ plsql.test_copy(:p_from => "abc", :p_to => nil, :p_to_double => nil)
29
+ # => { :p_to => "abc", :p_to_double => "abcabc" }
30
+ plsql.hr.test_uppercase('xxx') # => "XXX"
31
+ plsql.test_package.test_uppercase('xxx') # => 'XXX'
32
+
33
+ plsql.logoff
34
+
35
+ == REQUIREMENTS:
36
+
37
+ * Requires ruby-oci8 library to connect to Oracle
38
+
39
+ == INSTALL:
40
+
41
+ * sudo gem install ruby-plsql
42
+
43
+ == LICENSE:
44
+
45
+ (The MIT License)
46
+
47
+ Copyright (c) 2008 Raimonds Simanovskis
48
+
49
+ Permission is hereby granted, free of charge, to any person obtaining
50
+ a copy of this software and associated documentation files (the
51
+ 'Software'), to deal in the Software without restriction, including
52
+ without limitation the rights to use, copy, modify, merge, publish,
53
+ distribute, sublicense, and/or sell copies of the Software, and to
54
+ permit persons to whom the Software is furnished to do so, subject to
55
+ the following conditions:
56
+
57
+ The above copyright notice and this permission notice shall be
58
+ included in all copies or substantial portions of the Software.
59
+
60
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
61
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
62
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
63
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
64
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
65
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
66
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
data/config/hoe.rb ADDED
@@ -0,0 +1,70 @@
1
+ require 'ruby_plsql/version'
2
+
3
+ AUTHOR = 'Raimonds Simanovskis' # can also be an array of Authors
4
+ EMAIL = "raymonds72@gmail.com"
5
+ DESCRIPTION = "ruby-plsql gem provides simple Ruby API for calling Oracle PL/SQL procedures."
6
+ GEM_NAME = 'ruby-plsql' # what ppl will type to install your gem
7
+ RUBYFORGE_PROJECT = 'ruby-plsql' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+
11
+ @config_file = "~/.rubyforge/user-config.yml"
12
+ @config = nil
13
+ RUBYFORGE_USERNAME = "unknown"
14
+ def rubyforge_username
15
+ unless @config
16
+ begin
17
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
18
+ rescue
19
+ puts <<-EOS
20
+ ERROR: No rubyforge config file found: #{@config_file}
21
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
22
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
23
+ EOS
24
+ exit
25
+ end
26
+ end
27
+ RUBYFORGE_USERNAME.replace @config["username"]
28
+ end
29
+
30
+
31
+ REV = nil
32
+ # UNCOMMENT IF REQUIRED:
33
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
34
+ VERS = RubyPlsql::VERSION::STRING + (REV ? ".#{REV}" : "")
35
+ RDOC_OPTS = ['--quiet', '--title', 'ruby_plsql documentation',
36
+ "--opname", "index.html",
37
+ "--line-numbers",
38
+ "--main", "README",
39
+ "--inline-source"]
40
+
41
+ class Hoe
42
+ def extra_deps
43
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
44
+ @extra_deps
45
+ end
46
+ end
47
+
48
+ # Generate all the Rake tasks
49
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
50
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
51
+ p.developer(AUTHOR, EMAIL)
52
+ p.description = DESCRIPTION
53
+ p.summary = DESCRIPTION
54
+ p.url = HOMEPATH
55
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
56
+ p.test_globs = ["test/**/test_*.rb"]
57
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
58
+
59
+ # == Optional
60
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
61
+ #p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
62
+
63
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
64
+
65
+ end
66
+
67
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
68
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
69
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
70
+ hoe.rsync_args = '-av --delete --ignore-errors'
@@ -0,0 +1,17 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
16
+
17
+ require 'ruby_plsql'
@@ -0,0 +1,42 @@
1
+ module PLSQL
2
+
3
+ module PackageClassMethods
4
+ def find(schema, package)
5
+ if schema.select_first("
6
+ SELECT object_name FROM all_objects
7
+ WHERE owner = :owner
8
+ AND object_name = :package
9
+ AND object_type = 'PACKAGE'",
10
+ schema.schema_name, package.to_s.upcase)
11
+ new(schema, package)
12
+ else
13
+ nil
14
+ end
15
+ end
16
+ end
17
+
18
+ class Package
19
+ extend PackageClassMethods
20
+
21
+ def initialize(schema, package)
22
+ @schema = schema
23
+ @package = package.to_s.upcase
24
+ @procedures = {}
25
+ end
26
+
27
+ private
28
+
29
+ def method_missing(method, *args)
30
+ if procedure = @procedures[method]
31
+ procedure.exec(*args)
32
+ elsif procedure = Procedure.find(@schema, method, @package)
33
+ @procedures[method] = procedure
34
+ procedure.exec(*args)
35
+ else
36
+ raise ArgumentError, "No PL/SQL procedure found"
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,172 @@
1
+ module PLSQL
2
+
3
+ module ProcedureClassMethods
4
+ def find(schema, procedure, package = nil)
5
+ if package.nil? && schema.select_first("
6
+ SELECT object_name FROM all_procedures
7
+ WHERE owner = :owner
8
+ AND object_name = :object_name
9
+ AND procedure_name IS NULL
10
+ ", schema.schema_name, procedure.to_s.upcase)
11
+ new(schema, procedure)
12
+ elsif package && schema.select_first("
13
+ SELECT object_name FROM all_procedures
14
+ WHERE owner = :owner
15
+ AND object_name = :object_name
16
+ AND procedure_name = :procedure_name
17
+ ", schema.schema_name, package, procedure.to_s.upcase)
18
+ new(schema, procedure, package)
19
+ else
20
+ nil
21
+ end
22
+ end
23
+ end
24
+
25
+ class Procedure
26
+ extend ProcedureClassMethods
27
+
28
+ def initialize(schema, procedure, package = nil)
29
+ @schema = schema
30
+ @procedure = procedure.to_s.upcase
31
+ @package = package
32
+ @arguments = {}
33
+ @return = nil
34
+ num_rows = @schema.connection.exec("
35
+ SELECT argument_name, position, data_type, in_out, data_length, data_precision, data_scale
36
+ FROM all_arguments
37
+ WHERE owner = :owner
38
+ AND object_name = :object_name
39
+ AND NVL(package_name,'nil') = :package
40
+ ", @schema.schema_name, @procedure, @package ? @package : 'nil'
41
+ ) do |r|
42
+ argument_name, position, data_type, in_out, data_length, data_precision, data_scale = r
43
+ if argument_name
44
+ @arguments[argument_name.downcase.to_sym] = {
45
+ :position => position,
46
+ :data_type => data_type,
47
+ :in_out => in_out,
48
+ :data_length => data_length,
49
+ :data_precision => data_precision,
50
+ :data_scale => data_scale
51
+ }
52
+ else
53
+ @return = {
54
+ :data_type => data_type,
55
+ :in_out => in_out,
56
+ :data_length => data_length,
57
+ :data_precision => data_precision,
58
+ :data_scale => data_scale
59
+ }
60
+ end
61
+ end
62
+ @argument_list = @arguments.keys.sort {|k1, k2| @arguments[k1][:position] <=> @arguments[k2][:position]}
63
+ @out_list = @argument_list.select {|k| @arguments[k][:in_out] == 'OUT'}
64
+ end
65
+
66
+ def exec(*args)
67
+ sql = "BEGIN\n"
68
+ sql << ":return := " if @return
69
+ sql << "#{@procedure}("
70
+ # Named arguments
71
+ args_list = []
72
+ args_hash = {}
73
+ if args.size == 1 and args[0].is_a?(Hash)
74
+ sql << args[0].map do |k,v|
75
+ raise ArgumentError, "Wrong argument passed to PL/SQL procedure" unless @arguments[k]
76
+ args_list << k
77
+ args_hash[k] = v
78
+ "#{k.to_s} => :#{k.to_s}"
79
+ end.join(', ')
80
+ # Sequential arguments
81
+ else
82
+ raise ArgumentError, "Too many arguments passed to PL/SQL procedure" if args.size > @argument_list.size
83
+ i = 0
84
+ sql << args.map do |v|
85
+ k = @argument_list[i]
86
+ i += 1
87
+ args_list << k
88
+ args_hash[k] = v
89
+ ":#{k.to_s}"
90
+ end.join(', ')
91
+ end
92
+ sql << ");\n"
93
+ sql << "END;\n"
94
+
95
+ cursor = @schema.connection.parse(sql)
96
+
97
+ args_list.each do |k|
98
+ data_type, data_length = plsql_to_ruby_data_type(@arguments[k])
99
+ cursor.bind_param(":#{k.to_s}", ruby_value_to_ora_value(args_hash[k], data_type), data_type, data_length)
100
+ end
101
+
102
+ if @return
103
+ data_type, data_length = plsql_to_ruby_data_type(@return)
104
+ cursor.bind_param(":return", nil, data_type, data_length)
105
+ end
106
+
107
+ cursor.exec
108
+
109
+ # if function
110
+ if @return
111
+ result = ora_value_to_ruby_value(cursor[':return'])
112
+ # if procedure with output parameters
113
+ elsif @out_list.size > 0
114
+ result = {}
115
+ @out_list.each do |k|
116
+ result[k] = ora_value_to_ruby_value(cursor[":#{k}"])
117
+ end
118
+ # if procedure without output parameters
119
+ else
120
+ result = nil
121
+ end
122
+ cursor.close
123
+ result
124
+ end
125
+
126
+ private
127
+
128
+ def plsql_to_ruby_data_type(argument)
129
+ case argument[:data_type]
130
+ when "VARCHAR2"
131
+ [String, argument[:data_length] ? argument[:data_length] : 4000]
132
+ when "NUMBER"
133
+ [OraNumber, nil]
134
+ when "DATE"
135
+ [DateTime, nil]
136
+ when "TIMESTAMP"
137
+ [Time, nil]
138
+ # CLOB
139
+ # BLOB
140
+ else
141
+ [String, 4000]
142
+ end
143
+ end
144
+
145
+ def ruby_value_to_ora_value(val, type)
146
+ if type == OraNumber
147
+ val.is_a?(Bignum) ? val.to_f : val
148
+ else
149
+ val
150
+ end
151
+ end
152
+
153
+ def ora_number_to_ruby_number(num)
154
+ if num.to_i == num.to_f
155
+ num.to_i
156
+ else
157
+ num.to_f
158
+ end
159
+ end
160
+
161
+ def ora_value_to_ruby_value(val)
162
+ case val
163
+ when OraNumber
164
+ ora_number_to_ruby_number(val)
165
+ else
166
+ val
167
+ end
168
+ end
169
+
170
+ end
171
+
172
+ end
@@ -0,0 +1,103 @@
1
+ module PLSQL
2
+ class Schema
3
+ @@schemas = {}
4
+
5
+ class <<self
6
+ def find_or_new(connection_alias)
7
+ connection_alias ||= :default
8
+ if @@schemas[connection_alias]
9
+ @@schemas[connection_alias]
10
+ else
11
+ @@schemas[connection_alias] = self.new
12
+ end
13
+ end
14
+
15
+ end
16
+
17
+ def initialize(conn = nil, schema = nil, first = true)
18
+ self.connection = conn
19
+ @schema_name = schema ? schema.to_s.upcase : nil
20
+ @first = first
21
+ end
22
+
23
+ def connection
24
+ @connection
25
+ end
26
+
27
+ def connection=(conn)
28
+ @connection = conn
29
+ if @connection
30
+ @procedures = {}
31
+ @packages = {}
32
+ @schemas = {}
33
+ else
34
+ @procedures = nil
35
+ @packages = nil
36
+ @schemas = nil
37
+ end
38
+ end
39
+
40
+ def logoff
41
+ connection.logoff
42
+ self.connection = nil
43
+ end
44
+
45
+ def schema_name
46
+ return nil unless connection
47
+ @schema_name ||= select_first("SELECT SYS_CONTEXT('userenv','session_user') FROM dual")[0]
48
+ end
49
+
50
+ def select_first(sql, *bindvars)
51
+ cursor = connection.exec(sql, *bindvars)
52
+ result = cursor.fetch
53
+ cursor.close
54
+ result
55
+ end
56
+
57
+ def commit
58
+ connection.commit
59
+ end
60
+
61
+ def rollback
62
+ connection.rollback
63
+ end
64
+
65
+ private
66
+
67
+ def method_missing(method, *args)
68
+ raise ArgumentError, "No PL/SQL connection" unless connection
69
+ if procedure = @procedures[method]
70
+ procedure.exec(*args)
71
+ elsif procedure = Procedure.find(self, method)
72
+ @procedures[method] = procedure
73
+ procedure.exec(*args)
74
+ elsif package = @packages[method]
75
+ package
76
+ elsif package = Package.find(self, method)
77
+ @packages[method] = package
78
+ elsif schema = @schemas[method]
79
+ schema
80
+ elsif schema = find_other_schema(method)
81
+ @schemas[method] = schema
82
+ else
83
+ raise ArgumentError, "No PL/SQL procedure found"
84
+ end
85
+ end
86
+
87
+ def find_other_schema(name)
88
+ return nil unless @first && connection
89
+ if select_first("SELECT username FROM all_users WHERE username = :username", name.to_s.upcase)
90
+ Schema.new(connection, name, false)
91
+ else
92
+ nil
93
+ end
94
+ end
95
+
96
+ end
97
+ end
98
+
99
+ module Kernel
100
+ def plsql(connection_alias = nil)
101
+ PLSQL::Schema.find_or_new(connection_alias)
102
+ end
103
+ end