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,146 @@
1
+ module PLSQL
2
+
3
+ module VariableClassMethods #:nodoc:
4
+ def find(schema, variable, package, override_schema_name = nil)
5
+ variable_upcase = variable.to_s.upcase
6
+ schema.select_all(
7
+ "SELECT text FROM all_source
8
+ WHERE owner = :owner
9
+ AND name = :object_name
10
+ AND type = 'PACKAGE'
11
+ AND UPPER(text) LIKE :variable_name",
12
+ override_schema_name || schema.schema_name, package, "%#{variable_upcase}%").each do |row|
13
+ if row[0] =~ /^\s*#{variable_upcase}\s+(CONSTANT\s+)?([A-Z0-9_. %]+(\([\w\s,]+\))?)\s*(NOT\s+NULL)?\s*((:=|DEFAULT).*)?;\s*(--.*)?$/i
14
+ return new(schema, variable, package, $2.strip, override_schema_name)
15
+ end
16
+ end
17
+ nil
18
+ end
19
+ end
20
+
21
+ class Variable #:nodoc:
22
+ extend VariableClassMethods
23
+
24
+ attr_reader :schema_name, :package_name, :variable_name #:nodoc:
25
+
26
+ def initialize(schema, variable, package, variable_type, override_schema_name = nil)
27
+ @schema = schema
28
+ @schema_name = override_schema_name || schema.schema_name
29
+ @variable_name = variable.to_s.upcase
30
+ @package_name = package
31
+ @variable_type = variable_type.upcase
32
+ @metadata = metadata(@variable_type)
33
+ end
34
+
35
+ def value
36
+ @variable_get_proc ||= VariableProcedure.new(@schema, self, :get, @metadata)
37
+ ProcedureCall.new(@variable_get_proc).exec
38
+ end
39
+
40
+ def value=(new_value)
41
+ @variable_set_proc ||= VariableProcedure.new(@schema, self, :set, @metadata)
42
+ ProcedureCall.new(@variable_set_proc, [new_value]).exec
43
+ new_value
44
+ end
45
+
46
+ private
47
+
48
+ def metadata(type_string)
49
+ case type_string
50
+ when /^(VARCHAR2|CHAR|NVARCHAR2|NCHAR)(\((\d+)[\s\w]*\))?$/
51
+ {:data_type => $1, :data_length => $3.to_i, :in_out => 'IN/OUT'}
52
+ when /^(CLOB|NCLOB|BLOB)$/,
53
+ /^(NUMBER)(\(.*\))?$/, /^(PLS_INTEGER|BINARY_INTEGER)$/,
54
+ /^(DATE|TIMESTAMP|TIMESTAMP WITH TIME ZONE|TIMESTAMP WITH LOCAL TIME ZONE)$/
55
+ {:data_type => $1, :in_out => 'IN/OUT'}
56
+ when /^INTEGER$/
57
+ {:data_type => 'NUMBER', :in_out => 'IN/OUT'}
58
+ when /^BOOLEAN$/
59
+ {:data_type => 'PL/SQL BOOLEAN', :in_out => 'IN/OUT'}
60
+ when /^(\w+\.)?(\w+)\.(\w+)%TYPE$/
61
+ schema = $1 ? plsql.send($1.chop) : plsql
62
+ table = schema.send($2.downcase.to_sym)
63
+ column = table.columns[$3.downcase.to_sym]
64
+ {:data_type => column[:data_type], :data_length => column[:data_length], :sql_type_name => column[:sql_type_name], :in_out => 'IN/OUT'}
65
+ when /^(\w+\.)?(\w+)$/
66
+ schema = $1 ? @schema.root_schema.send($1.chop) : @schema
67
+ begin
68
+ type = schema.send($2.downcase.to_sym)
69
+ raise ArgumentError unless type.is_a?(PLSQL::Type)
70
+ typecode = case type.typecode
71
+ when 'COLLECTION' then 'TABLE'
72
+ else 'OBJECT'
73
+ end
74
+ {:data_type => typecode, :data_length => nil, :sql_type_name => "#{type.schema_name}.#{type.type_name}", :in_out => 'IN/OUT'}
75
+ rescue ArgumentError
76
+ raise ArgumentError, "Package variable data type #{type_string} is not object type defined in schema"
77
+ end
78
+ when /^(\w+\.)?(\w+)%ROWTYPE$/
79
+ schema = $1 ? plsql.send($1.chop) : plsql
80
+ table = schema.send($2.downcase.to_sym)
81
+ record_metadata = {
82
+ :data_type => 'PL/SQL RECORD',
83
+ :in_out => 'IN/OUT',
84
+ :fields => {}
85
+ }
86
+ table.columns.each do |name, column|
87
+ record_metadata[:fields][name] =
88
+ {:data_type => column[:data_type], :data_length => column[:data_length], :sql_type_name => column[:sql_type_name],
89
+ :position => column[:position], :in_out => 'IN/OUT'}
90
+ end
91
+ record_metadata
92
+ else
93
+ raise ArgumentError, "Package variable data type #{type_string} is not supported"
94
+ end
95
+ end
96
+
97
+ # wrapper class to simulate Procedure class for ProcedureClass#exec
98
+ class VariableProcedure #:nodoc:
99
+ attr_reader :arguments, :argument_list, :return, :out_list, :schema
100
+
101
+ def initialize(schema, variable, operation, metadata)
102
+ @schema = schema
103
+ @variable = variable
104
+ @operation = operation
105
+ @metadata = metadata
106
+
107
+ @out_list = [[]]
108
+
109
+ case @operation
110
+ when :get
111
+ @argument_list = [[]]
112
+ @arguments = [{}]
113
+ @return = [@metadata]
114
+ when :set
115
+ @argument_list = [[:value]]
116
+ @arguments = [{:value => @metadata}]
117
+ @return = [nil]
118
+ end
119
+
120
+ end
121
+
122
+ def overloaded?
123
+ false
124
+ end
125
+
126
+ def procedure
127
+ nil
128
+ end
129
+
130
+ def call_sql(params_string)
131
+ sql = (schema_name = @variable.schema_name) ? "#{schema_name}." : ""
132
+ sql << "#{@variable.package_name}.#{@variable.variable_name}"
133
+ case @operation
134
+ when :get
135
+ # params string contains assignment to return variable
136
+ "#{params_string} #{sql};\n"
137
+ when :set
138
+ "#{sql} := #{params_string};\n"
139
+ end
140
+ end
141
+
142
+ end
143
+
144
+ end
145
+
146
+ end
@@ -0,0 +1,3 @@
1
+ module PLSQL #:nodoc:
2
+ VERSION = File.read(File.dirname(__FILE__)+'/../../VERSION').chomp
3
+ end
@@ -0,0 +1,41 @@
1
+ module PLSQL
2
+
3
+ module ViewClassMethods #:nodoc:
4
+ def find(schema, view)
5
+ if schema.select_first(
6
+ "SELECT view_name FROM all_views
7
+ WHERE owner = :owner
8
+ AND view_name = :view_name",
9
+ schema.schema_name, view.to_s.upcase)
10
+ new(schema, view)
11
+ # search for synonym
12
+ elsif (row = schema.select_first(
13
+ "SELECT v.owner, v.view_name
14
+ FROM all_synonyms s, all_views v
15
+ WHERE s.owner = :owner
16
+ AND s.synonym_name = :synonym_name
17
+ AND v.owner = s.table_owner
18
+ AND v.view_name = s.table_name
19
+ UNION ALL
20
+ SELECT v.owner, v.view_name
21
+ FROM all_synonyms s, all_views v
22
+ WHERE s.owner = 'PUBLIC'
23
+ AND s.synonym_name = :synonym_name
24
+ AND v.owner = s.table_owner
25
+ AND v.view_name = s.table_name",
26
+ schema.schema_name, view.to_s.upcase, view.to_s.upcase))
27
+ new(schema, row[1], row[0])
28
+ else
29
+ nil
30
+ end
31
+ end
32
+ end
33
+
34
+ class View < Table
35
+ extend ViewClassMethods
36
+
37
+ alias :view_name :table_name #:nodoc:
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1 @@
1
+ require "ruby_plsql"
@@ -0,0 +1,18 @@
1
+ require "time"
2
+ require "date"
3
+ require "bigdecimal"
4
+
5
+ %w(connection sql_statements schema
6
+ procedure subprogram_call procedure_call
7
+ pipelined_function pipelined_function_call
8
+ package variable
9
+ table view sequence type
10
+ version helpers).each do |file|
11
+ require "plsql/#{file}"
12
+ end
13
+
14
+ if defined?(JRUBY_VERSION)
15
+ require "plsql/jdbc_connection"
16
+ else
17
+ require "plsql/oci_connection"
18
+ end
@@ -0,0 +1,87 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "flash-gordons-ruby-plsql"
8
+ s.version = "0.5.0"
9
+ s.licenses = ['MIT']
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Raimonds Simanovskis"]
12
+ s.date = "2012-04-16"
13
+ s.description = "ruby-plsql gem provides simple Ruby API for calling Oracle PL/SQL procedures.\nIt could be used both for accessing Oracle PL/SQL API procedures in legacy applications\nas well as it could be used to create PL/SQL unit tests using Ruby testing libraries.\n"
14
+ s.email = "raimonds.simanovskis@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ "Gemfile",
20
+ "History.txt",
21
+ "License.txt",
22
+ "README.md",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "lib/plsql/connection.rb",
26
+ "lib/plsql/helpers.rb",
27
+ "lib/plsql/jdbc_connection.rb",
28
+ "lib/plsql/oci8_patches.rb",
29
+ "lib/plsql/oci_connection.rb",
30
+ "lib/plsql/package.rb",
31
+ "lib/plsql/procedure.rb",
32
+ "lib/plsql/procedure_call.rb",
33
+ "lib/plsql/schema.rb",
34
+ "lib/plsql/sequence.rb",
35
+ "lib/plsql/sql_statements.rb",
36
+ "lib/plsql/table.rb",
37
+ "lib/plsql/type.rb",
38
+ "lib/plsql/variable.rb",
39
+ "lib/plsql/version.rb",
40
+ "lib/plsql/view.rb",
41
+ "lib/ruby-plsql.rb",
42
+ "lib/ruby_plsql.rb",
43
+ "ruby-plsql.gemspec",
44
+ "spec/plsql/connection_spec.rb",
45
+ "spec/plsql/package_spec.rb",
46
+ "spec/plsql/procedure_spec.rb",
47
+ "spec/plsql/schema_spec.rb",
48
+ "spec/plsql/sequence_spec.rb",
49
+ "spec/plsql/sql_statements_spec.rb",
50
+ "spec/plsql/table_spec.rb",
51
+ "spec/plsql/type_spec.rb",
52
+ "spec/plsql/variable_spec.rb",
53
+ "spec/plsql/version_spec.rb",
54
+ "spec/plsql/view_spec.rb",
55
+ "spec/spec.opts",
56
+ "spec/spec_helper.rb"
57
+ ]
58
+ s.homepage = "http://github.com/rsim/ruby-plsql"
59
+ s.require_paths = ["lib"]
60
+ s.rubygems_version = "1.8.11"
61
+ s.summary = "Ruby API for calling Oracle PL/SQL procedures."
62
+
63
+ if s.respond_to? :specification_version then
64
+ s.specification_version = 3
65
+
66
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
67
+ s.add_development_dependency(%q<jeweler>, ["1.8.3"])
68
+ s.add_development_dependency(%q<rspec>, ["2.9"])
69
+ s.add_development_dependency(%q<activerecord>, ["3.2.3"])
70
+ s.add_development_dependency(%q<activerecord-oracle_enhanced-adapter>, ["1.4.1"])
71
+ s.add_development_dependency(%q<ruby-oci8>, ["2.1.5"])
72
+ else
73
+ s.add_dependency(%q<jeweler>, ["1.8.3"])
74
+ s.add_dependency(%q<rspec>, ["2.9"])
75
+ s.add_dependency(%q<activerecord>, ["3.2.3"])
76
+ s.add_dependency(%q<activerecord-oracle_enhanced-adapter>, ["1.4.1"])
77
+ s.add_dependency(%q<ruby-oci8>, ["2.1.5"])
78
+ end
79
+ else
80
+ s.add_dependency(%q<jeweler>, ["1.8.3"])
81
+ s.add_dependency(%q<rspec>, ["2.9"])
82
+ s.add_dependency(%q<activerecord>, ["3.2.3"])
83
+ s.add_dependency(%q<activerecord-oracle_enhanced-adapter>, ["1.4.1"])
84
+ s.add_dependency(%q<ruby-oci8>, ["2.1.5"])
85
+ end
86
+ end
87
+
@@ -0,0 +1,495 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "Connection" do
6
+
7
+ before(:all) do
8
+ @raw_conn = get_connection
9
+ @conn = PLSQL::Connection.create( @raw_conn )
10
+ @conn.set_time_zone
11
+ end
12
+
13
+ after(:all) do
14
+ unless defined?(JRuby)
15
+ @raw_conn.logoff rescue nil
16
+ else
17
+ @raw_conn.close rescue nil
18
+ end
19
+ end
20
+
21
+ describe "create and destroy" do
22
+ before(:all) do
23
+ @raw_conn1 = get_connection
24
+ end
25
+
26
+ before(:each) do
27
+ @conn1 = PLSQL::Connection.create( @raw_conn1 )
28
+ @conn1.set_time_zone
29
+ end
30
+
31
+ it "should create connection" do
32
+ @conn1.raw_connection.should == @raw_conn1
33
+ end
34
+
35
+ unless defined?(JRuby)
36
+ it "should be oci connection" do
37
+ @conn1.should be_oci
38
+ @conn1.raw_driver.should == :oci
39
+ end
40
+ else
41
+ it "should be jdbc connection" do
42
+ @conn1.should be_jdbc
43
+ @conn1.raw_driver.should == :jdbc
44
+ end
45
+ end
46
+
47
+ it "should logoff connection" do
48
+ @conn1.logoff.should be_true
49
+ end
50
+
51
+ end
52
+
53
+ # Ruby 1.8 and 1.9
54
+ unless defined?(JRuby)
55
+ describe "OCI data type conversions" do
56
+ it "should translate PL/SQL VARCHAR2 to Ruby String" do
57
+ @conn.plsql_to_ruby_data_type(:data_type => "VARCHAR2", :data_length => 100).should == [String, 100]
58
+ @conn.plsql_to_ruby_data_type(:data_type => "VARCHAR2", :data_length => nil).should == [String, 32767]
59
+ end
60
+
61
+ it "should translate PL/SQL CLOB to Ruby String" do
62
+ @conn.plsql_to_ruby_data_type(:data_type => "CLOB", :data_length => 100_000).should == [OCI8::CLOB, nil]
63
+ @conn.plsql_to_ruby_data_type(:data_type => "CLOB", :data_length => nil).should == [OCI8::CLOB, nil]
64
+ end
65
+
66
+ it "should translate PL/SQL NUMBER to Ruby OraNumber" do
67
+ @conn.plsql_to_ruby_data_type(:data_type => "NUMBER", :data_length => 15).should == [OraNumber, nil]
68
+ end
69
+
70
+ it "should translate PL/SQL DATE to Ruby DateTime" do
71
+ @conn.plsql_to_ruby_data_type(:data_type => "DATE", :data_length => nil).should == [DateTime, nil]
72
+ end
73
+
74
+ it "should translate PL/SQL TIMESTAMP to Ruby Time" do
75
+ @conn.plsql_to_ruby_data_type(:data_type => "TIMESTAMP", :data_length => nil).should == [Time, nil]
76
+ end
77
+
78
+ it "should not translate Ruby Fixnum when OraNumber type specified" do
79
+ @conn.ruby_value_to_ora_value(100, OraNumber).should eql(100)
80
+ end
81
+
82
+ it "should translate Ruby Bignum value to OraNumber when OraNumber type specified" do
83
+ ora_number = @conn.ruby_value_to_ora_value(12345678901234567890, OraNumber)
84
+ ora_number.class.should == OraNumber
85
+ ora_number.to_s.should == "12345678901234567890"
86
+ # OraNumber has more numeric comparison methods in ruby-oci8 2.0
87
+ ora_number.should == OraNumber.new("12345678901234567890") if OCI8::VERSION >= '2.0.0'
88
+ end
89
+
90
+ it "should translate Ruby String value to OCI8::CLOB when OCI8::CLOB type specified" do
91
+ large_text = "x" * 100_000
92
+ ora_value = @conn.ruby_value_to_ora_value(large_text, OCI8::CLOB)
93
+ ora_value.class.should == OCI8::CLOB
94
+ ora_value.size.should == 100_000
95
+ ora_value.rewind
96
+ ora_value.read.should == large_text
97
+ end
98
+
99
+ it "should translate Oracle OraNumber integer value to Fixnum" do
100
+ @conn.ora_value_to_ruby_value(OraNumber.new(100)).should eql(100)
101
+ end
102
+
103
+ it "should translate Oracle OraNumber float value to BigDecimal" do
104
+ @conn.ora_value_to_ruby_value(OraNumber.new(100.11)).should eql(BigDecimal("100.11"))
105
+ end
106
+
107
+ # ruby-oci8 2.0 returns DATE as Time or DateTime
108
+ if OCI8::VERSION < '2.0.0'
109
+ it "should translate Oracle OraDate value to Time" do
110
+ now = OraDate.now
111
+ @conn.ora_value_to_ruby_value(now).should eql(now.to_time)
112
+ end
113
+ end
114
+
115
+ it "should translate Oracle CLOB value to String" do
116
+ large_text = "x" * 100_000
117
+ clob = OCI8::CLOB.new(@raw_conn, large_text)
118
+ @conn.ora_value_to_ruby_value(clob).should == large_text
119
+ end
120
+
121
+ end
122
+
123
+ # JRuby
124
+ else
125
+
126
+ describe "JDBC data type conversions" do
127
+ it "should translate PL/SQL VARCHAR2 to Ruby String" do
128
+ @conn.plsql_to_ruby_data_type(:data_type => "VARCHAR2", :data_length => 100).should == [String, 100]
129
+ @conn.plsql_to_ruby_data_type(:data_type => "VARCHAR2", :data_length => nil).should == [String, 32767]
130
+ end
131
+
132
+ it "should translate PL/SQL NUMBER to Ruby BigDecimal" do
133
+ @conn.plsql_to_ruby_data_type(:data_type => "NUMBER", :data_length => 15).should == [BigDecimal, nil]
134
+ end
135
+
136
+ it "should translate PL/SQL DATE to Ruby DateTime" do
137
+ @conn.plsql_to_ruby_data_type(:data_type => "DATE", :data_length => nil).should == [DateTime, nil]
138
+ end
139
+
140
+ it "should translate PL/SQL TIMESTAMP to Ruby Time" do
141
+ @conn.plsql_to_ruby_data_type(:data_type => "TIMESTAMP", :data_length => nil).should == [Time, nil]
142
+ end
143
+
144
+ it "should not translate Ruby Fixnum when BigDecimal type specified" do
145
+ @conn.ruby_value_to_ora_value(100, BigDecimal).should == java.math.BigDecimal.new(100)
146
+ end
147
+
148
+ it "should translate Ruby Bignum value to BigDecimal when BigDecimal type specified" do
149
+ big_decimal = @conn.ruby_value_to_ora_value(12345678901234567890, BigDecimal)
150
+ big_decimal.should == java.math.BigDecimal.new("12345678901234567890")
151
+ end
152
+
153
+ it "should translate Ruby String value to Java::OracleSql::CLOB when Java::OracleSql::CLOB type specified" do
154
+ large_text = "x" * 100_000
155
+ ora_value = @conn.ruby_value_to_ora_value(large_text, Java::OracleSql::CLOB)
156
+ ora_value.class.should == Java::OracleSql::CLOB
157
+ ora_value.length.should == 100_000
158
+ ora_value.getSubString(1, ora_value.length) == large_text
159
+ ora_value.freeTemporary
160
+ end
161
+
162
+ it "should translate Ruby nil value to nil when Java::OracleSql::CLOB type specified" do
163
+ ora_value = @conn.ruby_value_to_ora_value(nil, Java::OracleSql::CLOB)
164
+ ora_value.should be_nil
165
+ end
166
+
167
+ it "should translate Oracle BigDecimal integer value to Fixnum" do
168
+ @conn.ora_value_to_ruby_value(BigDecimal("100")).should eql(100)
169
+ end
170
+
171
+ it "should translate Oracle BigDecimal float value to BigDecimal" do
172
+ @conn.ora_value_to_ruby_value(BigDecimal("100.11")).should eql(BigDecimal("100.11"))
173
+ end
174
+
175
+ it "should translate Oracle CLOB value to String" do
176
+ large_text = "āčē" * 100_000
177
+ clob = @conn.ruby_value_to_ora_value(large_text, Java::OracleSql::CLOB)
178
+ @conn.ora_value_to_ruby_value(clob).should == large_text
179
+ clob.freeTemporary
180
+ end
181
+
182
+ it "should translate empty Oracle CLOB value to nil" do
183
+ clob = @conn.ruby_value_to_ora_value(nil, Java::OracleSql::CLOB)
184
+ @conn.ora_value_to_ruby_value(clob).should be_nil
185
+ end
186
+
187
+ end
188
+
189
+ end
190
+
191
+ describe "SQL SELECT statements" do
192
+
193
+ it "should execute SQL statement and return first result" do
194
+ @now = Time.local(2008,05,31,23,22,11)
195
+ @conn.select_first("SELECT 'abc',123,123.456,
196
+ TO_DATE('#{@now.strftime("%Y-%m-%d %H:%M:%S")}','YYYY-MM-DD HH24:MI:SS')
197
+ FROM dual").should == ["abc",123,123.456,@now]
198
+ end
199
+
200
+ it "should execute SQL statement and return first result as hash" do
201
+ @now = Time.local(2008,05,31,23,22,11)
202
+ @conn.select_hash_first("SELECT 'abc' a, 123 b, 123.456 c,
203
+ TO_DATE('#{@now.strftime("%Y-%m-%d %H:%M:%S")}', 'YYYY-MM-DD HH24:MI:SS') d
204
+ FROM dual").should == {:a => "abc", :b => 123, :c => 123.456, :d => @now}
205
+ end
206
+
207
+ it "should execute SQL statement with bind parameters and return first result" do
208
+ @today = Date.parse("2008-05-31")
209
+ @now = Time.local(2008,05,31,23,22,11)
210
+ @conn.select_first("SELECT :1,:2,:3,:4,:5 FROM dual",
211
+ 'abc',123,123.456,@now,@today).should == ["abc",123,123.456,@now,Time.parse(@today.to_s)]
212
+ end
213
+
214
+ it "should execute SQL statement with NULL values and return first result" do
215
+ @now = Time.local(2008,05,31,23,22,11)
216
+ @conn.select_first("SELECT NULL,123,123.456,
217
+ TO_DATE('#{@now.strftime("%Y-%m-%d %H:%M:%S")}','YYYY-MM-DD HH24:MI:SS')
218
+ FROM dual").should == [nil,123,123.456,@now]
219
+ end
220
+
221
+ if defined?(JRuby)
222
+
223
+ it "should execute SQL statement with NULL values as bind parameters and return first result" do
224
+ @today = Date.parse("2008-05-31")
225
+ @now = Time.local(2008,05,31,23,22,11)
226
+ @conn.select_first("SELECT :1,:2,:3,:4,:5 FROM dual",
227
+ nil,123,123.456,@now,@today).should == [nil,123,123.456,@now,Time.parse(@today.to_s)]
228
+ end
229
+
230
+ end
231
+
232
+ it "should execute SQL statement and return all results" do
233
+ @now = Time.local(2008,05,31,23,22,11)
234
+ @conn.select_all("SELECT 'abc',123,123.456,
235
+ TO_DATE('#{@now.strftime("%Y-%m-%d %H:%M:%S")}','YYYY-MM-DD HH24:MI:SS')
236
+ FROM dual
237
+ UNION ALL SELECT 'abc',123,123.456,
238
+ TO_DATE('#{@now.strftime("%Y-%m-%d %H:%M:%S")}','YYYY-MM-DD HH24:MI:SS')
239
+ FROM dual").should == [["abc",123,123.456,@now],["abc",123,123.456,@now]]
240
+ end
241
+
242
+ it "should execute SQL statement and return all results as hash" do
243
+ @now = Time.local(2008,05,31,23,22,11)
244
+ @conn.select_hash_all("SELECT 'abc' a, 123 b, 123.456 c,
245
+ TO_DATE('#{@now.strftime("%Y-%m-%d %H:%M:%S")}','YYYY-MM-DD HH24:MI:SS') d
246
+ FROM dual
247
+ UNION ALL SELECT 'def' a, 123 b, 123.456 c,
248
+ TO_DATE('#{@now.strftime("%Y-%m-%d %H:%M:%S")}','YYYY-MM-DD HH24:MI:SS') d
249
+ FROM dual").should == [{:a=>"abc",:b=>123,:c=>123.456,:d=>@now},{:a=>"def",:b=>123,:c=>123.456,:d=>@now}]
250
+ end
251
+
252
+ it "should execute SQL statement with bind parameters and return all results" do
253
+ @now = Time.local(2008,05,31,23,22,11)
254
+ @conn.select_all("SELECT :1,:2,:3,:4 FROM dual UNION ALL SELECT :1,:2,:3,:4 FROM dual",
255
+ 'abc',123,123.456,@now,'abc',123,123.456,@now).should == [["abc",123,123.456,@now],["abc",123,123.456,@now]]
256
+ end
257
+
258
+ it "should execute SQL statement and yield all results in block" do
259
+ @now = Time.local(2008,05,31,23,22,11)
260
+ @conn.select_all("SELECT 'abc',123,123.456,
261
+ TO_DATE('#{@now.strftime("%Y-%m-%d %H:%M:%S")}','YYYY-MM-DD HH24:MI:SS')
262
+ FROM dual
263
+ UNION ALL SELECT 'abc',123,123.456,
264
+ TO_DATE('#{@now.strftime("%Y-%m-%d %H:%M:%S")}','YYYY-MM-DD HH24:MI:SS')
265
+ FROM dual") do |r|
266
+ r.should == ["abc",123,123.456,@now]
267
+ end.should == 2
268
+ end
269
+
270
+ it "should execute SQL statement with bind parameters and yield all results in block" do
271
+ @now = Time.local(2008,05,31,23,22,11)
272
+ @conn.select_all("SELECT :1,:2,:3,:4 FROM dual UNION ALL SELECT :1,:2,:3,:4 FROM dual",
273
+ 'abc',123,123.456,@now,'abc',123,123.456,@now) do |r|
274
+ r.should == ["abc",123,123.456,@now]
275
+ end.should == 2
276
+ end
277
+
278
+ end
279
+
280
+ describe "PL/SQL procedures" do
281
+ before(:all) do
282
+ @random = rand(1000)
283
+ @now = Time.local(2008,05,31,23,22,11)
284
+ sql = <<-SQL
285
+ CREATE OR REPLACE FUNCTION test_add_random (p_number NUMBER, p_varchar IN OUT VARCHAR2, p_date IN OUT DATE)
286
+ RETURN NUMBER
287
+ IS
288
+ BEGIN
289
+ RETURN p_number + #{@random};
290
+ END test_add_random;
291
+ SQL
292
+ @conn.exec(sql).should be_true
293
+ end
294
+
295
+ after(:all) do
296
+ @conn.exec "DROP FUNCTION test_add_random"
297
+ end
298
+
299
+ it "should parse PL/SQL procedure call and bind parameters and exec and get bind parameter value" do
300
+ sql = <<-SQL
301
+ BEGIN
302
+ :result := test_add_random (:p_number, :p_varchar, :p_date);
303
+ END;
304
+ SQL
305
+ cursor = @conn.parse(sql)
306
+ cursor.bind_param(":result", nil, :data_type => 'NUMBER', :in_out => 'OUT')
307
+ cursor.bind_param(":p_number", 100, :data_type => 'NUMBER', :in_out => 'IN')
308
+ cursor.bind_param(":p_varchar", "abc", :data_type => 'VARCHAR2', :in_out => 'IN/OUT')
309
+ cursor.bind_param(":p_date", @now, :data_type => 'DATE', :in_out => 'IN/OUT')
310
+ cursor.exec
311
+ cursor[":result"].should == @random + 100
312
+ cursor[":p_varchar"].should == "abc"
313
+ cursor[":p_date"].should == @now
314
+ cursor.close.should be_nil
315
+ end
316
+
317
+ end
318
+
319
+ describe "commit and rollback" do
320
+ before(:all) do
321
+ @conn.exec("CREATE TABLE test_commit (dummy VARCHAR2(100))").should be_true
322
+ @conn.autocommit = false
323
+ @conn.should_not be_autocommit
324
+ end
325
+
326
+ after(:all) do
327
+ @conn.exec "DROP TABLE test_commit"
328
+ end
329
+
330
+ after(:each) do
331
+ @conn.exec "DELETE FROM test_commit"
332
+ @conn.commit
333
+ end
334
+
335
+ it "should do commit" do
336
+ @conn.exec("INSERT INTO test_commit VALUES ('test')")
337
+ @conn.commit
338
+ @conn.select_first("SELECT COUNT(*) FROM test_commit")[0].should == 1
339
+ end
340
+
341
+ it "should do rollback" do
342
+ @conn.exec("INSERT INTO test_commit VALUES ('test')")
343
+ @conn.rollback
344
+ @conn.select_first("SELECT COUNT(*) FROM test_commit")[0].should == 0
345
+ end
346
+
347
+ it "should do commit and rollback should not undo commited transaction" do
348
+ @conn.exec("INSERT INTO test_commit VALUES ('test')")
349
+ @conn.commit
350
+ @conn.rollback
351
+ @conn.select_first("SELECT COUNT(*) FROM test_commit")[0].should == 1
352
+ end
353
+
354
+ end
355
+
356
+ describe "prefetch rows" do
357
+ after(:each) do
358
+ @conn.prefetch_rows = 1 # set back to default
359
+ end
360
+
361
+ it "should set prefetch rows for connection" do
362
+ sql = "SELECT 1 FROM dual UNION ALL SELECT 1/0 FROM dual"
363
+ @conn.prefetch_rows = 2
364
+ lambda {
365
+ @conn.cursor_from_query(sql)
366
+ }.should raise_error(/divisor is equal to zero/)
367
+ @conn.prefetch_rows = 1
368
+ lambda {
369
+ @conn.cursor_from_query(sql)
370
+ }.should_not raise_error
371
+ end
372
+
373
+ it "should fetch just one row when using select_first" do
374
+ sql = "SELECT 1 FROM dual UNION ALL SELECT 1/0 FROM dual"
375
+ @conn.prefetch_rows = 2
376
+ lambda {
377
+ @conn.select_first(sql)
378
+ }.should_not raise_error
379
+ end
380
+
381
+ end
382
+
383
+ describe "describe synonym" do
384
+ before(:all) do
385
+ @conn.exec "CREATE SYNONYM hr.synonym_for_dual FOR sys.dual"
386
+ end
387
+
388
+ after(:all) do
389
+ @conn.exec "DROP SYNONYM hr.synonym_for_dual"
390
+ end
391
+
392
+ it "should describe local synonym" do
393
+ @conn.describe_synonym('HR','SYNONYM_FOR_DUAL').should == ['SYS', 'DUAL']
394
+ @conn.describe_synonym('hr','synonym_for_dual').should == ['SYS', 'DUAL']
395
+ @conn.describe_synonym(:hr,:synonym_for_dual).should == ['SYS', 'DUAL']
396
+ end
397
+
398
+ it "should return nil on non-existing synonym" do
399
+ @conn.describe_synonym('HR','SYNONYM_FOR_XXX').should be_nil
400
+ @conn.describe_synonym('hr','synonym_for_xxx').should be_nil
401
+ @conn.describe_synonym(:hr,:synonym_for_xxx).should be_nil
402
+ end
403
+
404
+ it "should describe public synonym" do
405
+ @conn.describe_synonym('PUBLIC','DUAL').should == ['SYS', 'DUAL']
406
+ @conn.describe_synonym('PUBLIC','dual').should == ['SYS', 'DUAL']
407
+ @conn.describe_synonym('PUBLIC',:dual).should == ['SYS', 'DUAL']
408
+ end
409
+
410
+ end
411
+
412
+ describe "session information" do
413
+ it "should get database version" do
414
+ # using Oracle version 10.2.0.4 for unit tests
415
+ @conn.database_version.should == DATABASE_VERSION.split('.').map{|n| n.to_i}
416
+ end
417
+
418
+ it "should get session ID" do
419
+ @conn.session_id.should == @conn.select_first("SELECT USERENV('SESSIONID') FROM dual")[0].to_i
420
+ end
421
+ end
422
+
423
+ describe "drop ruby temporary tables" do
424
+ after(:all) do
425
+ @conn.drop_all_ruby_temporary_tables
426
+ end
427
+
428
+ it "should drop all ruby temporary tables" do
429
+ tmp_table = "ruby_111_222_333"
430
+ @conn.exec "CREATE GLOBAL TEMPORARY TABLE #{tmp_table} (dummy CHAR(1))"
431
+ lambda { @conn.select_first("SELECT * FROM #{tmp_table}") }.should_not raise_error
432
+ @conn.drop_all_ruby_temporary_tables
433
+ lambda { @conn.select_first("SELECT * FROM #{tmp_table}") }.should raise_error(/table or view does not exist/)
434
+ end
435
+
436
+ it "should drop current session ruby temporary tables" do
437
+ tmp_table = "ruby_#{@conn.session_id}_222_333"
438
+ @conn.exec "CREATE GLOBAL TEMPORARY TABLE #{tmp_table} (dummy CHAR(1))"
439
+ lambda { @conn.select_first("SELECT * FROM #{tmp_table}") }.should_not raise_error
440
+ @conn.drop_session_ruby_temporary_tables
441
+ lambda { @conn.select_first("SELECT * FROM #{tmp_table}") }.should raise_error(/table or view does not exist/)
442
+ end
443
+
444
+ it "should not drop other session ruby temporary tables" do
445
+ tmp_table = "ruby_#{@conn.session_id+1}_222_333"
446
+ @conn.exec "CREATE GLOBAL TEMPORARY TABLE #{tmp_table} (dummy CHAR(1))"
447
+ lambda { @conn.select_first("SELECT * FROM #{tmp_table}") }.should_not raise_error
448
+ @conn.drop_session_ruby_temporary_tables
449
+ lambda { @conn.select_first("SELECT * FROM #{tmp_table}") }.should_not raise_error
450
+ end
451
+
452
+ end
453
+
454
+ describe "logoff" do
455
+ before(:each) do
456
+ # restore connection before each test
457
+ reconnect_connection
458
+ end
459
+
460
+ after(:all) do
461
+ @conn.exec "DROP TABLE test_dummy_table" rescue nil
462
+ end
463
+
464
+ def reconnect_connection
465
+ @raw_conn = get_connection
466
+ @conn = PLSQL::Connection.create( @raw_conn )
467
+ @conn.set_time_zone
468
+ end
469
+
470
+ it "should drop current session ruby temporary tables" do
471
+ tmp_table = "ruby_#{@conn.session_id}_222_333"
472
+ @conn.exec "CREATE GLOBAL TEMPORARY TABLE #{tmp_table} (dummy CHAR(1))"
473
+ lambda { @conn.select_first("SELECT * FROM #{tmp_table}") }.should_not raise_error
474
+ @conn.logoff
475
+ reconnect_connection
476
+ lambda { @conn.select_first("SELECT * FROM #{tmp_table}") }.should raise_error(/table or view does not exist/)
477
+ end
478
+
479
+ it "should rollback any uncommited transactions" do
480
+ tmp_table = "ruby_#{@conn.session_id}_222_333"
481
+ old_autocommit = @conn.autocommit?
482
+ @conn.autocommit = false
483
+ @conn.exec "CREATE GLOBAL TEMPORARY TABLE #{tmp_table} (dummy CHAR(1))"
484
+ @conn.exec "CREATE TABLE test_dummy_table (dummy CHAR(1))"
485
+ @conn.exec "INSERT INTO test_dummy_table VALUES ('1')"
486
+ # logoff will drop ruby temporary tables, it should do rollback before drop table
487
+ @conn.logoff
488
+ reconnect_connection
489
+ @conn.select_first("SELECT * FROM test_dummy_table").should == nil
490
+ @conn.autocommit = old_autocommit
491
+ end
492
+
493
+ end
494
+
495
+ end