🐘 Non-blocking, event-driven Swift client for PostgreSQL built on SwiftNIO.
Features:
- A
PostgresConnection
which allows you to connect to, authorize with, query, and retrieve results from a PostgreSQL server - A
PostgresClient
which pools and manages connections - An async/await interface that supports backpressure
- Automatic conversions between Swift primitive types and the Postgres wire format
- Integrated with the Swift server ecosystem, including use of SwiftLog and ServiceLifecycle.
- Designed to run efficiently on all supported platforms (tested extensively on Linux and Darwin systems)
- Support for
Network.framework
when available (e.g. on Apple platforms) - Supports running on Unix Domain Sockets
Check out the PostgresNIO API docs for a detailed look at all of the classes, structs, protocols, and more.
Interested in an example? We prepared a simple Birthday example in the Snippets folder.
Add PostgresNIO
as dependency to your Package.swift
:
dependencies: [
.package(url: "https://github.com/vapor/postgres-nio.git", from: "1.21.0"),
...
]
Add PostgresNIO
to the target you want to use it in:
targets: [
.target(name: "MyFancyTarget", dependencies: [
.product(name: "PostgresNIO", package: "postgres-nio"),
])
]
To create a PostgresClient
, which pools connections for you, first create a configuration object:
import PostgresNIO
let config = PostgresClient.Configuration(
host: "localhost",
port: 5432,
username: "my_username",
password: "my_password",
database: "my_database",
tls: .disable
)
Next you can create you client with it:
let client = PostgresClient(configuration: config)
Once you have create your client, you must run()
it:
await withTaskGroup(of: Void.self) { taskGroup in
taskGroup.addTask {
await client.run() // !important
}
// You can use the client while the `client.run()` method is not cancelled.
// To shutdown the client, cancel its run method, by cancelling the taskGroup.
taskGroup.cancelAll()
}
Once a client is running, queries can be sent to the server. This is straightforward:
let rows = try await client.query("SELECT id, username, birthday FROM users")
The query will return a PostgresRowSequence
, which is an AsyncSequence of PostgresRow
s.
The rows can be iterated one-by-one:
for try await row in rows {
// do something with the row
}
However, in most cases it is much easier to request a row's fields as a set of Swift types:
for try await (id, username, birthday) in rows.decode((Int, String, Date).self) {
// do something with the datatypes.
}
A type must implement the PostgresDecodable
protocol in order to be decoded from a row. PostgresNIO provides default implementations for most of Swift's builtin types, as well as some types provided by Foundation:
Bool
Bytes
,Data
,ByteBuffer
Date
UInt8
,Int16
,Int32
,Int64
,Int
Float
,Double
String
UUID
Sending parameterized queries to the database is also supported (in the coolest way possible):
let id = 1
let username = "fancyuser"
let birthday = Date()
try await client.query("""
INSERT INTO users (id, username, birthday) VALUES (\(id), \(username), \(birthday))
""",
logger: logger
)
While this looks at first glance like a classic case of SQL injection 😱, PostgresNIO's API ensures that this usage is safe. The first parameter of the query(_:logger:)
method is not a plain String
, but a PostgresQuery
, which implements Swift's ExpressibleByStringInterpolation
protocol. PostgresNIO uses the literal parts of the provided string as the SQL query and replaces each interpolated value with a parameter binding. Only values which implement the PostgresEncodable
protocol may be interpolated in this way. As with PostgresDecodable
, PostgresNIO provides default implementations for most common types.
Some queries do not receive any rows from the server (most often INSERT
, UPDATE
, and DELETE
queries with no RETURNING
clause, not to mention most DDL queries). To support this, the query(_:logger:)
method is marked @discardableResult
, so that the compiler does not issue a warning if the return value is not used.
Please see SECURITY.md for details on the security process.