SQLite.swift Documentation

repository·master·Indexed 27 days ago

https://github.com/stephencelis/sqlite.swift

A type-safe, Swift-language layer over SQLite3 that provides compile-time confidence in SQL statement syntax and intent. It features a type-safe, optional-aware SQL expression builder and a lightweight C API wrapper for manual statement preparation. The library supports various installation methods including Swift Package Manager, CocoaPods, and Carthage, and offers integration with SQLCipher for encryption and FTS5 for full-text search.

Tokens
11.8K
Snippets
50
Records
72
Agent score
95%

What's inside SQLite.swift

  1. Use SQLCipher with SQLite.swift via Swift Package Manager

    master

    To enable encryption using SQLCipher, specify the SQLCipher trait.

    Note for Xcode users: As of Xcode 26.2 (17C52), you cannot select traits directly in the Xcode UI. You must create a local wrapper package (e.g., AppDependencies) to pull in the dependency with the SQLCipher trait enabled. Ensure you include linkerSettings to link the SQLCipher framework first if other dependencies link standard sqlite3 to prevent conflicts.

    Once configured, you can use Connection methods to manage keys:

    • db.key("secret"): Sets the encryption key.
    • db.rekey("new secret"): Changes the key on an existing encrypted database.
    • db.sqlcipher_export(...): Encrypts an existing unencrypted database.
    // In your local wrapper package's Package.swift
    // swift-tools-version: 6.1
    import PackageDescription
    
    let package = Package(
        name: "AppDependencies",
        // ... platforms and products ...
        dependencies: [
            .package(
                url: "https://github.com/stephencelis/SQLite.swift.git",
                from: "0.16.0",
                traits: ["SQLCipher"])
        ],
        targets: [
            .target(
                name: "AppDependencies",
                dependencies: [
                    .product(name: "SQLite", package: "SQLite.swift")
                ],
                linkerSettings: [
                  .linkedFramework("SQLCipher")
                ]
            )
        ]
    )
    
    // Usage in your app
    import SQLite
    
    let db = try Connection("path/to/encrypted.sqlite3")
    try db.key("secret")
    try db.rekey("new secret")
    
    // To encrypt an existing database
    let db = try Connection("path/to/unencrypted.sqlite3")
    try db.sqlcipher_export(.uri("encrypted.sqlite3"), key: "secret")
  2. Configure WAL (Write-Ahead Logging) mode

    master

    WAL mode improves concurrency by allowing readers and writers to operate simultaneously.

    • Enable at connection time: Connection("path", journalMode: .wal) (this also sets synchronous = .normal).
    • Enable on existing connection: try db.enableWAL(). This is idempotent.
    • Manual mode setting: Use try db.setJournalMode(.wal) to detect if the mode was successfully applied, or the non-throwing db.journalMode = .truncate for fire-and-forget.

    Warning: Avoid WAL on network file systems (NFS, SMB, iCloud Drive) as it can cause corruption.

  3. Connect to a database in SQLite.swift

    master

    SQLite.swift provides several ways to establish a connection depending on your requirements:

    • Read-Write Databases: Standard connections for full database access.
    • Read-Only Databases: Connections restricted to read operations.
    • Shared Group Containers: Accessing databases within an App Group for sharing between extensions and apps.
    • In-Memory Databases: Using :memory: for transient, high-speed storage.
    • URI Parameters: Using URI strings to configure connection behavior.

    Connections are thread-safe and will close when the connection object is deallocated.

  4. Iterate and access values from query results

    master

    Prepared queries execute lazily when iterated. Each row is returned as a Row object. You can access column values using subscripting with an Expression<T>.

    Important behaviors:

    • Expression<T> values are automatically unwrapped (assumed non-NULL).
    • Expression<T?> values remain wrapped as optionals.
    • Subscripting a Row will force a try and abort execution if an error occurs. To handle errors manually, use Row.get(_:).
    • The iterator can throw undeclared database errors during iteration.
    for user in try db.prepare(users) {
        print("id: \(user[id]), email: \(user[email]), name: \(user[name])")
    }
    
    // Manual error handling for column access
    for user in try db.prepare(users) {
        do {
            print("name: \(try user.get(name))")
        } catch {
            // handle
        }
    }
  5. Create a table with SQLite.swift

    master

    Use the create function on a Table object to generate CREATE TABLE statements.

    Important Note on Nullability:

    • Expression<T> (e.g., id) automatically generates NOT NULL constraints.
    • Expression<T?> (e.g., name) allows NULL values.

    Table Options:

    • temporary: true: Adds a TEMPORARY clause (table is dropped when the connection closes). Default is false.
    • ifNotExists: true: Adds an IF NOT EXISTS clause to prevent errors if the table already exists. Default is false.
  6. Work with Codable types

    master

    SQLite.swift supports inserting, updating, and retrieving Encodable and Decodable types.

    Inserting and Updating

    Use .insert(encodable) or .update(encodable) on queries. Both methods accept an optional userInfo dictionary for custom encoding behavior and otherSetters for additional column updates.

    Warning: Unless a .filter() is applied, calling .update() on a Codable type will update all rows in the table.

    Retrieving

    Use .decode() on a Row to transform it into a Decodable type. For complex scenarios (like the Facade pattern), you can use row.decoder() to manually inspect containers and decode specific subclasses.

    // Inserting
    struct User: Encodable {
        let name: String
    }
    try db.run(users.insert(User(name: "test")))
    
    // Updating
    try db.run(users.filter(id == userId).update(user))
    
    // Retrieving
    let loadedUsers: [User] = try db.prepare(users).map { row in
        return try row.decode()
    }
  7. Join tables and handle column namespacing

    master

    Use the join function to combine tables.

    Namespacing: When joining tables with identical column names (e.g., both have an id column), you must disambiguate using namespacing. You namespace a column by subscripting the query/table object: users[id] becomes users.id.

    Aliasing: To join a table to itself, use the alias function to give the table a new name.

    // Basic Join
    users.join(posts, on: user_id == users[id])
    
    // Namespacing to avoid ambiguity
    let query = users.join(posts, on: user_id == users[id])
    
    // Table Aliasing
    let managers = users.alias("managers")
    let query = users.join(managers, on: managers[id] == users[managerId])
    
    // Accessing namespaced columns from a Row
    user[users[id]]    // returns "users"."id"
    user[managers[id]] // returns "managers"."id"