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>::ValueTypethe traitType<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))
}
