-
Notifications
You must be signed in to change notification settings - Fork 202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: easier and more flexible loading of sqlite extensions #586
Conversation
3fd1cc5
to
ceb76cc
Compare
c7d625a
to
175c05e
Compare
What about using the method
Unsure if I like this. I don't know if I like that SQLite gets in to the business of knowing how to constantize things. I feel this should be a responsibility of the Rails SQLite adapter layer rather than the SQLite gem itself. I could see something like: Database.new(":memory:", extensions: [SQLean::Crypto]) Is there a particular use case where we would want to support strings in the extensions array outside of Rails? |
@tenderlove Thanks for the feedback!
Oh, that's a better idea. I'll switch it up.
I don't disagree? But the Rails sqlite adapter is currently just shoveling the Doing it in Rails also limits the use of SQLite extensions to Rails 8.1+, which might be the best reason to do it in this gem.
Not that I know of, beyond perhaps other web frameworks with the same problem space. |
Updated to use |
First change: Database#load_extension supports an extension specifier, which is anything that responds to `#to_path` like Pathname or the SQLean gem modules. When passed an object that responds to `#to_path`, that method's return value is used as the filesystem path for the extension, allowing a calling convention like: db.load_extension(SQLean::VSV) where prior to this change that would need to be the more verbose: db.load_extension(SQLean::VSV.to_path) Second change: Database.new supports `extensions:` option parameter, which is an array of paths or extension specifiers to be loaded. This commit updates the Database documentation with lots of examples and explanation, but to sum up: Database.new(":memory:", extensions: [SQLean::Crypto, "/path/to/extension"]) My hope is that we can now make a very small change to Rails to allow injection of database extensions via the config/database.yml file using this constructor param.
f73cafc
to
41e20fa
Compare
@tenderlove Thanks again for the feedback. I've removed constantization from this PR since I can point people at https://fractaledmind.github.io/2023/12/24/enhancing-rails-installing-extensions/ for an explanation of how to load extensions in an existing Rails app. I'll submit a PR to Rails for constantizing the database config extension strings. |
The `sqlite3` gem v2.4.0 introduces support for loading extensions that are passed to the Database constructor. This feature leverages that feature to allow configuration of extensions using either filesystem paths or the names of modules that respond to `.to_path`. This commit documents the feature in both SQLite3Adapter rdoc and the "Configuring" guide. It also extends and improves the documentation around general SQLite3Adapter configuration. See sparklemotion/sqlite3-ruby#586 for more information.
The `sqlite3` gem v2.4.0 introduces support for loading extensions that are passed to the Database constructor. This feature leverages that feature to allow configuration of extensions using either filesystem paths or the names of modules that respond to `.to_path`. This commit documents the feature in both SQLite3Adapter rdoc and the "Configuring" guide. It also extends and improves the documentation around general SQLite3Adapter configuration. See sparklemotion/sqlite3-ruby#586 for more information.
The `sqlite3` gem v2.4.0 introduces support for loading extensions that are passed to the Database constructor. This feature leverages that feature to allow configuration of extensions using either filesystem paths or the names of modules that respond to `.to_path`. This commit documents the feature in both SQLite3Adapter rdoc and the "Configuring" guide. It also extends and improves the documentation around general SQLite3Adapter configuration. See sparklemotion/sqlite3-ruby#586 for more information.
The `sqlite3` gem v2.4.0 introduces support for loading extensions that are passed to the Database constructor. This feature leverages that feature to allow configuration of extensions using either filesystem paths or the names of modules that respond to `.to_path`. This commit documents the feature in both SQLite3Adapter rdoc and the "Configuring" guide. It also extends and improves the documentation around general SQLite3Adapter configuration. See sparklemotion/sqlite3-ruby#586 for more information.
The sqlite3-ruby recently published an improvement to make it easier and more flexible to load extensions: sparklemotion/sqlite3-ruby#586 Ruby modules for SQLite extensions should implement the interface: interface _ExtensionSpecifier def to_path: () → String end A complementary change in Rails takes advantage of this interface to integrate the primary configuration file with the new sqlite3-ruby interface for extension loading: rails/rails#53827 The gem template provided here already has a similar method: def loadable_path The change proposed here is to modify the gem template to provide an alias to loadable_path as to_path.o # example SqliteVec.to_path # => returns same result as loadable_path As a result of this change, Ruby gems published with this tool will conform to the new interface supported in sqlite3-ruby and Rails.
The sqlite3-ruby recently published an improvement to make it easier and more flexible to load extensions: sparklemotion/sqlite3-ruby#586 Ruby modules for SQLite extensions should implement the interface: interface _ExtensionSpecifier def to_path: () → String end A complementary change in Rails takes advantage of this interface to integrate the primary configuration file with the new sqlite3-ruby interface for extension loading: rails/rails#53827 The gem template provided here already has a similar method: def loadable_path The change proposed here is to modify the gem template to provide an alias to loadable_path as to_path.o # example SqliteVec.to_path # => returns same result as loadable_path As a result of this change, Ruby gems published with this tool will conform to the new interface supported in sqlite3-ruby and Rails.
Using sqlite extensions in a Rails app is currently a bit tricky.
Assuming you can figure out how to compile it, where do you put the extension on disk? If it's provided by a gem, how do you get a reliable path that works robustly across environments and upgrades?
Do you monkeypatch the sqlite3 adapter to call
#load_extension
, or do you explicitly inject the extension yourself before any query that needs it?Using sqlite extensions shouldn't be this hard.
The goal of this PR is to enable injection of sqlite extensions by specifying the name of a Ruby constant in a Rails database config:
0. Establishing a packaging convention
Over this past weekend, I packaged up the https://github.com/nalgeon/sqlean/ extensions as a Ruby gem: https://github.com/flavorjones/sqlean-ruby
The gem offers a set of modules that simply return the filesystem path of the extension files. Each module implements this interface, which I'll call
_ExtensionSpecifier
:So, for example, on my machine:
which means I can load the extension like so:
I think this is a reasonable self-describing pattern for an extensions gem to offer. Assuming a future world where other people package up their extensions like this ...
1.
Database#load_extension
contractDatabase#load_extension
now supports_ExtensionSpecifier
in addition to filesystem paths.When passed an object that responds to
#to_path
, that method's return value is used as the filesystem path for the extension, allowing a terser calling convention than above:2.
Database#new
supportsextensions:
Database#new
supports anextensions:
keyword argument which accepts:String
filesystem path_ExtensionSpecifier
object3. Future work
I'll open a PR with Rails to constantize class names that appear in
config/database.yml
so they're then passed through to the gem. That should unlock usage like: