8.9 KiB
DataLiteCore/Connection
A class representing a connection to an SQLite database.
Overview
The Connection class manages the connection to an SQLite database. It provides an interface
for preparing SQL queries, managing transactions, and handling errors. This class serves as the
main object for interacting with the database.
Opening a New Connection
Use the init(location:options:) initializer to open a database connection. Specify the
database's location using the Location parameter and configure connection settings with the
Options parameter.
do {
let connection = try Connection(
location: .file(path: "~/example.db"),
options: [.readwrite, .create]
)
print("Connection established")
} catch {
print("Failed to connect: \(error)")
}
Closing the Connection
The Connection class automatically closes the database connection when the object is
deallocated (deinit). This ensures proper cleanup even if the object goes out of scope.
Delegate
The Connection class can optionally use a delegate to handle specific events during the
connection lifecycle, such as tracing SQL statements or responding to transaction actions.
The delegate must conform to the ConnectionDelegate protocol, which provides methods for
handling these events.
Custom SQL Functions
The Connection class allows you to add custom SQL functions using subclasses of Function.
You can create either scalar functions (which return a single value) or aggregate
functions (which perform operations across multiple rows). Both types can be used directly in
SQL queries.
To add or remove custom functions, use the add(function:) and remove(function:) methods
of the Connection class.
Preparing SQL Statements
The Connection class provides functionality for preparing SQL statements that can be
executed multiple times with different parameter values. The prepare(sql:options:) method
takes a SQL query as a string and an optional Statement/Options parameter to configure
the behavior of the statement. It returns a Statement object that can be executed.
do {
let statement = try connection.prepare(
sql: "SELECT * FROM users WHERE age > ?",
options: [.persistent]
)
// Bind parameters and execute the statement
} catch {
print("Error preparing statement: \(error)")
}
Executing SQL Scripts
The Connection class allows you to execute a series of SQL statements using the SQLScript
structure. The SQLScript structure is designed to load and process multiple SQL queries
from a file, URL, or string.
You can create an instance of SQLScript with the SQL script content and then pass it to the
execute(sql:) method of the Connection class to execute the script.
let script: SQLScript = """
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO users (name) VALUES ('Alice');
INSERT INTO users (name) VALUES ('Bob');
"""
do {
try connection.execute(sql: script)
print("Script executed successfully")
} catch {
print("Error executing script: \(error)")
}
Transaction Handling
By default, the Connection class operates in autocommit mode, where each SQL statement is
automatically committed after execution. In this mode, each statement is treated as a separate
transaction, eliminating the need for explicit transaction management. To determine whether the
connection is in autocommit mode, use the isAutocommit property.
For manual transaction management, use beginTransaction(_:) to start a transaction, and
commitTransaction() or rollbackTransaction() to either commit or roll back the
transaction.
do {
try connection.beginTransaction()
try connection.execute(sql: "INSERT INTO users (name) VALUES ('Alice')")
try connection.execute(sql: "INSERT INTO users (name) VALUES ('Bob')")
try connection.commitTransaction()
print("Transaction committed successfully")
} catch {
try? connection.rollbackTransaction()
print("Error during transaction: \(error)")
}
Learn more in the SQLite Transaction Documentation.
Error Handling
The Connection class uses Swift's throwing mechanism to handle errors. Errors in database
operations are propagated using throws, allowing you to catch and handle specific issues in
your application.
SQLite-related errors, such as invalid SQL queries, connection failures, or issues with
transaction management, throw an Connection/Error struct. These errors conform to the
Error protocol, and you can handle them using Swift's do-catch syntax to manage exceptions
in your code.
do {
let statement = try connection.prepare(
sql: "SELECT * FROM users WHERE age > ?",
options: []
)
} catch let error as Error {
print("SQLite error: \(error.mesg), Code: \(error.code)")
} catch {
print("Unexpected error: \(error)")
}
Multithreading
The Connection class supports multithreading, but its behavior depends on the selected
thread-safety mode. You can configure the desired mode using the Options parameter in the
init(location:options:) method.
Multi-thread (Options/nomutex): This mode allows SQLite to be used across multiple
threads. However, it requires that no Connection instance or its derived objects (e.g.,
prepared statements) are accessed simultaneously by multiple threads.
let connection = try Connection(
location: .file(path: "~/example.db"),
options: [.readwrite, .nomutex]
)
Serialized (Options/fullmutex): In this mode, SQLite uses internal mutexes to ensure
thread safety. This allows multiple threads to safely share Connection instances and their
derived objects.
let connection = try Connection(
location: .file(path: "~/example.db"),
options: [.readwrite, .fullmutex]
)
- Important: The
Connectionclass does not include built-in synchronization for shared resources. Developers must implement custom synchronization mechanisms, such as usingDispatchQueue, when sharing resources across threads.
For more details, see the Using SQLite in Multi-Threaded Applications.
Encryption
The Connection class supports transparent encryption and re-encryption of databases using the
apply(_:name:) and rekey(_:name:) methods. This allows sensitive data to be securely
stored on disk.
Applying an Encryption Key
To open an encrypted database or encrypt a new one, call apply(_:name:) immediately after
initializing the connection, and before executing any SQL statements.
let connection = try Connection(
path: "~/secure.db",
options: [.readwrite, .create]
)
try connection.apply(Key.passphrase("secret-password"))
- If the database is already encrypted, the key must match the one previously used.
- If the database is unencrypted, applying a key will encrypt it on first write.
You can use either a passphrase, which is internally transformed into a key, or a raw key:
try connection.apply(Key.raw(data: rawKeyData))
- Important: The encryption key must be applied before any SQL queries are executed. Otherwise, the database may remain unencrypted or unreadable.
Rekeying the Database
To change the encryption key of an existing database, you must first apply the current key
using apply(_:name:), then call rekey(_:name:) with the new key.
let connection = try Connection(
path: "~/secure.db",
options: [.readwrite]
)
try connection.apply(Key.passphrase("old-password"))
try connection.rekey(Key.passphrase("new-password"))
- Important:
rekey(_:name:)requires that the correct current key has already been applied viaapply(_:name:). If the wrong key is used, the operation will fail with an error.
Attached Databases
Both apply(_:name:) and rekey(_:name:) accept an optional name parameter to operate
on an attached database. If omitted, they apply to the main database.
Topics
Errors
Error
Initializers
LocationOptionsinit(location:options:)init(path:options:)
Connection State
isAutocommitisReadonlybusyTimeout
PRAGMA Accessors
applicationIDforeignKeysjournalModesynchronoususerVersion
Delegation
addDelegate(_:)removeDelegate(_:)
SQLite Lifecycle
initialize()shutdown()
Custom SQL Functions
add(function:)remove(function:)
Statement Preparation
prepare(sql:options:)
Script Execution
execute(sql:)execute(raw:)
PRAGMA Execution
get(pragma:)set(pragma:value:)
Transactions
beginTransaction(_:)commitTransaction()rollbackTransaction()
Encryption Keys
Connection/Keyapply(_:name:)rekey(_:name:)