Skip to content

Commit 93a928d

Browse files
fabianfettgwynne
andauthored
README update for async/await (#254)
Co-authored-by: Gwynne Raskind <[email protected]>
1 parent 7f290f2 commit 93a928d

File tree

1 file changed

+133
-156
lines changed

1 file changed

+133
-156
lines changed

README.md

Lines changed: 133 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<img src="https://user-images.githubusercontent.com/1342803/59061804-5548e280-8872-11e9-819f-14f19f16fcb6.png" height="64" alt="PostgresNIO">
2-
<br>
3-
<a href="https://docs.vapor.codes/4.0/">
2+
3+
<a href="https://github.com/swift-server/sswg/blob/main/process/incubation.md#graduated-level">
4+
<img src="https://img.shields.io/badge/sswg-incubating-green.svg" alt="SSWG Incubating">
5+
</a>
6+
<a href="https://api.vapor.codes/postgres-nio/main/PostgresNIO/">
47
<img src="http://img.shields.io/badge/read_the-docs-2196f3.svg" alt="Documentation">
58
</a>
69
<a href="https://discord.gg/vapor">
@@ -18,211 +21,185 @@
1821
<br>
1922
<br>
2023

21-
🐘 Non-blocking, event-driven Swift client for PostgreSQL built on [SwiftNIO](https://github.com/apple/swift-nio).
22-
23-
### Major Releases
24-
25-
The table below shows a list of PostgresNIO major releases alongside their compatible NIO and Swift versions.
26-
27-
|Version|NIO|Swift|SPM|
28-
|-|-|-|-|
29-
|1.0|2.0+|5.2+|`from: "1.0.0"`|
30-
31-
Use the SPM string to easily include the dependendency in your `Package.swift` file.
32-
33-
```swift
34-
.package(url: "https://github.com/vapor/postgres-nio.git", from: ...)
35-
```
36-
37-
### Supported Platforms
38-
39-
PostgresNIO supports the following platforms:
40-
41-
- Ubuntu 16.04+
42-
- macOS 10.15+
43-
44-
### Security
45-
46-
Please see [SECURITY.md](https://github.com/vapor/.github/blob/main/SECURITY.md) for details on the security process.
47-
48-
## Overview
49-
50-
PostgresNIO is a client package for connecting to, authorizing, and querying a PostgreSQL server. At the heart of this module are NIO channel handlers for parsing and serializing messages in PostgreSQL's proprietary wire protocol. These channel handlers are combined in a request / response style connection type that provides a convenient, client-like interface for performing queries.
51-
52-
Support for both simple (text) and parameterized (binary) querying is provided out of the box alongside a `PostgresData` type that handles conversion between PostgreSQL's wire format and native Swift types.
53-
54-
### Motivation
55-
56-
Most Swift implementations of Postgres clients are based on the [libpq](https://www.postgresql.org/docs/11/libpq.html) C library which handles transport internally. Building a library directly on top of Postgres' wire protocol using SwiftNIO should yield a more reliable, maintainable, and performant interface for PostgreSQL databases.
57-
58-
### Goals
59-
60-
This package is meant to be a low-level, unopinionated PostgreSQL wire-protocol implementation for Swift. The hope is that higher level packages can share PostgresNIO as a foundation for interacting with PostgreSQL servers without needing to duplicate complex logic.
61-
62-
Because of this, PostgresNIO excludes some important concepts for the sake of simplicity, such as:
63-
64-
- Connection pooling
65-
- Swift `Codable` integration
66-
- Query building
67-
68-
If you are looking for a PostgreSQL client package to use in your project, take a look at these higher-level packages built on top of PostgresNIO:
69-
70-
- [`vapor/postgres-kit`](https://github.com/vapor/postgresql)
71-
72-
### Dependencies
24+
🐘 Non-blocking, event-driven Swift client for PostgreSQL built on [SwiftNIO].
7325

74-
This package has four dependencies:
26+
Features:
7527

76-
- [`apple/swift-nio`](https://github.com/apple/swift-nio) for IO
77-
- [`apple/swift-nio-ssl`](https://github.com/apple/swift-nio-ssl) for TLS
78-
- [`apple/swift-log`](https://github.com/apple/swift-log) for logging
79-
- [`apple/swift-metrics`](https://github.com/apple/swift-metrics) for metrics
28+
- A `PostgresConnection` which allows you to connect to, authorize with, query, and retrieve results from a PostgreSQL server
29+
- An async/await interface that supports backpressure
30+
- Automatic conversions between Swift primitive types and the Postgres wire format
31+
- Integrated with the Swift server ecosystem, including use of [SwiftLog].
32+
- Designed to run efficiently on all supported platforms (tested extensively on Linux and Darwin systems)
33+
- Support for `Network.framework` when available (e.g. on Apple platforms)
8034

81-
This package has no additional system dependencies.
35+
PostgresNIO does not have a `ConnectionPool` as of today, but this is a feature high on our list. If
36+
you need a `ConnectionPool` today, please have a look at Vapor's [PostgresKit].
8237

8338
## API Docs
8439

85-
Check out the [PostgresNIO API docs](https://api.vapor.codes/postgres-nio/main/PostgresNIO/) for a detailed look at all of the classes, structs, protocols, and more.
40+
Check out the [PostgresNIO API docs](https://api.vapor.codes/postgres-nio/main/PostgresNIO/) for a
41+
detailed look at all of the classes, structs, protocols, and more.
8642

87-
## Getting Started
43+
## Getting started
8844

89-
This section will provide a quick look at using PostgresNIO.
45+
#### Adding the dependency
9046

91-
### Creating a Connection
92-
93-
The first step to making a query is creating a new `PostgresConnection`. The minimum requirements to create one are a `SocketAddress` and `EventLoop`.
47+
Add `PostgresNIO` as dependency to your `Package.swift`:
9448

9549
```swift
96-
import PostgresNIO
97-
98-
let eventLoop: EventLoop = ...
99-
let conn = try PostgresConnection.connect(
100-
to: .makeAddressResolvingHost("my.psql.server", port: 5432),
101-
on: eventLoop
102-
).wait()
103-
defer { try! conn.close().wait() }
50+
dependencies: [
51+
.package(url: "https://github.com/vapor/postgres-nio.git", from: "1.8.0"),
52+
...
53+
]
10454
```
10555

106-
Note: These examples will make use of `wait()` for simplicity. This is appropriate if you are using PostgresNIO on the main thread, like for a CLI tool or in tests. However, you should never use `wait()` on an event loop.
107-
108-
There are a few ways to create a `SocketAddress`:
109-
110-
- `init(ipAddress: String, port: Int)`
111-
- `init(unixDomainSocketPath: String)`
112-
- `makeAddressResolvingHost(_ host: String, port: Int)`
113-
114-
There are also some additional arguments you can supply to `connect`.
115-
116-
- `tlsConfiguration` An optional `TLSConfiguration` struct. If supplied, the PostgreSQL connection will be upgraded to use SSL.
117-
- `serverHostname` An optional `String` to use in conjunction with `tlsConfiguration` to specify the server's hostname.
118-
119-
`connect` will return a future `PostgresConnection`, or an error if it could not connect. Make sure you close the connection before it deinitializes.
120-
121-
### Authentication
122-
123-
Once you have a connection, you will need to authenticate with the server using the `authenticate` method.
124-
56+
Add `PostgresNIO` to the target you want to use it in:
12557
```swift
126-
try conn.authenticate(
127-
username: "your_username",
128-
database: "your_database",
129-
password: "your_password"
130-
).wait()
58+
targets: [
59+
.target(name: "MyFancyTarget", dependencies: [
60+
.product(name: "PostgresNIO", package: "postgres-nio"),
61+
])
62+
]
13163
```
13264

133-
This requires a username. You may supply a database name and password if needed.
65+
#### Creating a connection
13466

135-
### Database Protocol
136-
137-
Interaction with a server revolves around the `PostgresDatabase` protocol. This protocol includes methods like `query(_:)` for executing SQL queries and reading the resulting rows.
138-
139-
`PostgresConnection` is the default implementation of `PostgresDatabase` provided by this package. Assume `db` here is the connection from the previous example.
67+
To create a connection, first create a connection configuration object:
14068

14169
```swift
14270
import PostgresNIO
14371

144-
let db: PostgresDatabase = ...
145-
// now we can use client to do queries
72+
let config = PostgresConnection.Configuration(
73+
connection: .init(
74+
host: "localhost",
75+
port: 5432
76+
),
77+
authentication: .init(
78+
username: "my_username",
79+
database: "my_database",
80+
password: "my_password"
81+
),
82+
tls: .disable
83+
)
14684
```
14785

148-
### Simple Query
86+
A connection must be created on a SwiftNIO `EventLoop`. In most server use cases, an
87+
`EventLoopGroup` is created at app startup and closed during app shutdown.
88+
89+
```swift
90+
import NIOPosix
14991

150-
Simple (or text) queries allow you to execute a SQL string on the connected PostgreSQL server. These queries do not support binding parameters, so any values sent must be escaped manually.
92+
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
15193

152-
These queries are most useful for schema or transactional queries, or simple selects. Note that values returned by simple queries will be transferred in the less efficient text format.
94+
// Much later
95+
try eventLoopGroup.syncShutdown()
96+
```
15397

154-
`simpleQuery` has two overloads, one that returns an array of rows, and one that accepts a closure for handling each row as it is returned.
98+
A `Logger` is also required.
15599

156100
```swift
157-
let rows = try db.simpleQuery("SELECT version()").wait()
158-
print(rows) // [["version": "12.x.x"]]
101+
import Logging
159102

160-
try db.simpleQuery("SELECT version()") { row in
161-
print(row) // ["version": "12.x.x"]
162-
}.wait()
103+
let logger = Logger(label: "postgres-logger")
163104
```
164105

165-
### Parameterized Query
106+
Now we can put it together:
166107

167-
Parameterized (or binary) queries allow you to execute a SQL string on the connected PostgreSQL server. These queries support passing bound parameters as a separate argument. Each parameter is represented in the SQL string using incrementing placeholders, starting at `$1`.
108+
```swift
109+
import PostgresNIO
110+
import NIOPosix
111+
import Logging
112+
113+
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
114+
let logger = Logger(label: "postgres-logger")
115+
116+
let config = PostgresConnection.Configuration(
117+
connection: .init(
118+
host: "localhost",
119+
port: 5432
120+
),
121+
authentication: .init(
122+
username: "my_username",
123+
database: "my_database",
124+
password: "my_password"
125+
),
126+
tls: .disable
127+
)
128+
129+
let connection = try await PostgresConnection.connect(
130+
on eventLoop: eventLoopGroup.next(),
131+
configuration: config,
132+
id connectionID: 1,
133+
logger: logger
134+
)
135+
136+
// Close your connection once done
137+
try await connection.close()
138+
139+
// Shutdown the EventLoopGroup, once all connections are closed.
140+
try eventLoopGroup.syncShutdown()
141+
```
168142

169-
These queries are most useful for selecting, inserting, and updating data. Data for these queries is transferred using the highly efficient binary format.
143+
#### Querying
170144

171-
Just like `simpleQuery`, `query` also offers two overloads. One that returns an array of rows, and one that accepts a closure for handling each row as it is returned.
145+
Once a connection is established, queries can be sent to the server. This is very straightforward:
172146

173147
```swift
174-
let rows = try db.query("SELECT * FROM planets WHERE name = $1", ["Earth"]).wait()
175-
print(rows) // [["id": 42, "name": "Earth"]]
176-
177-
try db.query("SELECT * FROM planets WHERE name = $1", ["Earth"]) { row in
178-
print(row) // ["id": 42, "name": "Earth"]
179-
}.wait()
148+
let rows = try await connection.query("SELECT id, username, birthday FROM users", logger: logger)
180149
```
181150

182-
### Rows and Data
183-
184-
Both `simpleQuery` and `query` return the same `PostgresRow` type. Columns can be fetched from the row using the `column(_: String)` method.
151+
The query will return a [`PostgresRowSequence`], which is an AsyncSequence of [`PostgresRow`]s. The rows can be iterated one-by-one:
185152

186153
```swift
187-
let row: PostgresRow = ...
188-
let version = row.column("version")
189-
print(version) // PostgresData?
154+
for try await row in rows {
155+
// do something with the row
156+
}
190157
```
191158

192-
`PostgresRow` columns are stored as `PostgresData`. This struct contains the raw bytes returned by PostgreSQL as well as some information for parsing them, such as:
159+
#### Decoding from PostgresRow
193160

194-
- Postgres column type
195-
- Wire format: binary or text
196-
- Value as array of bytes
197-
198-
`PostgresData` has a variety of convenience methods for converting column data to usable Swift types.
161+
However, in most cases it is much easier to request a row's fields as a set of Swift types:
199162

200163
```swift
201-
let data: PostgresData= ...
164+
for try await (id, username, birthday) in rows.decode((Int, String, Date).self, context: .default) {
165+
// do something with the datatypes.
166+
}
167+
```
202168

203-
print(data.string) // String?
169+
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:
204170

205-
// Postgres only supports signed Ints.
206-
print(data.int) // Int?
207-
print(data.int16) // Int16?
208-
print(data.int32) // Int32?
209-
print(data.int64) // Int64?
171+
- `Bool`
172+
- `Bytes`, `Data`, `ByteBuffer`
173+
- `Date`
174+
- `UInt8`, `Int16`, `Int32`, `Int64`, `Int`
175+
- `Float`, `Double`
176+
- `String`
177+
- `UUID`
210178

211-
// 'char' can be interpreted as a UInt8.
212-
// It will show in db as a character though.
213-
print(data.uint8) // UInt8?
179+
#### Querying with parameters
214180

215-
print(data.bool) // Bool?
181+
Sending parameterized queries to the database is also supported (in the coolest way possible):
216182

217-
print(try data.jsonb(as: Foo.self)) // Foo?
183+
```swift
184+
let id = 1
185+
let username = "fancyuser"
186+
let birthday = Date()
187+
try await connection.query("""
188+
INSERT INTO users (id, username, birthday) VALUES (\(id), \(username), \(birthday))
189+
""",
190+
logger: logger
191+
)
192+
```
218193

219-
print(data.float) // Float?
220-
print(data.double) // Double?
194+
While this looks at first glance like a classic case of [SQL injection](https://en.wikipedia.org/wiki/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.
221195

222-
print(data.date) // Date?
223-
print(data.uuid) // UUID?
196+
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.
224197

225-
print(data.numeric) // PostgresNumeric?
226-
```
198+
## Security
199+
200+
Please see [SECURITY.md](https://github.com/vapor/.github/blob/main/SECURITY.md) for details on the security process.
227201

228-
`PostgresData` is also used for sending data _to_ the server via parameterized values. To create `PostgresData` from a Swift type, use the available intializer methods.
202+
[EventLoopGroupConnectionPool]: https://github.com/vapor/async-kit/blob/main/Sources/AsyncKit/ConnectionPool/EventLoopGroupConnectionPool.swift
203+
[AsyncKit]: https://github.com/vapor/async-kit/
204+
[SwiftNIO]: https://github.com/apple/swift-nio
205+
[SwiftLog]: https://github.com/apple/swift-log

0 commit comments

Comments
 (0)