Skip to content

Commit 91d4563

Browse files
authored
Merge pull request rails#53827 from flavorjones/flavorjones-support-sqlite-extensions
Support loading SQLite3 extensions with `config/database.yml`
2 parents 95deab7 + b599d4c commit 91d4563

File tree

4 files changed

+98
-9
lines changed

4 files changed

+98
-9
lines changed

activerecord/CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
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+
``` yaml
8+
development:
9+
adapter: sqlite3
10+
extensions:
11+
- SQLean::UUID # module name responding to `.to_path`
12+
- .sqlpkg/nalgeon/crypto/crypto.so # or a filesystem path
13+
- <%= AppExtensions.location %> # or ruby code returning a path
14+
```
15+
16+
*Mike Dalessio*
17+
118
* `ActiveRecord::Middleware::ShardSelector` supports granular database connection switching.
219

320
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: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3333,7 +3333,7 @@ Now the behavior is clear, that we are only using the connection information in
33333333
33343334
#### Configuring an SQLite3 Database
33353335
3336-
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.
3336+
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 because it is a zero configuration database that just works, but you can always change it later.
33373337
33383338
Here's the section of the default configuration file (`config/database.yml`) with connection information for the development environment:
33393339

@@ -3345,7 +3345,20 @@ development:
33453345
timeout: 5000
33463346
```
33473347

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.
3348+
[SQLite extensions](https://sqlite.org/loadext.html) are supported when using `sqlite3` gem v2.4.0 or later by configuring `extensions`:
3349+
3350+
``` yaml
3351+
development:
3352+
adapter: sqlite3
3353+
extensions:
3354+
- SQLean::UUID # module name responding to `.to_path`
3355+
- .sqlpkg/nalgeon/crypto/crypto.so # or a filesystem path
3356+
- <%= AppExtensions.location %> # or ruby code returning a path
3357+
```
3358+
3359+
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.
3360+
3361+
Other configuration options are described in the [SQLite3Adapter documentation]( https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html).
33493362

33503363
#### Configuring a MySQL or MariaDB Database
33513364

0 commit comments

Comments
 (0)