Skip to content

Commit a39d12d

Browse files
committed
Support loading SQLite3 extensions with config/database.yml
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.
1 parent 95deab7 commit a39d12d

File tree

4 files changed

+90
-8
lines changed

4 files changed

+90
-8
lines changed

activerecord/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
* SQLite extensions can be configured in `config/database.yml`.
2+
3+
The database configuration option `extensions:` allows an application to load SQLite extensions
4+
when using `sqlite3` >= v2.4.0. The array members may be filesystem paths or the names of
5+
modules that respond to `.to_path`.
6+
7+
*Mike Dalessio*
8+
19
* `ActiveRecord::Middleware::ShardSelector` supports granular database connection switching.
210

311
A new configuration option, `class_name:`, is introduced to

activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,30 @@
1919

2020
module ActiveRecord
2121
module ConnectionAdapters # :nodoc:
22-
# = Active Record SQLite3 Adapter
22+
# = Active Record \SQLite3 Adapter
2323
#
24-
# The SQLite3 adapter works with the sqlite3-ruby drivers
25-
# (available as gem from https://rubygems.org/gems/sqlite3).
24+
# The \SQLite3 adapter works with the sqlite3[https://sparklemotion.github.io/sqlite3-ruby/]
25+
# driver.
2626
#
2727
# Options:
2828
#
29-
# * <tt>:database</tt> - Path to the database file.
29+
# * +:database+ (String): Filesystem path to the database file.
30+
# * +:statement_limit+ (Integer): Maximum number of prepared statements to cache per database connection. (default: 1000)
31+
# * +:timeout+ (Integer): Timeout in milliseconds to use when waiting for a lock. (default: no wait)
32+
# * +:strict+ (Boolean): Enable or disable strict mode. When enabled, this will
33+
# {disallow double-quoted string literals in SQL
34+
# statements}[https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted].
35+
# (default: see strict_strings_by_default)
36+
# * +:extensions+ (Array): (<b>requires sqlite3 v2.4.0</b>) Each entry specifies a sqlite extension
37+
# to load for this database. The entry may be a filesystem path, or the name of a class that
38+
# responds to +.to_path+ to provide the filesystem path for the extension. See {sqlite3-ruby
39+
# documentation}[https://sparklemotion.github.io/sqlite3-ruby/SQLite3/Database.html#class-SQLite3::Database-label-SQLite+Extensions]
40+
# for more information.
41+
#
42+
# There may be other options available specific to the SQLite3 driver. Please read the
43+
# documentation for
44+
# {SQLite::Database.new}[https://sparklemotion.github.io/sqlite3-ruby/SQLite3/Database.html#method-c-new]
45+
#
3046
class SQLite3Adapter < AbstractAdapter
3147
ADAPTER_NAME = "SQLite"
3248

@@ -58,12 +74,19 @@ def dbconsole(config, options = {})
5874

5975
##
6076
# :singleton-method:
61-
# Configure the SQLite3Adapter to be used in a strict strings mode.
62-
# This will disable double-quoted string literals, because otherwise typos can silently go unnoticed.
63-
# For example, it is possible to create an index for a non existing column.
77+
#
78+
# Configure the SQLite3Adapter to be used in a "strict strings" mode. When enabled, this will
79+
# {disallow double-quoted string literals in SQL
80+
# statements}[https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted],
81+
# which may prevent some typographical errors like creating an index for a non-existent
82+
# column. The default is +false+.
83+
#
6484
# If you wish to enable this mode you can add the following line to your application.rb file:
6585
#
6686
# config.active_record.sqlite3_adapter_strict_strings_by_default = true
87+
#
88+
# This can also be configured on individual databases by setting the +strict:+ option.
89+
#
6790
class_attribute :strict_strings_by_default, default: false
6891

