diff --git a/postgres-macros/src/lib.rs b/postgres-macros/src/lib.rs index 905de29..dde84aa 100644 --- a/postgres-macros/src/lib.rs +++ b/postgres-macros/src/lib.rs @@ -19,7 +19,7 @@ pub fn derive_fromsql(input: TokenStream) -> TokenStream { .into() } -#[proc_macro_derive(Enum, attributes(postgres))] +#[proc_macro_derive(Enum)] pub fn derive_enum(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input); diff --git a/postgres-macros/tests/fail-nightly/enum_array_to_enum_literal.stderr b/postgres-macros/tests/fail-nightly/enum_array_to_enum_literal.stderr index 54b69ab..f05e547 100644 --- a/postgres-macros/tests/fail-nightly/enum_array_to_enum_literal.stderr +++ b/postgres-macros/tests/fail-nightly/enum_array_to_enum_literal.stderr @@ -1,20 +1,20 @@ -error[E0277]: the trait bound `Role: Query, EnumVariant<"user">)>>>>` is not satisfied +error[E0277]: the trait bound `Role: sqlm_postgres::query::Query, EnumVariant<"user">)>>>>` is not satisfied --> tests/fail-nightly/enum_array_to_enum_literal.rs:17:10 | 17 | .await | -^^^^^ | || - | |the trait `Query, EnumVariant<"user">)>>>>` is not implemented for `Role`, which is required by `Sql<'_, sqlm_postgres::types::Array, EnumVariant<"user">)>>>, _>: IntoFuture` + | |the trait `sqlm_postgres::query::Query, EnumVariant<"user">)>>>>` is not implemented for `Role`, which is required by `Sql<'_, sqlm_postgres::types::Array, EnumVariant<"user">)>>>, _>: IntoFuture` | help: remove the `.await` | - = help: the following other types implement trait `Query`: - <() as Query<()>> - as Query::Type>>> - as Query>> - > as Query>> - as Query::Type>>> - as Query>> - as Query::Type>>>> - > as Query>> + = help: the following other types implement trait `sqlm_postgres::query::Query`: + <() as sqlm_postgres::query::Query<()>> + as sqlm_postgres::query::Query::Type>>> + as sqlm_postgres::query::Query>> + > as sqlm_postgres::query::Query>> + as sqlm_postgres::query::Query::Type>>> + as sqlm_postgres::query::Query>> + as sqlm_postgres::query::Query::Type>>>> + > as sqlm_postgres::query::Query>> and $N others = note: required for `Sql<'_, sqlm_postgres::types::Array, EnumVariant<"user">)>>>, Role>` to implement `IntoFuture` diff --git a/postgres-macros/tests/fail-nightly/enum_extra_variant.stderr b/postgres-macros/tests/fail-nightly/enum_extra_variant.stderr index 39740db..fba906c 100644 --- a/postgres-macros/tests/fail-nightly/enum_extra_variant.stderr +++ b/postgres-macros/tests/fail-nightly/enum_extra_variant.stderr @@ -1,17 +1,17 @@ -error[E0277]: the trait bound `Vec: Query, StructColumn, EnumVariant<"user">)>, "role">)>>` is not satisfied +error[E0277]: the trait bound `Vec: sqlm_postgres::query::Query, StructColumn, EnumVariant<"user">)>, "role">)>>` is not satisfied --> tests/fail-nightly/enum_extra_variant.rs:26:59 | 26 | let _: Vec = sql!("SELECT id, role FROM users").await.unwrap(); | -^^^^^ | || - | |the trait `Query, StructColumn, EnumVariant<"user">)>, "role">)>>` is not implemented for `Vec`, which is required by `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<"user">)>, "role">)>, _>: IntoFuture` + | |the trait `sqlm_postgres::query::Query, StructColumn, EnumVariant<"user">)>, "role">)>>` is not implemented for `Vec`, which is required by `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<"user">)>, "role">)>, _>: IntoFuture` | help: remove the `.await` | - = help: the following other types implement trait `Query`: - as Query::Type>>> - as Query>> - as Query::Type>>>> - > as Query>> - > as Query>>> - as Query>> + = help: the following other types implement trait `sqlm_postgres::query::Query`: + as sqlm_postgres::query::Query::Type>>> + as sqlm_postgres::query::Query>> + as sqlm_postgres::query::Query::Type>>>> + > as sqlm_postgres::query::Query>> + > as sqlm_postgres::query::Query>>> + as sqlm_postgres::query::Query>> = note: required for `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<"user">)>, "role">)>, Vec>` to implement `IntoFuture` diff --git a/postgres-macros/tests/fail-nightly/enum_missing_variant.stderr b/postgres-macros/tests/fail-nightly/enum_missing_variant.stderr index d54a2bf..a743f67 100644 --- a/postgres-macros/tests/fail-nightly/enum_missing_variant.stderr +++ b/postgres-macros/tests/fail-nightly/enum_missing_variant.stderr @@ -1,17 +1,17 @@ -error[E0277]: the trait bound `Vec: Query, StructColumn, EnumVariant<"user">)>, "role">)>>` is not satisfied +error[E0277]: the trait bound `Vec: sqlm_postgres::query::Query, StructColumn, EnumVariant<"user">)>, "role">)>>` is not satisfied --> tests/fail-nightly/enum_missing_variant.rs:20:59 | 20 | let _: Vec = sql!("SELECT id, role FROM users").await.unwrap(); | -^^^^^ | || - | |the trait `Query, StructColumn, EnumVariant<"user">)>, "role">)>>` is not implemented for `Vec`, which is required by `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<"user">)>, "role">)>, _>: IntoFuture` + | |the trait `sqlm_postgres::query::Query, StructColumn, EnumVariant<"user">)>, "role">)>>` is not implemented for `Vec`, which is required by `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<"user">)>, "role">)>, _>: IntoFuture` | help: remove the `.await` | - = help: the following other types implement trait `Query`: - as Query::Type>>> - as Query>> - as Query::Type>>>> - > as Query>> - > as Query>>> - as Query>> + = help: the following other types implement trait `sqlm_postgres::query::Query`: + as sqlm_postgres::query::Query::Type>>> + as sqlm_postgres::query::Query>> + as sqlm_postgres::query::Query::Type>>>> + > as sqlm_postgres::query::Query>> + > as sqlm_postgres::query::Query>>> + as sqlm_postgres::query::Query>> = note: required for `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<"user">)>, "role">)>, Vec>` to implement `IntoFuture` diff --git a/postgres-macros/tests/fail-nightly/enum_variant_mismatch.stderr b/postgres-macros/tests/fail-nightly/enum_variant_mismatch.stderr index 817c4a1..e0fa1ff 100644 --- a/postgres-macros/tests/fail-nightly/enum_variant_mismatch.stderr +++ b/postgres-macros/tests/fail-nightly/enum_variant_mismatch.stderr @@ -1,17 +1,17 @@ -error[E0277]: the trait bound `Vec: Query, StructColumn, EnumVariant<"user">)>, "role">)>>` is not satisfied +error[E0277]: the trait bound `Vec: sqlm_postgres::query::Query, StructColumn, EnumVariant<"user">)>, "role">)>>` is not satisfied --> tests/fail-nightly/enum_variant_mismatch.rs:23:59 | 23 | let _: Vec = sql!("SELECT id, role FROM users").await.unwrap(); | -^^^^^ | || - | |the trait `Query, StructColumn, EnumVariant<"user">)>, "role">)>>` is not implemented for `Vec`, which is required by `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<"user">)>, "role">)>, _>: IntoFuture` + | |the trait `sqlm_postgres::query::Query, StructColumn, EnumVariant<"user">)>, "role">)>>` is not implemented for `Vec`, which is required by `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<"user">)>, "role">)>, _>: IntoFuture` | help: remove the `.await` | - = help: the following other types implement trait `Query`: - as Query::Type>>> - as Query>> - as Query::Type>>>> - > as Query>> - > as Query>>> - as Query>> + = help: the following other types implement trait `sqlm_postgres::query::Query`: + as sqlm_postgres::query::Query::Type>>> + as sqlm_postgres::query::Query>> + as sqlm_postgres::query::Query::Type>>>> + > as sqlm_postgres::query::Query>> + > as sqlm_postgres::query::Query>>> + as sqlm_postgres::query::Query>> = note: required for `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<"user">)>, "role">)>, Vec>` to implement `IntoFuture` diff --git a/postgres-macros/tests/fail-nightly/literal_array_to_literal.stderr b/postgres-macros/tests/fail-nightly/literal_array_to_literal.stderr index 5b2a77d..67ae8bc 100644 --- a/postgres-macros/tests/fail-nightly/literal_array_to_literal.stderr +++ b/postgres-macros/tests/fail-nightly/literal_array_to_literal.stderr @@ -1,20 +1,20 @@ -error[E0277]: the trait bound `String: Query>>` is not satisfied +error[E0277]: the trait bound `String: sqlm_postgres::query::Query>>` is not satisfied --> tests/fail-nightly/literal_array_to_literal.rs:6:10 | 6 | .await | -^^^^^ | || - | |the trait `Query>>` is not implemented for `String`, which is required by `Sql<'_, sqlm_postgres::types::Array>, _>: IntoFuture` + | |the trait `sqlm_postgres::query::Query>>` is not implemented for `String`, which is required by `Sql<'_, sqlm_postgres::types::Array>, _>: IntoFuture` | help: remove the `.await` | - = help: the following other types implement trait `Query`: - <() as Query<()>> - as Query::Type>>> - as Query>> - > as Query>> - as Query::Type>>> - as Query>> - as Query::Type>>>> - > as Query>> + = help: the following other types implement trait `sqlm_postgres::query::Query`: + <() as sqlm_postgres::query::Query<()>> + as sqlm_postgres::query::Query::Type>>> + as sqlm_postgres::query::Query>> + > as sqlm_postgres::query::Query>> + as sqlm_postgres::query::Query::Type>>> + as sqlm_postgres::query::Query>> + as sqlm_postgres::query::Query::Type>>>> + > as sqlm_postgres::query::Query>> and $N others = note: required for `Sql<'_, sqlm_postgres::types::Array>, String>` to implement `IntoFuture` diff --git a/postgres-macros/tests/fail-stable/enum_array_to_enum_literal.stderr b/postgres-macros/tests/fail-stable/enum_array_to_enum_literal.stderr index 7c78aaf..0675c44 100644 --- a/postgres-macros/tests/fail-stable/enum_array_to_enum_literal.stderr +++ b/postgres-macros/tests/fail-stable/enum_array_to_enum_literal.stderr @@ -1,20 +1,20 @@ -error[E0277]: the trait bound `Role: Query, EnumVariant<10465144470622129318>)>>>>` is not satisfied +error[E0277]: the trait bound `Role: sqlm_postgres::query::Query, EnumVariant<10465144470622129318>)>>>>` is not satisfied --> tests/fail-stable/enum_array_to_enum_literal.rs:17:10 | 17 | .await | -^^^^^ | || - | |the trait `Query, EnumVariant<10465144470622129318>)>>>>` is not implemented for `Role`, which is required by `Sql<'_, sqlm_postgres::types::Array, EnumVariant<10465144470622129318>)>>>, _>: IntoFuture` + | |the trait `sqlm_postgres::query::Query, EnumVariant<10465144470622129318>)>>>>` is not implemented for `Role`, which is required by `Sql<'_, sqlm_postgres::types::Array, EnumVariant<10465144470622129318>)>>>, _>: IntoFuture` | help: remove the `.await` | - = help: the following other types implement trait `Query`: - as Query>> - > as Query>> - > as Query>>> - as Query>> - as Query::Type>>> - as Query::Type>>>> - > as Query>> - as Query>> + = help: the following other types implement trait `sqlm_postgres::query::Query`: + as sqlm_postgres::query::Query>> + > as sqlm_postgres::query::Query>> + > as sqlm_postgres::query::Query>>> + as sqlm_postgres::query::Query>> + as sqlm_postgres::query::Query::Type>>> + as sqlm_postgres::query::Query::Type>>>> + > as sqlm_postgres::query::Query>> + as sqlm_postgres::query::Query>> and $N others = note: required for `Sql<'_, sqlm_postgres::types::Array, EnumVariant<10465144470622129318>)>>>, Role>` to implement `IntoFuture` diff --git a/postgres-macros/tests/fail-stable/enum_extra_variant.stderr b/postgres-macros/tests/fail-stable/enum_extra_variant.stderr index edd8d5a..a3dcd81 100644 --- a/postgres-macros/tests/fail-stable/enum_extra_variant.stderr +++ b/postgres-macros/tests/fail-stable/enum_extra_variant.stderr @@ -1,17 +1,17 @@ -error[E0277]: the trait bound `Vec: Query, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>>` is not satisfied +error[E0277]: the trait bound `Vec: sqlm_postgres::query::Query, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>>` is not satisfied --> tests/fail-stable/enum_extra_variant.rs:26:59 | 26 | let _: Vec = sql!("SELECT id, role FROM users").await.unwrap(); | -^^^^^ | || - | |the trait `Query, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>>` is not implemented for `Vec`, which is required by `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>, _>: IntoFuture` + | |the trait `sqlm_postgres::query::Query, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>>` is not implemented for `Vec`, which is required by `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>, _>: IntoFuture` | help: remove the `.await` | - = help: the following other types implement trait `Query`: - as Query>> - > as Query>> - > as Query>>> - as Query>> - as Query::Type>>> - as Query::Type>>>> + = help: the following other types implement trait `sqlm_postgres::query::Query`: + as sqlm_postgres::query::Query>> + > as sqlm_postgres::query::Query>> + > as sqlm_postgres::query::Query>>> + as sqlm_postgres::query::Query>> + as sqlm_postgres::query::Query::Type>>> + as sqlm_postgres::query::Query::Type>>>> = note: required for `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>, Vec>` to implement `IntoFuture` diff --git a/postgres-macros/tests/fail-stable/enum_missing_variant.stderr b/postgres-macros/tests/fail-stable/enum_missing_variant.stderr index 9a9afb5..5e0fc8e 100644 --- a/postgres-macros/tests/fail-stable/enum_missing_variant.stderr +++ b/postgres-macros/tests/fail-stable/enum_missing_variant.stderr @@ -1,17 +1,17 @@ -error[E0277]: the trait bound `Vec: Query, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>>` is not satisfied +error[E0277]: the trait bound `Vec: sqlm_postgres::query::Query, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>>` is not satisfied --> tests/fail-stable/enum_missing_variant.rs:20:59 | 20 | let _: Vec = sql!("SELECT id, role FROM users").await.unwrap(); | -^^^^^ | || - | |the trait `Query, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>>` is not implemented for `Vec`, which is required by `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>, _>: IntoFuture` + | |the trait `sqlm_postgres::query::Query, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>>` is not implemented for `Vec`, which is required by `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>, _>: IntoFuture` | help: remove the `.await` | - = help: the following other types implement trait `Query`: - as Query>> - > as Query>> - > as Query>>> - as Query>> - as Query::Type>>> - as Query::Type>>>> + = help: the following other types implement trait `sqlm_postgres::query::Query`: + as sqlm_postgres::query::Query>> + > as sqlm_postgres::query::Query>> + > as sqlm_postgres::query::Query>>> + as sqlm_postgres::query::Query>> + as sqlm_postgres::query::Query::Type>>> + as sqlm_postgres::query::Query::Type>>>> = note: required for `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>, Vec>` to implement `IntoFuture` diff --git a/postgres-macros/tests/fail-stable/enum_variant_mismatch.stderr b/postgres-macros/tests/fail-stable/enum_variant_mismatch.stderr index a162ca8..b42bc11 100644 --- a/postgres-macros/tests/fail-stable/enum_variant_mismatch.stderr +++ b/postgres-macros/tests/fail-stable/enum_variant_mismatch.stderr @@ -1,17 +1,17 @@ -error[E0277]: the trait bound `Vec: Query, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>>` is not satisfied +error[E0277]: the trait bound `Vec: sqlm_postgres::query::Query, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>>` is not satisfied --> tests/fail-stable/enum_variant_mismatch.rs:23:59 | 23 | let _: Vec = sql!("SELECT id, role FROM users").await.unwrap(); | -^^^^^ | || - | |the trait `Query, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>>` is not implemented for `Vec`, which is required by `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>, _>: IntoFuture` + | |the trait `sqlm_postgres::query::Query, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>>` is not implemented for `Vec`, which is required by `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>, _>: IntoFuture` | help: remove the `.await` | - = help: the following other types implement trait `Query`: - as Query>> - > as Query>> - > as Query>>> - as Query>> - as Query::Type>>> - as Query::Type>>>> + = help: the following other types implement trait `sqlm_postgres::query::Query`: + as sqlm_postgres::query::Query>> + > as sqlm_postgres::query::Query>> + > as sqlm_postgres::query::Query>>> + as sqlm_postgres::query::Query>> + as sqlm_postgres::query::Query::Type>>> + as sqlm_postgres::query::Query::Type>>>> = note: required for `Sql<'_, Struct<(StructColumn, StructColumn, EnumVariant<10465144470622129318>)>, 18137070463969723500>)>, Vec>` to implement `IntoFuture` diff --git a/postgres-macros/tests/fail-stable/literal_array_to_literal.stderr b/postgres-macros/tests/fail-stable/literal_array_to_literal.stderr index da23344..4aa33fc 100644 --- a/postgres-macros/tests/fail-stable/literal_array_to_literal.stderr +++ b/postgres-macros/tests/fail-stable/literal_array_to_literal.stderr @@ -1,20 +1,20 @@ -error[E0277]: the trait bound `String: Query>>` is not satisfied +error[E0277]: the trait bound `String: sqlm_postgres::query::Query>>` is not satisfied --> tests/fail-stable/literal_array_to_literal.rs:6:10 | 6 | .await | -^^^^^ | || - | |the trait `Query>>` is not implemented for `String`, which is required by `Sql<'_, sqlm_postgres::types::Array>, _>: IntoFuture` + | |the trait `sqlm_postgres::query::Query>>` is not implemented for `String`, which is required by `Sql<'_, sqlm_postgres::types::Array>, _>: IntoFuture` | help: remove the `.await` | - = help: the following other types implement trait `Query`: - as Query>> - > as Query>> - > as Query>>> - as Query>> - as Query::Type>>> - as Query::Type>>>> - > as Query>> - as Query>> + = help: the following other types implement trait `sqlm_postgres::query::Query`: + as sqlm_postgres::query::Query>> + > as sqlm_postgres::query::Query>> + > as sqlm_postgres::query::Query>>> + as sqlm_postgres::query::Query>> + as sqlm_postgres::query::Query::Type>>> + as sqlm_postgres::query::Query::Type>>>> + > as sqlm_postgres::query::Query>> + as sqlm_postgres::query::Query>> and $N others = note: required for `Sql<'_, sqlm_postgres::types::Array>, String>` to implement `IntoFuture` diff --git a/postgres/Cargo.toml b/postgres/Cargo.toml index a89dba3..9ffcfbc 100644 --- a/postgres/Cargo.toml +++ b/postgres/Cargo.toml @@ -3,7 +3,7 @@ name = "sqlm-postgres" version = "0.1.0" edition = "2021" authors = ["Markus Ast "] -description = "simple `sql!` macro that works similar to `format!`" +description = "`sql!` to write compile-time checked database queries similar to how `format!` works" license = "MIT OR Apache-2.0" repository = "https://github.com/rkusa/sqlm" @@ -34,7 +34,9 @@ tracing = "0.1" uuid = { version = "1.4", optional = true } [dev-dependencies] -tokio = { version = "1.0", features = ["macros"] } +bytes = "1.6" +time = { version = "0.3" } +tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] } postgres-types = { version = "0.2", features = ["derive"] } uuid = { version = "1.4", features = ["v4"] } diff --git a/postgres/src/connection.rs b/postgres/src/connection.rs index d6d09d3..67c2723 100644 --- a/postgres/src/connection.rs +++ b/postgres/src/connection.rs @@ -10,6 +10,18 @@ use tokio_postgres::Row; use crate::error::ErrorKind; use crate::Error; +/// A trait used to allow functions to accept connections without having to explicit about whether +/// it's a transaction or not. +/// +/// # Example +/// ``` +/// # use sqlm_postgres::{sql, Connection}; +/// pub async fn fetch_username(id: i64, conn: impl Connection) -> Result { +/// sql!("SELECT name FROM users WHERE id = {id}") +/// .run_with(conn) +/// .await +/// } +/// ``` pub trait Connection: Send + Sync { fn query_one<'a>( &'a self, diff --git a/postgres/src/error.rs b/postgres/src/error.rs index e8277f9..e264e03 100644 --- a/postgres/src/error.rs +++ b/postgres/src/error.rs @@ -3,9 +3,10 @@ use std::{error, fmt}; use http_error::{HttpError, StatusCode}; use tracing::Span; +/// An error communicating with the Postgres server. #[derive(Debug)] pub struct Error { - pub kind: ErrorKind, + pub(crate) kind: ErrorKind, span: Span, } @@ -26,6 +27,7 @@ impl Error { } } + /// Whether this is a row not found error. pub fn is_row_not_found(&self) -> bool { matches!(self.kind, ErrorKind::RowNotFound) } diff --git a/postgres/src/future.rs b/postgres/src/future.rs index ed34495..07a1238 100644 --- a/postgres/src/future.rs +++ b/postgres/src/future.rs @@ -23,13 +23,14 @@ where } } +/// A future for executing an sql query. pub struct SqlFuture<'a, T> { future: Pin> + Send + 'a>>, marker: PhantomData<&'a ()>, } impl<'a, T> SqlFuture<'a, T> { - pub fn new(sql: Sql<'a, Cols, T>) -> Self + pub(crate) fn new(sql: Sql<'a, Cols, T>) -> Self where T: Query + Send + Sync + 'a, Cols: Send + Sync + 'a, @@ -73,7 +74,10 @@ impl<'a, T> SqlFuture<'a, T> { } } - pub fn with_connection(sql: Sql<'a, Cols, T>, conn: impl super::Connection + 'a) -> Self + pub(crate) fn with_connection( + sql: Sql<'a, Cols, T>, + conn: impl super::Connection + 'a, + ) -> Self where T: Query + Send + Sync + 'a, Cols: Send + Sync + 'a, diff --git a/postgres/src/lib.rs b/postgres/src/lib.rs index b6b6c26..159a38e 100644 --- a/postgres/src/lib.rs +++ b/postgres/src/lib.rs @@ -1,5 +1,53 @@ #![cfg_attr(nightly_column_names, feature(adt_const_params))] #![cfg_attr(nightly_column_names, allow(incomplete_features))] +#![forbid(unsafe_code)] + +//! An [`sql!`] macro to write compile-time checked database queries similar to how [`format!`] +//! works. +//! +//! # Example +//! +//! ``` +//! use sqlm_postgres::{sql, Enum, FromRow, FromSql, ToSql}; +//! +//! # #[tokio::main] +//! # async fn main() -> Result<(), Box> { +//! let id: i64 = 1; +//! let user: User = sql!("SELECT * FROM users WHERE id = {id}").await?; +//! +//! #[derive(Debug, FromRow)] +//! struct User { +//! id: i64, +//! name: String, +//! role: Role, +//! } +//! +//! #[derive(Debug, Default, FromSql, ToSql, Enum)] +//! #[postgres(name = "role")] +//! enum Role { +//! #[default] +//! #[postgres(name = "user")] +//! User, +//! #[postgres(name = "admin")] +//! Admin, +//! } +//! # Ok(()) +//! # } +//! ``` +//! +//! # Usage +//! - Add `sqlm_postgres` to your dependencies +//! - Make the `DATABASE_URL` env variable available during compile time (e.g. via adding an `.env` +//! file) +//! - Start using the [`sql!`] macro (no further setup necessary; a connection pool is automatically +//! created for you) +//! +//! # Caveats +//! - Automatically creates a global connection pool for you with no way to opt out +//! - Compile-time checks cannot be disabled. Thus also requires database access on your CI. +//! - Does not know whether rows returned from Postgres are nullable and consequentially +//! requires all types to implement [`Default::default`], which it falls back to if Postgres +//! returns null. // Necessary to have `::sqlm_postgres::` available in tests #[cfg(test)] @@ -8,9 +56,12 @@ extern crate self as sqlm_postgres; mod connection; mod error; mod future; +#[doc(hidden)] pub mod internal; +mod macros; mod query; mod row; +#[doc(hidden)] pub mod types; use std::env; @@ -19,25 +70,64 @@ use std::str::FromStr; use std::sync::Arc; pub use connection::Connection; -pub use deadpool_postgres::{Client, Transaction}; use deadpool_postgres::{ClientWrapper, Manager, ManagerConfig, Pool, RecyclingMethod}; pub use error::Error; use error::ErrorKind; pub use future::SqlFuture; +pub use macros::{sql, Enum, FromRow}; use once_cell::sync::OnceCell; -pub use query::Query; +use query::Query; pub use row::{FromRow, Row}; -pub use sqlm_postgres_macros::{sql, Enum, FromRow}; pub use tokio_postgres; use tokio_postgres::config::SslMode; -use tokio_postgres::types::ToSql; +pub use tokio_postgres::types::{FromSql, ToSql}; use tokio_postgres::NoTls; pub use types::SqlType; static POOL: OnceCell = OnceCell::new(); +/// A database transaction. +pub type Transaction<'t> = deadpool_postgres::Transaction<'t>; + +/// An asynchronous PostgreSQL client (basically a non-transactional connection). +pub type Client = deadpool_postgres::Client; + +/// Establish a database connection. +/// +/// This function is automatically called when awaiting queries created with [`sql!`]. When first +/// called, a connection pool is created, which expects the env variable `DATABASE_URL` to be set. +/// +/// When having multiple sequential queries, it is recommended to manually establish a connection +/// and pass it to [`sql!`] via [`Sql::run_with`]. +/// +/// # Examples +/// +/// ``` +/// # use sqlm_postgres::{sql, connect, Enum, FromRow, FromSql, ToSql}; +/// # #[tokio::main] +/// # async fn main() -> Result<(), Box> { +/// let conn = connect().await?; +/// let name: String = sql!("SELECT name FROM users WHERE id = {id}", id = 1i64) +/// .run_with(conn) +/// .await?; +/// # Ok(()) +/// # } +/// ``` +/// ``` +/// # use sqlm_postgres::{sql, connect, Enum, FromRow, FromSql, ToSql}; +/// # #[tokio::main] +/// # async fn main() -> Result<(), Box> { +/// let mut conn = connect().await?; +/// let tx = conn.transaction().await?; +/// let name: String = sql!("SELECT name FROM users WHERE id = {id}", id = 1i64) +/// .run_with(&tx) +/// .await?; +/// tx.commit().await?; +/// # Ok(()) +/// # } +/// ``` #[tracing::instrument] -pub async fn connect() -> Result { +pub async fn connect() -> Result { // Don't trace connect, as this would create an endless loop of connecting again and // again when persisting the connect trace! let pool = POOL.get_or_try_init(|| { @@ -79,16 +169,25 @@ pub async fn connect() -> Result { Ok(conn) } +/// The struct created by [`sql!`]; executed by calling `.await`. pub struct Sql<'a, Cols, T> { - // TODO: not pub? + // Fields need to be public so that they can be set by the macro invocation. + #[doc(hidden)] pub query: &'static str, + #[doc(hidden)] pub parameters: &'a [&'a (dyn ToSql + Sync)], + #[doc(hidden)] pub transaction: Option<&'a Transaction<'a>>, + #[doc(hidden)] pub connection: Option<&'a ClientWrapper>, + #[doc(hidden)] pub marker: PhantomData<(Cols, T)>, } impl<'a, Cols, T> Sql<'a, Cols, T> { + /// Manually pass a connection or transaction to a query created with [`sql!`]. + /// + /// See [`connect`] for examples. pub fn run_with(self, conn: impl Connection + 'a) -> SqlFuture<'a, T> where T: Query + Send + Sync + 'a, diff --git a/postgres/src/macros.rs b/postgres/src/macros.rs new file mode 100644 index 0000000..045e768 --- /dev/null +++ b/postgres/src/macros.rs @@ -0,0 +1,68 @@ +/// Creates a parameterized, compile-time checked database query that accepts parameters similar to +/// the [`format!`] macro. +/// +/// The compile-time checks requrire a database connection, expecting a `DATABASE_URL` env to be set +/// accordingly. +/// +/// The returned type can either be a struct (that implements [`FromRow`]), a literal (string, +/// interger, ...), or a [`Vec`] or [`Option`] of the former. +/// +/// A connection is automatically established, but also be explicitly set via +/// [`Sql::run_with`]. +/// +/// # Examples +/// +/// ``` +/// # use sqlm_postgres::sql; +/// # #[tokio::main] +/// # async fn main() -> Result<(), Box> { +/// let name: String = sql!("SELECT name FROM users WHERE id = {id}", id = 1i64).await?; +/// # Ok(()) +/// # } +/// ``` +/// +/// [`FromRow`]: super::FromRow +/// [`Sql::run_with`]: super::Sql::run_with +pub use sqlm_postgres_macros::sql; +/// A derive necessary to support compile checks between Postgres and Rust enums. +/// +/// In addition, enums also need to implement `tokio_postgres`'s [`FromSql`] and [`ToSql`], so it +/// can be read from and written to Postgres. +/// +/// # Example +/// ``` +/// use sqlm_postgres::{Enum, FromSql, ToSql}; +/// #[derive(Debug, Default, FromSql, ToSql, Enum)] +/// #[postgres(name = "role")] +/// enum Role { +/// #[default] +/// #[postgres(name = "user")] +/// User, +/// #[postgres(name = "admin")] +/// Admin, +/// } +/// ``` +/// +/// [`FromSql`]: crate::FromSql +/// [`ToSql`]: crate::ToSql +pub use sqlm_postgres_macros::Enum; +/// Derive [`FromRow`] for a struct, required read a query result into a struct. +/// +/// Each struct property must have a [`Default::default`] implementation (used for null values; you +/// can of course use [`Option`] as its default is simply [`None`]). +/// Alternatively, the default value can be set using a `#[sqlm(default = ...)]` attribute. +/// +/// # Example +/// +/// ``` +/// #[derive(sqlm_postgres::FromRow)] +/// struct User { +/// id: i64, +/// name: String, +/// #[sqlm(default = time::OffsetDateTime::UNIX_EPOCH)] +/// created_at: time::OffsetDateTime, +/// } +/// ``` +/// +/// [`FromRow`]: trait@crate::FromRow +pub use sqlm_postgres_macros::FromRow; diff --git a/postgres/src/row.rs b/postgres/src/row.rs index 4f82878..fcbcb59 100644 --- a/postgres/src/row.rs +++ b/postgres/src/row.rs @@ -1,11 +1,18 @@ use std::marker::PhantomData; use std::ops::Deref; +/// A row of data returned from Postgres. pub struct Row { row: tokio_postgres::Row, marker: PhantomData, } +/// A trait for types that can be created from a [`Row`] (a postgres row containing columns as +/// constraint by `Cols`). +/// +/// This is usually derived via [`FromRow`] and not implemented manually. +/// +/// [`FromRow`]: `derive@crate::FromRow` pub trait FromRow: Sized { fn from_row(row: Row) -> Result; } diff --git a/postgres/src/types.rs b/postgres/src/types.rs index bd8c070..ed3ebed 100644 --- a/postgres/src/types.rs +++ b/postgres/src/types.rs @@ -6,6 +6,99 @@ use tokio_postgres::types::{FromSqlOwned, ToSql}; use crate::{Error, Sql}; +/// A trait used to which Rust type a Postgres type is read into. +/// +/// # Example +/// +/// This can be useful to implement manually when e.g. reading a Postgres string column into an +/// enum. +/// +/// ``` +/// #[derive(Debug, Default, Clone, Copy)] +/// pub enum Role { +/// #[default] +/// User, +/// Admin, +/// } +/// +/// impl sqlm_postgres::SqlType for Role { +/// type Type = String; +/// } +/// +/// impl Role { +/// pub fn as_str(&self) -> &'static str { +/// match self { +/// Self::User => "user", +/// Self::Admin => "admin", +/// } +/// } +/// } +/// +/// impl<'a> sqlm_postgres::FromSql<'a> for Role { +/// fn from_sql( +/// ty: &postgres_types::Type, +/// raw: &'a [u8], +/// ) -> Result> { +/// use std::str::FromStr; +/// Ok(Role::from_str(<&str>::from_sql(ty, raw)?)?) +/// } +/// +/// fn accepts(ty: &postgres_types::Type) -> bool { +/// <&str as sqlm_postgres::FromSql<'_>>::accepts(ty) +/// } +/// } +/// +/// impl sqlm_postgres::ToSql for Role { +/// fn to_sql( +/// &self, +/// ty: &postgres_types::Type, +/// out: &mut bytes::BytesMut, +/// ) -> Result> +/// where +/// Self: Sized, +/// { +/// <&str as sqlm_postgres::ToSql>::to_sql(&self.as_str(), ty, out) +/// } +/// +/// fn accepts(ty: &postgres_types::Type) -> bool +/// where +/// Self: Sized, +/// { +/// <&str as sqlm_postgres::ToSql>::accepts(ty) +/// } +/// +/// fn to_sql_checked( +/// &self, +/// ty: &postgres_types::Type, +/// out: &mut bytes::BytesMut, +/// ) -> Result> { +/// <&str as sqlm_postgres::ToSql>::to_sql_checked(&self.as_str(), ty, out) +/// } +/// } +/// +/// impl std::str::FromStr for Role { +/// type Err = UnknownRole; +/// +/// fn from_str(s: &str) -> Result { +/// match s { +/// "user" => Ok(Role::User), +/// "admin" => Ok(Role::Admin), +/// s => Err(UnknownRole(s.to_string())), +/// } +/// } +/// } +/// +/// #[derive(Debug)] +/// pub struct UnknownRole(String); +/// +/// impl std::fmt::Display for UnknownRole { +/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +/// write!(f, "Unknown bevel lhs `{}`", self.0) +/// } +/// } +/// +/// impl std::error::Error for UnknownRole {} +/// ``` pub trait SqlType { type Type;