Skip to content

Commit 3fd1cc5

Browse files
committed
feat: Database#load_extension supports an extension specifier
interface _ExtensionSpecifier def sqlite_extension_path: () → String end So when passed an object that responds to `#sqlite_extension_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.sqlite_extension_path) I think supporting this calling convention will also make it easier to inject sqlite extensions via a Rails database config file. Stay tuned.
1 parent e609300 commit 3fd1cc5

File tree

5 files changed

+57
-9
lines changed

5 files changed

+57
-9
lines changed

.rdoc_options

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ exclude:
1717
- "vendor"
1818
- "ports"
1919
- "tmp"
20+
- "pkg"
2021
hyperlink_all: false
2122
line_numbers: false
2223
locale:

ext/sqlite3/database.c

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -771,14 +771,8 @@ collation(VALUE self, VALUE name, VALUE comparator)
771771
}
772772

773773
#ifdef HAVE_SQLITE3_LOAD_EXTENSION
774-
/* call-seq: db.load_extension(file)
775-
*
776-
* Loads an SQLite extension library from the named file. Extension
777-
* loading must be enabled using db.enable_load_extension(true) prior
778-
* to calling this API.
779-
*/
780774
static VALUE
781-
load_extension(VALUE self, VALUE file)
775+
load_extension_internal(VALUE self, VALUE file)
782776
{
783777
sqlite3RubyPtr ctx;
784778
int status;
@@ -997,7 +991,7 @@ init_sqlite3_database(void)
997991
rb_define_private_method(cSqlite3Database, "db_filename", db_filename, 1);
998992

999993
#ifdef HAVE_SQLITE3_LOAD_EXTENSION
1000-
rb_define_method(cSqlite3Database, "load_extension", load_extension, 1);
994+
rb_define_private_method(cSqlite3Database, "load_extension_internal", load_extension_internal, 1);
1001995
#endif
1002996

1003997
#ifdef HAVE_SQLITE3_ENABLE_LOAD_EXTENSION

lib/sqlite3/database.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,38 @@ def busy_handler_timeout=(milliseconds)
658658
end
659659
end
660660

661+
# call-seq:
662+
# load_extension(extension_specifier) -> Database
663+
#
664+
# Loads an SQLite extension library from the named file. Extension loading must be enabled using
665+
# Database#enable_load_extension prior to using this method.
666+
#
667+
# See also: https://www.sqlite.org/loadext.html
668+
#
669+
# [Parameters]
670+
# - extension_specifier (String | +_ExtensionSpecifier+) If a String, it is the filesystem path
671+
# to the sqlite extension file. If an object that responds to #sqlite_extension_path, the
672+
# return value of that method is used as the filesystem path to the sqlite extension file.
673+
#
674+
# +_ExtensionSpecifier+ describes the following interface:
675+
#
676+
# interface _ExtensionSpecifier
677+
# def sqlite_extension_path: () → String
678+
# end
679+
#
680+
# For example, the +sqlean+ ruby gem offers a set of classes that implement this interface,
681+
# allowing a calling convention like:
682+
#
683+
# db.load_extension(SQLean::VSV)
684+
#
685+
def load_extension(extension_specifier)
686+
if extension_specifier.respond_to?(:sqlite_extension_path)
687+
extension_specifier = extension_specifier.sqlite_extension_path
688+
end
689+
load_extension_internal(extension_specifier)
690+
end
691+
692+
661693
# A helper class for dealing with custom functions (see #create_function,
662694
# #create_aggregate, and #create_aggregate_handler). It encapsulates the
663695
# opaque function object that represents the current invocation. It also

lib/sqlite3/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
module SQLite3
22
# (String) the version of the sqlite3 gem, e.g. "2.1.1"
3-
VERSION = "2.3.0"
3+
VERSION = "2.4.0.dev"
44
end

test/test_database.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,10 +653,31 @@ def test_strict_mode
653653
def test_load_extension_with_nonstring_argument
654654
db = SQLite3::Database.new(":memory:")
655655
skip("extensions are not enabled") unless db.respond_to?(:load_extension)
656+
656657
assert_raises(TypeError) { db.load_extension(1) }
657658
assert_raises(TypeError) { db.load_extension(Pathname.new("foo.so")) }
658659
end
659660

661+
def test_load_extension_with_an_extension_descriptor
662+
extension_descriptor = Class.new do
663+
def sqlite_extension_path
664+
"path/to/extension"
665+
end
666+
end.new
667+
668+
class << db
669+
attr_reader :load_extension_internal_path
670+
671+
def load_extension_internal(path)
672+
@load_extension_internal_path = path
673+
end
674+
end
675+
676+
db.load_extension(extension_descriptor)
677+
678+
assert_equal("path/to/extension", db.load_extension_internal_path)
679+
end
680+
660681
def test_load_extension_error
661682
db = SQLite3::Database.new(":memory:")
662683
assert_raises(SQLite3::Exception) { db.load_extension("path/to/foo.so") }

0 commit comments

Comments
 (0)