Overcome `impl has stricter requirements than trait` restriction

Greetings!

I want make trait wrapper for SQL drivers and I trying to to something like this (example with sqlx):

pub trait Row {
    fn get<T>(&self, idx: usize) -> Result<T>;
}

impl<TRow> Row for TRow
where
    TRow: sqlx::Row,
    usize: sqlx::ColumnIndex<TRow>,
{
    fn get<T>(&self, idx: usize) -> Result<T>
    where
        for<'any> T: sqlx::Type<<TRow as sqlx::Row>::Database>
            + sqlx::Decode<'any, <TRow as sqlx::Row>::Database>,
    {
        self.try_get(idx).map_err(Error::from)
    }
}

but obviously I get E0276 error impl has stricter requirements than trait and I don't know how overcome this.

sqlx::Row::try_get have requirements in method:

    fn try_get<'r, T, I>(&'r self, index: I) -> Result<T, Error>
    where
        I: ColumnIndex<Self>,
        T: Decode<'r, Self::Database> + Type<Self::Database>,

and because of that I can't move those requiments to impl where section.

Just think about it – you can't do exactly what you want.

Every SQL driver will have a different, specific, means of retrieving column values and converting them to any given type T. Also, not every possible type T might be at all convertible from SQL values. Thus, the T will inevitably need the appropriate trait bounds; you can't make it so generic that the type variable admits any type.

You will have to rewrite the trait declaration in such a way that the T type parameter has the required bounds. E.g. this compiles.

1 Like

That exactly the reason why I need do something like this:

    where
        for<'any> T: sqlx::Type<<TRow as sqlx::Row>::Database>
            + sqlx::Decode<'any, <TRow as sqlx::Row>::Database>,
    {

in implementation of trait and not declaration.

As I already said, I can't do this:

pub trait Row: sqlx::Row {
    fn get<T>(&self, idx: usize) -> Result<T>
    where
        T: for<'any> Decode<'any, Self::Database> + Type<Self::Database>,
        usize: ColumnIndex<Self>;
}

because when I add tiberius it wouldn't work.

You can't do that. The constraint also depends on the return type, T. You can't leave it out, you need to include it in the trait bounds.

You never mentioned tiberius before. What is it, and why does "adding" it make the correct trait declaration and impl "not work"?

I need 3 databases: Postgres, M$ SQL and Oracle, so I'm writing trait wrapper to support different drivers. And because of that I need to way to overcome E0276 error.
P.S. I can't use odbc-api because of LGPL and scratch container requiment.

This still doesn't explain what Tiberius is and why it doesn't work. SQLx is already an abstraction over different databases, which include all 3 you want to support, as proven by its documentation. If you abstract over SQLx types and traits, your code will work with Postgres, MySQL, SQLite, and MS-SQL.

tiberius is still alive M$ SQL server driver.

because with another driver, tiberius for example, it will be look like this:

use sqlx::{ColumnIndex, Decode, Type};
use tiberius::FromSql;

type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

pub trait Row: sqlx::Row {
    fn get<T>(&self, idx: usize) -> Result<T>
    where
        for<'any> T: Decode<'any, Self::Database> + Type<Self::Database> + tiberius::FromSql<'any>,
        usize: ColumnIndex<Self>;
}

impl<R> Row for R
where
    R: sqlx::Row,
{
    fn get<T>(&self, idx: usize) -> Result<T>
    where
        for<'any> T: Decode<'any, Self::Database> + Type<Self::Database> + tiberius::FromSql<'any>,
        usize: ColumnIndex<Self>,
    {
        let ret = self.try_get(idx)?;
        Ok(ret)
    }
}

impl Row for tiberius::Row {
    fn get<T>(&self, idx: usize) -> Result<T>
    where
        for<'any> T: Decode<'any, Self::Database> + Type<Self::Database> + FromSql<'any>,
        usize: ColumnIndex<Self>,
    {
        todo!()
    }
}

fn main() {
    println!("");
}

and it won't even compile, because tiberius::Row doesn't implement sqlx::Row trait.

sqlx doesn't support Oracle, and MSSQL doesn't fully implement (for what I need is NUMERIC type) and whey have plan to make drivers to proprietary databases in AGPLv3.

In the case where you want to abstract over multiple different database drivers/abstractions, you have two options:

  1. Swap out the bounds on the method's T type variable for your own trait, and implement it differently for the appropriate database drivers.
  2. Or, make the Row trait itself generic over the database driver.
2 Likes

Ow, yeah, you right, thanks for tip. Before you comment I totally forgot that I can implement additional wrapper-trait like this:

pub trait Row: Sized {
    fn get<T>(&self, idx: usize) -> Result<T>
    where
        T: Decode<Self>;
}

pub trait Decode<TRow>: Sized {
    fn get(row: &TRow, idx: usize) -> Result<Self>;
}

impl<T, TRow> Decode<TRow> for T
where
    TRow: sqlx::Row,
    for<'any> T: sqlx::Type<<TRow as sqlx::Row>::Database>
        + sqlx::Decode<'any, <TRow as sqlx::Row>::Database>,
    usize: sqlx::ColumnIndex<TRow>,
{
    fn get(row: &TRow, idx: usize) -> Result<Self> {
        row.try_get(idx).map_err(FetchError::from)
    }
}

impl<TRow> Row for TRow
where
    TRow: sqlx::Row,
{
    fn get<T>(&self, idx: usize) -> Result<T>
    where
        T: Decode<Self>,
    {
        T::get(self, idx)
    }
}

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.