use derive_builder::Builder; use interim_pgtypes::pg_attribute::PgAttribute; use serde::{Deserialize, Serialize}; use sqlx::{Decode, Postgres, Row as _, TypeInfo as _, ValueRef as _, postgres::PgRow, query_as}; use thiserror::Error; use uuid::Uuid; use crate::client::AppDbClient; use crate::encodable::Encodable; use crate::presentation::Presentation; /// A materialization of a database column, fit for consumption by an end user. /// /// There may be zero or more fields per column/attribute in a Postgres view. /// There may in some case also be fields with no underlying column, if it has /// been removed or altered. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Field { /// Internal ID for application use. pub id: Uuid, /// Name of the database column. pub name: String, /// Optional human friendly label. pub label: Option, /// Refer to documentation for `Presentation`. pub presentation: sqlx::types::Json, /// Width of UI table column in pixels. pub width_px: i32, } impl Field { /// Constructs a brand new field to be inserted into the application db. pub fn insert() -> InsertableFieldBuilder { InsertableFieldBuilder::default() } /// Generate a default field config based on an existing column's name and /// type. pub fn default_from_attr(attr: &PgAttribute) -> Option { Presentation::default_from_attr(attr).map(|presentation| Self { id: Uuid::now_v7(), name: attr.attname.clone(), label: None, presentation: sqlx::types::Json(presentation), width_px: 200, }) } pub fn get_value_encodable(&self, row: &PgRow) -> Result { let value_ref = row .try_get_raw(self.name.as_str()) .or(Err(ParseError::FieldNotFound))?; let type_info = value_ref.type_info(); let ty = type_info.name(); dbg!(&ty); Ok(match ty { "TEXT" | "VARCHAR" => { Encodable::Text( as Decode>::decode(value_ref).unwrap()) } "UUID" => { Encodable::Uuid( as Decode>::decode(value_ref).unwrap()) } _ => return Err(ParseError::UnknownType), }) } pub fn belonging_to_lens(lens_id: Uuid) -> BelongingToLensQuery { BelongingToLensQuery { lens_id } } } #[derive(Clone, Debug)] pub struct BelongingToLensQuery { lens_id: Uuid, } impl BelongingToLensQuery { pub async fn fetch_all(self, app_db: &mut AppDbClient) -> Result, sqlx::Error> { query_as!( Field, r#" select id, name, label, presentation as "presentation: sqlx::types::Json", width_px from fields where lens_id = $1 "#, self.lens_id ) .fetch_all(&mut *app_db.conn) .await } } #[derive(Builder, Clone, Debug)] pub struct InsertableField { lens_id: Uuid, name: String, #[builder(default)] label: Option, presentation: Presentation, #[builder(default = 200)] width_px: i32, } impl InsertableField { pub async fn insert(self, app_db: &mut AppDbClient) -> Result { query_as!( Field, r#" insert into fields (id, lens_id, name, label, presentation, width_px) values ($1, $2, $3, $4, $5, $6) returning id, name, label, presentation as "presentation: sqlx::types::Json", width_px "#, Uuid::now_v7(), self.lens_id, self.name, self.label, sqlx::types::Json::<_>(self.presentation) as sqlx::types::Json, self.width_px, ) .fetch_one(&mut *app_db.conn) .await } } impl InsertableFieldBuilder { pub fn default_from_attr(attr: &PgAttribute) -> Self { Self { name: Some(attr.attname.clone()), presentation: Presentation::default_from_attr(attr), ..Self::default() } } } /// Error when parsing a sqlx value to JSON #[derive(Debug, Error)] pub enum ParseError { #[error("incompatible json type")] BadJsonType, #[error("field not found in row")] FieldNotFound, #[error("unknown postgres type")] UnknownType, }