A small, pure-Swift wrapper over a vendored SQLite amalgamation — prepared statements, typed values, and sqlite3-compatible result formatting, with FTS5 full-text search and sqlite-vec semantic search available behind opt-in traits. Runs everywhere Swift does: macOS, iOS, tvOS, watchOS, visionOS, Linux, Android, and Windows.
- Self-contained. The SQLite engine is vendored — via
stephencelis/CSQLite, pinned to an exact version — and statically linked. No systemlibsqlite3, so the engine version is identical on every platform and every feature is available even where the system build strips it out. - Typed and safe. Values are a
SQLiteValueenum (.null/.integer/.real/.text/.blob); parameters bind out-of-band (?and:name), so there is no string-interpolation injection surface. - On-device search. Opt into FTS5 (bm25) and/or sqlite-vec (
vec0cosine / L2 KNN) at build time — the same engine that fits on an iPhone or an Android handset. - Faithful formatting.
ResultFormatterreproduces the sqlite3 shell's output modes (list, csv, json, column, box, markdown, …).
// Package.swift
dependencies: [
.package(url: "https://github.com/Cocoanetics/SQLiteKit", from: "0.1.0"),
],
targets: [
.target(
name: "MyApp",
dependencies: [
.product(name: "SQLiteKit", package: "SQLiteKit"),
]
),
]To turn on the search engines, enable the traits — both are off by default:
.package(url: "https://github.com/Cocoanetics/SQLiteKit",
from: "0.1.0",
traits: ["FTS5", "SQLiteVec"]),import SQLiteKit
let db = try SQLiteDatabase(.file("library.sqlite"))
// or: let db = try SQLiteDatabase.inMemory()
try db.evaluate("""
CREATE TABLE book(id INTEGER PRIMARY KEY, title TEXT NOT NULL, year INTEGER);
""")
// Parameters bind out-of-band — no escaping, no injection.
try db.execute(
"INSERT INTO book(title, year) VALUES (?, ?);",
[.text("The Swift Programming Language"), .integer(2014)]
)
print(db.lastInsertRowID) // 1
let results = try db.evaluate("SELECT title, year FROM book ORDER BY year;")
for row in results[0].rows {
print(row[0], row[1]) // .text("…") .integer(2014)
}let rows = try db.evaluate(
"SELECT id FROM book WHERE year >= :from AND year < :to;",
[":from": .integer(2000), ":to": .integer(2020)]
)[0].rowslet insert = try SQLiteStatement(db, "INSERT INTO book(title, year) VALUES (?, ?);")
for (title, year) in catalog {
try insert.bind([.text(title), .integer(Int64(year))])
_ = try insert.step()
insert.reset()
}try db.execute("SELECT title FROM book;") { row in
print(row["title"] ?? .null) // SQLiteRow subscripts by index or column name
}let set = try db.evaluate("SELECT id, title FROM book;")[0]
print(ResultFormatter(mode: .box, showHeader: true).render(set))
// ┌────┬───────────────────────────────┐
// │ id │ title │
// ├────┼───────────────────────────────┤
// │ 1 │ The Swift Programming Language │
// └────┴───────────────────────────────┘Modes: .list, .csv, .json, .column, .table, .box, .markdown,
.html, .tabs, .quote, .insert, .line, .ascii.
try db.evaluate("CREATE VIRTUAL TABLE docs USING fts5(title, body);")
try db.execute("INSERT INTO docs(title, body) VALUES (?, ?);",
[.text("SQLite FTS"), .text("Full text search. Full ranking.")])
// bm25 relevance ranking via the special `rank` column.
let hits = try db.evaluate("""
SELECT title FROM docs WHERE docs MATCH 'full' ORDER BY rank;
""")Column filters (body:term), boolean operators (a AND b), and phrase queries
all work as in upstream FTS5.
sqlite-vec is compiled statically into
the engine, giving you a vec0 virtual table for cosine / L2 nearest-neighbor
search — entirely on-device.
try db.evaluate("""
CREATE VIRTUAL TABLE docs USING vec0(
doc_id INTEGER PRIMARY KEY,
embedding float[768] distance_metric=cosine
);
""")
// Bind embeddings as packed little-endian float32 — ~6 KB for a 1536-d vector
// vs. ~20 KB as a JSON literal.
func packed(_ floats: [Float]) -> Data {
var data = Data(capacity: floats.count * 4)
for f in floats {
var le = f.bitPattern.littleEndian
withUnsafeBytes(of: &le) { data.append(contentsOf: $0) }
}
return data
}
let insert = try SQLiteStatement(db, "INSERT INTO docs(doc_id, embedding) VALUES (?, ?);")
try insert.bind([.integer(1), .blob(packed(embedding))])
_ = try insert.step()
// KNN: the query vector bound as a blob; results ordered by distance.
let knn = try db.evaluate("""
SELECT doc_id, distance FROM docs
WHERE embedding MATCH ? AND k = 10
ORDER BY distance;
""", [.blob(packed(queryVector))])Pair it with FTS5 for hybrid keyword + semantic retrieval.
| Trait | Effect | Default |
|---|---|---|
FTS5 |
Compiles SQLite with -DSQLITE_ENABLE_FTS5 (full-text search + bm25). |
off |
SQLiteVec |
Compiles in the sqlite-vec amalgamation (vec0 vector search). |
off |
Enable when depending on the package via traits: ["FTS5", "SQLiteVec"], or when
building directly with swift build --traits FTS5,SQLiteVec. Each trait
recompiles the engine, so they are opt-in.
SQLiteDatabase—init(_:readonly:)/.inMemory();evaluate(_:)andevaluate(_:_:)returning[ResultSet]; streamingexecute(_:_:row:);lastInsertRowID,changes;tableNames(),schemaSQL(of:),nonGeneratedColumns(of:);enableSafeMode()+attachTargets(in:)for sandboxing hosts;backup(to:); staticquoteIdentifier(_:)andlibVersion.SQLiteStatement—init(_:_:);bind([SQLiteValue])/bind(_:_:)/bind([String: SQLiteValue]);step() -> SQLiteRow?;reset().SQLiteValue—.null | .integer(Int64) | .real(Double) | .text(String) | .blob(Data),Equatable & Sendable.SQLiteRow/ResultSet— typed rows, subscriptable by index and column name.ResultFormatter— sqlite3-compatible rendering.
The package also vends a Sqlite3Shell product — the argv parser plus the
dot-command / REPL engine that reproduces the sqlite3 command-line shell
(.tables, .schema, .dump, .import, .mode, -safe, …). It is
ArgumentParser-free and IO-agnostic: it reads, writes, and authorizes file paths
through ShellKit.Shell, so a host
can drive it in-process on any platform — Android included — and confine it to a
sandbox. SDK-only consumers that depend on the SQLiteKit product never build
this target (or ShellKit) into their link.
.product(name: "Sqlite3Shell", package: "SQLiteKit"),import Sqlite3Shell
import ShellKit
let code = try await Sqlite3Executable.run(
argv: ["mydb.sqlite", "SELECT * FROM users;"],
stdin: Shell.current.stdin,
stdout: Shell.current.stdout,
stderr: Shell.current.stderr)Cocoanetics/SwiftPorts wraps this
driver in an ArgumentParser command to ship the sqlite3 executable, and
SwiftBash registers it as a native builtin.
macOS 13+, iOS 16+, tvOS 16+, watchOS 9+, visionOS 1+, Linux, Android, Windows. Swift 6.2+.
- SQLite — public domain, by D. Richard Hipp and the SQLite team. Vendored
via
stephencelis/CSQLite. - sqlite-vec — © Alex Garcia, dual-licensed
MIT /
Apache-2.0. Vendored under
Sources/CSQLiteVec.
SQLiteKit was extracted from
Cocoanetics/SwiftPorts, where it
backs the sqlite3 command-line port.
MIT © Cocoanetics / Oliver Drobnik. See LICENSE.