|
1 | 1 | # NIOPostgres |
2 | 2 |
|
3 | | -🐘 Non-blocking, event-driven Swift client for PostgreSQL. |
| 3 | +🐘 Non-blocking, event-driven Swift client for PostgreSQL built on [SwiftNIO](https://github.com/apple/swift-nio). |
| 4 | + |
| 5 | +### Major Releases |
| 6 | + |
| 7 | +The table below shows a list of NIOPostgres major releases alongside their compatible NIO and Swift versions. |
| 8 | + |
| 9 | +Version | NIO | Swift | SPM |
| 10 | +--- | --- | --- | --- |
| 11 | +1.0 (alpha) | 2.0+ | 5.0+ | `from: "1.0.0-alpha"` |
| 12 | + |
| 13 | +Use the SPM string to easily include the dependendency in your `Package.swift` file. |
| 14 | + |
| 15 | +```swift |
| 16 | +.package(url: "https://github.com/vapor/nio-postgres.git", from: ...) |
| 17 | +``` |
| 18 | + |
| 19 | +### Supported Platforms |
| 20 | + |
| 21 | +NIOPostgres supports the following platforms: |
| 22 | + |
| 23 | +- Ubuntu 14.04+ |
| 24 | +- macOS 10.12+ |
| 25 | + |
| 26 | +## Overview |
| 27 | + |
| 28 | +NIOPostgres 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. |
| 29 | + |
| 30 | +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. |
| 31 | + |
| 32 | +### Motiviation |
| 33 | + |
| 34 | +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. |
| 35 | + |
| 36 | +### Goals |
| 37 | + |
| 38 | +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 NIOPostgres as a foundation for interacting with PostgreSQL servers without needing to duplicate complex logic. |
| 39 | + |
| 40 | +Because of this, NIOPostgres excludes some important concepts for the sake of simplicity, such as: |
| 41 | + |
| 42 | +- Connection pooling |
| 43 | +- Swift `Codable` integration |
| 44 | +- Query building |
| 45 | + |
| 46 | +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 NIOPostgres: |
| 47 | + |
| 48 | +- [`vapor/postgres-kit`](https://github.com/vapor/postgresql) |
| 49 | + |
| 50 | +### Dependencies |
| 51 | + |
| 52 | +This package has four dependencies: |
| 53 | + |
| 54 | +- [`apple/swift-nio`](https://github.com/apple/swift-nio) for IO |
| 55 | +- [`apple/swift-nio-ssl`](https://github.com/apple/swift-nio-ssl) for TLS |
| 56 | +- [`apple/swift-log`](https://github.com/apple/swift-logs) for logging |
| 57 | +- [`apple/swift-metrics`](https://github.com/apple/swift-metrics) for metrics |
| 58 | + |
| 59 | +This package has no additional system dependencies. |
| 60 | + |
| 61 | +## API Docs |
| 62 | + |
| 63 | +Check out the [NIOPostgres API docs]((https://api.vapor.codes/nio-postgres/master/NIOPostgres/index.html)) for a detailed look at all of the classes, structs, protocols, and more. |
| 64 | + |
| 65 | +## Getting Started |
| 66 | + |
| 67 | +This section will provide a quick look at using NIOPostgres. |
| 68 | + |
| 69 | +### Creating a Connection |
| 70 | + |
| 71 | +The first step to making a query is creating a new `PostgresConnection`. The minimum requirements to create one are a `SocketAddress` and `EventLoop`. |
| 72 | + |
| 73 | +```swift |
| 74 | +import NIOPostgres |
| 75 | + |
| 76 | +let eventLoop: EventLoop = ... |
| 77 | +let conn = try PostgresConnection.connect( |
| 78 | + to: .makeAddressResolvingHost("my.psql.server", port: 5432), |
| 79 | + on: eventLoop |
| 80 | +).wait() |
| 81 | +``` |
| 82 | + |
| 83 | +Note: These examples will make use of `wait()` for simplicity. This is appropriate if you are using NIOPostgres on the main thread, like for a CLI tool or in tests. However, you should never use `wait()` on an event loop. |
| 84 | + |
| 85 | +There are a few ways to create a `SocketAddress`: |
| 86 | + |
| 87 | +- `init(ipAddress: String, port: Int)` |
| 88 | +- `init(unixDomainSocketPath: String)` |
| 89 | +- `makeAddressResolvingHost(_ host: String, port: Int)` |
| 90 | + |
| 91 | +There are also some additional arguments you can supply to `connect`. |
| 92 | + |
| 93 | +- `tlsConfiguration` An optional `TLSConfiguration` struct. If supplied, the PostgreSQL connection will be upgraded to use SSL. |
| 94 | +- `serverHostname` An optional `String` to use in conjunction with `tlsConfiguration` to specify the server's hostname. |
| 95 | + |
| 96 | +`connect` will return a future `PostgresConnection`, or an error if it could not connect. |
| 97 | + |
| 98 | +### Client Protocol |
| 99 | + |
| 100 | +Interaction with a server revolves around the `PostgresClient` protocol. This protocol includes methods like `query(_:)` for executing SQL queries and reading the resulting rows. |
| 101 | + |
| 102 | +`PostgresConnection` is the default implementation of `PostgresClient` provided by this package. Assume the client here is the connection from the previous example. |
| 103 | + |
| 104 | +```swift |
| 105 | +import NIOPostgres |
| 106 | + |
| 107 | +let client: PostgresClient = ... |
| 108 | +// now we can use client to do queries |
| 109 | +``` |
| 110 | + |
| 111 | +### Simple Query |
| 112 | + |
| 113 | +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. |
| 114 | + |
| 115 | +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. |
| 116 | + |
| 117 | +`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. |
| 118 | + |
| 119 | +```swift |
| 120 | +let rows = try client.simpleQuery("SELECT version()").wait() |
| 121 | +print(rows) // [["version": "11.0.0"]] |
| 122 | + |
| 123 | +try client.simpleQuery("SELECT version()") { row in |
| 124 | + print(row) // ["version": "11.0.0"] |
| 125 | +}.wait() |
| 126 | +``` |
| 127 | + |
| 128 | +### Parameterized Query |
| 129 | + |
| 130 | +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`. |
| 131 | + |
| 132 | +These queries are most useful for selecting, inserting, and updating data. Data for these queries is transferred using the highly efficient binary format. |
| 133 | + |
| 134 | +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. |
| 135 | + |
| 136 | +```swift |
| 137 | +let rows = try client.query("SELECT * FROM planets WHERE name = $1", ["Earth"]).wait() |
| 138 | +print(rows) // [["id": 42, "name": "Earth"]] |
| 139 | + |
| 140 | +try client.query("SELECT * FROM planets WHERE name = $1", ["Earth"]) { row in |
| 141 | + print(row) // ["id": 42, "name": "Earth"] |
| 142 | +}.wait() |
| 143 | +``` |
| 144 | + |
| 145 | +### Rows and Data |
| 146 | + |
| 147 | +Both `simpleQuery` and `query` return the same `PostgresRow` type. Columns can be fetched from the row using the `column(_: String)` method. |
| 148 | + |
| 149 | +```swift |
| 150 | +let row: PostgresRow = ... |
| 151 | +let version = row.column("version") |
| 152 | +print(version) // PostgresData? |
| 153 | +``` |
| 154 | + |
| 155 | +`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: |
| 156 | + |
| 157 | +- Postgres column type |
| 158 | +- Wire format: binary or text |
| 159 | +- Value as array of bytes |
| 160 | + |
| 161 | +`PostgresData` has a variety of convenience methods for converting column data to usable Swift types. |
| 162 | + |
| 163 | +```swift |
| 164 | +let data: PostgresData= ... |
| 165 | + |
| 166 | +print(data.string) // String? |
| 167 | + |
| 168 | +print(data.int) // Int? |
| 169 | +print(data.int8) // Int8? |
| 170 | +print(data.int16) // Int16? |
| 171 | +print(data.int32) // Int32? |
| 172 | +print(data.int64) // Int64? |
| 173 | + |
| 174 | +print(data.uint) // UInt? |
| 175 | +print(data.uint8) // UInt8? |
| 176 | +print(data.uint16) // UInt16? |
| 177 | +print(data.uint32) // UInt32? |
| 178 | +print(data.uint64) // UInt64? |
| 179 | + |
| 180 | +print(data.float) // Float? |
| 181 | +print(data.double) // Double? |
| 182 | + |
| 183 | +print(data.date) // Date? |
| 184 | +print(data.uuid) // UUID? |
| 185 | + |
| 186 | +print(data.numeric) // PostgresNumeric? |
| 187 | +``` |
| 188 | + |
| 189 | +`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. |
0 commit comments