Refactor entire codebase and rewrite documentation

This commit is contained in:
2025-10-10 18:06:34 +03:00
parent b4e9755c15
commit 8e471f2b9f
74 changed files with 3405 additions and 4149 deletions

View File

@@ -0,0 +1,94 @@
# Multithreading Strategies
Coordinate SQLite safely across queues, actors, and Swift concurrency using DataLiteCore.
SQLite remains fundamentally serialized, so deliberate connection ownership and scheduling are
essential for correctness and performance. DataLiteCore does not include a built-in connection
pool, but its deterministic behavior and configuration options allow you to design synchronization
strategies that match your workload.
## Core Guidelines
- **One connection per queue or actor**. Keep each ``Connection`` confined to a dedicated serial
`DispatchQueue` or an `actor` to ensure ordered execution and predictable statement lifecycles.
- **Do not share statements across threads**. ``Statement`` instances are bound to their parent
``Connection`` and are not thread-safe.
- **Scale with multiple connections**. For concurrent workloads, use a dedicated writer connection
alongside a pool of readers so long-running transactions dont block unrelated operations.
```swift
actor Database {
private let connection: Connection
init(path: String) throws {
connection = try Connection(
path: path,
options: [.readwrite, .create, .fullmutex]
)
connection.busyTimeout = 5_000 // wait up to 5 seconds for locks
}
func insertUser(name: String) throws {
let statement = try connection.prepare(
sql: "INSERT INTO users(name) VALUES (?)"
)
try statement.bind(name, at: 1)
try statement.step()
}
}
```
Encapsulating database work in an `actor` or serial queue aligns naturally with Swift Concurrency
while maintaining safe access to SQLites synchronous API.
## Synchronization Options
- ``Connection/Options/nomutex`` — disables SQLites internal mutexes (multi-thread mode). Each
connection must be accessed by only one thread at a time.
```swift
let connection = try Connection(
location: .file(path: "/path/to/sqlite.db"),
options: [.readwrite, .nomutex]
)
```
- ``Connection/Options/fullmutex`` — enables serialized mode with full internal locking. A single
``Connection`` may be shared across threads, but global locks reduce throughput.
```swift
let connection = try Connection(
location: .file(path: "/path/to/sqlite.db"),
options: [.readwrite, .fullmutex]
)
```
SQLite defaults to serialized mode, but concurrent writers still contend for locks. Plan long
transactions carefully and adjust ``ConnectionProtocol/busyTimeout`` to handle `SQLITE_BUSY`
conditions gracefully.
- SeeAlso: [Using SQLite In Multi-Threaded Applications](https://sqlite.org/threadsafe.html)
## Delegates and Side Effects
``ConnectionDelegate`` and ``ConnectionTraceDelegate`` callbacks execute synchronously on SQLites
internal thread. Keep them lightweight and non-blocking. Offload work to another queue when
necessary to prevent deadlocks or extended lock holds.
```swift
final class Logger: ConnectionTraceDelegate {
private let queue = DispatchQueue(label: "logging")
func connection(
_ connection: ConnectionProtocol,
trace sql: ConnectionTraceDelegate.Trace
) {
queue.async {
print("SQL:", sql.expandedSQL)
}
}
}
```
This pattern keeps tracing responsive and prevents SQLites internal thread from being blocked by
slow I/O or external operations.