Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get back one row's data in rusqlite?

Tags:

sqlite

rust

  • rustc 1.38.0 (625451e37 2019-09-23)
  • rusqlite 0.20.0

I'm writing a program where I need to get back the id from the last insertion that sqlite just created.

db.execute("insert into short_names (short_name) values (?1)",params![short]).expect("db insert fail");

let id = db.execute("SELECT id FROM short_names WHERE short_name = '?1';",params![&short]).query(NO_PARAMS).expect("get record id fail");

let receiver = db.prepare("SELECT id FROM short_names WHERE short_name = "+short+";").expect("");
let id = receiver.query(NO_PARAMS).expect("");
println!("{:?}",id);

What I should be getting back is the id value sqlite automatically assigned with AUTOINCREMENT.

I'm getting this compiler Error:

error[E0599]: no method named `query` found for type `std::result::Result<usize, rusqlite::Error>` in the current scope
  --> src/main.rs:91:100
   |
91 |         let id = db.execute("SELECT id FROM short_names WHERE short_name = '?1';",params![&short]).query(NO_PARAMS).expect("get record id fail");
   |                                                                                                    ^^^^^

error[E0369]: binary operation `+` cannot be applied to type `&str`
  --> src/main.rs:94:83
   |
94 |         let receiver = db.prepare("SELECT id FROM short_names WHERE short_name = "+short+";").expect("");
   |                                   ------------------------------------------------^----- std::string::String
   |                                   |                                               |
   |                                   |                                               `+` cannot be used to concatenate a `&str` with a `String`
   |                                   &str
help: `to_owned()` can be used to create an owned `String` from a string reference. String concatenation appends the string on the right to the string on the left and may require reallocation. This requires ownership of the string on the left
   |
94 |         let receiver = db.prepare("SELECT id FROM short_names WHERE short_name = ".to_owned()+&short+";").expect("");
   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^

error[E0277]: `rusqlite::Rows<'_>` doesn't implement `std::fmt::Debug`
  --> src/main.rs:96:25
   |
96 |         println!("{:?}",id);
   |                         ^^ `rusqlite::Rows<'_>` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
   |
   = help: the trait `std::fmt::Debug` is not implemented for `rusqlite::Rows<'_>`
   = note: required by `std::fmt::Debug::fmt`

Line 94: I understand that rust's String is not the right type for the execute call, but I'm not sure what to do instead.

I suspect what needs to happen is the short_names table needs to be pulled form the database and then from the rust representation of the table for get the id that matches the short I'm trying to work with. I've been going off this example as a jumping off point, but It's dereferenced it's usefulness. The program I'm writing calls another program and then babysits it while this other program runs. To reduce over head I'm trying to not use OOP for this current program.

How should I structure my request to the database to get by the id I need?

like image 837
9716278 Avatar asked Oct 18 '19 11:10

9716278


2 Answers

Okay. First off, we are going to use a struct, because, unlike in Java, it is literally equivalent to not using one in this case, except that you gain in being able to keep things tidy.

You're trying to emulate Connection::last_insert_rowid(), which isn't a terribly smart thing to do, particularly if you are not in a transaction. We're also going to clear this up for you in a nice and neat fashion:

use rusqlite::{Connection};

pub struct ShortName {
    pub id: i64,
    pub name: String
}

pub fn insert_shortname(db: &Connection, name: &str) -> Result<ShortName, rusqlite::Error> {
    let mut rtn = ShortName {
        id: 0,
        name: name.to_string()
    };
    db.execute("insert into short_names (short_name) values (?)",&[name])?;
    rtn.id = db.last_insert_rowid();
    Ok(rtn)
}

You can convince yourself that it works with this test:

#[test]
fn it_works() {
    let conn = Connection::open_in_memory().expect("Could not test: DB not created");
    let input:Vec<bool> = vec![];
    conn.execute("CREATE TABLE short_names (id INTEGER PRIMARY KEY AUTOINCREMENT, short_name TEXT NOT NULL)", input).expect("Creation failure");
    let output = insert_shortname(&conn, "Fred").expect("Insert failure");
    assert_eq!(output.id, 1);
}
like image 192
Sébastien Renauld Avatar answered Sep 29 '22 21:09

Sébastien Renauld


In rusqlite execute does not return a value. To return a value from a sqlite operation you need to use prepare and a variant of query. While much of Rust allows you to leave type up to the compiler, for rusqite you need to give the receiving variable a type.

There is not currently a way in rusqlite to take a single row out of a query. The type of rows is not a type iterator, so you need to progress over it with a while loop, that will progress based on the error type of rows. After the loop runs once it will return that there are no other row in rows and exit; if there is only one row from the query.

You can use query_named to modify the sql query your sanding. Using the named_params!{} macro will allow you to use a String to send information to the command.

use rusqlite::*;

fn main() {
    let short = "lookup".to_string(); // example of a string you might use
    let id:i64 = 0;
    { // open for db work
        let db = Connection::open("YourDB.db").expect("db conn fail");
        let mut receiver = db
            .prepare("SELECT * FROM short_names WHERE short_name = :short;")
            .expect("receiver failed");
        let mut rows = receiver
            .query_named(named_params!{ ":short": short })
            .expect("rows failed");
        while let Some(row) = rows.next().expect("while row failed") {
            id=row.get(0).expect("get row failed");
        }
    } // close db work
    println!("{}", id);
}

In the above example, we open a scope with {} around the database transaction, this will automatically close the db when it goes out of scope. Notice that we create our db connection and do all our work with the database solely inside the {}. This allows us to skip closing the db with the explicate command and is done by inference taken by the compiler from the scope: {}. The variables short and id, created in the scope of main(), are still available to the db scope and the rest of the scope of main(). While id is not assigned until the db scope, but it's defined outside of the scope, the scope of main, so that is where id's lifetime begins. id does not need to be mutable because it's only assigned once, if there is in fact only one row to retrieve, the while loop will only assign it once. Otherwise, if the database does not behave as expected this will result in an error.

like image 38
9716278 Avatar answered Sep 29 '22 22:09

9716278