intertwingly

It’s just data

Developing a DB2 interface for Ruby


Six years ago, I did not know PHP, but I did know C, COM, and JNI, and that was enough for me to “port” the existing PHP4 COM interface to support Java.  In the process, I learned a bit about PHP.

Now, I’m in a similar situation.  The current state of IBM DB2 support in Rails is somewhat... suboptimal.  Unfortunately, I don’t know enough of DB2 to be able to help much with that.  However, I do know enough of C, PHP, and Ruby to be able to port the PHP ibm_db2 support to Ruby.

Better yet, this time I have a set of test cases to start with.

tests

At the moment, there are 118 tests defined.  Porting them from PHP to Ruby would be tedious and error prone.  Here’s where a script could help.  This script doesn’t try to be a general purpose PHP to Ruby translator, instead it simply focuses on the idioms that are found in the existing phpt files.

Accompanying the script is an embedded ruby template for the resultant ruby code.

Finally, there is a simple test driver which defines what tests need to be run (note that I’m currently skipping test 002, and stopping on test 003).  It also contains an initialize function with userid/password stuff, a setup function which mirrors the existing connection.inc and PHP prepare.inc files.  Finally, there is a one line expect method which extracts the expected results from the end of each test file (phpt style).

You can peruse the current output of these tests here.  The first few tests have been verified, the rest may be garbage for all I know.  Over time, the set of tests to be run will be expanded; tests which are too difficult to automatically convert will simply be excluded in the first pass, and the accumulated set of excluded tests will effectively become a TODO list for a second pass.

code

Like PHP, Ruby provides for writing extensions.

The first step is to write an extconf.rb, which serves a similar purpose as the config.m4 file.

Next, there is the ibm_db2.c and ruby_ibm_db2.h files which were directly copied from the php pecl/ibm_db2 directory.  Generally, the structure has been retained, just a number of Zend/PHP’isms have been converted into Rubyisms.

I don’t know if this will result in an ‘ideal’ Ruby API — it certainly isn’t object oriented.  I just know that this API is the one that I have a ready set of unit tests for, and presumably most people will use it hidden behind an ActiveRecord interface anyway, so it may not much matter.  Furthermore, the closer this code is to the PHP extension, the more likely that there will be people who can help contribute to it.

At the present time, about 70% of the code is #ifdef'ed out, awaiting to be re-added as unit tests require.

rake

Pulling it all together is a Rakefile (rake being Ruby’s answer to make).  If you have a standard installation, this file will take care of everything from generating a Makefile, compiling and linking the shared library, generating tests, and even running the tests.  Simply change whatever files you like and execute

rake

James Snell helped me retrace my steps.  If you are interested in taking a look at this code, first you need to install Ruby and DB2.

cvs -d :pserver:cvsread@cvs.php.net:/repository checkout pecl/ibm_db2/tests
sudo apt-get install ruby1.8-dev
db2sampl
wget http://intertwingly.net/stories/2005/12/05/rubydb2.tgz
tar xzf rubydb2.tgz
cd rubydb2

You will need to edit lines 17 and/or 18 in test.rb, and line 7 in php2ruby.rb.  Then simply execute rake.  You should see

2 tests, 2 assertions, 0 failures, 0 errors

future

Further development is as simple as incrementing the point at which the tests.rb stops, executing rake, observing the failure, making a fix to the test generator and/or the shared library, repeating until all failures go away, and then going back to increment the break point in tests.rb and repeating the whole process over.

There’s probably plenty of cleanup to be done.  I know I am making too many copies of strings.  Where the PHP implementation produced messages and return codes, the Ruby implementation produces exceptions.  It would be nice if Connection and Statement objects weren’t simply opaque handles, but had methods defined on them — at a minimum a customized to_s method would be nice.  Things like php.ini probably need to handled as singleton attributes on the DB2 class instead.

But most of all, the point here is to build something that others can contribute to and maintain.

Down the road, it might make sense to explore a common extension API.  Most of the changes I have been making are fairly mechanical, replacing zend_parse_parameters with rb_scan_args, ZEND_FETCH_RESOURCE2 with Data_Get_Struct and the like.  Perhaps a unified set of macros could handle 80% of the work, with the remaining to be included in appropriately #ifdef'ed sections.