In a previous post, we discussed how to load SQLite extensions distributed as Ruby gems into a Ruby on Rails application. Unfortunately, there aren’t many SQLite extensions that are distributed as Ruby gems. So, in this post, we dig into how to install a wider range of SQLite extensions into our apps.
While SQLite is essentially feature complete when it comes to the SQL standard, there are still some times when you have specific needs for your database that SQLite doesn’t support. Luckily, SQLite offers a rich extension ecosystem. And even luckier still, there is an (unofficial) package manager—sqlpkg
—and a corresponding (unofficial) package registry. As of today, there are 62 extensions available in the registry. So, how can we install and load any of these extensions into our Rails app?1
The sqlpkg
package manager handles installing the appropriate executable file for the operating system it is running on. The stumbling block was installing the sqlpkg
executable itself for the appropriate operating system. Having used the webi installer for other projects, I decided to try and add a webi installer for sqlpkg
. After reading the source for a number of other webi installers, I cobbled together a working installer for sqlpkg
. You can see the pull request here. This made it simpler to install the sqlpkg
executable into your host machine, but it still recalled a manual step to install the extension into your app. My goal is always to embed as much of the configuration of my app into the app itself, so I wanted to find a way to install the extension into the app itself.
While stewing on this problem for weeks, I started pursuing other projects. One of those projects was providing a RubyGems wrapper for the Litestream utility. I used that project as a learning opportunity to learn how to bundle an executable into a Ruby gem that Bundler would naturally install correctly for the operating system of the host machine. I wrote more about that process and that gem previously, but that basic approach was precisely what I wanted for the sqlpkg
executable. So, I set out to create a Ruby gem that would install the sqlpkg
executable into your Rails app and released the sqlpkg-ruby
gem a couple of days ago.
Now it is possible to install the sqlpkg
package manager CLI into your application. The only requisite next step it to ensure that SQLite extensions installed via sqlpkg
are similarly installed and embedded into your application and then properly loaded into your app’s SQLite database.
So, I added a Railtie to the sqlpkg-ruby
gem that exposes a rails generate sqlpkg:install
generator. The installer does three things:
- creates an empty
.sqlpkg/
directory, which ensures thatsqlpkg
will run in “project scope” and not “global scope” (see thesqlpkg-cli
README for more information) - creates an empty
sqlpkg.lock
file, whichsqlpkg
will use to store information about the installed packages (see thesqlpkg-cli
README for more information) - creates an initializer file at
config/initializers/sqlpkg.rb
which will patch theSQLite3Adapter
to automatically load the extensions installed in the.sqlpkg/
directory whenever the database is opened
That initializer is the key to making this all work. It looks like this:
module SqlpkgLoader def configure_connection super @raw_connection.enable_load_extension(true) Dir.glob(".sqlpkg/**/*.{dll,so,dylib}") do |extension_path| @raw_connection.load_extension(extension_path) end @raw_connection.enable_load_extension(false) endend ActiveSupport.on_load(:active_record_sqlite3adapter) do prepend SqlpkgLoaderend
Taken together, these three steps make it possible to install SQLite extensions into your Rails app. Once properly integrated into your Rails application, you can install any extension listed on the sqlpkg
registry by executing:
$ bundle exec sqlpkg install PACKAGE_IDENTIFIER
When exploring the the sqlpkg
registry, the PACKAGE_IDENTIFIER
needed to install an extension is the title found in the cards, always in owner/name format. For example, to install the sqlite-vss
extension, which provides support for vector similarity search, you would find this package card on the sqlpkg.org site:
You would then install the extension into your Rails app by executing:
$ bundle exec sqlpkg install asg017/vss
You will see output similar to the following in your terminal:
(project scope)> installing asg017/vss...✓ installed package asg017/vss to .sqlpkg/asg017/vss
In addition to the new files in the .sqlpkg/
directory, you will also see a new entry in your sqlpkg.lock
file:
{ "packages": { "asg017/vss": { "owner": "asg017", "name": "vss", "version": "v0.1.2", "specfile": "https://github.com/nalgeon/sqlpkg/raw/main/pkg/asg017/vss.json", "assets": {} -- # removed for brevity } }}
And that’s it. You can now use the sqlite-vss
extension in your Rails app. You can do the same with any of the 62 SQLite extensions available on the sqlpkg
registry. Any downloaded extensions will automatically be loaded into your app’s SQLite database when it is opened and thus available for use.
I think this is another big step forward for SQLite on Rails. I hope you find it useful. If you have any questions or comments, please feel free to reach out to me on Twitter or in the GitHub repo.
All posts in this series #
- Part 1 — branch-specific databases
- Part 2 — fine-tuning SQLite configuration
- Part 3 — loading extensions
- Part 4 — setting up
Litestream
- Part 5 — optimizing compilation
- Part 6 — array columns
- Part 7 — local snapshots
- Part 8 — Rails improvements
- Part 9 — performance metrics
- Part 10 — custom primary keys
- Part 11 — more Rails improvements
- Part 12 — table schema and metadata
- Part 13 — prefixed ULID keys
- Part 14 — installing extensions