feat: Add SQLite extension loading support#110
Conversation
Adds ability to load SQLite extensions at runtime, enabling use of sqlite-vec and other extension modules. Changes: - Add enable_load_extension and load_extension methods to Connection - Add String-based constructor to Exception for custom error messages - Add linker flags for Homebrew SQLite on macOS (has extension support) - Add TDD tests for extension loading functionality This enables use cases like: - Loading sqlite-vec for vector similarity search - Loading FTS5 for full-text search - Loading custom SQLite extensions Closes crystal-lang#1
f2cb662 to
0f06a9d
Compare
|
|
||
| private def check(code) | ||
| raise Exception.new(self) unless code == 0 | ||
| raise Exception.new(@db) unless code == 0 |
| describe SQLite3::Exception do | ||
| describe ".new(String)" do | ||
| it "creates exception with message" do | ||
| ex = SQLite3::Exception.new("Test error message") | ||
| ex.message.should eq("Test error message") | ||
| ex.code.should eq(0) | ||
| end | ||
| end | ||
| end |
There was a problem hiding this comment.
Issue: Wrong spec file. Not needed, either.
There was a problem hiding this comment.
Issue The whole specs are wrong.
The really interesting spec will only run on macOS with sqlite3 installed through Homebrew, and otherwise silently skips (false success). At the very least a skipped spec must be explicit.
Maybe we could compile a dummy extension, or take an ENV parameter to an extension library, so we can at least configure CI.
There are no specs for testing that we can't load an extension when disabled.
There are also no specs that try to execute a query that uses the extension and asserts that it succeeds (enabled) or fails (not loaded).
| err_msg = Pointer(UInt8).null | ||
| result = LibSQLite3.load_extension(@db, path, entry_point, pointerof(err_msg)) | ||
| if result != 0 | ||
| msg = err_msg.null? ? "Unknown error" : String.new(err_msg) | ||
| raise Exception.new("Failed to load extension: #{msg}") | ||
| end |
There was a problem hiding this comment.
Issue: memory leak, err_msg has been allocated and must be freed.
| err_msg = Pointer(UInt8).null | |
| result = LibSQLite3.load_extension(@db, path, entry_point, pointerof(err_msg)) | |
| if result != 0 | |
| msg = err_msg.null? ? "Unknown error" : String.new(err_msg) | |
| raise Exception.new("Failed to load extension: #{msg}") | |
| end | |
| ret = LibSQLite3.load_extension(@db, path, entry_point, out err_msg) | |
| return unless ret == 0 | |
| msg = err_msg ? String.new(err_msg).tap { LibSQLite3.free(err_msg) } : "Unknown error" | |
| raise Exception.new("Failed to load extension: #{msg}") |
| {% if flag?(:darwin) %} | ||
| @[Link(ldflags: "-L/opt/homebrew/opt/sqlite/lib")] | ||
| {% end %} |
Summary
Add
enable_load_extensionandload_extensionmethods toSQLite3::Connectionfor dynamically loading SQLite extensions at runtime.Closes #106
Changes
sqlite3_enable_load_extensionandsqlite3_load_extensionConnection#enable_load_extension(enabled : Bool)methodConnection#load_extension(path, entry_point)method with full documentationUse Cases
SQLite extensions provide valuable functionality:
sqlite-vec: Vector similarity search for AI applicationssqlite-json: Enhanced JSON supportsqlite-fts5: Full-text searchWithout extension loading, Crystal applications cannot leverage these powerful SQLite extensions.
Testing
Example Usage