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

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?

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);
}

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.

Related

What is the best way to store an enum in a database in rusqlite?

If I have a simple struct, one attribute of which contains a simple enum, how can I best store examples of this struct with their enumerations in the rusqlite database? Something like:
use rusqlite::{params, Connection, Result};
enum Sex{
Unknown,
Male,
Female,
}
struct Person{
name: String,
sex: Sex
}
fn main() -> Result<()> {
let conn = Connection::open_in_memory()?;
conn.execute(
"CREATE TABLE people(
name TEXT NOT NULL,
sex TEXT NOT NULL
)",
(), // empty list of parameters.
)?;
let person_01 = Person{
name: String::from("Adam"),
sex: Sex::Male
};
conn.execute(
"INSERT INTO people (name, sex) VALUES (?1, ?2)",
(&person_01.name, &person_01.sex),
)?;
Ok(())
}
The problem is that sqlite only allows data of restricted types (NULL, INTEGER, REAL, TEXT), trying to use TEXT here for the enum gives the following error:
error[E0277]: the trait bound `Sex: ToSql` is not satisfied
--> src/main.rs:33:9
|
31 | conn.execute(
| ------- required by a bound introduced by this call
32 | "INSERT INTO tasklist (name, sex) VALUES (?1, ?2)",
33 | (&person_01.name, &person_01.sex),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `ToSql` is not implemented for `Sex`
This error makes sense, but what is the best way to implement this? Cast the enum to int? I read here that this is "removes the guarantee that values always represent the variants of the enum", which I agree with. It would be nicer to match the string.
I have tried to do this using strum, which allows me to add to_str & from_str to the enum, allowing it to be added to the database like so:
#[derive(strum_macros::Display, strum_macros::EnumString, Debug)]
enum Sex{
Unknown,
Male,
Female,
}
...
conn.execute(
"INSERT INTO people (name, sex) VALUES (?1, ?2)",
(&person_01.name, &person_01.sex.to_string())
)?;
and retrieved like so:
let mut stmt = conn.prepare("SELECT name, sex FROM people")?;
let person_itr = stmt.query_map([], |row|{
Ok(
Person{
name: row.get(0)?,
sex: Sex::from_str(String::as_str(&row.get(1)?)).unwrap(),
}
)
});
but this feels messy. Is there a better way?
I have seen here people manually implementing FromSqlRow for the enum, but is there a better (quicker) way?
The right way to handle this is to implement ToSql and FromSql directly on your enum. This will make using it substantially more ergonomic, and possibly more efficient since you don't first have to convert to a type with an allocation, like String.
It also means the conversion to/from a string doesn't "infect" every interaction you have with the database; the conversion becomes automatic. So, while there is a bit more boilerplate, it will pay off every time you use this type in conjunction with the database.
impl ToSql for Sex {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(self.to_string().into())
}
}
impl FromSql for Sex {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str()?.parse()
.map_err(|e| FromSqlError::Other(Box::new(e)))
}
}
Now you can just do this when converting to Person:
sex: row.get(1)?,
Note that FromSqlRow is a trait specific to a postgres client; rusqlite has no such trait. If you wanted to, you could create a factory method on Person that constructs from a Row. That's up to you.

executing method on variable pointer moves the variable itself?

