Regular Expressions in SQLite with Ruby

2010/01/31

Recently, I was writing an autocomplete method in a Ruby on Rails application. I wanted to find whole word matches, and words starting with the entered text.

The regex I wanted to use was like this:

word =~ /\W#{query}/

So, if the user entered *'and'*, I wanted to retrieve 'Andes' and 'Bill and Ben', but not 'candle'.

In development I was using an SQLite database, and SQLite does not yet implement a regular expression operator. Actually, it defines the `REGEX` operator, but it doesn't implement it - if you use it you get an error.

I tried writing the method with just the LIKE operator, but it was getting very long-winded: you have to jump through hoops to approximate the regex \W operator.

The REGEXP operator is just syntactic sugar for the (unimplemented) regexp() function. SQLite allows you to add external functions at runtime, so I realized that there must be a way around the limitation, but, initially I thought I had to implement regexp() as a C function.

I found an article about implementing regexp() in Ruby. It needed a bit of tweaking as in the Rails 2.3.x interface for SQLite ActiveRecord::ConnectionAdapters::SQLite3Adapter.initialize() takes 3 parameters, not 2.

I got `REGEXP` working by creating an initializer:

require 'active_record/connection_adapters/sqlite3_adapter'

class ActiveRecord::ConnectionAdapters::SQLite3Adapter
  def initialize(db, logger, config)
    super

    db.create_function('regexp', 2) do |func, pattern, expression|
       regexp = Regexp.new(pattern.to_s, Regexp::IGNORECASE)
       if expression.to_s.match(regexp)
         func.result = 1
       else
         func.result = 0
       end
     end
  end
end

The proof of the pudding:

./script/console
>> Noun.find(:all, :conditions => ['name LIKE ?', '%and%']).collect(&:name)
=> ["Candle", "Bill and Ben", "Andes"]
>> Noun.find(:all, :conditions => ['name REGEXP ?', '\Wand']).collect(&:name)
=> ["Bill and Ben", "Andes"]