Swiftpack.co - Package - vapor/postgres-nio
PostgresNIO
Documentation Team Chat MIT License Continuous Integration Swift 5.2

🐘 Non-blocking, event-driven Swift client for PostgreSQL built on SwiftNIO.

Major Releases

The table below shows a list of PostgresNIO major releases alongside their compatible NIO and Swift versions.

|Version|NIO|Swift|SPM| |-|-|-|-| |1.0|2.0+|5.2+|from: "1.0.0"|

Use the SPM string to easily include the dependendency in your Package.swift file.

.package(url: "https://github.com/vapor/postgres-nio.git", from: ...)

Supported Platforms

PostgresNIO supports the following platforms:

  • Ubuntu 16.04+
  • macOS 10.15+

Overview

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.

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.

Motivation

Most Swift implementations of Postgres clients are based on the libpq 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.

Goals

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.

Because of this, PostgresNIO excludes some important concepts for the sake of simplicity, such as:

  • Connection pooling
  • Swift Codable integration
  • Query building

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:

Dependencies

This package has four dependencies:

This package has no additional system dependencies.

API Docs

Check out the PostgresNIO API docs for a detailed look at all of the classes, structs, protocols, and more.

Getting Started

This section will provide a quick look at using PostgresNIO.

Creating a Connection

The first step to making a query is creating a new PostgresConnection. The minimum requirements to create one are a SocketAddress and EventLoop.

import PostgresNIO

let eventLoop: EventLoop = ...
let conn = try PostgresConnection.connect(
    to: .makeAddressResolvingHost("my.psql.server", port: 5432),
    on: eventLoop
).wait()

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.

There are a few ways to create a SocketAddress:

  • init(ipAddress: String, port: Int)
  • init(unixDomainSocketPath: String)
  • makeAddressResolvingHost(_ host: String, port: Int)

There are also some additional arguments you can supply to connect.

  • tlsConfiguration An optional TLSConfiguration struct. If supplied, the PostgreSQL connection will be upgraded to use SSL.
  • serverHostname An optional String to use in conjunction with tlsConfiguration to specify the server's hostname.

connect will return a future PostgresConnection, or an error if it could not connect.

Client Protocol

Interaction with a server revolves around the PostgresClient protocol. This protocol includes methods like query(_:) for executing SQL queries and reading the resulting rows.

PostgresConnection is the default implementation of PostgresClient provided by this package. Assume the client here is the connection from the previous example.

import PostgresNIO

let client: PostgresClient = ...
// now we can use client to do queries

Simple Query

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.

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.

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.

let rows = try client.simpleQuery("SELECT version()").wait()
print(rows) // [["version": "11.0.0"]]

try client.simpleQuery("SELECT version()") { row in
    print(row) // ["version": "11.0.0"]
}.wait()

Parameterized Query

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.

These queries are most useful for selecting, inserting, and updating data. Data for these queries is transferred using the highly efficient binary format.

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.

let rows = try client.query("SELECT * FROM planets WHERE name = $1", ["Earth"]).wait()
print(rows) // [["id": 42, "name": "Earth"]]

try client.query("SELECT * FROM planets WHERE name = $1", ["Earth"]) { row in
    print(row) // ["id": 42, "name": "Earth"]
}.wait()

Rows and Data

Both simpleQuery and query return the same PostgresRow type. Columns can be fetched from the row using the column(_: String) method.

let row: PostgresRow = ...
let version = row.column("version")
print(version) // PostgresData?

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:

  • Postgres column type
  • Wire format: binary or text
  • Value as array of bytes

PostgresData has a variety of convenience methods for converting column data to usable Swift types.

let data: PostgresData= ...

print(data.string) // String?

print(data.int) // Int?
print(data.int8) // Int8?
print(data.int16) // Int16?
print(data.int32) // Int32?
print(data.int64) // Int64?

print(data.uint) // UInt?
print(data.uint8) // UInt8?
print(data.uint16) // UInt16?
print(data.uint32) // UInt32?
print(data.uint64) // UInt64?

print(data.bool) // Bool?

print(try data.jsonb(as: Foo.self)) // Foo?

