wendylabsinc/sqlite-extension-kit
A modern, Swift-ergonomic package for building SQLite loadable extensions with full support for Apple platforms, macOS, and Linux.
Features
- Type-safe API: Swift-native types with comprehensive error handling
- Easy Extension Creation: Protocol-based design for creating loadable extensions
- Scalar Functions: Register custom functions that return single values
- Aggregate Functions: Implement custom aggregations with stateful processing
- Comprehensive Documentation: Full DocC documentation with examples
- Thoroughly Tested: Unit and integration tests covering all functionality
- Cross-platform: Works on macOS, iOS, and Linux
Requirements
- Swift 6.0 or later
- SQLite 3.x
- Platforms: macOS 15+, iOS 18+, tvOS 18+, watchOS 11+, Linux
Installation
Swift Package Manager
Add SQLiteExtensionKit to your Package.swift:
dependencies: [
.package(url: "https://github.com/wendylabsinc/sqlite-extension-kit", from: "0.0.3")
]Quick Start
Creating a Simple Extension
import SQLiteExtensionKit
public struct MyExtension: SQLiteExtensionModule {
public static let name = "my_extension"
public static func register(with db: SQLiteDatabase) throws {
// Register a scalar function
try db.createScalarFunction(name: "double") { context, args in
guard let first = args.first else {
context.resultNull()
return
}
context.result(first.intValue * 2)
}
}
}
// Export the entry point
@_cdecl("sqlite3_myextension_init")
public func sqlite3_myextension_init(
db: OpaquePointer?,
pzErrMsg: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?,
pApi: UnsafePointer<sqlite3_api_routines>?
) -> Int32 {
return MyExtension.entryPoint(db: db, pzErrMsg: pzErrMsg, pApi: pApi)
}If you need to customise the entry point manually, call initializeExtensionIfNeeded(pApi) before interacting with any SQLite APIs to ensure the extension table is initialised.
Building Your Extension
# Build the extension as a dynamic library
swift build -c release
# The extension will be built as a .dylib (macOS) or .so (Linux)Using Your Extension in SQLite
-- Load the extension
.load .build/release/libMyExtension.dylib
-- Use the function
SELECT double(21); -- Returns 42Examples
String Manipulation Functions
try db.createScalarFunction(name: "reverse", argumentCount: 1, deterministic: true) { context, args in
guard let first = args.first else {
context.resultNull()
return
}
let text = first.textValue
context.result(String(text.reversed()))
}Usage:
SELECT reverse('hello'); -- Returns 'olleh'Mathematical Functions
try db.createScalarFunction(name: "power", argumentCount: 2, deterministic: true) { context, args in
guard args.count == 2 else {
context.resultError("power() requires exactly 2 arguments")
return
}
let base = args[0].doubleValue
let exponent = args[1].doubleValue
context.result(pow(base, exponent))
}Usage:
SELECT power(2, 8); -- Returns 256.0Aggregate Functions
try db.createAggregateFunction(
name: "product",
argumentCount: 1,
step: { context, args in
guard let first = args.first else { return }
let value = first.doubleValue
context.withAggregateValue(initialValue: (product: 1.0, count: Int64(0))) { state in
if state.count == 0 {
state.product = value
} else {
state.product *= value
}
state.count += 1
}
},
final: { context in
if !context.withExistingAggregateValue((product: Double, count: Int64).self, clearOnExit: true, { state in
if state.count == 0 {
context.resultNull()
} else {
context.result(state.product)
}
}) {
context.resultNull()
}
}
)Usage:
SELECT product(value) FROM numbers;The helpers withAggregateValue(initialValue:) and withExistingAggregateValue(:clearOnExit::) store copyable state safely and release it when you are done. For complex reference types, fall back to `aggregateState(create:) / existingAggregateState(_:) — see the window-function examples in Sources/ExampleExtensions/WindowFunctions.swift`.
Virtual Tables
Register a Swift virtual table module and expose it to SQLite:
try db.registerVirtualTableModule(name: "keyvalue", module: KeyValueVirtualTable.self)
sqlite3_exec(db.pointer, "CREATE VIRTUAL TABLE kv USING keyvalue", nil, nil, nil)The sample KeyValueVirtualTable demonstrates how to implement the required protocols; the C glue is handled internally by SQLiteExtensionKit.
Write-enabled modules can override update(_:) to handle inserts, updates, and deletes:
mutating func update(_ operation: VirtualTableUpdateOperation) throws -> VirtualTableUpdateOutcome {
switch operation {
case let .insert(rowid, values):
storage.insert(values, preferredRowID: rowid)
return .handled(rowid: storage.lastInsertedRowID)
case let .update(originalRowid, newRowid, values):
storage.replace(rowID: originalRowid, with: values, preferredRowID: newRowid)
return .handled(rowid: newRowid ?? originalRowid)
case let .delete(rowid):
storage.remove(rowID: rowid)
return .handled(rowid: nil)
}
}Once the module is registered, ordinary SQL writes delegate back to Swift so you can manage persistence explicitly:
INSERT INTO kv(key, value) VALUES ('city', 'Paris');
UPDATE kv SET value = 'Berlin' WHERE key = 'city';
DELETE FROM kv WHERE key = 'city';Working with Different Value Types
SQLiteExtensionKit provides type-safe access to SQLite values:
try db.createScalarFunction(name: "type_demo") { context, args in
guard let value = args.first else {
context.resultNull()
return
}
switch value.type {
case .integer:
context.result("Integer: \(value.intValue)")
case .real:
context.result("Real: \(value.doubleValue)")
case .text:
context.result("Text: \(value.textValue)")
case .blob:
context.result("Blob with \(value.bytes) bytes")
case .null:
context.result("NULL value")
}
}API Reference
Core Types
SQLiteValue: Represents a SQLite value (integer, real, text, blob, or null)SQLiteContext: The execution context for returning results from functionsSQLiteDatabase: Wrapper for registering extensions with a databaseSQLiteExtensionModule: Protocol for defining extension modules
Function Types
ScalarFunction: Functions that return a single valueAggregateStepFunction: Step function for aggregatesAggregateFinalFunction: Finalize function for aggregates
Error Handling
SQLiteExtensionError: Errors that can occur during extension operations
Example Extensions
The package includes several example extensions demonstrating various capabilities:
StringFunctionsExtension
reverse(text): Reverse a stringrot13(text): ROT13 encodingtrim_all(text): Remove all whitespaceword_count(text): Count words in text
MathFunctionsExtension
power(x, y): x raised to the power of yfactorial(n): Calculate factorialfibonacci(n): Calculate nth Fibonacci numberproduct(x): Aggregate productstd_dev(x): Standard deviation aggregate
DataFunctionsExtension
hex_encode(blob): Encode as hexadecimalhex_decode(text): Decode hexadecimalbase64_encode(blob): Encode as base64base64_decode(text): Decode base64
Platform-specific helpers:
sha256(data): SHA-256 hash (macOS/iOS only)reverse_bytes(blob): Reverse byte order
Linux Docker Demo
Want to see the extension loading on a Linux system that relies on the distro-provided SQLite? Use the Docker example:
docker build -f Examples/LinuxDocker/Dockerfile -t sqlite-extension-kit-demo .
docker run --rm sqlite-extension-kit-demoThe container installs the system libsqlite3, runs the test suite, builds the ExampleExtensions product in release mode, and executes the LinuxDockerDemo helper. The demo links against the system library, registers the Swift string extension entry point, and executes a handful of SQL queries to show the results.
Want to exercise the same example directly on macOS? Use the helper script, which runs the standalone GRDB-backed executable and verifies the expected output:
Scripts/run-linuxdockerapp.shSee the GRDB integration guide for a deeper walkthrough of how the example registers SQLiteExtensionKit within GRDB and how you can apply the pattern in your own projects.
AdvancedFunctionsExtension
- JSON extraction and manipulation
- Regular expression matching and replacement
- String similarity (Levenshtein distance)
- UUID generation, timestamps
- URL encoding/decoding
See the Advanced Examples article in the DocC documentation for detailed examples.
WindowFunctionsExtension
- Aggregate-based window-like functions
- Moving averages, percentiles, median
- String aggregation
- Running totals
See the Advanced Examples article in the DocC documentation for detailed examples.
Virtual Table Architecture
- Protocol-based virtual table design
- Example key-value store implementation
- Reference architecture for custom data sources
See the Advanced Examples article in the DocC documentation for detailed examples.
Building and Testing
# Build the package (zero warnings)
swift build
# Run all tests (54 tests in 8 suites)
swift test
# Build in release mode
swift build -c release
# Run specific tests
swift test --filter StringFunctionsTestsCode Quality
- Zero Swift compiler warnings in strict Swift 6 concurrency mode
- 52 passing tests with comprehensive coverage
- Full DocC documentation with examples
- Thread-safe with
@unchecked Sendablewhere appropriate - Memory-safe aggregate context usage patterns
Documentation
Generate documentation using DocC:
swift package generate-documentationThe documentation includes comprehensive platform-specific deployment guides:
- Android Integration: JNI wrapper approach, Room integration, Gradle configuration
- iOS Integration: Static library approach, framework embedding
- Windows Deployment: DLL compilation, .NET integration, IIS deployment
- Linux Deployment: Shared object files, systemd services
Platform-Specific Notes
macOS/iOS
- Extensions are built as
.dylibfiles - CryptoKit is available for cryptographic functions
Linux
- Extensions are built as
.sofiles - Ensure SQLite development headers are installed:
apt-get install libsqlite3-dev
Best Practices
- Use Deterministic Functions: Mark functions as deterministic when they always return the same result for the same inputs
- Handle NULL Values: Always check for NULL values in your functions
- Error Handling: Use
context.resultError()to report errors to SQLite - Type Safety: Use the appropriate type accessors (
.intValue,.textValue, etc.) - Memory Management: Aggregate functions should properly manage their context memory
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is available under the MIT License.
Acknowledgments
Built with Swift 6.0 for modern, safe, and ergonomic SQLite extension development.
Package Metadata
Repository: wendylabsinc/sqlite-extension-kit
Default branch: main
README: README.md