libsql
libsql copied to clipboard
Joint row deserialization into structs
I have this common use case where I join two tables and I want to deserialize a row into separate structs. However, each row cannot be easily deserialized into separate structs via from_row
.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Default)]
pub struct User {
pub id: i32,
pub date: DateTime<Utc>,
pub group_id: i32,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Default)]
pub struct Group {
pub id: i32,
pub name: String,
}
The query is like
let rows = db_conn
.query(
"SELECT users.*, groups.*
FROM users
JOIN groups ON users.group_id = groups.id
WHERE users.user_id = ?1 LIMIT ?2
",
params![user_id.clone(), 10],
)
.await?;
while let Some(row) = rows.next().await? {
// there's no such thing:
let (users, group) = from_row<(User, Group)>(&row)?;
}
I have to do things like the following to be able to convert a Row
into separate structs:
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Default)]
pub struct UserWithGroup {
pub user: User,
pub group: Group,
}
// Implement from_row for UserWithGroup to deserialize from a database row
impl UserWithGroup {
pub fn from_row<'de>(row: &'de Row) -> Result<UserWithGroup, DeError> {
return Ok(Self {
user: User {
id: row.get(0).expect("id"),
date: DateTime::deserialize(
row.get_value(1).expect("date").into_deserializer(),
)?,
group_id: row.get(2).expect("group_id"),
},
group: Group {
id: row.get(3).expect("group_id"),
name: row.get(4).expect("name"),
},
});
}
}
To be able to do something like
while let Some(row) = rows.next().await? {
let users_with_group = UsersWithGroup::from_row(&row)?;
}
Is there a way to support from_row
for the JOIN
use cases maybe via tuples so that this manual work is not needed anymore?
We do face the same issues. Currently, our work around for this is to use #[serde(alias = 'some_unique_name')]
for each of the fields of the struct. So considering your use case, it will look something like:
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Default)]
pub struct User {
#[serde(alias='user_table_id')]
pub id: i32,
#[serde(alias='user_table_date')]
pub date: DateTime<Utc>,
#[serde(alias='user_table_group_id')]
pub group_id: i32,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Default)]
pub struct Group {
#[serde(alias='group_table_id')]
pub id: i32,
#[serde(alias='group_table_name')]
pub name: String,
}
And then modify your query to include those names, like so:
let rows = db_conn
.query(
"SELECT users.id as user_table_id,
users.date as user_table_date,
users.group_id as user_table_group_id,
groups.id as group_table_id,
groups.name as group_table_name
FROM users
JOIN groups ON users.group_id = groups.id
WHERE users.user_id = ?1 LIMIT ?2
",
params![user_id.clone(), 10],
)
.await?;
while let Some(row) = rows.next().await? {
let user = from_row<User>(&row)?;
let group = from_row<Group>(&row)?;
}
Although this seems like a lot of work, but since the query is mostly generated programmatically, this is a fair bit easier to work with. But I totally agree with you that if there was some solution to this directly from libsql, it would be a huge help.
i think it's ironic that this lib is written in rust but doesn't have builtin mechanism for struct building