rustrusqlite

Rust rusqlite cannot insert row


The following code fails to compile with:

55 |                 (":dataset_id", &dataset_id),
   |                                 ^^^^^^^^^^^ expected `u32`, found `i32`
pub fn save(&mut self, annotations: Vec<Record>, dataset_id: i32) -> Result<(), Error> {
        let mut tx = self.conn.transaction()?;
        for record in records {
            let json: String = record();
            let sql: &str =
                "INSERT INTO records (record_id, dataset_id, value)
                 VALUES (:record_id, :dataset_id, :value)";
            let mut statement = tx.prepare(sql)?;
            statement.execute(&[
                (":record_id", &record.id),
                (":dataset_id", &dataset_id),
                (":value", "hello world")]);
        };
        tx.commit()?;
        Ok(())
}

And if I remove dataset_id from my SQL statement and comment out the line: (":dataset_id", &dataset_id),

Then it fails to compile with:

56 |                 (":value", &"hello".to_string()),
   |                            ^^^^^^^^^^^^^^^^^^^^ expected `u32`, found struct `std::string::String`

Solution

  • The argument to execute is a P: Params, i.e. "any type that implements Params". The Rust compiler will not make a guess at what specific type you want and then process the argument accordingly. Instead, it will just resolve the type of the argument on its own and then see if it implements Params.

    This is your argument:

    &[(":record_id", &record.id),
      (":dataset_id", &dataset_id),
      (":value", "hello world")]
    

    On its own, what is the type? It's a reference to an array literal containing three tuples: a (&str, &u32), a (&str, &i32), and a (&str, &str).

    Lacking any further information, the compiler guesses that the first element is the canonical one, and thus tries to convert the others accordingly. Thus you get the "cannot convert &i32 to &u32" errors.

    What you need, however, is an array of (&str, &dyn ToSql) tuples.

    So you can do one of two things.

    First, explicitly cast the first param value to the right type:

    &[(":record_id", &record.id as &dyn ToSql),
      (":dataset_id", &dataset_id),
      (":value", "hello world")]
    

    Or second, use the named_params! macro that rusqlite provides, which is arguably prettier:

    statement.execute(named_params!{
        ":record_id": record.id,
        ":dataset_id": dataset_id,
        ":value": "hello world",
      });