Skip to content
Β 
Β 

Latest commit

Β 

History

467 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

PostgresNIO

Documentation MIT License Continuous Integration Swift 6.0+ SSWG Incubation Level: Graduated

🐘 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

API Docs

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

Getting started

Interested in an example? We prepared a simple Birthday example in the Snippets folder.

Adding the dependency

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"),
    ])
  ]

Creating a client

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()
}

Querying

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 PostgresRows. The rows can be iterated one-by-one:

for try await row in rows {
  // do something with the row
}

Decoding from PostgresRow

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

Querying with parameters

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.

Tracing

PostgresNIO can emit distributed tracing spans for database operations, but tracing is disabled by default.

After bootstrapping your tracer, opt in through either PostgresClient.Configuration.Options or PostgresConnection.Configuration.Options:

import PostgresNIO
import Tracing

InstrumentationSystem.bootstrap(MyTracer())

var config = PostgresClient.Configuration(
  host: "localhost",
  port: 5432,
  username: "my_username",
  password: "my_password",
  database: "my_database",
  tls: .disable
)

config.options.tracing.isEnabled = true

By default, PostgresNIO uses the bootstrapped global tracer when tracing is enabled. You can override that for tests or specialized setups:

config.options.tracing.tracer = myTracer

db.query.text uses a safe default and is only attached when PostgresNIO can treat the SQL as parameterized or when PostgresNIO generated the SQL itself and can provide a sanitized tracing form. Raw non-parameterized user SQL is omitted by default. If you want raw SQL text on every span, opt in explicitly:

config.options.tracing.queryTextPolicy = .recordAll

By default, statement metadata only uses exact low-cardinality information for operations whose semantics are already known to PostgresNIO, such as explicit prepare, explicit deallocate, and copyFrom. Generic query execution typically falls back to a target-based span name such as the database namespace.

If you want SQL verb grouping like SELECT, INSERT, or UPDATE on generic query spans for backends such as Datadog, opt in explicitly:

config.options.tracing.statementMetadataPolicy = .inferred

If you do not want optional statement metadata at all, disable it:

config.options.tracing.statementMetadataPolicy = .disabled

Tracing errors keep a privacy-preserving default description. If you want PostgresNIO to attach the primary server error message to failed spans, opt in explicitly:

config.options.tracing.errorDetailsPolicy = .message

If you want the full debug description on failed spans, including enriched query context and source locations when available, opt in explicitly:

config.options.tracing.errorDetailsPolicy = .debugDescription

Use .debugDescription only when that additional visibility is acceptable for your deployment and data handling requirements.

The first tracing release instruments query, prepared statement execution, explicit prepare, explicit deallocate, copyFrom, and withTransaction. It does not currently emit spans for connection establishment, authentication, pool maintenance, or LISTEN / NOTIFY.

Security

Please see SECURITY.md for details on the security process.

About

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

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages