async postgresql client integrated with xitca-web. Inspired and depend on rust-postgres
Compare to tokio-postgres
- Pros
- async/await native
- less heap allocation on query
- zero copy row data parsing
- quic transport layer for lossy database connection
- Cons
- no built in back pressure mechanism. possible to cause excessive memory usage if database requests are unbounded or not rate limited
- expose lifetime in public type params.(hard to return from function or contained in new types)
Features
-
Pipelining:
- offer both "implicit" and explicit API.
- support for more relaxed pipeline.
-
SSL/TLS support:
- powered by
rustls
- QUIC transport layer: offer transparent QUIC transport layer and proxy for lossy remote database connection
-
Connection Pool:
- built in connection pool with pipelining support enabled
Quick Start
use std::future::IntoFuture;
use xitca_postgres::{iter::AsyncLendingIterator, types::Type, Execute, Postgres, Statement};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let (cli, drv) = Postgres::new("postgres://postgres:postgres@localhost:5432")
.connect()
.await?;
tokio::spawn(drv.into_future());
"CREATE TEMPORARY TABLE foo (id SERIAL, name TEXT);
INSERT INTO foo (name) VALUES ('alice'), ('bob'), ('charlie');"
.execute(&cli)
.await?;
let stmt = Statement::named("INSERT INTO foo (name) VALUES ($1)", &[Type::TEXT]).execute(&cli).await?;
let rows_affected = stmt.bind(["david"]).execute(&cli).await?;
assert_eq!(rows_affected, 1);
let stmt = Statement::named(
"SELECT id, name FROM foo WHERE id = $1 AND name = $2",
&[Type::INT4, Type::TEXT],
)
.execute(&cli)
.await?;
let mut stream = stmt.bind_dyn(&[&1i32, &"alice"]).query(&cli).await?;
let row = stream.try_next().await?.ok_or("no row found")?;
let id = row.get::<i32>(0); assert_eq!(id, 1);
let name = row.get::<&str>("name"); assert_eq!(name, "alice");
assert!(stream.try_next().await?.is_none());
let mut stream = "SELECT id, name FROM foo WHERE name = 'david'".query(&cli).await?;
let row = stream.try_next().await?.ok_or("no row found")?;
let id = row.get(0).ok_or("no id found")?;
assert_eq!(id, "4");
let name = row.get("name").ok_or("no name found")?;
assert_eq!(name, "david");
Ok(())
}
Synchronous API
xitca_postgres::Client
can run outside of tokio async runtime and using blocking API to interact with database
use xitca_postgres::{Client, Error, ExecuteBlocking};
fn query(client: &Client) -> Result<(), Error> {
"SELECT 1".execute_blocking(client)?;
let stream = "SELECT 1".query_blocking(client)?;
for item in stream {
let row = item?;
let one = row.get(0).expect("database must return 1");
assert_eq!(one, "1");
}
Ok(())
}
Zero Copy Row Parse
Row data in xitca-postgres
is stored as atomic reference counted byte buffer. enabling cheap slicing and smart
pointer based zero copy parsing
use std::ops::Range;
use xitca_io::bytes::{Bytes, BytesStr};
use xitca_postgres::{row::Row, types::Type, FromSqlExt};
fn parse(row: Row<'_>) {
let bytes: Bytes = row.get_zc("foo");
let str: BytesStr = row.get_zc("bar");
struct Baz;
impl<'a> FromSqlExt<'a> for Baz {
fn from_sql_nullable_ext(
ty: &Type,
(range, buf): (&Range<usize>, &'a Bytes)
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
Ok(Baz)
}
fn accepts(ty: &Type) -> bool {
true
}
}
let foo: Baz = row.get_zc("baz");
}