simpleOracleJDBC 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # Simple Oracle JDBC
2
+
3
+ This gem provides a lightweight wrapper around a raw JDBC connection to make interaction between JRuby and Oracle easier. It only wraps commonly used features of JDBC, and makes the raw JDBC connection available to handle less common or more complex tasks.
4
+
5
+ ## Requirements
6
+
7
+ This gem will only work with JRuby as it uses the Java JDBC interface.
8
+
9
+ The Oracle JDBC drivers (ojdbc6.jar) need to be in the JRuby lib directory.
10
+
11
+ ## Simple Usage
12
+
13
+ The intended entry point is the Interface class. Various Interface methods will return Sql or DBCall objects. Note that the Sql and DBCall objects should generally be closed when they are no longer required to free up resources on the database.
14
+
15
+ require 'rubygems'
16
+ require 'simple_oracle_jdbc'
17
+
18
+ @interface = SimpleOracleJDBC::Interface.create('sodonnel',
19
+ 'sodonnel',
20
+ 'local11gr2.world',
21
+ 'localhost',
22
+ '1521')
23
+ # ... or create with an existing JDBC connection
24
+ # @interface = SimpleOracleJDBC.create_with_existing_connection(conn)
25
+
26
+ # Create a SimpleOracleJDBC::SQL object
27
+ sql = @interface.prepare_sql("select * from dual")
28
+
29
+ # execute the query against the database
30
+ sql.execute
31
+
32
+ # get the results back as an array
33
+ results = sql.all_array
34
+ puts "The returned row is #{results[0][0]}"
35
+ sql.close
36
+
37
+ # ... or a hash
38
+ # results = sql.execute.all_hash
39
+ #
40
+ # ... or use a block
41
+ # sql.execute.each_array do |row|
42
+ # process_the_row
43
+ # end
44
+
45
+ # Execute a procedure with an OUT parameter
46
+ proc = @interface.prepare_proc("begin :return := 'abc'; end;")
47
+ proc.execute([String, nil, :out])
48
+ puts "The returned value is #{proc[1]}"
49
+ proc.close
50
+
51
+ ## Datamapping
52
+
53
+ Basic Ruby types are mapped automatically into the correct types for JDBC interaction, and the JDBC types are mapped back to Ruby types when they are retrieved from the database.
54
+
55
+ ## TODO (AKA missing features)
56
+
57
+ Bindable types that are not yet supported:
58
+ * passing a ref_cursor into a proc
59
+ * binding a CLOB to a procedure
60
+
61
+ Types that cannot be retrieved from an SQL result set
62
+ * CLOB
63
+ * Cursor
64
+ * Long
65
+ * nvarchar etc
data/Rakefile.rb ADDED
@@ -0,0 +1,30 @@
1
+ desc "Generate RDoc"
2
+ task :doc => ['doc:generate']
3
+
4
+ namespace :doc do
5
+ project_root = File.expand_path(File.dirname(__FILE__))
6
+ doc_destination = File.join(project_root, 'doc')
7
+
8
+ begin
9
+ require 'yard'
10
+ require 'yard/rake/yardoc_task'
11
+
12
+ YARD::Rake::YardocTask.new(:generate) do |yt|
13
+ yt.files = Dir.glob(File.join(project_root, 'lib/**/*.rb')) + #, '**', '*.rb')) +
14
+ [ File.join(project_root, 'README.md') ]
15
+ puts yt.files
16
+ yt.options = ['--output-dir', doc_destination, '--readme', 'README.md']
17
+ end
18
+ rescue LoadError
19
+ desc "Generate YARD Documentation"
20
+ task :generate do
21
+ abort "Please install the YARD gem to generate rdoc."
22
+ end
23
+ end
24
+
25
+ desc "Remove generated documenation"
26
+ task :clean do
27
+ rm_r doc_dir if File.exists?(doc_destination)
28
+ end
29
+
30
+ end
@@ -0,0 +1,19 @@
1
+ require 'java'
2
+ require 'date'
3
+
4
+ module SimpleOracleJDBC
5
+ java_import 'oracle.jdbc.OracleDriver'
6
+ java_import 'oracle.jdbc.OracleConnectionWrapper'
7
+ java_import 'oracle.sql.TIMESTAMP'
8
+ java_import 'oracle.sql.NUMBER'
9
+ java_import 'oracle.jdbc.OracleTypes'
10
+ java_import 'java.sql.DriverManager'
11
+ java_import 'java.sql.SQLException'
12
+ end
13
+
14
+ require 'simple_oracle_jdbc/bindings'
15
+ require 'simple_oracle_jdbc/result_set'
16
+ #require 'interface'
17
+ require 'simple_oracle_jdbc/sql'
18
+ require 'simple_oracle_jdbc/interface'
19
+ require 'simple_oracle_jdbc/db_call'
@@ -0,0 +1,214 @@
1
+ module SimpleOracleJDBC
2
+ module Binding
3
+
4
+ # Provides a set of methods to map Ruby types to their JDBC equivalent and back again.
5
+
6
+
7
+ RUBY_TO_JDBC_TYPES = {
8
+ Date => OracleTypes::DATE,
9
+ Time => OracleTypes::TIMESTAMP,
10
+ String => OracleTypes::VARCHAR,
11
+ Fixnum => OracleTypes::INTEGER,
12
+ Integer => OracleTypes::INTEGER,
13
+ Bignum => OracleTypes::NUMERIC,
14
+ Float => OracleTypes::NUMERIC,
15
+ :refcursor => OracleTypes::CURSOR
16
+ }
17
+
18
+ # Given a JDBC prepared call or prepared statement, a value and a bind index, the value
19
+ # will be bound to JDBC statement.
20
+ #
21
+ # If value is a single value, ie not an array in is considered an IN parameter.
22
+ #
23
+ # If value is an array, then it should have either 2 or 3 elements.
24
+ #
25
+ # * 2 elements indictes the value is an IN parameter, element 0 indicates the type
26
+ # of the bind variable, and element 1 is the value, eg:
27
+ #
28
+ # [String, "Some_value"]
29
+ #
30
+ # * 3 elements indicates the value is an OUT or an IN OUT parameter (useful only when using
31
+ # stored procedures), eg:
32
+ #
33
+ # [String, "Some_value", :out]
34
+ # [:refcursor, nil, :out]
35
+ #
36
+ # When binding values, Ruby types are mapped to Java / JDBC types based on the type
37
+ # of the passed in Ruby object. The mapping is as follows:
38
+ #
39
+ # RUBY_TO_JDBC_TYPES = {
40
+ # Date => OracleTypes::DATE,
41
+ # Time => OracleTypes::TIMESTAMP,
42
+ # String => OracleTypes::VARCHAR,
43
+ # Fixnum => OracleTypes::INTEGER,
44
+ # Integer => OracleTypes::INTEGER,
45
+ # Bignum => OracleTypes::NUMERIC,
46
+ # Float => OracleTypes::NUMERIC,
47
+ # :refcursor => OracleTypes::CURSOR
48
+ # }
49
+ #
50
+ # Note that to bind a ref_cursor, there is no natural Ruby class, so it can only be bound using
51
+ # the array form for values.
52
+ #
53
+ # Also note that in this version, it is not possible to bind a ref_cursor into a procedure - it can
54
+ # only be retrieved.
55
+ def bind_value(obj, v, i)
56
+ type = v.class
57
+ value = v
58
+ if v.is_a? Array
59
+ # class is being overriden from the input
60
+ type = v[0]
61
+ value = v[1]
62
+
63
+ if v.length == 3
64
+ bind_out_parameter(obj, i, type)
65
+ end
66
+ end
67
+
68
+ if type == Date
69
+ bind_date(obj, value, i)
70
+ elsif type == Time
71
+ bind_time(obj, value, i)
72
+ elsif type == String
73
+ bind_string(obj, value, i)
74
+ elsif type == Fixnum or type == Integer
75
+ bind_int(obj, value, i)
76
+ elsif type == Float
77
+ bind_number(obj, value, i)
78
+ elsif type == :refcursor
79
+ bind_refcursor(obj, value, i)
80
+ else
81
+ raise UnknownBindType, type.to_s
82
+ end
83
+ end
84
+
85
+ # Given a open JDBC result set and a column index, the value is retrieved
86
+ # and mapped into a Ruby type.
87
+ #
88
+ # The columns are indexed from 1 in the array.
89
+ #
90
+ # If the retrieved value is null, nil is returned.
91
+ def retrieve_value(obj, i)
92
+ case obj.get_meta_data.get_column_type_name(i)
93
+ when 'NUMBER'
94
+ retrieve_number(obj, i)
95
+ when 'INTEGER'
96
+ retrieve_int(obj, i)
97
+ when 'DATE'
98
+ retrieve_time(obj, i)
99
+ when 'TIMESTAMP'
100
+ retrieve_time(obj, i)
101
+ when 'CHAR', 'VARCHAR2'
102
+ retrieve_string(obj, i)
103
+ else
104
+ raise UnknownSQLType, obj.get_meta_data.get_column_type_name(i)
105
+ end
106
+ end
107
+
108
+ # :nodoc:
109
+ def bind_out_parameter(obj, index, type)
110
+ internal_type = RUBY_TO_JDBC_TYPES[type] || OracleTypes::VARCHAR
111
+ obj.register_out_parameter(index, internal_type)
112
+ end
113
+
114
+ def bind_date(obj, v, i)
115
+ if v
116
+ # %Q is micro seconds since epoch. Divide by 1000 to get milli-sec
117
+ jdbc_date = Java::JavaSql::Date.new(v.strftime("%s").to_f * 1000)
118
+ obj.set_date(i, jdbc_date)
119
+ else
120
+ obj.set_null(i, OracleTypes::DATE)
121
+ end
122
+ end
123
+
124
+ def bind_time(obj, v, i)
125
+ if v
126
+ # Need to use an Oracle TIMESTAMP - dates don't allow a time to be specified
127
+ # for some reason, even though a date in Oracle contains a time.
128
+ jdbc_time = TIMESTAMP.new(Java::JavaSql::Timestamp.new(v.to_f * 1000))
129
+ obj.setTIMESTAMP(i, jdbc_time)
130
+ else
131
+ obj.set_null(i, OracleTypes::TIMESTAMP)
132
+ end
133
+ end
134
+
135
+ def bind_string(obj, v, i)
136
+ if v
137
+ obj.set_string(i, v)
138
+ else
139
+ obj.set_null(i, OracleTypes::VARCHAR)
140
+ end
141
+ end
142
+
143
+ def bind_int(obj, v, i)
144
+ if v
145
+ obj.set_int(i, v)
146
+ else
147
+ obj.set_null(i, OracleTypes::INTEGER)
148
+ end
149
+ end
150
+
151
+ def bind_number(obj, v, i)
152
+ if v
153
+ obj.set_number(i, Java::OracleSql::NUMBER.new(v))
154
+ else
155
+ obj.set_null(i, OracleTypes::NUMBER)
156
+ end
157
+ end
158
+
159
+ def bind_refcursor(obj, v, i)
160
+ if v
161
+ raise "not implemented"
162
+ end
163
+ end
164
+
165
+ def retrieve_date(obj, i)
166
+ jdate = obj.get_date(i)
167
+ if jdate
168
+ rt = Time.at(jdate.date_value.get_time.to_f / 1000)
169
+ Date.new(rt.year, rt.month, rt.day)
170
+ else
171
+ nil
172
+ end
173
+ end
174
+
175
+ def retrieve_time(obj, i)
176
+ jdate = obj.get_timestamp(i)
177
+ if jdate
178
+ Time.at(jdate.get_time.to_f / 1000)
179
+ else
180
+ nil
181
+ end
182
+ end
183
+
184
+ def retrieve_string(obj, i)
185
+ obj.get_string(i)
186
+ end
187
+
188
+ def retrieve_int(obj, i)
189
+ v = obj.get_int(i)
190
+ if obj.was_null
191
+ nil
192
+ else
193
+ v
194
+ end
195
+ end
196
+
197
+ def retrieve_number(obj, i)
198
+ v = obj.get_number(i)
199
+ if v
200
+ v.double_value
201
+ else
202
+ nil
203
+ end
204
+ end
205
+
206
+ def retrieve_refcursor(obj, i)
207
+ rset = obj.get_object(i)
208
+ results = Sql.new
209
+ results.result_set = rset
210
+ results
211
+ end
212
+
213
+ end
214
+ end
@@ -0,0 +1,105 @@
1
+ module SimpleOracleJDBC
2
+
3
+ class DBCall
4
+
5
+ # This class is used to prepare and execute stored procedure calls against
6
+ # the database. The interface is similar to the Sql class.
7
+ #
8
+ # @!attribute call
9
+ # @return [JDBC Callable Statement] Returns the raw JDBC callable statement
10
+ #
11
+
12
+ include Binding
13
+
14
+ attr_reader :call
15
+
16
+
17
+ # Takes a JDBC database connection and a procedure call and returns a
18
+ # SimpleOracleJDBC object after preparing the procedure call. The prepared
19
+ # JDBC callable statement is stored in @call
20
+ def self.prepare(conn, sql)
21
+ call = new(conn, sql)
22
+ end
23
+
24
+ # Takes a JDBC database connection, a procedure call and an optional set of binds and
25
+ # returns a SimpleOracleJDBC object after preparing and executing the procedure call.
26
+ def self.execute(conn, sql, *binds)
27
+ call = new(conn,sql)
28
+ call.execute(*binds)
29
+ call
30
+ end
31
+
32
+ # Similar to the class method prepare.
33
+ def initialize(conn, sql)
34
+ @connection = conn
35
+ @call = @connection.prepare_call(sql)
36
+ end
37
+
38
+ # Executes the prepared callable statement stored in @call.
39
+ #
40
+ # The passed list of bind variables are bound to the object before it is executed.
41
+ def execute(*binds)
42
+ @binds = binds
43
+ @binds.each_with_index do |b,i|
44
+ bind_value(@call, b, i+1)
45
+ end
46
+ begin
47
+ @call.execute
48
+ rescue Java::JavaSql::SQLException => sqle
49
+ if sqle.message =~ /no data found/
50
+ raise SimpleOracleJDBC::NoDataFound, sqle.to_s
51
+ elsif sqle.message =~ /too many rows/
52
+ raise SimpleOracleJDBC::TooManyRows, sqle.to_s
53
+ elsif sqle.message =~ /ORA-2\d+/
54
+ raise SimpleOracleJDBC::ApplicationError, sqle.to_s
55
+ else
56
+ raise
57
+ end
58
+ end
59
+ self
60
+ end
61
+
62
+ # Allows the bound values to be retrieved along with OUT or IN OUT parameters.
63
+ #
64
+ # The bind variables are indexed from 1.
65
+ #
66
+ # If a refcursor is return, it is retrieved as a SimpleOracleJDBC::Sql object. Other
67
+ # values are returned as Ruby classes, such as Date, Time, String, Float etc.
68
+ def [](i)
69
+ if i < 1
70
+ raise BindIndexOutOfRange, "Bind indexes must be greater or equal to one"
71
+ end
72
+ bind = @binds[i-1]
73
+ if bind.is_a? Array
74
+ # If its an array, it means it was in OUT or INOUT parameter
75
+ if bind[0] == Date
76
+ retrieve_date(@call, i)
77
+ elsif bind[0] == Time
78
+ retrieve_time(@call, i)
79
+ elsif bind[0] == String
80
+ retrieve_string(@call, i)
81
+ elsif bind[0] == Fixnum or bind[0] == Integer
82
+ retrieve_int(@call, i)
83
+ elsif bind[0] == Float
84
+ retrieve_number(@call, i)
85
+ elsif bind[0] == :refcursor
86
+ retrieve_refcursor(@call, i)
87
+ end
88
+ else
89
+ # If its not an array, it was just an IN, so just pull the bind
90
+ # out of the bind array. No need to get it from the DB object.
91
+ bind
92
+ end
93
+ end
94
+
95
+ # Closes the callable statement
96
+ def close
97
+ if @call
98
+ @call.close
99
+ @call = nil
100
+ end
101
+ @bind = nil
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,120 @@
1
+ module SimpleOracleJDBC
2
+
3
+ class NoDataFound < Exception
4
+ end
5
+ class TooManyRows < Exception
6
+ end
7
+ class ApplicationError < Exception
8
+ end
9
+ class NoResultSet < Exception
10
+ end
11
+ class BindIndexOutOfRange < Exception
12
+ end
13
+ class UnknownBindType < Exception
14
+ end
15
+ class UnknowSQLType < Exception
16
+ end
17
+
18
+ class Interface
19
+
20
+ # The Interface class is the intended entry point for the SimpleOracleJDBC gem.
21
+ #
22
+ # The SimpleOracleJDBC class provides a lightweight wrapper around a raw JDBC connection
23
+ # to make interaction between JRuby and Oracle easier. It only wraps commonly used features of JDBC,
24
+ # and makes the raw JDBC connection available to handle less common or more complex tasks.
25
+ #
26
+ # @!attribute connection
27
+ # @return [JDBC Oracle Database connection] Returns the raw JDBC database connection
28
+
29
+ attr_accessor :connection
30
+
31
+ # Factory method to create a new interface using an existing database connection.
32
+ # Returns an instance of SimpleOracleJDBC::Interface
33
+ def self.create_with_existing_connection(conn)
34
+ connector = self.new
35
+ connector.set_connection conn
36
+ connector
37
+ end
38
+
39
+ # Factory method to establish a new JDBC connection and create a new interface.
40
+ # Returns a SimpleOracleJDBC::Interface
41
+ def self.create(user, password, database_service, host=nil, port=nil)
42
+ conn = self.new
43
+ conn.connect(user, password, database_service, host, port)
44
+ conn
45
+ end
46
+
47
+ # Establishes a new database connection using the supplied parameters.
48
+ def connect(user, password, database_service, host=nil, port=nil)
49
+ oradriver = OracleDriver.new
50
+
51
+ DriverManager.registerDriver oradriver
52
+ @connection = DriverManager.get_connection "jdbc:oracle:thin:@#{host}:#{port}/#{database_service}", user, password
53
+ @connection.auto_commit = false
54
+ end
55
+
56
+ # Closes the database connection
57
+ def disconnect
58
+ if @connection
59
+ @connection.close
60
+ @connection = nil
61
+ end
62
+ end
63
+
64
+ # Performs a commit on the database connection
65
+ def commit
66
+ @connection.commit
67
+ end
68
+
69
+ # Performs a rollback on the database connection
70
+ def rollback
71
+ @connection.rollback
72
+ end
73
+
74
+ # Prepares a SQL statement using the Sql class.
75
+ #
76
+ # Returns a Sql object.
77
+ def prepare_sql(query)
78
+ Sql.prepare(@connection, query)
79
+ end
80
+
81
+ # Executes a SQL statement using the Sql class
82
+ #
83
+ # Returns a Sql object
84
+ def execute_sql(query, *binds)
85
+ Sql.execute(@connection, query, *binds)
86
+ end
87
+
88
+ # Prepares a stored procedure call using the DBCall class.
89
+ #
90
+ # Returns a DBCall object
91
+ def prepare_proc(sql)
92
+ DBCall.prepare(@connection, sql)
93
+ end
94
+
95
+ alias_method :prepare_call, :prepare_proc
96
+
97
+ # Executes a stored procedure call using the DBCall class.
98
+ #
99
+ # Returns a DBCall object
100
+ def execute_proc(sql, *binds)
101
+ DBCall.execute(@connection, sql, *binds)
102
+ end
103
+
104
+ alias_method :execute_call, :execute_proc
105
+
106
+ protected
107
+
108
+ def set_connection(conn)
109
+ @connection = conn
110
+ end
111
+
112
+ private
113
+
114
+ def initialize
115
+ end
116
+
117
+ end
118
+
119
+ end
120
+