dbd-jdbc 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ OVERVIEW
2
+ ========
3
+ The Jdbc driver for DBI runs only under JRuby, using JRuby's Java integration to act as a thin wrapper around JDBC Connections obtained through DriverManager. Theoretically this driver can support any database that has JDBC support, although the behavior of certain aspects of the DBI API will differ slightly based on the implementation of the JDBC driver and the underlying database (see LIMITATIONS)
4
+
5
+ INSTALLATION
6
+ ============
7
+ Besides the ruby files being installed with DBI (in DBD/Jdbc), the JDBC driver classes must be added to the JRuby classpath. One way to do this on UNIX machines is to place a JDBC driver jarfile in JRUBY_HOME/lib. Another way is to set the CLASSPATH environment variable. The jruby (or jruby.bat) script can also be modified to add to the classpath. In the future there may be more dynamic ways to add jarfiles to the classpath as well; check with the JRuby documentation.
8
+
9
+ USAGE
10
+ =====
11
+ This driver is used like any standard DBI driver, by requiring 'dbi' and obtaining a connection through the DBI.connect method. The DSN for the database should be "dbi:" followed by the standard Java database URL (jdbc:<subprotocol>:<subname>). In addition, the attributes hash passed to the connect method must contain the key/value pair "driver" => <jdbc driver class name>. For example (MySQL):
12
+
13
+ dbh = DBI.connect('dbi:jdbc:mysql://localhost:3306/test', 'anonymous', '', { "driver" => "com.mysql.jdbc.Driver" } )
14
+
15
+ SUPPORTED ATTRIBUTES
16
+ ====================
17
+ Besides the mandatory "driver" attribute that must be passed in the DBI connect method, there are additional attributes that can be passed to modify the behavior of the driver. In addition to setting these attributes during the connect call, they can be set on a driver handle using []= (e.g. dbh["autocommit"] = true). The currently supported attributes are:
18
+ 1) autocommit: sets the autoCommit status of the underlying JDBC Connection (there is a 1-1 relationship between a DBI Database instance and a JDBC Connection)
19
+ 2) nulltype: sets the java.sql.Types constant to use when binding nil to a Statement. See LIMITATIONS
20
+
21
+ LIMITATIONS
22
+ ===========
23
+ There are limitations to the JDBC driver that are largely based on incompatibilities between the DBI and JDBC APIs, and the differences in underlying JDBC driver implementations.
24
+
25
+ 1) Binding nil to statements is somewhat unreliable due to the fact that JDBC requires type information in PreparedStatement.setNull(int), but there is no type information associated with nil in ruby. E.g., statements like DBI.select_all("select * from a, b, c where col1=? and col2=?", "a", nil) might run into problems. One workaround is to hardcode NULL in the sql statement. If executing multiple inserts and some values might be nil, the driver will call setNull with the type specified in the driver attribute "nulltype". If the default fails with a given driver, try specifying different nulltypes such as "dbh['nulltype'] = java.sql.Types::NULL" or "dbh['nulltype'] = java.sql.Types::OTHER" to see if it will work.
26
+
27
+ 2) Type conversion in result sets: Java to Ruby type conversion in results of queries is more limited than in JDBC, since DBI returns an entire row at a time, without type information specified (unlike JDBC ResultSet where getInt, getString, etc allow each column to be typed appropriately). The driver attempts to convert each data type, relying mostly on getObject() and the JRuby type conversion system. Due to the fact that JDBC drivers are not constrained to the exact Java types returned for each SQL type, it is possible for some conversion oddities to arise (e.g. returning Java BigDecimal which isn't converted by JRuby, leading to a Java BigDecimal rather than Ruby Fixnum/BigDecimal).
28
+
29
+ 3) Type conversion in prepared statements: In addition to not being able to use type data when returning data, it isn't possible to specify type data directly when binding parameters either (when binding multiple parameters, DBI has ability to set type data, and when calling bind_param, this driver does not currently support type data in the attributes). Most conversions should work without problem, but there is ambiguity in the Ruby Float type since it can be treated as float or double (or BigDecimal) in Java (and FLOAT, DOUBLE, REAL, NUMERIC, DECIMAL, etc in java.sql.Types). setDouble() is used to try to retain the highest precision, but some problems have been seen (e.g. Sybase REAL works better with setFloat()). When doing inserts or retrieval, be sure that the driver keeps the desired precision (the only workaround is to change the database column type to something that works better for doubles with the given JDBC driver and database)
@@ -0,0 +1,42 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ task :default => [:test, :package]
5
+
6
+ desc "Run all tests"
7
+ task :test => [:test_all]
8
+
9
+ Rake::TestTask.new(:test_all) do |t|
10
+ t.test_files = FileList['test/**/test_*.rb']
11
+ t.libs << 'test'
12
+ t.libs.delete("lib") unless defined?(JRUBY_VERSION)
13
+ end
14
+
15
+
16
+ task :filelist do
17
+ puts FileList['pkg/**/*'].inspect
18
+ end
19
+
20
+ MANIFEST = FileList["lib/**/*.rb", "test/**/*.rb", "Rakefile", "README.txt"]
21
+
22
+ file "Manifest.txt" => :manifest
23
+ task :manifest do
24
+ File.open("Manifest.txt", "w") {|f| MANIFEST.each {|n| f << "#{n}\n"} }
25
+ end
26
+
27
+ Rake::Task['manifest'].invoke # Always regen manifest, so Hoe has up-to-date list of files
28
+
29
+ begin
30
+ require 'hoe'
31
+ Hoe.new("dbd-jdbc", "0.0.1") do |p|
32
+ p.rubyforge_name = "jruby-extras"
33
+ p.url = "http://jruby-extras.rubyforge.org/jruby-ldap"
34
+ p.author = "Ola Bini"
35
+ p.email = "ola.bini@gmail.com"
36
+ p.summary = "JDBC driver for DBI, originally by Kristopher Schmidt"
37
+ end.spec.dependencies.delete_if { |dep| dep.name == "hoe" }
38
+ rescue LoadError
39
+ puts "You really need Hoe installed to be able to package this gem"
40
+ rescue => e
41
+ puts "ignoring error while loading hoe: #{e.to_s}"
42
+ end
@@ -0,0 +1,289 @@
1
+ require 'java'
2
+ require File.dirname(__FILE__) + '/JdbcTypeConversions'
3
+
4
+ module DBI
5
+ module DBD
6
+ module Jdbc
7
+
8
+ include_class 'java.sql.Connection'
9
+
10
+ VERSION = "0.0.1"
11
+
12
+ def self.driver_name
13
+ "Jdbc"
14
+ end
15
+
16
+ class Driver < DBI::BaseDriver
17
+
18
+ def initialize
19
+ super("0.4.0")
20
+ #attributes specific to this class. All attributes in the
21
+ #connect attributes that aren't in this list will be
22
+ #applied to the database handle
23
+ @driverAttributes = [ "driver" ]
24
+ end
25
+
26
+ # Stolen from AR-JDBC
27
+ def driver_class(name)
28
+ return @driver_class if @driver_class
29
+
30
+ driver_class_const = (name[0...1].capitalize + name[1..name.length]).gsub(/\./, '_')
31
+ driver_class_name = name
32
+ Driver.module_eval do
33
+ include_class(driver_class_name) { driver_class_const }
34
+ end
35
+ driver_class = Driver.const_get(driver_class_const)
36
+ raise "You specify a driver for your JDBC connection" unless driver_class
37
+ @driver_class = driver_class
38
+ end
39
+
40
+ def load(name)
41
+ java.sql.DriverManager.registerDriver(driver_class(name).new)
42
+ end
43
+
44
+ def connect(dbname, user, auth, attr)
45
+ driverClass = attr["driver"]
46
+ raise InterfaceError.new('driver class name must be specified as "driver" in connection attributes') unless driverClass
47
+ load(driverClass)
48
+ connection = java.sql.DriverManager.getConnection("jdbc:"+dbname, user, auth)
49
+ dbh = Database.new(connection)
50
+ (attr.keys - @driverAttributes).each { |key| dbh[key] = attr[key] }
51
+ return dbh
52
+ rescue NativeException => error
53
+ raise DBI::DatabaseError.new(error.message)
54
+ end
55
+
56
+ end #Driver
57
+
58
+ class Database < DBI::BaseDatabase
59
+
60
+ include JdbcTypeConversions
61
+
62
+ def initialize(connection)
63
+ @connection = connection
64
+ @attributes = {
65
+ #works with Sybase and Mysql.
66
+ "nulltype" => java.sql.Types::VARCHAR
67
+ }
68
+ end
69
+
70
+ def disconnect
71
+ @connection.rollback unless self["autocommit"]
72
+ @connection.close
73
+ rescue NativeException => error
74
+ raise DBI::DatabaseError.new(error.message)
75
+ end
76
+
77
+ def prepare(sql)
78
+ return Statement.new(@connection.prepareStatement(sql), self["nulltype"])
79
+ rescue NativeException => error
80
+ raise DBI::DatabaseError.new(error.message)
81
+ end
82
+
83
+ def ping
84
+ return !@connection.isClosed
85
+ rescue NativeException => error
86
+ raise DBI::DatabaseError.new(error.message)
87
+ end
88
+
89
+ def commit
90
+ @connection.commit()
91
+ rescue NativeException => error
92
+ raise DBI::DatabaseError.new(error.message)
93
+ end
94
+
95
+ def rollback
96
+ @connection.rollback()
97
+ rescue NativeException => error
98
+ raise DBI::DatabaseError.new(error.message)
99
+ end
100
+
101
+ def tables
102
+ rs = @connection.getMetaData.getTables(nil, nil, nil, nil)
103
+ tables = []
104
+ while(rs.next())
105
+ tables << rs.getString(3)
106
+ end
107
+ return tables
108
+ rescue NativeException => error
109
+ raise DBI::DatabaseError.new(error.message)
110
+ end
111
+
112
+ def columns(table)
113
+ metaData = @connection.getMetaData()
114
+ rs = metaData.getColumns(nil, nil, table, nil)
115
+ columns = []
116
+ while(rs.next())
117
+ columns << {
118
+ "name" => rs.getString(4),
119
+ "sql_type" => jdbc_to_dbi_sqltype(rs.getShort(5)),
120
+ "type_name" => rs.getString(6),
121
+ "precision" => rs.getInt(7),
122
+ "scale" => rs.getInt(9),
123
+ "default" => rs.getString(13),
124
+ "nullable" => (rs.getInt(11) == 1)
125
+ }
126
+ end
127
+ return columns
128
+ rescue NativeException => error
129
+ raise DBI::DatabaseError.new(error.message)
130
+ end
131
+
132
+ def [](attribute)
133
+ attribute.downcase!
134
+ check_attribute(attribute)
135
+ case attribute
136
+ when "autocommit" then @connection.getAutoCommit()
137
+ else
138
+ @attributes[attribute]
139
+ end
140
+ rescue NativeException => error
141
+ raise DBI::DatabaseError.new(error.message)
142
+ end
143
+
144
+ def []=(attribute, value)
145
+ attribute.downcase!
146
+ check_attribute(attribute)
147
+ case attribute
148
+ when "autocommit" then @connection.setAutoCommit(value)
149
+ else
150
+ @attributes[attribute] = value
151
+ end
152
+ rescue NativeException => error
153
+ raise DBI::DatabaseError.new(error.message)
154
+ end
155
+
156
+ def __get_java_connection
157
+ return @connection
158
+ end
159
+
160
+ private
161
+
162
+ def check_attribute(attribute)
163
+ raise DBI::NotSupportedError.new("Database attribute #{attribute} is not supported") unless (attribute == "autocommit" || @attributes.has_key?(attribute))
164
+ end
165
+
166
+ end
167
+
168
+ class Statement < DBI::BaseStatement
169
+
170
+ include JdbcTypeConversions
171
+
172
+ def initialize(statement, nulltype)
173
+ @statement = statement
174
+ @nulltype = nulltype
175
+ @rows = nil
176
+ @data = []
177
+ end
178
+
179
+ def bind_param(param, value, attribs)
180
+ raise InterfaceError.new("Statement.bind_param only supports numeric placeholder numbers") unless param.is_a?(Fixnum)
181
+ if value.nil?
182
+ @statement.setNull(param, @nulltype)
183
+ elsif value.is_a?(String)
184
+ @statement.setString(param, value)
185
+ elsif value.is_a?(Fixnum)
186
+ #no reason not to coerce it to a long?
187
+ @statement.setLong(param, value)
188
+ elsif value.is_a?(Float)
189
+ #despite DBD spec saying Float->SQL Float, using Double gives
190
+ #better precision and passes tests that setFloat does not.
191
+ @statement.setDouble(param, value)
192
+ elsif value.is_a?(DBI::Date)
193
+ @statement.setDate(param, dbidate_to_jdbcdate(value))
194
+ elsif value.is_a?(DBI::Time)
195
+ @statement.setTime(param, dbitime_to_jdbctime(value))
196
+ elsif value.is_a?(DBI::Timestamp)
197
+ @statement.setTimestamp(param, dbitimestamp_to_jdbctimestamp(value))
198
+ else
199
+ @statement.setObject(param, value)
200
+ end
201
+ rescue NativeException => error
202
+ raise DBI::DatabaseError.new(error.message)
203
+ end
204
+
205
+ def execute
206
+ if @statement.execute()
207
+ @rs = @statement.getResultSet
208
+ @rows = nil
209
+ @data.clear
210
+ else
211
+ @rs = nil
212
+ @rows = @statement.getUpdateCount
213
+ end
214
+ rescue NativeException => error
215
+ raise DBI::DatabaseError.new(error.message)
216
+ end
217
+
218
+ def finish
219
+ @statement.close()
220
+ @rs = nil
221
+ @rows = nil
222
+ rescue NativeException => error
223
+ raise DBI::DatabaseError.new(error.message)
224
+ end
225
+
226
+ def fetch
227
+ if (@rs && @rs.next())
228
+ @data.clear
229
+ metaData = @rs.getMetaData()
230
+ (1..metaData.getColumnCount()).each do |columnNumber|
231
+ @data << get_value(columnNumber, @rs, metaData)
232
+ end
233
+ return @data
234
+ else
235
+ return nil
236
+ end
237
+ rescue NativeException => error
238
+ raise DBI::DatabaseError.new(error.message)
239
+ end
240
+
241
+ def column_info
242
+ info = Array.new
243
+ return info unless @rs
244
+ metaData = @rs.getMetaData()
245
+ (1..metaData.getColumnCount()).each do |columnNumber|
246
+ info << {
247
+ "name" => metaData.getColumnName(columnNumber),
248
+ "sql_type" => jdbc_to_dbi_sqltype(metaData.getColumnType(columnNumber)),
249
+ "type_name" => metaData.getColumnTypeName(columnNumber),
250
+ "precision" => metaData.getPrecision(columnNumber),
251
+ "scale" => metaData.getScale(columnNumber),
252
+ "nullable" => (metaData.isNullable(columnNumber) == 1)
253
+ }
254
+ end
255
+ return info
256
+ rescue NativeException => error
257
+ raise DBI::DatabaseError.new(error.message)
258
+ end
259
+
260
+ def rows
261
+ return @rows
262
+ end
263
+
264
+ private
265
+
266
+ def get_value(columnNumber, rs, metaData)
267
+ #note only map things that seem unlikely to coerce properly to jruby,
268
+ #since anything mapped as primitive type cannot be returned as null
269
+ return case metaData.getColumnType(columnNumber)
270
+ when java.sql.Types::BIT
271
+ rs.getBoolean(columnNumber)
272
+ when java.sql.Types::NUMERIC, java.sql.Types::DECIMAL
273
+ metaData.getScale(columnNumber) == 0 ? rs.getLong(columnNumber) : rs.getDouble(columnNumber)
274
+ when java.sql.Types::DATE
275
+ jdbcdate_to_dbidate(rs.getDate(columnNumber))
276
+ when java.sql.Types::TIME
277
+ jdbctime_to_dbitime(rs.getTime(columnNumber))
278
+ when java.sql.Types::TIMESTAMP
279
+ jdbctimestamp_to_dbitimestamp(rs.getTimestamp(columnNumber))
280
+ else
281
+ rs.getObject(columnNumber)
282
+ end
283
+ end
284
+
285
+ end # class Statement
286
+
287
+ end # module Jdbc
288
+ end # module DBD
289
+ end # module DBI
@@ -0,0 +1,89 @@
1
+ module JdbcTypeConversions
2
+
3
+ include_class 'java.util.Calendar'
4
+ include_class 'java.sql.Types'
5
+
6
+ def jdbc_to_dbi_sqltype(jdbctype)
7
+ return case jdbctype
8
+ when Types::BIGINT then DBI::SQL_BIGINT
9
+ when Types::BINARY then DBI::SQL_BINARY
10
+ when Types::BIT then DBI::SQL_BIT
11
+ when Types::CHAR then DBI::SQL_CHAR
12
+ when Types::DATE then DBI::SQL_DATE
13
+ when Types::DECIMAL then DBI::SQL_DECIMAL
14
+ when Types::DOUBLE then DBI::SQL_DOUBLE
15
+ when Types::FLOAT then DBI::SQL_FLOAT
16
+ when Types::INTEGER then DBI::SQL_INTEGER
17
+ when Types::LONGVARBINARY then DBI::SQL_LONGVARBINARY
18
+ when Types::LONGVARCHAR then DBI::SQL_LONGVARCHAR
19
+ when Types::NUMERIC then DBI::SQL_NUMERIC
20
+ when Types::REAL then DBI::SQL_REAL
21
+ when Types::SMALLINT then DBI::SQL_SMALLINT
22
+ when Types::TIME then DBI::SQL_TIME
23
+ when Types::TIMESTAMP then DBI::SQL_TIMESTAMP
24
+ when Types::TINYINT then DBI::SQL_TINYINT
25
+ when Types::VARBINARY then DBI::SQL_VARBINARY
26
+ when Types::VARCHAR then DBI::SQL_VARCHAR
27
+ else
28
+ DBI::SQL_OTHER
29
+ end
30
+ end
31
+
32
+ def dbidate_to_jdbcdate(dbidate)
33
+ cal = Calendar.getInstance()
34
+ set_calendar_date_fields(dbidate, cal)
35
+ return java.sql.Date.new(cal.getTime().getTime())
36
+ end
37
+
38
+ def dbitime_to_jdbctime(dbitime)
39
+ cal = Calendar.getInstance()
40
+ set_calendar_time_fields(dbitime, cal)
41
+ return java.sql.Time.new(cal.getTime().getTime())
42
+ end
43
+
44
+ def dbitimestamp_to_jdbctimestamp(dbitimestamp)
45
+ cal = Calendar.getInstance()
46
+ set_calendar_date_fields(dbitimestamp, cal)
47
+ set_calendar_time_fields(dbitimestamp, cal)
48
+ return java.sql.Timestamp.new(cal.getTime().getTime())
49
+ end
50
+
51
+ def jdbcdate_to_dbidate(jdbcdate)
52
+ return nil if jdbcdate.nil?
53
+ cal = get_calendar(jdbcdate)
54
+ return DBI::Date.new(cal.get(Calendar::YEAR), cal.get(Calendar::MONTH)+1, cal.get(Calendar::DAY_OF_MONTH))
55
+ end
56
+
57
+ def jdbctime_to_dbitime(jdbctime)
58
+ return nil if jdbctime.nil?
59
+ cal = get_calendar(jdbctime)
60
+ return DBI::Time.new(cal.get(Calendar::HOUR_OF_DAY), cal.get(Calendar::MINUTE), cal.get(Calendar::SECOND))
61
+ end
62
+
63
+ def jdbctimestamp_to_dbitimestamp(jdbctimestamp)
64
+ return nil if jdbctimestamp.nil?
65
+ cal = get_calendar(jdbctimestamp)
66
+ return DBI::Timestamp.new(cal.get(Calendar::YEAR), cal.get(Calendar::MONTH)+1, cal.get(Calendar::DAY_OF_MONTH), cal.get(Calendar::HOUR_OF_DAY), cal.get(Calendar::MINUTE), cal.get(Calendar::SECOND))
67
+ end
68
+
69
+ private
70
+
71
+ def set_calendar_date_fields(dbidate, cal)
72
+ cal.set(Calendar::YEAR, dbidate.year)
73
+ cal.set(Calendar::MONTH, dbidate.month-1)
74
+ cal.set(Calendar::DAY_OF_MONTH, dbidate.day)
75
+ end
76
+
77
+ def set_calendar_time_fields(dbitime, cal)
78
+ cal.set(Calendar::HOUR_OF_DAY, dbitime.hour)
79
+ cal.set(Calendar::MINUTE, dbitime.minute)
80
+ cal.set(Calendar::SECOND, dbitime.second)
81
+ end
82
+
83
+ def get_calendar(jdbctype)
84
+ cal = Calendar.getInstance()
85
+ cal.setTime(java.util.Date.new(jdbctype.getTime()))
86
+ return cal
87
+ end
88
+
89
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ extensions: []
3
+
4
+ homepage: http://jruby-extras.rubyforge.org/jruby-ldap
5
+ executables: []
6
+
7
+ version: !ruby/object:Gem::Version
8
+ version: 0.0.1
9
+ post_install_message:
10
+ date: 2008-10-08 22:00:00 +00:00
11
+ files:
12
+ - lib/dbd/Jdbc.rb
13
+ - lib/dbd/JdbcTypeConversions.rb
14
+ - Rakefile
15
+ - README.txt
16
+ rubygems_version: 1.2.0
17
+ rdoc_options:
18
+ - --main
19
+ - README.txt
20
+ signing_key:
21
+ cert_chain: []
22
+
23
+ name: dbd-jdbc
24
+ has_rdoc: true
25
+ platform: ruby
26
+ summary: JDBC driver for DBI, originally by Kristopher Schmidt
27
+ default_executable:
28
+ bindir: bin
29
+ required_rubygems_version: !ruby/object:Gem::Requirement
30
+ version:
31
+ requirements:
32
+ - - '>='
33
+ - !ruby/object:Gem::Version
34
+ version: "0"
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ version:
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: "0"
41
+ require_paths:
42
+ - lib
43
+ specification_version: 2
44
+ test_files: []
45
+
46
+ dependencies: []
47
+
48
+ description: ""
49
+ email: ola.bini@gmail.com
50
+ authors:
51
+ - Ola Bini
52
+ extra_rdoc_files:
53
+ - README.txt
54
+ requirements: []
55
+
56
+ rubyforge_project: jruby-extras
57
+ autorequire: