Skip to main content

postgresql/
postgresql.rs

1//! An async PostgreSQL client with a macro-only, compile-time checked query API.
2//!
3//! # Features
4//!
5//! * **Compile-time checked queries** — [`query!`] and [`query_as!`] connect to
6//!   the database at build time (when `DATABASE_URL` is set) and verify the
7//!   statement, its parameters and its result shape against your Rust types.
8//! * **Async, tokio-only** — one background task per connection; no busy
9//!   polling and no async runtime dependency beyond tokio.
10//! * **Connection pooling** — bounded pool with idle/lifetime limits and
11//!   connections returned automatically on drop.
12//! * **Ergonomic row mapping** — `#[derive(FromRow)]`, tuples, and scalars.
13//! * **JSON structs** — `#[derive(Json)]` maps a `serde` type straight to a
14//!   `json` / `jsonb` column, with no `serde_json::Value` in between.
15//! * **Iterator array binding** — bind `UNNEST` / `= ANY` arrays from a lazy
16//!   iterator with no intermediate `Vec`.
17//! * **TLS** — rustls with the stdx `crypto_rustls` provider (TLS 1.3).
18//! * **COPY** — binary `COPY ... FROM STDIN` / `TO STDOUT` for bulk loads.
19//! * **Scripts** — `execute_script` runs multi-statement SQL (migrations, `SET`
20//!   scripts, `VACUUM`) via the simple query protocol.
21//! * **`LISTEN` / `NOTIFY`** — [`Connection::notifications`] streams
22//!   notifications from the server.
23//! * **Cancellation** — an abandoned query is drained in the background and,
24//!   if it is still running two seconds later, cancelled with a
25//!   `CancelRequest`.
26//! * **Zero-copy accessors** — [`Row::get_str`] / [`Row::get_bytes`] borrow
27//!   from the received frame.
28//! * **Transactions without lifetimes** — `Connection`, `Pool`, `PooledConnection`
29//!   and `Transaction` all implement [`Executor`], so generic code works with any
30//!   of them.
31//!
32//! # Quick start
33//!
34//! ```ignore
35//! use postgresql::{query, query_as, FromRow};
36//!
37//! #[derive(FromRow, Debug)]
38//! struct User {
39//!     id: i32,
40//!     name: String,
41//!     email: Option<String>,
42//! }
43//!
44//! # async fn run(pool: postgresql::Pool) -> Result<(), postgresql::Error> {
45//! # use postgresql::{query, query_as};
46//! // Compile-time checked when DATABASE_URL is set at build time.
47//! let users: Vec<User> = query_as!(User,
48//!     "SELECT id, name, email FROM users WHERE age >= $1",
49//!     18)
50//!     .fetch_all(&pool)
51//!     .await?;
52//!
53//! let count: i64 = query_as!(i64, "SELECT count(*)::int8 FROM users")
54//!     .fetch_one(&pool)
55//!     .await?;
56//!
57//! let affected: u64 = query!("UPDATE users SET active = true WHERE age > $1", 18)
58//!     .execute(&pool)
59//!     .await?;
60//! # Ok(())
61//! # }
62//! ```
63//!
64//! # Connecting
65//!
66//! ```no_run
67//! use postgresql::{Connection, Pool};
68//!
69//! # async fn run() -> Result<(), postgresql::Error> {
70//! let conn = Connection::connect("postgres://user:pass@localhost/app").await?;
71//! let pool = Pool::connect("postgres://user:pass@localhost/app?pool_size=16", None).await?;
72//! # let _ = (conn, pool);
73//! # Ok(())
74//! # }
75//! ```
76//!
77//! Only `postgres://` and `postgresql://` URLs are accepted. Supported query
78//! parameters are `sslmode`, `sslrootcert`, `connect_timeout` (seconds),
79//! `application_name`, `options`, and the pool settings `pool_size`,
80//! `min_connections`, `acquire_timeout`, `idle_timeout` and `max_lifetime`
81//! (seconds). `timezone` sets the IANA zone used for zone-less
82//! `timestamp`/`date`/`time` values (default UTC), and `statement_cache_size`
83//! bounds the prepared-statement cache (`0` disables it, for transaction-mode
84//! poolers). `max_message_len` bounds the largest backend message the client
85//! will buffer, in bytes (default 128 MiB); `max_scram_iterations` caps the
86//! SCRAM iteration count the server may request (default 2,000,000). Unknown
87//! parameters are rejected so typos surface immediately.
88//!
89//! `connect_timeout` bounds the **whole** connection attempt: the TCP connect,
90//! the TLS handshake and authentication. A server that accepts TCP and then
91//! stalls cannot hang `Connection::connect`.
92//!
93//! # TLS is required
94//!
95//! This client **always** connects over TLS; plaintext connections are not
96//! possible. `sslmode=disable`, `prefer` and `allow` are rejected with a
97//! configuration error. `sslmode` selects the verification level:
98//!
99//! | `sslmode` | Behaviour |
100//! | --- | --- |
101//! | `require` (default) | TLS, certificate **not** verified (like libpq's `require`) |
102//! | `verify-ca` | TLS, certificate chain verified, hostname **not** checked |
103//! | `verify-full` | TLS, chain and hostname verified |
104//!
105//! TLS uses rustls with the stdx `crypto_rustls` provider (TLS 1.3 only).
106//!
107//! # Transactions
108//!
109//! ```ignore
110//! # async fn run(pool: postgresql::Pool) -> Result<(), postgresql::Error> {
111//! use postgresql::query;
112//! let txn = pool.begin().await?;
113//! query!("INSERT INTO accounts (id, balance) VALUES ($1, $2)", 1i32, 100i64)
114//!     .execute(&txn)
115//!     .await?;
116//! txn.commit().await?;
117//!
118//! // Dropping without committing rolls back and returns the connection.
119//! # Ok(())
120//! # }
121//! ```
122//!
123//! # Generic code over any executor
124//!
125//! [`Executor`] is a sealed marker implemented by [`Connection`],
126//! [`PooledConnection`], [`Pool`] and [`Transaction`]. Queries are always run
127//! through the macros, so generic helpers look like this:
128//!
129//! ```ignore
130//! async fn count_users<E: postgresql::Executor>(db: &E, min_age: i32)
131//!     -> Result<i64, postgresql::Error>
132//! {
133//! # use postgresql::query_as;
134//!     query_as!(i64, "SELECT count(*)::int8 FROM users WHERE age >= $1", min_age)
135//!         .fetch_one(db)
136//!         .await
137//! }
138//! ```
139//!
140//! # Iterator array binding (`UNNEST` / `= ANY`)
141//!
142//! [`array()`](crate::array) wraps a lazy iterator as a PostgreSQL array without allocating an
143//! intermediate `Vec`:
144//!
145//! ```ignore
146//! # async fn run(db: impl postgresql::Executor, invitations: Vec<(i32, String)>) -> Result<(), postgresql::Error> {
147//! use postgresql::query;
148//! query!(
149//!     "INSERT INTO invitations (id, name)
150//!      SELECT * FROM unnest($1::int4[], $2::text[])",
151//!     postgresql::array(invitations.iter().map(|i| i.0)),
152//!     postgresql::array(invitations.iter().map(|i| i.1.as_str())),
153//! )
154//! .execute(&db)
155//! .await?;
156//! # Ok(())
157//! # }
158//! ```
159//!
160//! `Vec<T>` and `&[T]` bind as arrays directly; `array(iter)` is for lazy
161//! iterators and is single-use.
162//!
163//! # Bulk loads (`COPY`)
164//!
165//! [`copy_in!`] and [`copy_out!`] check the `COPY` statement against the target
166//! type at build time (by resolving the relation in the catalog) and stream
167//! binary rows. `#[derive(ToRow)]` encodes a struct's fields in declaration
168//! order.
169//!
170//! ```ignore
171//! # async fn run(conn: postgresql::Connection) -> Result<(), postgresql::Error> {
172//! use postgresql::copy_in;
173//! let copy = copy_in!(conn, (i32, String), "COPY users (id, name) FROM STDIN BINARY").await?;
174//! for i in 0..1_000_000i32 {
175//!     copy.write_row(&(i, format!("user-{i}"))).await?;
176//! }
177//! let copied = copy.finish().await?;
178//! # Ok(())
179//! # }
180//! ```
181//!
182//! Only `COPY ... FROM STDIN BINARY` and `COPY ... TO STDOUT BINARY` are
183//! supported.
184//!
185//! # Scripts, notifications and cancellation
186//!
187//! ```ignore
188//! use postgresql::{ScriptExecutor, query};
189//!
190//! # async fn run(conn: postgresql::Connection) -> Result<(), postgresql::Error> {
191//! // Multi-statement scripts (unchecked, parameterless).
192//! conn.execute_script("CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT)").await?;
193//!
194//! // LISTEN / NOTIFY.
195//! conn.execute_script("LISTEN jobs").await?;
196//! let mut notifications = conn.notifications();
197//! query!("NOTIFY jobs, 'go'").execute(&conn).await?;
198//! let n = notifications.recv().await.unwrap();
199//! # let _ = n;
200//!
201//! // Dropping a row stream drains the result and, if the query is still
202//! // running after a short delay, cancels it server-side.
203//! # Ok(())
204//! # }
205//! ```
206//!
207//! # Supported types
208//!
209//! | Rust type | PostgreSQL type |
210//! | --- | --- |
211//! | `bool` | `bool` |
212//! | `i16`, `i32`, `i64` | `int2`, `int4`, `int8` |
213//! | `f32`, `f64` | `float4`, `float8` |
214//! | `String` / `&str` | `text`, `varchar`, `bpchar`, `name` |
215//! | `Vec<u8>` / `&[u8]` | `bytea` |
216//! | `uuid::Uuid` | `uuid` |
217//! | `time::DateTime` | `timestamptz`, `timestamp`, `date`, `time`, `timetz` |
218//! | `serde_json::Value` | `json`, `jsonb` |
219//! | `#[derive(Json)]` types, [`struct@Json<T>`] | `json`, `jsonb` |
220//! | `ipnetwork::IpNetwork` | `inet`, `cidr` |
221//! | [`Interval`] | `interval` |
222//! | `Option<T>` | `NULL` if `None` |
223//! | `Vec<T>`, `&[T]`, [`Array`] | arrays |
224//!
225//! `numeric` / `decimal` is **not supported yet**. Cast it in SQL to a
226//! supported type (`amount::text` for exact values, `amount::float8` for
227//! approximate ones) or store it as `int8` / `f64`.
228//!
229//! Decoding a column into a Rust type it cannot represent (say an `int4`
230//! column into `bool`) is an error, not a reinterpretation of the bytes.
231//!
232//! # Limits and unsupported values
233//!
234//! * Arrays must be **one-dimensional**; a multi-dimensional array is a decode
235//!   error.
236//! * PostgreSQL's `infinity` / `-infinity` `date` and `timestamp` values are
237//!   rejected with a decode error: they are not a real instant and cannot be
238//!   represented as a [`time::DateTime`].
239//! * Temporal values are bounded by `time::DateTime`'s range, **years −9999 to
240//!   9999**, even though PostgreSQL can represent dates from 4713 BC to 294276
241//!   AD. A value outside that range is a decode error. `time` and `timetz`
242//!   values must fall within a single day (`24:00:00` is rejected).
243//! * `time` and `timetz` are **decode-only**. They decode to a
244//!   `time::DateTime` whose civil fields are the time of day (on the Unix
245//!   epoch day); there is no encoder for them (use `Timestamp` / `Date` to
246//!   write temporal values).
247//! * A single backend message, and a single binary `COPY` row, is bounded by
248//!   `max_message_len` (128 MiB by default). A larger message or row is a
249//!   decode error rather than unbounded buffering.
250//! * `COPY ... FROM STDIN` and `COPY ... TO STDOUT` can only be run through
251//!   [`copy_in!`](crate::copy_in) / [`copy_out!`](crate::copy_out). Running one
252//!   through `query!` / `query_as!` or `execute_script` is an error; the client
253//!   leaves COPY mode and keeps the connection usable.
254//! * SCRAM authentication is capped at `max_scram_iterations` iterations
255//!   (2,000,000 by default); the server's PBKDF2 work runs on the blocking
256//!   pool, so it never stalls an async worker. The password is prepared with
257//!   SASLprep before key derivation.
258//! * Not yet supported: SCRAM channel binding (`SCRAM-SHA-256-PLUS`),
259//!   multi-host connection URLs, the `numeric` / `decimal` type, and streaming
260//!   replication (`CopyBothResponse`).
261//! * [`Connection::notifications`] delivers notifications through a bounded
262//!   broadcast channel; a subscriber that falls behind misses the oldest
263//!   notifications instead of buffering them without bound.
264//!
265//! The examples below use a fictional schema, so they are marked `ignore`
266//! (the `query!` macro checks the SQL against a live database at build time).
267//! The same code paths are exercised by the crate's integration tests.
268//!
269//! # JSON
270//!
271//! The preferred way to map a Rust type to a `json` / `jsonb` column is
272//! `#[derive(Json)]`, together with serde's `Serialize` and `Deserialize`:
273//!
274//! ```ignore
275//! use postgresql::{FromRow, Json, query, query_as};
276//!
277//! #[derive(serde::Serialize, serde::Deserialize, Json, Debug)]
278//! struct User {
279//!     name: String,
280//!     admin: bool,
281//! }
282//!
283//! #[derive(FromRow, Debug)]
284//! struct Something {
285//!     id: i32,
286//!     name: String,
287//!     user: User,
288//! }
289//!
290//! # async fn run(pool: postgresql::Pool, something: Something) -> Result<(), postgresql::Error> {
291//! let rows: Vec<Something> = query_as!(Something, "SELECT id, name, user FROM something")
292//!     .fetch_all(&pool)
293//!     .await?;
294//! query!("UPDATE something SET user = $1", something.user)
295//!     .execute(&pool)
296//!     .await?;
297//! # let _ = rows;
298//! # Ok(())
299//! # }
300//! ```
301//!
302//! `#[derive(Json)]` makes the type usable directly as a field, a parameter, a
303//! tuple element, or a whole result column, and its `jsonb` type is checked at
304//! compile time like any other. `Option<T>` gives a nullable column and
305//! `Vec<T>` a `jsonb[]`.
306//!
307//! A fully automatic mapping for any `Serialize` / `Deserialize` type is not
308//! possible: a blanket `impl<T: DeserializeOwned> FromSql for T` would overlap
309//! the impls for `i32`, `String` and every other built-in type, so the mapping
310//! has to be opted into. Two forms are provided:
311//!
312//! * `#[derive(Json)]` — preferred; see above.
313//! * [`Json<T>`] — for cases the derive cannot cover: a type that
314//!   already derives [`FromRow`] (deriving `Json` too would define `FromRow`
315//!   and [`RowShape`] twice), or a value with no nameable type, such as
316//!   `HashMap<String, i32>`:
317//!
318//! ```ignore
319//! # use std::collections::HashMap;
320//! use postgresql::{FromRow, Json, query};
321//!
322//! #[derive(FromRow)]
323//! struct Something {
324//!     id: i32,
325//!     attributes: Json<HashMap<String, i32>>,
326//! }
327//!
328//! # async fn run(pool: postgresql::Pool, something: Something) -> Result<(), postgresql::Error> {
329//! query!("UPDATE something SET attributes = $1", something.attributes)
330//!     .execute(&pool)
331//!     .await?;
332//! # Ok(())
333//! # }
334//! ```
335//!
336//! Both forms encode as `jsonb` (with the usual version byte) and accept both
337//! `json` and `jsonb` columns on decode. Writing to an actual `json` (not
338//! `jsonb`) column needs the same SQL cast as `serde_json::Value`
339//! (`$1::jsonb::json`).
340//!
341//! # Compile-time checking
342//!
343//! When `DATABASE_URL` is set at build time and `STDX_POSTGRESQL_CHECK` is not
344//! `false`, the query and COPY macros connect to the database and check the
345//! statement. `query!` / `query_as!` verify the SQL, the number and types of
346//! parameters, and the result columns (names, types, nullability) against the
347//! target type. `copy_in!` / `copy_out!` verify the COPY column list against
348//! the target type by resolving the relation in `pg_catalog`. A mismatch is a
349//! compile error. When the SQL is not a string literal, or checking is
350//! disabled, the code compiles and is checked at runtime instead.
351//!
352//! # Authentication
353//!
354//! SCRAM-SHA-256 is used for password authentication; MD5 is rejected. The
355//! server's SCRAM challenge is validated (bounded iteration count and salt,
356//! nonce, server signature), so a hostile endpoint cannot make the client do
357//! unbounded work during login.
358
359#![forbid(unsafe_code)]
360#![warn(missing_docs)]
361
362extern crate self as postgresql;
363
364mod array;
365mod config;
366mod connection;
367mod copy;
368mod decode;
369mod encode;
370mod error;
371mod interval;
372mod json;
373mod pool;
374mod query;
375mod row;
376mod shape;
377mod stream;
378mod transaction;
379pub mod types;
380
381pub use array::{Array, array};
382pub use config::{Config, PoolConfig, SslMode};
383pub use connection::{CancelToken, Connection, Notification, RowStream, encode_copy_row};
384pub use copy::{CopyIn, CopyOut, FromCopyRow, ToRow};
385pub use decode::FromSql;
386pub use encode::{Date, IsNull, StaticType, Timestamp, ToSql};
387pub use error::Error;
388pub use interval::Interval;
389pub use json::Json;
390pub use pool::{Pool, PooledConnection};
391pub use postgresql_derive::{FromRow, Json, ToRow};
392pub use postgresql_macros::{copy_in, copy_out, query, query_as};
393pub use query::{Executor, Query, QueryAs, ScriptExecutor};
394pub use row::{Column, Columns, FromRow, Row};
395pub use shape::{ColumnSpec, RowShape};
396pub use transaction::Transaction;
397
398/// Items used by macro expansions. Not part of the stable public API.
399#[doc(hidden)]
400pub mod __private {
401    pub use bytes::{Bytes, BytesMut};
402    pub use serde;
403
404    use crate::{
405        connection::Connection,
406        copy::{CopyIn, CopyOut, FromCopyRow, ToRow},
407        encode::{StaticType, ToSql},
408        error::Error,
409        types::Oid,
410    };
411    pub use crate::{
412        decode::{json_accepts, json_from_sql},
413        encode::json_encode,
414        shape::verify_shape,
415        types::JSONBOID,
416    };
417
418    /// Starts a typed binary `COPY ... FROM STDIN` (used by `copy_in!`).
419    pub async fn copy_in<T: ToRow>(conn: &Connection, sql: &str) -> Result<CopyIn<T>, Error> {
420        conn.copy_in(sql).await.map(CopyIn::new)
421    }
422
423    /// Starts a typed binary `COPY ... TO STDOUT` (used by `copy_out!`).
424    pub async fn copy_out<T: FromCopyRow>(conn: &Connection, sql: &str, oids: &[Oid]) -> Result<CopyOut<T>, Error> {
425        let max_row_len = conn.max_message_len();
426        conn.copy_out(sql)
427            .await
428            .map(|stream| CopyOut::new(stream, oids.to_vec(), max_row_len))
429    }
430
431    /// Compile-time check that a parameter's Rust type matches the OID the
432    /// server inferred for the placeholder.
433    ///
434    /// Emitted once per macro argument; a mismatch is a compile error.
435    pub fn check_param<const EXPECTED: Oid, T: StaticType>(_value: &T) {
436        const {
437            assert!(<T as StaticType>::OID == EXPECTED, "parameter type does not match the query");
438        }
439    }
440
441    /// Borrows a parameter, checking at compile time that its Rust type matches
442    /// the server-inferred OID, and returns a reference suitable for binding.
443    pub fn param<const EXPECTED: Oid, T: ToSql + StaticType>(value: &T) -> &T {
444        const {
445            assert!(<T as StaticType>::OID == EXPECTED, "parameter type does not match the query");
446        }
447        value
448    }
449
450    /// Benchmark helper: builds shared column metadata for `oids`. Not part of
451    /// the stable API and not for use outside benchmarks.
452    pub fn bench_columns(oids: &[Oid]) -> std::sync::Arc<crate::row::Columns> {
453        let fields = oids
454            .iter()
455            .map(|&type_oid| postgresql_protocol::backend::FieldDescription {
456                name: String::new(),
457                table_oid: 0,
458                column_attr: 0,
459                type_oid,
460                type_size: -1,
461                type_mod: -1,
462                format: 1,
463            })
464            .collect::<Vec<_>>();
465        crate::row::Columns::from_fields(&fields)
466    }
467
468    /// Benchmark helper: parses a raw `DataRow` payload. Not part of the stable
469    /// API and not for use outside benchmarks.
470    pub fn bench_parse_row(columns: std::sync::Arc<crate::row::Columns>, payload: &Bytes) -> Result<crate::Row, Error> {
471        crate::row::Row::parse(columns, payload, time::TimeZone::UTC)
472    }
473}