🐘 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 - 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.
- 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)
PostgresNIO does not provide a ConnectionPool
as of today, but this is a feature high on our list. If you need a ConnectionPool
today, please have a look at Vapor's PostgresKit.
Check out the PostgresNIO API docs for a detailed look at all of the classes, structs, protocols, and more.
Add PostgresNIO
as dependency to your Package.swift
:
dependencies: [
.package(url: "https://github.com/vapor/postgres-nio.git", from: "1.8.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 connection, first create a connection configuration object:
import PostgresNIO
let config = PostgresConnection.Configuration(
connection: .init(
host: "localhost",
port: 5432
),
authentication: .init(
username: "my_username",
database: "my_database",
password: "my_password"
),
tls: .disable
)
A connection must be created on a SwiftNIO EventLoop
. In most server use cases, an
EventLoopGroup
is created at app startup and closed during app shutdown.
import NIOPosix
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
// Much later
try eventLoopGroup.syncShutdown()
A Logger
is also required.
import Logging
let logger = Logger(label: "postgres-logger")
Now we can put it together:
import PostgresNIO
import NIOPosix
import Logging
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let logger = Logger(label: "postgres-logger")
let config = PostgresConnection.Configuration(
connection: .init(
host: "localhost",
port: 5432
),
authentication: .init(
username: "my_username",
database: "my_database",
password: "my_password"
),
tls: .disable
)
let connection = try await PostgresConnection.connect(
on eventLoop: eventLoopGroup.next(),
configuration: config,
id connectionID: 1,
logger: logger
)
// Close your connection once done
try await connection.close()
// Shutdown the EventLoopGroup, once all connections are closed.
try eventLoopGroup.syncShutdown()
Once a connection is established, queries can be sent to the server. This is very straightforward:
let rows = try await connection.query("SELECT id, username, birthday FROM users", logger: logger)
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, context: .default) {
// 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 connection.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.