diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 332c07c2bc43..9abfc5799e56 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,20 @@
+* SQLite extensions can be configured in `config/database.yml`.
+
+ The database configuration option `extensions:` allows an application to load SQLite extensions
+ when using `sqlite3` >= v2.4.0. The array members may be filesystem paths or the names of
+ modules that respond to `.to_path`:
+
+ ``` yaml
+ development:
+ adapter: sqlite3
+ extensions:
+ - SQLean::UUID # module name responding to `.to_path`
+ - .sqlpkg/nalgeon/crypto/crypto.so # or a filesystem path
+ - <%= AppExtensions.location %> # or ruby code returning a path
+ ```
+
+ *Mike Dalessio*
+
* `ActiveRecord::Middleware::ShardSelector` supports granular database connection switching.
A new configuration option, `class_name:`, is introduced to
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 66e7716194ff..cdf2c3ff41c2 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -19,14 +19,30 @@
module ActiveRecord
module ConnectionAdapters # :nodoc:
- # = Active Record SQLite3 Adapter
+ # = Active Record \SQLite3 Adapter
#
- # The SQLite3 adapter works with the sqlite3-ruby drivers
- # (available as gem from https://rubygems.org/gems/sqlite3).
+ # The \SQLite3 adapter works with the sqlite3[https://sparklemotion.github.io/sqlite3-ruby/]
+ # driver.
#
# Options:
#
- # * :database - Path to the database file.
+ # * +:database+ (String): Filesystem path to the database file.
+ # * +:statement_limit+ (Integer): Maximum number of prepared statements to cache per database connection. (default: 1000)
+ # * +:timeout+ (Integer): Timeout in milliseconds to use when waiting for a lock. (default: no wait)
+ # * +:strict+ (Boolean): Enable or disable strict mode. When enabled, this will
+ # {disallow double-quoted string literals in SQL
+ # statements}[https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted].
+ # (default: see strict_strings_by_default)
+ # * +:extensions+ (Array): (requires sqlite3 v2.4.0) Each entry specifies a sqlite extension
+ # to load for this database. The entry may be a filesystem path, or the name of a class that
+ # responds to +.to_path+ to provide the filesystem path for the extension. See {sqlite3-ruby
+ # documentation}[https://sparklemotion.github.io/sqlite3-ruby/SQLite3/Database.html#class-SQLite3::Database-label-SQLite+Extensions]
+ # for more information.
+ #
+ # There may be other options available specific to the SQLite3 driver. Please read the
+ # documentation for
+ # {SQLite::Database.new}[https://sparklemotion.github.io/sqlite3-ruby/SQLite3/Database.html#method-c-new]
+ #
class SQLite3Adapter < AbstractAdapter
ADAPTER_NAME = "SQLite"
@@ -58,12 +74,19 @@ def dbconsole(config, options = {})
##
# :singleton-method:
- # Configure the SQLite3Adapter to be used in a strict strings mode.
- # This will disable double-quoted string literals, because otherwise typos can silently go unnoticed.
- # For example, it is possible to create an index for a non existing column.
+ #
+ # Configure the SQLite3Adapter to be used in a "strict strings" mode. When enabled, this will
+ # {disallow double-quoted string literals in SQL
+ # statements}[https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted],
+ # which may prevent some typographical errors like creating an index for a non-existent
+ # column. The default is +false+.
+ #
# If you wish to enable this mode you can add the following line to your application.rb file:
#
# config.active_record.sqlite3_adapter_strict_strings_by_default = true
+ #
+ # This can also be configured on individual databases by setting the +strict:+ option.
+ #
class_attribute :strict_strings_by_default, default: false
NATIVE_DATABASE_TYPES = {
@@ -125,10 +148,16 @@ def initialize(...)
@last_affected_rows = nil
@previous_read_uncommitted = nil
@config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
+
+ extensions = @config.fetch(:extensions, []).map do |extension|
+ extension.safe_constantize || extension
+ end
+
@connection_parameters = @config.merge(
database: @config[:database].to_s,
results_as_hash: true,
default_transaction_mode: :immediate,
+ extensions: extensions
)
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 16724be1635c..83b1f4580cb4 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -15,6 +15,12 @@ class SQLite3AdapterTest < ActiveRecord::SQLite3TestCase
class DualEncoding < ActiveRecord::Base
end
+ class SQLiteExtensionSpec
+ def self.to_path
+ "/path/to/sqlite3_extension"
+ end
+ end
+
def setup
@conn = SQLite3Adapter.new(
database: ":memory:",
@@ -1089,6 +1095,30 @@ def test_integer_cpk_column_returns_false_for_rowid
end
end
+ def test_sqlite_extensions_are_constantized_for_the_client_constructor
+ mock_adapter = Class.new(SQLite3Adapter) do
+ class << self
+ attr_reader :new_client_arg
+
+ def new_client(config)
+ @new_client_arg = config
+ end
+ end
+ end
+
+ conn = mock_adapter.new({
+ database: ":memory:",
+ adapter: "sqlite3",
+ extensions: [
+ "/string/literal/path",
+ "ActiveRecord::ConnectionAdapters::SQLite3AdapterTest::SQLiteExtensionSpec",
+ ]
+ })
+ conn.send(:connect)
+
+ assert_equal(["/string/literal/path", SQLiteExtensionSpec], conn.class.new_client_arg[:extensions])
+ end
+
private
def assert_logged(logs)
subscriber = SQLSubscriber.new
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 83781c9856ae..21c562ac589d 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -3335,6 +3335,8 @@ Now the behavior is clear, that we are only using the connection information in
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.
+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.
+
Here's the section of the default configuration file (`config/database.yml`) with connection information for the development environment:
```yaml
@@ -3345,7 +3347,20 @@ development:
timeout: 5000
```
-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.
+[SQLite extensions](https://sqlite.org/loadext.html) are supported when using `sqlite3` gem v2.4.0 or later by configuring `extensions`:
+
+``` yaml
+development:
+ adapter: sqlite3
+ extensions:
+ - SQLean::UUID # module name responding to `.to_path`
+ - .sqlpkg/nalgeon/crypto/crypto.so # or a filesystem path
+ - <%= AppExtensions.location %> # or ruby code returning a path
+```
+
+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.
+
+Other configuration options are described in the [SQLite3Adapter documentation]( https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html).
#### Configuring a MySQL or MariaDB Database