print(data.float) // Float?
print(data.double) // Double?

print(data.date) // Date?
print(data.uuid) // UUID?

print(data.numeric) // PostgresNumeric?

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.

Library development

If you want to contribute to the library development, here is how to get started.

Testing

To run the test, you need to start a local PostgreSQL database using Docker.

If you have Docker installed and running, you can use Docker Compose to start PostgreSQL:

The following command will download the required containers to run the test and start them:

$ docker-compose up -d psql-11

You can choose to run one of the following PostgreSQL version: psql-11, psql-10, psql-9, psql-ssl.

From another console or from Xcode, you can then run the test:

$ swift test

You can check that the test are passing, before adding your own to the test suite.

Finally, you can shut down and clean up Docker test environment with:

$ docker-compose down --volumes

Github

link
Stars: 70

Used By

Total: 0

Releases

RawRepresentable + PostgresData - 2020-04-24 20:47:11

This patch was authored and released by @tanner0101.

Add default PostgresDataConvertible conformance to RawRepresentable where the raw value is postgres convertible (#105, vapor/postgres-kit#179).

Too many binds error - 2020-04-24 17:53:08

This patch was authored and released by @tanner0101.

Throw an error if too many binds (>= Int16.max) are sent in a parameterized query (#103, fixes #102).

PostgresNIO 1.0.0 - 2020-04-22 14:05:04

Docs: https://github.com/vapor/postgres-nio/blob/master/README.md

More information on Vapor 4 official release: https://forums.swift.org/t/vapor-4-official-release-begins/34802

Add query metadata - 2020-04-03 17:07:37

This patch was authored and released by @tanner0101.

Adds new API for accessing query metadata via conn.query (fixes #93).

conn.query("...", onMetadata: { metadata in 
    print(metadata.rows) // Int?
}) { row in 
    print(row) // PostgresRow
}.wait()

This is a breaking change since conn.query now returns PostgresQueryResult instead of [PostgresRow]. However, PostgresQueryResult conforms to Collection so most code should be unaffected.

let result = try conn.query("...").wait()
for row in result {
    print(row) // PostgresRow
}
print(result.metadata) // PostgresQueryMetadata
print(result.rows) // [PostgresRow]

Update to Metrics 2.0 - 2020-03-06 17:50:59

This patch was authored and released by @tanner0101.

Updates to SwiftMetrics 2.0 which adds a new case the TimeUnit enum (#88, fixes #87)

Send terminate message on close - 2020-03-04 22:30:02

This patch was authored and released by @tanner0101.

A postgres terminate (X) message is now sent before the connection closes (#86, fixes #84).

Release Candidate 1 - 2020-02-28 19:57:34

Updates to Swift 5.2 and macOS 10.15. Replaces CMD5 module with SwiftCrypto.

Release candidates represent the final shift toward focusing on bug fixes and documentation. Breaking changes will only be accepted for critical issues. We expect a final release of this package shortly after Swift 5.2's release date.

Support Swift.Set - 2020-02-19 20:19:17

Adds support for converting Swift.Set to / from a native Postgres array.

Handle NAME Type in PropertyData.string Property - 2020-02-18 19:34:48

Allows data that is contained as the .name type to be accessed through the PostgresData.string property.

var buffer = ByteBufferAllocator().buffer(capacity: 13)
buffer.writeString("password_hash")

let data = PostgresData(.name, value: buffer)
let string = data.string

// string == "password_hash"

char(n) (BPCHAR) length fix - 2020-02-12 21:31:13

The char(n) (BPCHAR) data type now correctly supports values longer than one byte when converting to fixed-width integers or strings (fixes #71, fixes #79, #72).

Fix Numeric String Serialization - 2020-01-24 00:38:46

Fixes a bug causing NUMERIC string serialization to produce incorrect values when numbers have more than 4 zeroes before or after the decimal place. (#78, fixes #77)

add PostgresData+JSON support - 2020-01-22 21:53:45

Adds new methods and properties for using the JSON data type. These methods mirror the existing methods for JSONB.

User-defined Enum + Optional Fix - 2020-01-22 17:24:34

Bound optional values that are nil should no longer result in an unexpected type warning (fixes https://github.com/vapor/postgres-nio/issues/74)

PostgresData will now return strings correctly for user-defined types like enums.

Remove BPCHAR to Swift Integer conversion - 2019-12-26 17:18:27

Removes support for converting PostgresData containing BPCHAR bytes (CHARACTER(n), CHAR, etc) to Swift integers. Postgres CHAR(n) fields may be padded with extra zero-bytes which makes conversion tricky. Since Postgres intends CHARACTER fields to store string values, PostgresData should only support conversion to Swift strings. To store 8-bits natively, use Postgres "char" type (note the quotes).

PostgresNIO 1.0.0 Beta 2.3 - 2019-12-13 22:00:32

Publicize useful PostgresRow properties (#70)

PostgresNIO 1.0.0 Beta 2.2 - 2019-12-12 04:07:37

  • Added PostgresConnection.addListener(channel:handler:) (#60)

This method lets you add handlers for LISTEN / NOTIFY messages.

// add notification listener to connection
conn.addListener(channel: "example") { context, notification in
    print(notification) // notification payload
    context.stop() // stop listening
}
// subscribe to notifications on this connection
_ = try conn.simpleQuery("LISTEN example").wait()
// send notification w/ empty payload
_ = try conn.simpleQuery("NOTIFY example").wait()
  • Adds PostgresData(array:elementType:) and PostgresData.array methods for untyped arrays (#67)

  • UInt8 can now decode from CHAR / BPCHAR (#41)

  • Added a ByteToMessageDecoderVerifier test (#68)

PostgresNIO 1.0.0 Beta 2.1 - 2019-12-11 00:19:43

  • Fixes PostgresData(double:) byte serialization (#65)

PostgresNIO 1.0.0 Beta 2 - 2019-12-09 19:00:32

  • Added support for custom Logger passing. (#58)

  • query(...) now sends bind types allowing for Postgres to cast binds automatically. (#63)

  • Renamed PostgresClient to PostgresDatabase (#58)

  • Fixed a leaking promise (#57)

  • Enabled test discovery on Linux (#64)

PostgresNIO 1.0.0 Alpha 1.6 - 2019-10-18 19:39:50

  • A warning will now be printed to the logger if supplied binds do not match what the query is expecting. (#54, #52)

PostgresNIO 1.0.0 Alpha 1.5 - 2019-10-15 00:29:04

  • PostgresData now conforms to PostgresDataConvertible. (#51, #43)

PostgresNIO 1.0.0 Alpha 1.4 - 2019-08-12 20:32:46

New:

  • Improved PostgresData.description (also improves PostgresRow.description) (#49)

Fixed:

  • Fixed numerous uses of ByteBuffer "unsafe" APIs (#44, #45, #46, #47, #48)

PostgresNIO 1.0.0 Alpha 1.3 - 2019-06-25 22:01:54

New:

  • PostgresData(bytes:) and postgresData.bytes methods for going to / from [UInt8] (#38)
  • Foundation.Data now conforms to PostgresDataConvertible (#38)
  • PostgresData(jsonb:) and postgresData.jsonb() methods for going to / from JSON (#36)
  • New PostgresJSONBCodable protocol for allowing codable types to automatically serialize as JSONB (#36)

PostgresNIO 1.0.0 Alpha 1.2 - 2019-06-12 03:51:00

New:

  • Added Bool support to PostgresData. (#32, #33)

PostgresNIO 1.0.0 Alpha 1.1 - 2019-06-07 19:36:26

New:

  • Conforms UUID to PostgresDataConvertible
  • Conforms Optional to PostgresDataConvertible
  • Conforms PostgresData to CustomDebugStringConvertible

PostgresNIO 1.0.0 Alpha 1 - 2019-06-06 20:04:27

More information on Vapor 4 alpha releases:

https://medium.com/@codevapor/vapor-4-alpha-1-releases-begin-94a4bc79dd9a

API Docs:

https://api.vapor.codes/postgres-nio/master/PostgresNIO/index.html