1

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

smitop
  • 4,770
  • 2
  • 20
  • 53
he4d
  • 13
  • 3
  • Your code is correct so far. Although I don't 100% understand why the `Value` wrapper is necessary, you could just return `T::ValueType` directly. So the error is in the code you don't show. Please provide the content of the `get_actual_value` method. – Finomnis Aug 28 '22 at 11:03
  • If you post code, please make sure that it is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). Code that I can copy&paste, and that produces the exact error you claim it does. Yours gives me `cannot find value 'retval' in this scope`. – Finomnis Aug 28 '22 at 11:04
  • I don't understand what you mean with *"if for example, i got an f64 back"* - Rust is a very typesafe language. You can't get different things back. So the magic step that you are missing will have to be inside of the `get_actual_value()` function, and without knowing how your api is that retrieves the `String` or `f64`, I sadly cannot give you any advice. – Finomnis Aug 28 '22 at 11:10
  • @Finomnis: I updated the code example to make my intention clearer. Thank you so far – he4d Aug 28 '22 at 11:39

1 Answers1

0

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))
}
Finomnis
  • 18,094
  • 1
  • 20
  • 27
  • This is awesome @Finomnis - Thank you very much. Could you please explain to me why I don't need to implement the traits Decode and Type completely and this is enough `for<'a> T::ValueType: sqlx::Decode<'a, sqlx::Mssql> + sqlx::Type`? – he4d Aug 28 '22 at 12:38
  • 1
    Ah... 1 second more of thinking... ValueType is either f64 or String which is already implemented in the core of sqlx. Thanks again! – he4d Aug 28 '22 at 12:50
  • Yes, exactly. You are 100% correct :) – Finomnis Aug 28 '22 at 15:38