Home > database >  sqlx: implementing Decode and Type<DB> for associated trait type
sqlx: implementing Decode and Type<DB> for associated trait type

Time:08-28

I am trying to create a library which gets Values for multiple Tags from an SQL Database. Depending on the TagType which can be Analog or String - I need to return the matching value type. I tried to achieve this using a associated type named ValueType in the Tag trait

This is what i have got until now:

use sqlx::Row;

pub trait Tag {
    type ValueType;

    fn name(&self) -> String;
    fn tagtype(&self) -> TagType;
}

pub enum TagType {
    Analog = 1,
    String = 3,
}

pub struct AnalogTag(String);
pub struct StringTag(String);

impl Tag for AnalogTag {
    type ValueType = f64;

    fn name(&self) -> String {
        self.0.to_string()
    }

    fn tagtype(&self) -> TagType {
        TagType::Analog
    }
}

impl Tag for StringTag {
    type ValueType = String;

    fn name(&self) -> String {
        self.0.to_string()
    }

    fn tagtype(&self) -> TagType {
        TagType::String
    }
}

pub struct Value<T> {
    pub val: T,
    pub quality: i8,
}

impl<T> Value<T> {
    fn new(val: T, quality: i8) -> Self {
        Self { val, quality }
    }
}

pub async fn get_actual_value<T: Tag>(
    db_pool: sqlx::MssqlPool,
    tag: T,
) -> Result<Value<T::ValueType>, sqlx::Error> {
    let table = match tag.tagtype() {
        TagType::Analog => "AnalogLive",
        TagType::String => "StringLive",
    };
    let result = sqlx::query("SELECT Value, Quality FROM @P1 WHERE Tagname = @P2")
        .bind(table)
        .bind(tag.name())
        .fetch_one(&db_pool)
        .await?;
    let val = result.get("Value");
    let quality: i8 = result.get("Quality");
    Ok(Value::new(val, quality))
}

Anyhow, this will not work. I need to implement sqlx::Decode and Type<Mssql> traits but don't know how this can be done for ValueType that is an associated type of the trait Tag

the trait sqlx::Decode<'_, Mssql> is not implemented for <T as Tag>::ValueType the trait Type<Mssql> is not implemented for <T as Tag>::ValueType

Any help would be appreciated!

EDIT Updated the code-example to a minimal reproducible example

CodePudding user response:

You are running into the problem here where you want to convert a type only known at runtime (the TagType enum) into a type known at compile time (T::ValueType). This is not trivial and requires some trickery. You have to understand that the compiler has zero knowledge about what tag.tagtype() will return at compile time.

Luckily, the result::get() function already has the hard work for that problem implemented in it.

So with a little bit of extra trait restrictions for T::ValueType, you can get this to work:

use sqlx::Row;

pub trait Tag {
    type ValueType;

    fn name(&self) -> String;
    fn tagtype(&self) -> TagType;
}

pub enum TagType {
    Analog = 1,
    String = 3,
}

pub struct AnalogTag(String);
pub struct StringTag(String);

impl Tag for AnalogTag {
    type ValueType = f64;

    fn name(&self) -> String {
        self.0.to_string()
    }

    fn tagtype(&self) -> TagType {
        TagType::Analog
    }
}

impl Tag for StringTag {
    type ValueType = String;

    fn name(&self) -> String {
        self.0.to_string()
    }

    fn tagtype(&self) -> TagType {
        TagType::String
    }
}

pub struct Value<T> {
    pub val: T,
    pub quality: i8,
}

impl<T> Value<T> {
    fn new(val: T, quality: i8) -> Self {
        Self { val, quality }
    }
}

pub async fn get_actual_value<T>(
    db_pool: sqlx::MssqlPool,
    tag: T,
) -> Result<Value<T::ValueType>, sqlx::Error>
where
    T: Tag,
    for<'a> T::ValueType: sqlx::Decode<'a, sqlx::Mssql>   sqlx::Type<sqlx::Mssql>,
{
    let table = match tag.tagtype() {
        TagType::Analog => "AnalogLive",
        TagType::String => "StringLive",
    };
    let result = sqlx::query("SELECT Value, Quality FROM @P1 WHERE Tagname = @P2")
        .bind(table)
        .bind(tag.name())
        .fetch_one(&db_pool)
        .await?;
    let quality: i8 = result.get("Quality");

    let val: T::ValueType = result.get("Value");
    Ok(Value::new(val, quality))
}
  • Related