A little history. The company I work for has an old system that was written in house for a period of almost 20 years and it’s done in dBase. Once cool and allmighty, it’s not longer, dare we say, a system that is at the forefront of computer technology. It has a numerous problems, i.e. multiple users locking tables, AD based security for each individual file/table, slow, etc. Anyways long story short, we decided to convert our system to a web based one, with a Debian/Apache/Passenger/MySQL/Ruby backend – and whatever at the frontend.
Obviosly we had a problem of converting old data to mysql format, with ruby only supporting reading of dbf file via ruby dbf gem – which is great if you only need to read files. Unfortunatly – we couldn’t just abandon our system – since too much depended on it – we need to have a slow transition – where some stuff being phased out as it’s being replaced by web based app. :. , we needed to be able to write back to dbf files. Let me save you some time – I search long and hard, and there is no way to do it. Or so I and most of the commenters on the Internet thought at the time of this writing (Jan, 09). Until I had an epiphany – ODBC!!! I was actually reading an article about PHP, and thought about one of my first experinces – when I had to pull data from MS Access (back in ‘99) into MySQL using ODBC.
So below is the solution and explanation of the process. Let me tell you right now – I don’t know if this will work on Linux. My dev box is WinXP and apps that will be deployed will be deployed on Win2k3 for vairous other reasons. Please leave a comment if you got it working on Linux.
Setup a DSN for your connection.
- Go to Control Panel > Asministrative Tools > ODBC
- Select System DSN tab and add a new connection
- Select Driver do Microsfot dBase (*.dbf) – if you are missing that – you might need to install db2k running time, that you should have if you working with dBase files.
- Select Version 5
- Give it a name – i.e. rubydbf – somethign unique and descriptive – this analogous to your db name in your MySQL connections
- Select the directory where your dbf files sit – in my case – C:Projectsdbf
- Select appropriate indexes for your dbf files – either *.ndx or *.mdx files.
Fire up shell – we are going to test our connection.
- We need to make sure we have all the gems:
gem install dbi
gem install dbd-odbc
- Let’s test if it worked:
irb
require 'dbi'
require 'odbc'
conn = DBI.connect('DBI:ODBC:rubydbf','','')
If you didn’t get any error messages – that means that you are fine. You can try to run
conn.tablesfor a list of tables available to you through this connection.
- Let’s try to run a couple of select statements. Let me warn you right away – and I think it’s a dBase driver issue ( there are a couple of other driver’s I haven’t tried, that might be better) – some of the standard DBI ODBC commands just don’t work. I’ll give the list of the ones I found working and most useful at the end of the post.
rs = conn.select_all('select * from product')That throws back all the data sitting in the product table, rs being your reqular ruby array. So you can do all the nice things such as
rs.sizeorrs[3], etc. Obviously you are limited by your RAM and pc – but most modern pc’s should handle your older dbf files. The reason I’m saying this is that I found most sql statement unexecutable – for exampleselect * from product limit 2– won’t work. Of course you can use things likeselect_many()– but I think it’s just easier to do all of your magic in ruby, simply cause ruby is going to be faster and easier in this case, thou I don’t not advocate that in general – DBs are good at manipulating data – leave it to them.Also another way to do it is to use DBI’s prepare and execute statements:
q = conn.prepare('select * from product where partid = 14')
q.execute()
rs = q.fetch_all()This approach lets you use band variables – which might be useful – if you are not just converting data, but need to pull dynamic result sets – i.e. dated reports or something.
- Now that we can read data, let try to insert data – which is the main point of this exercise:
q = conn.prepare("insert into product (description) values('cool widget')")
q.execute()If you run a select now you should see a new record in your table. As – I said unfortunatly you can’t do limits and so on, so you can dump all you data in an array
rs = conn.select_all('select * from product')and then dors.lastto see your new and shiny row
That is it. Should be enough to get you started, let me know if you run into problems – someone else mgiht think of something or sometimes – I have an epifany – just like this one time ![]()
Have fun! or what amounts to it in dBase world
6 Comments
Thanks for the useful information.
I’m trying to use this method to insert data and I’m running into problems with a field of type logical. No matter what I use (0,1,”F”,”T”,”False”,”True”) I get a “data type mismatch” error. If I try to use true or false as a parameter I get “can’t convert True into string”. Any ideas?
Ryan,
that’s dBase specific. Not really in ODBC spec. But I think it can be mapped to to SQL_BIT type in ODBC universe. Also odbc can’t handle indexing columns of field (value) type Logical. I believe mysql actually supports sql_bit data type as well.
Hope that helps.
Sorry I misread, your question – you are inserting into DBF, not MySql.
I haven’t tried it myself.
Hmm. Logical is indeed a scalar data type – or boolean. From what I can see everyone suggests to use just put in True or False.
Hmm can’t test it here. I looked in a couple of my tables and don’t see Logical used anywhere. If you have sample table that you can send me – I can take a look.
Sorry
One more update:
I went through all my major tables and they all seem to have only 4 datatypes used – I don’t know if it’s odbc that reads it that way or tables themselves – I don’t have a dbase environment to access them, but I only get Char, Numeric, Date and nil.
———
EDIT
———
Ok it looks like that for some other reasons our dBase expert never used logical – he just used Char to store Y & N or T&F. So I can’t test it here. Email me your sample dbf and I’ll try to take a look.
I have a dBase IV DBF that’s used to store PNG files: each record consists of two text fields and a binary field with the complete PNG file. (It’s used by a graphics compositing tool.)
I’m trying to develop a script that will automate adding several PNGs at once to the DB, but I’ve run into a problem: DBI seems to think each record has only the text fields–e.g. after selecting all records into rs, rs[0][3] is nil.
I don’t know if this is a problem with Ruby, DBI, or the ODBC dBase driver; similar shortcomings have led me away from Python tools, and I’m beginning to wonder if ANYTHING deals with binary fields in DBFs. I’m curious if you can shed some light on this dilemma.
Rob,
email me a sample of your dbf file I will take a look – I haven’t dealt with fields like that before – so I’m not sure. To avoid that – on our end – we actually save images on the drive and just store absolute links to each image in the dbf file. Image storage in a db is not usually a good idea. – Makes db bloated pretty quickly – doesn’t add search functionality to the db – just slows it down. My email is nick(dot)gorbikoff(at)gmail.com .
Cheers,
Nick