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.