I'm using the rusqlite crate and am doing some queries, basically i'm just trying to check the length of the query results before trying to proceed, however I'm running into error[E0382]: use of moved value: key_rows whilst trying to compile, I don't understand since i'm borrowing a reference to the variable so it wouldn't move it's local in memory?
Maybe it's due to the method i'm calling on the variable's pointer?
Full compiler error:
error[E0382]: use of moved value: `key_rows`
--> src/handle.rs:126:16
|
108 | let key_rows = key_stmt.query_map(&[(":key", key.as_str())], |row| {
| -------- move occurs because `key_rows` has type `MappedRows<'_, [closure#src/handle.rs:108:66: 113:6]>`, which does not implement the `Copy` trait
...
117 | if(&key_rows.count() == &0){
| ------- `key_rows` moved due to this method call
...
126 | for row in key_rows {
| ^^^^^^^^ value used here after move
|
note: this function takes ownership of the receiver `self`, which moves `key_rows`
Erroneous code:
let mut key_stmt = conn.prepare("SELECT id , key FROM key_table WHERE key = :key;").unwrap();
let key_rows = key_stmt.query_map(&[(":key", key.as_str())], |row| {
Ok(Table {
id: row.get(0)?,
payload: row.get(1)?,
})
}).unwrap();
//Checking that the key exists:
if(&key_rows.count() == &0){
panic!("Can't find the key...")
}
//Putting in a default value since the compiler is worried.
let mut reference_id : i32 = 0;
//For loop is nessessary since MappedRow type cannot be indexed regularly (weird)
for row in key_rows {
reference_id = row.unwrap().id;
println!("{:?}", reference_id.to_string());
}
You aren't borrowing key_rows then checking the count, but rather calling key_rows.count(), then borrowing the result.
Iterator#count consumes the entire iterator, returning how many elements were traversed.
let mut reference_id = key_rows.next().unwrap_or_else(|| panic!("Can't find the key..."));
// Remove the code past this point if you are only expecting one, or the first value.
for row in key_rows {
reference_id = row.unwrap().id;
println!("{:?}", reference_id.to_string());
}

How to return a single element from a Vec from a function?

I'm new to Rust, and I'm trying to make an interface where the user can choose a file by typing the filename from a list of available files.
This function is supposed to return the DirEntry corresponding to the chosen file:
fn ask_user_to_pick_file(available_files: Vec<DirEntry>) -> DirEntry {
println!("Which month would you like to sum?");
print_file_names(&available_files);
let input = read_line_from_stdin();
let chosen = available_files.iter()
.find(|dir_entry| dir_entry.file_name().into_string().unwrap() == input )
.expect("didnt match any files");
return chosen
}
However, it appears chosen is somehow borrowed here? I get the following error:
35 | return chosen
| ^^^^^^ expected struct `DirEntry`, found `&DirEntry`
Is there a way I can "unborrow" it? Or do I have to implement the Copy trait for DirEntry?
If it matters I don't care about theVec after this method, so if "unborrowing" chosen destroys the Vec, thats okay by me (as long as the compiler agrees).
Use into_iter() instead of iter() so you get owned values instead of references out of the iterator. After that change the code will compile and work as expected:
fn ask_user_to_pick_file(available_files: Vec<DirEntry>) -> DirEntry {
println!("Which month would you like to sum?");
print_file_names(&available_files);
let input = read_line_from_stdin();
let chosen = available_files
.into_iter() // changed from iter() to into_iter() here
.find(|dir_entry| dir_entry.file_name().into_string().unwrap() == input)
.expect("didnt match any files");
chosen
}

How do I add an element to the value of a hash map that pairs strings with vectors of strings?

Like many new Rustaceans, I've been working my way through the Rust Book. I'm reading through the chapter on collections, and I'm stuck on one of the exercises. It reads:
Using a hash map and vectors, create a text interface to allow a user to add employee names to a department in a company. For example, “Add Sally to Engineering” or “Add Amir to Sales.” Then let the user retrieve a list of all people in a department or all people in the company by department, sorted alphabetically.
Here is my code so far:
use std::collections::hash_map::OccupiedEntry;
use std::collections::hash_map::VacantEntry;
use std::collections::HashMap;
use std::io;
fn main() {
println!("Welcome to the employee database text interface.\nHow can I help you?");
let mut command = String::new();
io::stdin()
.read_line(&mut command)
.expect("Failed to read line.");
command = command.to_lowercase();
let commands = command.split_whitespace().collect::<Vec<&str>>();
let mut department = commands[3];
let mut name = commands[1];
let mut employees = HashMap::new();
if commands[0] == "add" {
match employees.entry(department) {
VacantEntry(entry) => entry.entry(department).or_insert(vec![name]),
OccupiedEntry(entry) => entry.get_mut().push(name),
}
}
}
The compiler returns the following error:
error[E0532]: expected tuple struct/variant, found struct `VacantEntry`
--> src/main.rs:26:13
|
26 | VacantEntry(entry) => entry.entry(department).or_insert(vec![name]),
| ^^^^^^^^^^^ did you mean `VacantEntry { /* fields */ }`?
error[E0532]: expected tuple struct/variant, found struct `OccupiedEntry`
--> src/main.rs:27:13
|
27 | OccupiedEntry(entry) => entry.get_mut().push(name),
| ^^^^^^^^^^^^^ did you mean `OccupiedEntry { /* fields */ }`?
I'm not exactly sure what I'm doing wrong. What do these errors mean, and what can I do to fix them and get my code to compile?
You need to understand the difference between an enum variant and the type of that variant. The variants of Entry are Vacant, and Occupied. You need to match against those variants, not their types.
One way to fix your code would be like this:
use std::collections::hash_map::Entry::{Vacant, Occupied};
match employees.entry(department) {
Vacant(entry) => { entry.insert(vec![name]); },
Occupied(mut entry) => { entry.get_mut().push(name); },
}
But a much simpler solution would to use the return value of or_insert, which is a reference to the vector, and push onto it.
employees
.entry(department)
.or_insert(Vec::new())
.push(name);

Dump in memory sqlite using jooq context to byte[]

Need to now get byte[] from in memory db as below.
DSLContext dsl = DSL.using("jdbc:sqlite::memory:");
Can we use DSLContext to get inputstream / byteArray ?
If multiple such "in memory" contexts are created in separate threads, can there be any race condition w.r.t sqlite read /write from DSLContext side ?
The jOOQ side of your question is quite easy to answer. What you're doing there is incomplete. If you're using the DSL.using(String), DSL.using(String, Properties), or DSL.using(String, String, String) methods, you will get a "resourceful" DSLContext, which you have to close yourself (in order to close the underlying JDBC connection. E.g.:
try (DSLContext dsl = DSL.using("jdbc:sqlite::memory:") {
...
}
Do note that jOOQ creates an underlying JDBC connection for you and operates on that for all methods called on dsl. Apart from that, everything works exactly the same way as if you had been using a JDBC connection as such:
try (Connection connection = DriverManager.getConnection("jdbc:sqlite::memory:") {
...
}
Regarding your specific questions:
Can we use DSLContext to get inputstream / byteArray ?
Of course, just fetch a byte array from your database using jOOQ, there's nothing special about it.
If multiple such "in memory" contexts are created in separate threads, can there be any race condition w.r.t sqlite read /write from DSLContext side ?
Without formally validating the docs, this can be checked empirically relatively simply:
try (
DSLContext ctx1 = DSL.using("jdbc:sqlite::memory:");
DSLContext ctx2 = DSL.using("jdbc:sqlite::memory:");
) {
ctx1.execute("create table x (i int primary key, j varchar(10))");
ctx1.execute("insert into x values (1, 'c1')");
ctx2.execute("create table x (i int primary key, j varchar(10))");
ctx2.execute("insert into x values (1, 'c2')");
System.out.println(ctx1.fetch("select i, j from x"));
System.out.println(ctx2.fetch("select i, j from x"));
}
Not only is there no exception when re-creating the table x, there is also no constraint violation on the second insertion of the primary key value 1. The output being:
+----+----+
| i|j |
+----+----+
| 1|c1 |
+----+----+
+----+----+
| i|j |
+----+----+
| 1|c2 |
+----+----+
And, as soon as you close the connection / DSLContext, the data is gone

Resources