tursodatabase / libsql

libSQL is a fork of SQLite that is both Open Source, and Open Contributions.

Home Page:https://turso.tech/libsql

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Joint row deserialization into structs

aminya opened this issue · comments

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.