JDBC access with distributed ruby

by ewout

JDBC, java database connectivity, is the standard database driver interface on the java platform. Since java is ubiquitous, most database vendors provide JDBC drivers to access their data. When a ruby application requires using a legacy data source, sometimes the only option is going through JDBC.

The database toolkit Sequel can use JDBC data sources, but only when running on JRuby. Although JRuby is compatible with ruby 1.8.7, not every application can be run on it, especially when it depends on gems that define C extensions.

Fortunately, distributed ruby exists. It allows a server to expose an object which its clients can use like any local ruby object. A JRuby server can expose the Sequel object, which other ruby clients can use to access JDBC data sources.

Both server-side and client-side code is pretty straightforward. The client should also depend on the sequel gem, since only objects and not their class definitions can be marshaled over distributed ruby.

# server side
require 'drb'
require 'java'
require 'rubygems'
require 'sequel'
DRb.start_service 'druby://localhost:20000', Sequel
# It might be needed to instantiate the driver here,
# so it is available when a connection string is given.
# client side
require 'drb'
require 'rubygems'
require 'sequel'
sequel = DRbObject.new nil, 'druby://localhost:20000'
db = sequel.connect("jdbc connection string here")
db.from('table').each {|r| puts r[:id]} # play!


Inside the JDBC driver, native java exceptions can be raised. JRuby wraps these exceptions in a NativeException class, so ruby can rescue them and provide a stack trace. Distributed ruby provides stack traces for exceptions raised in a remote ruby, but it cannot handle NativeException because the class does not exist in MRI ruby. In short, when an exception is raised by java, the following cryptic error message will appear.

DRb::DRbUnknownError: NativeException

To fix this and get a full stack trace of NativeExceptions, the class needs to be defined in the client.

class ::NativeException < RuntimeError ; end
Fork me on GitHub