ulid als scalar hinzugefügt

This commit is contained in:
2026-06-04 22:42:57 +02:00
parent b3b7b75ea8
commit 5fb2b3666f
14 changed files with 163 additions and 20 deletions

View File

@@ -0,0 +1 @@
DROP DOMAIN ULID CASCADE;

View File

@@ -0,0 +1 @@
CREATE DOMAIN ULID AS CHAR(26);

View File

@@ -1,6 +1,7 @@
CREATE TABLE IF NOT EXISTS typen (
typ_id UUID PRIMARY KEY,
id CHAR(26) UNIQUE NOT NULL,
-- id CHAR(26) UNIQUE NOT NULL,
id ULID UNIQUE NOT NULL,
typname VARCHAR NOT NULL,
erstellt_am TIMESTAMP WITH TIME ZONE NOT NULL,
geaendert_am TIMESTAMP WITH TIME ZONE NOT NULL

View File

@@ -2,8 +2,15 @@ use async_graphql::{ComplexObject, SimpleObject};
use crate::scalar::{Time, Ulid};
#[derive(sqlx::FromRow, SimpleObject, Clone, Debug)]
#[graphql(complex)]
// #[derive(Debug, sqlx::FromRow)]
// pub struct TypDb {
// pub id: String,
// pub typname: String,
// pub erstellt_am: Time,
// pub geaendert_am: Time,
// }
#[derive(SimpleObject, Clone, sqlx::FromRow)]
pub struct Typ {
/// Die Ulid eines Gerätetypen
pub id: Ulid,
@@ -18,5 +25,16 @@ pub struct Typ {
pub geaendert_am: Time,
}
// impl From<TypDb> for Typ {
// fn from(db: TypDb) -> Self {
// Self {
// id: Ulid(ulid::Ulid::from_string(&db.id).expect("stored ULID is valid")),
// typname: db.typname,
// erstellt_am: db.erstellt_am,
// geaendert_am: db.geaendert_am,
// }
// }
// }
#[ComplexObject]
impl Typ {}

View File

@@ -11,6 +11,8 @@ impl Repository {
let typen = sqlx::query_as::<_, model::Typ>(QUERY).fetch_all(db).await?;
// let typen: Vec<model::Typ> = typen.into_iter().map(model::Typ::from).collect();
Ok(typen)
}
}

View File

@@ -19,6 +19,7 @@ impl Repository {
.bind(id)
.fetch_one(db)
.await?;
// .map(model::Typ::from)?;
Ok(row)
}

View File

@@ -13,7 +13,7 @@ impl Repository {
typen: &[entity::TypErstellen], // input: &[Typ],
) -> Result<Vec<model::Typ>, Error> {
let typ_id: Vec<Id> = typen.iter().map(|t| t.typ_id).collect();
let id: Vec<Ulid> = typen.iter().map(|t| t.id.to_string()).collect();
let id: Vec<Ulid> = typen.iter().map(|t| t.id).collect();
let typnamen: Vec<String> = typen.iter().map(|t| t.typname.clone()).collect();
let erstellt_am: Vec<Time> = typen.iter().map(|t| t.erstellt_am).collect();
let geaendert_am: Vec<Time> = typen.iter().map(|t| t.geaendert_am).collect();
@@ -38,6 +38,8 @@ impl Repository {
.fetch_all(db)
.await?;
// let rows: Vec<model::Typ> = rows.into_iter().map(model::Typ::from).collect();
Ok(rows)
}
}

View File

@@ -18,12 +18,13 @@ impl Repository {
let typ = sqlx::query_as::<_, model::Typ>(QUERY)
.bind(typ.typ_id)
.bind(typ.id.to_string())
.bind(typ.id)
.bind(typ.erstellt_am)
.bind(typ.geaendert_am)
.bind(&typ.typname)
.fetch_one(db)
.await?;
// .map(model::Typ::from)?;
Ok(typ)
}

View File

@@ -15,9 +15,10 @@ impl Repository {
"#;
let typ = sqlx::query_as::<_, model::Typ>(QUERY)
.bind(typ.id.to_string())
.bind(typ.id)
.fetch_one(db)
.await?;
// .map(model::Typ::from)?;
Ok(typ)
}

View File

@@ -17,11 +17,12 @@ impl Repository {
"#;
let typ = sqlx::query_as::<_, model::Typ>(QUERY)
.bind(typ.id.to_string())
.bind(typ.id)
.bind(typ.geaendert_am)
.bind(&typ.typname)
.fetch_one(db)
.await?;
// .map(model::Typ::from)?;
Ok(typ)
}

View File

@@ -1,18 +1,18 @@
use anyhow::Error;
use chrono::Utc;
use ulid::Ulid;
use super::Service;
use crate::domain::typ::{
entity,
model::{self, TypErstelleInput},
};
use crate::scalar::Ulid;
impl Service {
pub async fn typ_erstellen(&self, input: TypErstelleInput) -> Result<model::Typ, Error> {
let typ_erstellen = entity::TypErstellen {
typ_id: Ulid::new().into(),
id: Ulid::new().to_string(),
typ_id: ulid::Ulid::new().into(),
id: Ulid(ulid::Ulid::new()),
typname: input.typname,
erstellt_am: Utc::now(),
geaendert_am: Utc::now(),

View File

@@ -1,11 +1,13 @@
use anyhow::Error;
use chrono::Utc;
use ulid::Ulid;
use super::Service;
use crate::domain::typ::{
entity,
model::{self, TypErstelleInput},
use crate::{
domain::typ::{
entity,
model::{self, TypErstelleInput},
},
scalar::Ulid,
};
impl Service {
@@ -16,8 +18,8 @@ impl Service {
let typen_erstellen: Vec<entity::TypErstellen> = input
.iter()
.map(|t| entity::TypErstellen {
typ_id: Ulid::new().into(),
id: Ulid::new().to_string(),
typ_id: ulid::Ulid::new().into(),
id: Ulid(ulid::Ulid::new()),
typname: t.typname.clone(),
erstellt_am: Utc::now(),
geaendert_am: Utc::now(),

View File

@@ -1,3 +1,4 @@
use async_graphql::*;
/// It is easier to track each type alias if this file is located on the top-level directory (here)
/// than in each domain. Also, separating them will create a lot of duplicate code.
use uuid::Uuid;
@@ -9,7 +10,95 @@ pub type Time = chrono::DateTime<chrono::Utc>;
/// When expected as an input type, any string (such as "4") or integer (such as 4) input value will be accepted as an ID.
pub type Id = Uuid;
/// ödsklfjsdf
/// debug_assert_eq!(ödsklfjsdfasdfas
/// ödsklfjsdfasdfasdfsa,
pub type Ulid = String;
// pub type Ulid = String;
// pub type Ulid = ulid::Ulid;
// #[derive(Clone, Copy, Eq, PartialEq)]
// pub struct Ulid(pub ulid::Ulid);
//
// /// The ULID scalar type represents a Universally Unique Lexicographically Sortable Identifier as defined by the ULID specification.
// /// ULIDs are 26-character strings that are URL-safe, case-insensitive, and lexicographically sortable,
// /// making them ideal for distributed systems requiring time-ordered unique identifiers.
// #[Scalar]
// impl ScalarType for Ulid {
// fn parse(value: Value) -> InputValueResult<Self> {
// match value {
// Value::String(s) => {
// let ulid = ulid::Ulid::from_string(&s).map_err(InputValueError::custom)?;
// Ok(Ulid(ulid))
// }
// _ => Err(InputValueError::expected_type(value)),
// }
// }
//
// fn to_value(&self) -> Value {
// Value::String(self.0.to_string())
// }
// }
//
// impl fmt::Display for Ulid {
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// write!(f, "{}", self.0) // delegiert an Ulid::to_string()
// }
// }
//
//
//
use sqlx::{
postgres::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef},
Decode, Encode, Postgres, Type,
};
// #[derive(sqlx::Type)]
// #[sqlx(type_name = "ULID")]
// #[sqlx(no_pg_array)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Ulid(pub ulid::Ulid);
#[Scalar]
impl ScalarType for Ulid {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::String(s) => {
let ulid = ulid::Ulid::from_string(&s).map_err(InputValueError::custom)?;
Ok(Ulid(ulid))
}
_ => Err(InputValueError::expected_type(value)),
}
}
fn to_value(&self) -> Value {
Value::String(self.0.to_string())
}
}
impl Type<Postgres> for Ulid {
fn type_info() -> PgTypeInfo {
PgTypeInfo::with_name("ULID")
}
fn compatible(ty: &PgTypeInfo) -> bool {
<String as Type<Postgres>>::compatible(ty)
}
}
impl<'r> Decode<'r, Postgres> for Ulid {
fn decode(value: PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let s = <String as Decode<Postgres>>::decode(value)?;
Ok(Ulid(ulid::Ulid::from_string(s.trim())?))
}
}
impl<'q> Encode<'q, Postgres> for Ulid {
fn encode_by_ref(
&self,
buf: &mut PgArgumentBuffer,
) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
<String as Encode<Postgres>>::encode(self.0.to_string(), buf)
}
}
impl PgHasArrayType for Ulid {
fn array_type_info() -> PgTypeInfo {
PgTypeInfo::with_name("_ULID")
}
}

23
src/sqlx_datatype.rs Normal file
View File

@@ -0,0 +1,23 @@
// // SQLx sieht den Typ als Text (PostgreSQL: CHAR, VARCHAR, TEXT) impl Type<Postgres> for Ulid {
// fn type_info() -> sqlx::postgres::PgTypeInfo {
// // CHAR(26) ist in PostgreSQL intern ein Text-Typ
// <String as Type<Postgres>>::type_info()
// }
// }
//
// impl<'r> Decode<'r, Postgres> for MyUlid {
// fn decode(
// value: sqlx::postgres::PgValueRef<'r>,
// ) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
// let s = <String as Decode<Postgres>>::decode(value)?;
// Ulid::from_string(&s)
// .map(MyUlid)
// .map_err(|e| format!("Invalid ULID: {}", e).into())
// }
// }
//
// impl<'q> Encode<'q, Postgres> for MyUlid {
// fn encode_by_ref(&self, buf: &mut sqlx::postgres::PgArgumentBuffer) -> sqlx::encode::IsNull {
// <String as Encode<Postgres>>::encode(self.0.to_string(), buf)
// }
// }