6992
NATIVE_DATABASE_TYPES = {
@@ -125,10 +148,16 @@ def initialize(...)
125148
@last_affected_rows = nil
126149
@previous_read_uncommitted = nil
127150
@config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
151+
152+
extensions = @config.fetch(:extensions, []).map do |extension|
153+
extension.safe_constantize || extension
154+
end
155+
128156
@connection_parameters = @config.merge(
129157
database: @config[:database].to_s,
130158
results_as_hash: true,
131159
default_transaction_mode: :immediate,
160+
extensions: extensions
132161
)
133162
end
134163

activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ class SQLite3AdapterTest < ActiveRecord::SQLite3TestCase
1515
class DualEncoding < ActiveRecord::Base
1616
end
1717

18+
class SQLiteExtensionSpec
19+
def self.to_path
20+
"/path/to/sqlite3_extension"
21+
end
22+
end
23+
1824
def setup
1925
@conn = SQLite3Adapter.new(
2026
database: ":memory:",
@@ -1089,6 +1095,30 @@ def test_integer_cpk_column_returns_false_for_rowid
10891095
end
10901096
end
10911097

1098+
def test_sqlite_extensions_are_constantized_for_the_client_constructor
1099+
mock_adapter = Class.new(SQLite3Adapter) do
1100+
class << self
1101+
attr_reader :new_client_arg
1102+
1103+
def new_client(config)
1104+
@new_client_arg = config
1105+
end
1106+
end
1107+
end
1108+
1109+
conn = mock_adapter.new({
1110+
database: ":memory:",
1111+
adapter: "sqlite3",
1112+
extensions: [
1113+
"/string/literal/path",
1114+
"ActiveRecord::ConnectionAdapters::SQLite3AdapterTest::SQLiteExtensionSpec",
1115+
]
1116+
})
1117+
conn.send(:connect)
1118+
1119+
assert_equal(["/string/literal/path", SQLiteExtensionSpec], conn.class.new_client_arg[:extensions])
1120+
end
1121+
10921122
private
10931123
def assert_logged(logs)
10941124
subscriber = SQLSubscriber.new

guides/source/configuring.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3335,6 +3335,8 @@ Now the behavior is clear, that we are only using the connection information in
33353335
33363336
Rails comes with built-in support for [SQLite3](https://www.sqlite.org), which is a lightweight serverless database application. While Rails better configures SQLite for production workloads, a busy production environment may overload SQLite. Rails defaults to using an SQLite database when creating a new project, but you can always change it later.
33373337
3338+
NOTE: Rails uses an SQLite3 database for data storage by default because it is a zero configuration database that just works. Rails also supports MySQL (including MariaDB) and PostgreSQL "out of the box", and has plugins for many database systems. If you are using a database in a production environment Rails most likely has an adapter for it.
3339+
33383340
Here's the section of the default configuration file (`config/database.yml`) with connection information for the development environment:
33393341

33403342
```yaml
@@ -3345,7 +3347,20 @@ development:
33453347
timeout: 5000
33463348
```
33473349

3348-
NOTE: Rails uses an SQLite3 database for data storage by default because it is a zero configuration database that just works. Rails also supports MySQL (including MariaDB) and PostgreSQL "out of the box", and has plugins for many database systems. If you are using a database in a production environment Rails most likely has an adapter for it.
3350+
[SQLite extensions](https://sqlite.org/loadext.html) are supported when using `sqlite3` gem v2.4.0 or later by configuring `extensions`:
3351+
3352+
``` yaml
3353+
development:
3354+
adapter: sqlite3
3355+
extensions:
3356+
- SQLean::UUID # module name responding to `.to_path`
3357+
- .sqlpkg/nalgeon/crypto/crypto.so # or a filesystem path
3358+
- <%= AppExtensions.location %> # or ruby code returning a path
3359+
```
3360+
3361+
Many useful features can be added to SQLite through extensions. You may wish to browse the [SQLite extension hub](https://sqlpkg.org/) or use gems like [`sqlpkg-ruby`](https://github.com/fractaledmind/sqlpkg-ruby) and [`sqlean-ruby`](https://github.com/flavorjones/sqlean-ruby) that simplify extension management.
3362+
3363+
Other configuration options are described in the [SQLite3Adapter documentation]( https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html).
33493364

33503365
#### Configuring a MySQL or MariaDB Database
33513366

0 commit comments

Comments
 (0)