cartel-pg 0.20.0

Async PostgreSQL driver for the dope runtime
Documentation
mod error;
pub use error::Error;

pub mod api;
pub mod dsl;
pub mod port;
pub mod protocol;
pub mod reply;
pub mod schema;
pub mod scram;

use o3::{alloc, buffer::storage};
use std::{cmp, mem, ops};

pub use cartel_core::deadline::RequestPhase;
#[doc(hidden)]
pub use cartel_core::sql;
pub use cartel_gen::{PgRow, PgTable, pg_instance, query_group};
#[doc(hidden)]
pub use dope_fiber;
pub use dope_fiber::abi::Fiber;

#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Timestamp(pub i64);

#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Date(pub i32);

pub struct Ltree(storage::Str);

impl TryFrom<&mut String> for Ltree {
    type Error = alloc::Exhausted;

    fn try_from(s: &mut String) -> Result<Self, Self::Error> {
        storage::Str::try_from(s).map(Self)
    }
}

impl ops::Deref for Ltree {
    type Target = str;

    fn deref(&self) -> &str {
        self.0.as_str()
    }
}

impl AsRef<str> for Ltree {
    fn as_ref(&self) -> &str {
        self.0.as_str()
    }
}

impl PartialEq for Ltree {
    fn eq(&self, other: &Self) -> bool {
        self.0.as_str() == other.0.as_str()
    }
}

impl PartialEq<str> for Ltree {
    fn eq(&self, other: &str) -> bool {
        self.0.as_str() == other
    }
}

impl PartialEq<&str> for Ltree {
    fn eq(&self, other: &&str) -> bool {
        self.0.as_str() == *other
    }
}

impl Eq for Ltree {}

/// An owned PostgreSQL identifier used by operations that retain its name.
pub struct Identifier(storage::Str);

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ParseError {
    Empty,
    Nul { position: usize },
    Storage(alloc::Exhausted),
}

impl TryFrom<&mut String> for Identifier {
    type Error = ParseError;

    fn try_from(value: &mut String) -> Result<Self, Self::Error> {
        if value.is_empty() {
            return Err(ParseError::Empty);
        }
        if let Some(position) = value.as_bytes().iter().position(|byte| *byte == 0) {
            return Err(ParseError::Nul { position });
        }
        storage::Str::try_from(value)
            .map(Self)
            .map_err(ParseError::Storage)
    }
}

impl ops::Deref for Identifier {
    type Target = str;

    fn deref(&self) -> &str {
        self.0.as_str()
    }
}

impl AsRef<str> for Identifier {
    fn as_ref(&self) -> &str {
        self.0.as_str()
    }
}

pub struct Text(storage::Str);

impl TryFrom<&mut String> for Text {
    type Error = alloc::Exhausted;

    fn try_from(s: &mut String) -> Result<Self, Self::Error> {
        storage::Str::try_from(s).map(Self)
    }
}

impl ops::Deref for Text {
    type Target = str;
    fn deref(&self) -> &str {
        self.0.as_str()
    }
}

impl AsRef<str> for Text {
    fn as_ref(&self) -> &str {
        self.0.as_str()
    }
}

impl PartialEq for Text {
    fn eq(&self, other: &Self) -> bool {
        self.0.as_str() == other.0.as_str()
    }
}

impl PartialEq<str> for Text {
    fn eq(&self, other: &str) -> bool {
        self.0.as_str() == other
    }
}

impl PartialEq<&str> for Text {
    fn eq(&self, other: &&str) -> bool {
        self.0.as_str() == *other
    }
}

impl Eq for Text {}

pub struct Jsonb(storage::Str);

impl Jsonb {
    pub fn as_bytes(&self) -> &[u8] {
        self.0.as_bytes()
    }
}

impl TryFrom<&mut String> for Jsonb {
    type Error = alloc::Exhausted;

    fn try_from(s: &mut String) -> Result<Self, Self::Error> {
        storage::Str::try_from(s).map(Self)
    }
}

impl AsRef<str> for Jsonb {
    fn as_ref(&self) -> &str {
        self.0.as_str()
    }
}

impl ops::Deref for Jsonb {
    type Target = str;
    fn deref(&self) -> &str {
        self.0.as_str()
    }
}

impl PartialOrd for Text {
    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Text {
    fn cmp(&self, other: &Self) -> cmp::Ordering {
        self.0.as_str().cmp(other.0.as_str())
    }
}

#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Uuid(pub [u8; 16]);

impl Uuid {
    pub const NIL: Self = Self([0u8; 16]);

    pub const fn from_bytes(bytes: [u8; 16]) -> Self {
        Self(bytes)
    }

    pub const fn into_bytes(self) -> [u8; 16] {
        self.0
    }

    pub const fn as_bytes(&self) -> &[u8; 16] {
        &self.0
    }
}

/// Credentials cannot be formatted as a whole.
///
pub struct Config {
    user: String,
    password: Password,
    database: String,
    search_path: Option<Identifier>,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ConfigField {
    User,
    Password,
    Database,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ConfigError {
    Nul { field: ConfigField, position: usize },
    PasswordCapacity,
    Allocation(alloc::Error),
}

pub(crate) use scram::Password;

impl Config {
    pub fn new(user: String, password: String, database: String) -> Result<Self, ConfigError> {
        let password = Password::new(password);
        Self::without_nul(ConfigField::User, &user)?;
        Self::without_nul(ConfigField::Password, password.as_str())?;
        Self::without_nul(ConfigField::Database, &database)?;
        Ok(Self {
            user,
            password: password.try_prepare()?,
            database,
            search_path: None,
        })
    }

    pub fn search_path(mut self, schema: Identifier) -> Self {
        self.search_path = Some(schema);
        self
    }

    pub(crate) fn user(&self) -> &str {
        &self.user
    }

    pub(crate) fn password(&self) -> &Password {
        &self.password
    }

    pub(crate) fn database(&self) -> &str {
        &self.database
    }

    pub(crate) fn schema(&self) -> Option<&str> {
        self.search_path.as_deref()
    }

    fn without_nul(field: ConfigField, value: &str) -> Result<(), ConfigError> {
        match value.as_bytes().iter().position(|byte| *byte == 0) {
            Some(position) => Err(ConfigError::Nul { field, position }),
            None => Ok(()),
        }
    }
}

const _: () = assert!(mem::size_of::<Password>() == mem::size_of::<String>());