Skip to content

customerio/SyncSqlCipher

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SyncSqlCipher

A synchronous, DispatchQueue-based SQLCipher wrapper for Swift.

SyncSqlCipher gives you full-strength AES-256 encrypted SQLite with a plain synchronous API — no async/await, no actors, no structured concurrency required. All database work serialises on a private dispatch queue; nested calls (including from within migrations) are safe via reentrancy detection.

Requirements

Platform Minimum version
iOS 13.0
macOS 10.15
visionOS 1.0 (SPM only)
  • Swift 5.10+
  • Xcode 16+ (required for Swift Testing in the test suite)

Installation

Swift Package Manager

Add the package to your Package.swift:

dependencies: [
    .package(url: "https://github.com/customerio/SyncSqlCipher.git", from: "1.0.0"),
],
targets: [
    .target(name: "YourTarget", dependencies: ["SyncSqlCipher"]),
]

Or add it in Xcode via File › Add Package Dependencies.

CocoaPods

pod 'SyncSqlCipher', '~> 1.0'

The pod compiles CSqlCipher and the Swift layer into a single framework — no separate CSqlCipher pod or SQLite3 system library is needed.

Quick start

import SyncSqlCipher

// Open or create an encrypted database
let db = try Database(path: "/path/to/store.db", key: "my-passphrase")

// DDL
try db.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")

// Write
try db.execute("INSERT INTO users (name) VALUES (?)", "Alice")

// Read
let rows  = try db.query("SELECT * FROM users")
let count = try db.scalarQuery("SELECT COUNT(*) FROM users", as: Int.self)

Transactions and multi-statement blocks

Use withConnection to group related statements. The Connection passed to the closure is expired immediately after the closure returns — using it afterwards throws SqlCipherError.connectionExpired.

let insertedID: Int64? = try db.withConnection { conn in
    try conn.execute("BEGIN")
    try conn.execute("INSERT INTO users (name) VALUES (?)", "Bob")
    let id = try conn.scalarQuery("SELECT last_insert_rowid()", as: Int64.self)
    try conn.execute("COMMIT")
    return id
}

Migrations

Define migrations by conforming to the Migration protocol and call db.migrate(_:) once at startup. Each migration runs in its own transaction; already-applied migrations are skipped automatically.

struct CreateUsers: Migration {
    let id = "001_create_users"
    func up(_ ctx: MigrationContext) throws {
        try ctx.execute(
            CreateTable("users") {
                ColumnDefinition("id",   .integer, primaryKey: true)
                ColumnDefinition("name", .text,    notNull: true)
            }
        )
    }
}

struct AddEmail: Migration {
    let id = "002_add_email"
    func up(_ ctx: MigrationContext) throws {
        try ctx.execute(AlterTable("users").addColumn("email", .text))
    }
}

try db.migrate([CreateUsers(), AddEmail()])

Entity persistence

Conform any Codable struct to Entity for one-line save and fetch:

struct User: Entity, Equatable {
    typealias ID = Int?
    static let tableName  = TableName("users")
    static let primaryKey: WritableKeyPath<User, Int?> & Sendable = \.id

    var id:    Int?
    var name:  String
    var email: String
}

// Insert — SQLite assigns the id; the returned copy has it filled in
var user = User(id: nil, name: "Alice", email: "alice@example.com")
user = try db.save(user)   // user.id is now Optional(1)

// Update
user.name = "Alicia"
try db.save(user)

// Fetch all
let users: [User] = try db.fetchAll(User.self)

// Fetch by primary key
let found: User? = try db.fetch(User.self, id: 1)

Query builder

The Select, Insert, Update, and DDL builders provide a type-safe layer over raw SQL:

let table = TableName("users")
let nameCol = ColumnRef("name", in: table)

let query = Select(from: table)
    .where(nameCol.like("%Alice%"))
    .orderBy(nameCol)
    .limit(10)

let rows = try db.query(query)

Codable decoding

Decode result rows directly into Decodable types:

struct UserRecord: Decodable {
    let id: Int
    let name: String
}

let users: [UserRecord] = try db.query(
    "SELECT id, name FROM users ORDER BY name",
    as: UserRecord.self
)

Rekeying

Change the database encryption key at runtime:

try db.rekey("new-passphrase")

Thread safety

Database is thread-safe. All public methods dispatch synchronously onto a private serial queue. Reentrancy is safe — nested calls on the same queue execute directly without deadlocking. Connection objects are not thread-safe and must not be used outside the withConnection closure.

License

SyncSqlCipher is released under the MIT License.

SQLCipher is Copyright © 2008-2012 Zetetic LLC and is released under a BSD-style license.

About

A Synchronous interface for SqlCipher that parallels SqlCipherKit. Because this doesn't utilize swift async in any way, it can introduce UI latency if used on the UI thread.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors