| Crates.io | pg_stream |
| lib.rs | pg_stream |
| version | 0.1.0 |
| created_at | 2025-10-26 23:19:50.649527+00 |
| updated_at | 2025-10-26 23:19:50.649527+00 |
| description | A low-level, zero-overhead Rust implementation of the Postgres wire protocol. |
| homepage | |
| repository | https://github.com/tneely/pg_stream |
| max_upload_size | |
| id | 1902006 |
| size | 219,624 |
A low-level, zero-overhead Rust implementation of the Postgres wire protocol.
pg_stream provides direct access to the Postgres frontend/backend protocol, giving you full control over connection management, query execution, and data transfer. Unlike higher-level database libraries, this crate focuses on protocol implementation without abstraction overhead.
use pg_proto::startup::{ConnectionBuilder, AuthenticationMode};
#[tokio::main]
async fn main() -> pg_stream::startup::Result<()> {
let stream = tokio::net::TcpStream::connect("localhost:5432").await?;
let (mut conn, startup) = ConnectionBuilder::new("postgres")
.database("mydb")
.auth(AuthenticationMode::Password("secret".into()))
.connect(stream)
.await?;
println!("Connected to server version: {}",
startup.parameters.get("server_version").unwrap());
// Execute a simple query
conn.put_query("SELECT version()")
.flush()
.await?;
// Read response
loop {
let frame = conn.read_frame().await?;
// Handle frame...
if matches!(frame.code, backend::MessageCode::READY_FOR_QUERY) {
break;
}
}
Ok(())
}
Supported authentication modes:
AuthenticationMode::Trust - No password requiredAuthenticationMode::Password(String) - Cleartext password authenticationOther authentication methods (SASL, MD5, Kerberos, etc.) are not yet implemented.
The crate provides full support for the extended query protocol with prepared statements:
// Parse a prepared statement
conn.put_parse("my_stmt", "SELECT $1::int + $2::int", &[
ParameterKind::Int4,
ParameterKind::Int4,
])
.flush()
.await?;
// Bind parameters and execute
conn.put_bind("", "my_stmt", &[
BindParameter::text("5"),
BindParameter::text("10"),
], ResultFormat::Text)
.put_execute("", None)
.put_sync()
.flush()
.await?;
Call Postgres functions directly via the protocol:
use pg_proto::messages::frontend::{FunctionArg, FormatCode};
// Call sqrt function (OID 1344)
conn.put_fn_call(
1344,
&[FunctionArg::text("9")],
FormatCode::Text
)
.flush()
.await?;
Note: Function OIDs are not guaranteed to be stable across Postgres versions or installations. Look them up dynamically via system catalogs for production use.
Connect with TLS using a custom upgrade function:
let stream = tokio::net::TcpStream::connect("localhost:5432").await.unwrap();
stream.set_nodelay(true).unwrap();
let (pg_stream, startup) = ConnectionBuilder::new("postgres")
.connect_with_tls(stream, async |s| {
let mut root_cert_store = tokio_rustls::rustls::RootCertStore::empty();
let cert_bytes = pem_to_der("/certs/ca.crt").await?;
root_cert_store.add(cert_bytes.into()).unwrap();
let config = tokio_rustls::rustls::ClientConfig::builder()
.with_root_certificates(root_cert_store)
.with_no_client_auth();
let connector = TlsConnector::from(Arc::new(config));
let server_name = "localhost".try_into().unwrap();
let stream = connector.connect(server_name, s).await?;
Ok(stream)
})
.await
.unwrap();
The crate supports all major frontend protocol messages:
put_query()put_parse() for prepared statementsput_bind() to bind parametersput_describe() for statement/portal metadataput_execute() to run a portalput_close() to deallocate resourcesput_flush() to send buffered messagesput_sync() to end an extended query sequenceput_fn_call() to invoke functionsThis crate is designed for scenarios where you need maximum control and minimum overhead:
bytes::BytesMutPgStream