Swift guide

Query data, manage transactions, select extensions, and restore backups in Apple apps.

These recipes use an open db from the Swift quickstart. Import Oliphaunt and call database methods from an async context.

Query application data

try await db.execute("""
CREATE TABLE IF NOT EXISTS notes (
    id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    body text NOT NULL
)
""")
let inserted = try await db.query(
    "INSERT INTO notes (body) VALUES ($1) RETURNING id",
    parameters: [.string("First note")]
)
let id: Int64? = try inserted.rows[0].value(named: "id")

Parameters bind values without SQL interpolation. Decode a result using a Swift type compatible with its PostgreSQL column type. SQL null becomes an optional value.

Run a transaction

try await db.transaction { tx in
    try await tx.execute(
        "INSERT INTO notes (body) VALUES ($1)",
        parameters: [.string("First")]
    )
    try await tx.execute(
        "INSERT INTO notes (body) VALUES ($1)",
        parameters: [.string("Second")]
    )
}

Returning commits and throwing rolls back. Use tx for every statement in the callback. Do not call the outer database or issue manual transaction-lifecycle commands. Use tx.rollback() for explicit rollback; savepoints are supported.

Choose process isolation

The quickstart uses direct mode. For a separate database process, follow mobile broker setup. Broker mode requires its own storage and restore path; use the recipes below for direct mode.

Back up and restore

Direct mode stays bound to one database root and configuration for the lifetime of the application process. Closing a handle does not let that process switch to another root. Prepare the restored data, then use it on the next launch.

Use archive bytes for export, not a live directory copy:

let archive = try await db.backup()
try await db.close()
try await OliphauntDatabase.restore(destination: restoredURL, bytes: archive)

On a subsequent application launch, open the restored destination:

let restored = try await OliphauntDatabase.open(
    configuration: OliphauntConfiguration(storage: .directory(restoredURL))
)
try await restored.close()

Here restoredURL is an app-owned file URL for a new or empty directory. Restore rejects nonempty destinations. Reapply the original database's required extension selection when reopening. Physical backups require a compatible native runtime.

Add an extension

The base Swift package is extension-free. Add the generated product to your application before selecting it at open.

  1. Install Bun, then obtain the Swift extension generator from the Oliphaunt source package matching your Swift dependency.
  2. Download the matching vector Swift extension carrier JSON from its release.
  3. Generate a local Swift package with the command below, replacing the two input paths.
bun /path/to/oliphaunt/src/native/sdks/swift/tools/render-extension-products.mts \
  --extension-carrier /path/to/oliphaunt-extension-vector-0.3.0-swift-extension-carrier.json \
  --extensions vector \
  --output-dir ./OliphauntExtensions

The output directory must not already exist. Add it to Xcode as a local package and link the generated OliphauntExtensionVector product to your app. Select its resource when opening:

import OliphauntExtensionVector

let db = try await OliphauntDatabase.open(
    configuration: OliphauntConfiguration(extensions: [OliphauntExtensionVector.resource])
)
try await db.execute("CREATE EXTENSION IF NOT EXISTS vector")

For multiple extensions, supply each required carrier with another --extension-carrier option and list the SQL names in --extensions. The generated package includes their required dependencies. Check the catalog before selecting an extension.

Applications that need ICU collations also link the OliphauntICU SwiftPM product. Choose the ICU seed when creating a new iOS database that uses ICU collations.

Cancel and close

Cancelling a Swift task alone does not interrupt PostgreSQL. Use try await db.cancel() to request interruption, then await and handle the database operation's outcome.

Close explicitly with try await db.close(). Close rejects new work and drains earlier accepted work. A shutdown failure leaves the handle terminal. Keep UI updates on the main actor and let the SDK run database work on its owner queue.

A transaction callback error is rethrown after successful rollback. If rollback or independent database recovery also fails, the SDK exposes both causes; see errors.

Troubleshooting

SymptomCheck
Invalid storage URLUse an app-owned file URL, not a remote URL
Missing extensionLink and register its generated Swift product before open
Missing runtime resourcesThe app target links Oliphaunt and includes its resolved framework
Root already in useKeep one owner and close it before reopening
Background writes are interruptedKeep transactions short and follow mobile lifecycle guidance