Common Row wrapper for PG & CH clients

Hello everyone! There are two packages https://github.com/sfackler/rust-postgres and https://github.com/suharev7/clickhouse-rs . And both of them have Row struct with get method:

postgres:

pub fn get<'a, I, T>(&'a self, idx: I) -> T
    where
        I: RowIndex + fmt::Display,
        T: FromSql<'a> {
        ...
    }

clickhouse:

pub fn get<T, I>(&'a self, col: I) -> Result<T>
    where
        T: FromSql<'a>,
        I: ColumnIdx + Copy {
        ...
    }

I want to create struct or trait(that can be implemented for both postgres::Row and clickhouse;:types::Row), but can't do that because of every of Row's get methods have own bounds - FromSql + Row/ColumnIdx. Is it possible to create something like this?

trait CommonClient {
  fn query(query: String) -> Vec<Box<dyn CommonRow>>;
}

trait CommonRow {
  fn get_value<T, U>(&self, key: U) -> T;
}

impl CommonView for postgres::Row {
  ...?
}

impl CommonView for clickhouse::types::Row {
  ...?
}

and have interface like:

// cli is postgres or clickhouse client wrapper with stored connection in it
fn execute(cli: Box<dyn CommonClient>) -> Vec<Box<dyn CommonRow>> {
    cli.query("select user_id from user")
}

Got a solution, but it is ugly and not universal :frowning:

pub struct SuperRow<'a> {
    pub(crate) pg_row: Option<tokio_postgres::row::Row>,
    pub(crate) ch_row: Option<clickhouse_rs::types::Row<'a, Simple>>
}

impl<'a> SuperRow<'a> {
    pub fn get<'b, T: FromSql<'b> + CFromSql<'b>, U: RowIndex + fmt::Display + ColumnIdx + Copy>(&'b self, key: U) -> Option<T> {
        if let Some(row) = self.pg_row.as_ref() {
            return Some(row.get::<U, T>(key))
        }

        if let Some(row) = self.ch_row.as_ref() {
            return Some(row.get(key).unwrap())
        }

        None
    }
}

For this solution, an enum would look a lot better.

pub enum SuperRow<'a> {
    Postgres(tokio_postgres::row::Row),
    Clickhouse(clickhouse_rs::types::Row<'a, Simple>)
}

A trait solution might be possible but feels like it'd be pretty tricky. You can create a generic trait and have separate bounds on the impls, something like:

trait Generic<T> {}
struct S {}
impl<T: std::fmt::Debug> Generic<T> for S {}

Maybe something like that would work? I'm not sure, hopefully someone more experienced with more complex traits can chime in.

1 Like

Thank you very much for reply! Enums looks really better than structs in this case. Tried trait with generic types and found a problem that original generic types as for methods, this way i'm having to specify just one type in Client

It becomes impossible to:

let a: i64 = row.get("id");
let b: &str = row.get("username");

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.