How to create Vec for sqlx values?

I'm trying to write a basic query builder for sqlx but I'm failing at the very basics. I want to have a Vec<ValueType> so that I can then pass ValueType to the sqlx::query(....).bind function. The bind has this signature

pub fn bind<T: 'q + Send + Encode<'q, DB> + Type<DB>>(mut self, value: T) -> Self {...}

Here is my code

trait SqlxValue<'q>: 'q + Send + Encode<'q, Postgres> + Type<Postgres>{}

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    let mut v: Vec<Box<dyn SqlxValue<'_>>> = Vec::new();
    v.push(Box::new(3));
    v.push(Box::new("str"));

    sqlx::query("insert into abc (a) values($1)")
        .bind(v.pop());
    Ok(())
}

But I'm getting this error

50  |     let mut v: Vec<Box<dyn SqlxValue<'_>>> = Vec::new();
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `SqlxValue` cannot be made into an object
    = help: consider turning `type_info` into a method by giving it a `&self` argument or constraining it so it does not apply to trait objects
    = help: consider turning `compatible` into a method by giving it a `&self` argument or constraining it so it does not apply to trait objects
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
   --> /Users/alexei/.cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-core-0.5.9/src/types/mod.rs:175:8
    |
175 |     fn type_info() -> DB::TypeInfo;
    |        ^^^^^^^^^ ...because associated function `type_info` has no `self` parameter
...
184 |     fn compatible(ty: &DB::TypeInfo) -> bool {
    |        ^^^^^^^^^^ ...because associated function `compatible` has no `self` parameter

Question: how to define a container (Vec) that contains values that can be passed to the bind(..)?

The Type trait appears to be not object safe (because it has methods without a self argument).

I'm unfamiliar with the crate in question and I also don't know why exactly you need to put different types of values into the same array (as trait objects), so I can't really help you unfortunately; the compiler is correct that you can't just create a trait object for Type<…> or anything with Type<…> as supertrait.

2 Likes

I also don't know why exactly you need to put different types of values into the same array (as trait objects)

I'd like to have a SQL query builder. So I want to have some struct that holds some abstract query representation with the bound values. When the user is done with the query building, it should be possible somehow to get the final SQL query as a string containing ? or $1 and the bound values and pass them all to sql::query(query).bind(var1).bind(var2).

I took the Vec as an example. I might be any other container that holds the values, HashMap, etc. I got stuck at the very beginning.

I suggest use enum instead,
and provide deref function.
like

pub enum Bind{
  INT(i32),
  STRING(String), 
}

and you can use

 let mut v: Vec<Bind> = Vec::new();
 sqlx::query("insert into abc (a) values($1)")
        .bind(v.pop());

and you can match the data in fn bind to solve lower logic, like

match data {
   Bind::INT(i32_data) => {},
   Bind::STRING(string) => {},
}

Thank you. This seems to work. It also means a bunch of boiler plate code. My initial idea was to have SQL builder that is generic and independent of sqlx. This approach means I have to track the supported types for Postgres, MySql, etc. by sqlx and eventually other libraries. Tedious but possible.

is this a instance funtion ? do not understand this clearly .

if you write this, maybe you need call it by

pub struct XXX{

}
impl XXX{
    fn type_info() -> DB::TypeInfo;
}
fn main{
    XXX::type_info();

  // rather than 
  // let c = XXX{};
  // x.type_info();
}

design it first.

type_info is defined in Type by sqlx (see the @steffahn's answer). I do not call it directly.

If all you want to do to the trait object is bind it to a query, you can create your own trait for that, I guess:

use sqlx::{Database, Encode, Postgres, Type, database::HasArguments, query::Query};

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    let mut v: Vec<Box<dyn Bindable<Postgres>>> = Vec::new();
    v.push(Box::new(3));
    v.push(Box::new("str"));

    v.pop().unwrap().bind_to(sqlx::query("insert into abc (a) values($1)"));
    Ok(())
}

trait Bindable<DB: Database> {
    fn bind_to<'q>(
        self: Box<Self>,
        query: Query<'q, DB, <DB as HasArguments<'q>>::Arguments>,
    ) -> Query<'q, DB, <DB as HasArguments<'q>>::Arguments>
    where
        Self: 'q;
}

impl<DB: Database, T: Send + for<'q> Encode<'q, DB> + Type<DB>> Bindable<DB> for T {
    fn bind_to<'q>(
        self: Box<Self>,
        query: Query<'q, DB, <DB as HasArguments<'q>>::Arguments>,
    ) -> Query<'q, DB, <DB as HasArguments<'q>>::Arguments>
    where
        Self: 'q,
    {
        query.bind(*self)
    }
}

Pure magic :smiley: Thanks a bunch @steffahn

I have to meditate on this solution. As far as I understand now the point is to make the trait object safe and you achieved this by moving the 'q into a method type constraint:

where
  Self: 'q

so that the 'q does not need to be present in the trait definition. Correct me if I'm wrong. A very cool trick.

Another question in this context... Query<'q, DB, <DB as HasArguments<'q>>::Arguments> makes the Bindable trait very much SqlxBindable. Is it possible to extract this part and make it parameterizable to have a generic Bindable independent of the library?

The thing with 'q is not the main point. It could also be a parameter, giving a dyn Bindable<'q, DB>; but I don't really know anything about the role of that lifetime 'q in sqlx, I assumed the same type of dyn Bindable… would possibly need to be used either with queries with different 'q or with a lifetime that is not yet existent at the time of creating the Bindable, so I kept the bind_to method generic over 'q and added a higher-ranked trait bound.

The main point is that a dyn Bindable<…> type does no longer (need to) support any methods without a self-parameter the way that Type::type_info needs. The dyn Bindable<…> of course can/will still internally provide the relevant type_info to the .bind method's implementation, but it can do so by dispatching to the right monomorphized to_bind method, and that method has a self argument.

The name Bindable is just an arbitrary name chosen because I needed to choose a name.

I'm not familiar with the crates involved, I don't know what other types besides Query have a bind method; hence no idea about the potential for generalization.

I had a similar use case and ended up using the query_with (or query_as_with) method and an implementation of Arguments (in my case PgArguments) instead of a Vec:

use sqlx::{postgres::PgArguments, Arguments};

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    let mut args = PgArguments::default();
    args.add(5);
    args.add("foo");
    sqlx::query_with("insert into abc (a,b) values ($1, $2)", args);
    Ok(())
}
2 Likes

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.