Rust Diesel (async): allow duplicate values under UniqueViolation - rust

So I am trying to do an insert to the db and I want a field in the table to have multiple values while other fields remain unique
let result = diesel::insert_into(MyTable::table)
.values(&item)
.execute_async(storage.get_pool())
.await
.map_err(|e: AsyncError| match e {
AsyncError::Error(diesel::result::Error::DatabaseError(
DatabaseErrorKind::UniqueViolation,
_,
)) => StorageError::UniqueViolation(),
_ => StorageError::AsyncDbError(e),
})?;
Ok(())
}
I added the following code but I am still getting UniqueViolation error, so should I use a different error handling method or do I need to set the unique constraint at the beginning or is it possible to allow single field to have duplicate values?
let result = diesel::insert_into(MyTable::table)
.values(&item)
.on_conflict(MyTable::a)
.do_update()
.set(MyTable::a.eq(&item.a))
.execute_async(storage.get_pool())
.await
.map_err(|e: AsyncError| match e {
AsyncError::Error(diesel::result::Error::DatabaseError(
DatabaseErrorKind::UniqueViolation,
_,
)) => StorageError::UniqueViolation(),
_ => StorageError::AsyncDbError(e),
})?;
Ok(())
}
expected to pass without raising the UniqueViolation error

Related

Why does a value created inside a function borrow and how can I avoid this pattern?

I'm new to rust but an engineer of over 6 years in various other languages from Javascript to Go.
I'm wondering why here the value is borrowed when I convert the response body to an "object".
I understand that the function owns the value and then the value is destroyed when the function returns BUT functions exist to create and return values. So there's clearly something fairly big I'm missing here. Can someone set me straight?
let response = match self
.client
.index(IndexParts::IndexId(index, id))
.body(json!({
"index": index,
"body": doc,
}))
.send()
.await
{
Ok(response) => response,
Err(err) => {
return Err(Box::new(err));
}
};
let response_body = match response.json::<Value>().await {
Ok(response_body) => response_body,
Err(err) => {
return Err(Box::new(err));
}
};
let response_map = response_body.as_object();
Ok(response_map)
I understand that the function owns the value and then the value is destroyed when the function returns BUT functions exist to create and return values. So there's clearly something fairly big I'm missing here.
You need to return an owned value, not a reference into a local. I assume what you're doing now boils down to:
fn foo() -> &Map<String, Value> {
let x = serde_json::json!({}); // except you get it by http
x.as_object().unwrap() // except you do proper error handling
}
This doesn't compile because you're returning the reference to a local value. Instead, you need to return the value itself:
fn foo() -> Map<String, Value> {
let x = serde_json::json!({}); // except you get it by http
match x {
Value::Object(o) => o,
_ => unreachable!(), // you'd return Err(...)
}
}
But even this is more complicated than you need. Since you already deserialize the value yourself, and handle the errors, you can simply ask serde to deliver a Map<String, Value> to begin with:
let response_body = match response.json::<Map<String, Value>>().await {
Ok(response_body) => response_body,
Err(err) => ...
};
Of course, you'll also need to adjust the return type to return the actual value instead of a reference.

Accept multiple values on proc macro attribute

I wanted to be able to retrieve the content from an attribute like this:
#[foreign_key(table = "some_table", column = "some_column")]
This is how I am trying:
impl TryFrom<&&Attribute> for EntityFieldAnnotation {
type Error = syn::Error;
fn try_from(attribute: &&Attribute) -> Result<Self, Self::Error> {
if attribute.path.is_ident("foreign_key") {
match attribute.parse_args()? {
syn::Meta::NameValue(nv) =>
println!("NAME VALUE: {:?}, {:?}, {:?}",
nv.path.get_ident(),
nv.eq_token.to_token_stream(),
nv.lit.to_token_stream(),
),
_ => println!("Not interesting")
}
} else {
println!("No foreign key")
}
// ... More Rust code
}
Everything works fine if I just put in there only one NameValue. When I add the comma,
everything brokes.
The only error:
error: unexpected token
How can I fix my logic to enable the possibility of have more than just one NameValue?
Thanks
UPDATE: While writing this answer, I had forgotten that Meta has List variant as well which gives you NestedMeta. I would generally prefer doing that instead of what I did in the answer below for more flexibility.
Although, for your particular case, using Punctuated still seems simpler and cleaner to me.
MetaNameValue represents only a single name-value pair. In your case it is delimited by ,, so, you need to parse all of those delimited values as MetaNameValue instead.
Instead of calling parse_args, you can use parse_args_with along with Punctuated::parse_terminated:
use syn::{punctuated::Punctuated, MetaNameValue, Token};
let name_values: Punctuated<MetaNameValue, Token![,]> = attribute.parse_args_with(Punctuated::parse_terminated).unwrap(); // handle error instead of unwrap
Above name_values has type Punctuated which is an iterator. You can iterate over it to get various MetaNameValue in your attribute.
Updates based on comments:
Getting value out as String from MetaNameValue:
let name_values: Result<Punctuated<MetaNameValue, Token![,]>, _> = attr.parse_args_with(Punctuated::parse_terminated);
match name_values {
Ok(name_value) => {
for nv in name_value {
println!("Meta NV: {:?}", nv.path.get_ident());
let value = match nv.lit {
syn::Lit::Str(v) => v.value(),
_ => panic!("expeced a string value"), // handle this err and don't panic
};
println!( "Meta VALUE: {:?}", value )
}
},
Err(_) => todo!(),
};

How to use values from an array in matching ranges of values with ..= in rust?

I'm learning rust and I found something I can't just find in google.
I was experimenting with match and I wanted to use values from an array with the ..= syntax.
I know I'm doing something wrong, but I only know Js and Python and I feel I'm missing something basic that it's just known but not explained.
pub fn match_statement() {
println!("Match statement----------------------------------");
let mut country_code=0;
let country_codes_range: [i64; 4] = [1,999,50,66];
let country = match country_code {
34 => "Spain",
46 => "Sweden",
country_codes_range[0]..=country_codes_range[1] => "unknown",
_ => "invalid",
};
country_code=invalid_country;
println!(
"The {} country code is {} because is out of the range [{},{}]",
country, invalid_country, country_codes_range[0], country_codes_range[1]
);
}
the error I get is:
expected one of =>, #, if, or |, found [
on the line
country_codes_range[0]..=country_codes_range[1] => "unknown"
I don't know if the issue lies in my calling of items of the array, an incorrect use of ..= or another thing
Also, I guess I would get a similar error if I used a tuple instead of an array?
Thanks for your help
Rust needs to know the "values" of each match arm at compile time, so what you're describing isn't possible, instead you'll get an error saying runtime values cannot be references in patterns.
If you know what country_codes_range will be at compile time, you can make it available at compile time using const:
fn match_statement() {
let country_code = 123;
const COUNTRY_CODES_RANGE: [i64; 4] = [1, 999, 50, 66];
const FIRST: i64 = COUNTRY_CODES_RANGE[0];
const SECOND: i64 = COUNTRY_CODES_RANGE[1];
let country = match country_code {
34 => "spain",
46 => "sweden",
FIRST..=SECOND => "unknown",
_ => "invalid",
};
// ...
}
Note, the intermediate consts FIRST and SECOND are needed because currently Rust's parser doesn't support the a[i] syntax in patterns, though that is a separate problem to having a match use runtime values

Why does matching on the result of `find_one` return an Option instead of the expected type?

I'm trying to use the .get method on a BSON OrderedDocument that I'm retrieving from a MongoDB query. To handle any errors from the query I use a match operator on the query.
let id: String = "example".to_string();
let doc = match db.media.find_one(
Some(doc! {
"id" : id
}),
None,
) {
Ok(c) => c,
Err(e) => {
// do stuff with the error
return;
}
};
println!("{:?}", doc.get("field"));
This returns an error for the last line:
error[E0599]: no method named get found for type std::option::Option<bson::ordered::OrderedDocument> in the current scope
This must mean that the type returned from a match operation is an Option, not an OrderedDocument as I expected. Why is the c variable returned (in the example above) of type Option instead of the query's BSON document type, and how can I get the required type returned from a match? Or is this the wrong way of going about it?
The type returned from a match operation is whatever you put in it. In this case, the type is that of c.
find_one returns a Result<Option<Document>>. Since your patterns only match on the Result part, you get the inner Option. One solution is to use some more precise patterns:
let doc = match db.media.find_one(Some(doc! { "id": id }), None) {
Ok(Some(c)) => c,
Ok(None) => {
println!("Nothing found");
return;
}
Err(e) => {
println!("An error occurred: {:?}", e);
return;
}
};

Handling io::Result<DirEntry> without return on Err

I am trying to handle io::Result<DirEntry> returned from iterating on items of std::fs::read_dir() function. My concern is how to get the value of DirEntry when applying match from Result when Ok
let files = match fs::read_dir(&dir_path) {
Ok(items) => items,
//I actually want to leave function if there is an error here
Err(_) => return Err("Cannot read directory items".to_string()),
};
for item in files { // item: io::Result<DirEntry>
match item {
Ok(de) => de,// how to get `de` out of this scope??
//here I just want to print error and loop for next item
Err(_) => println!("{:?} cannot be accessed", item),
};
//do something with `de`
}
I tried also the following
let files = match fs::read_dir(&dir_path) {
Ok(items) => items,
Err(_) => return Err("Cannot read directory items".to_string()),
};
for item in files {
let file: DirEntry; // I get compile error for use of possibly uninitialized `file`
match item {
Ok(de) => file = de,
Err(_) => println!("{:?} cannot be accessed", item),
};
//do somthing with file
}
Maybe there is a better way for handling Result without using match in cases like this?
Your attempt declaring a variable outside the match is on the right track. You're getting an error about a possibly uninitialized variable because you're not forcing the flow of execution to progress to the next iteration on the Err branch. You can do so by adding continue to the Err branch. Then the variable can be initialized in the same way as the files variable, by assigning the result of the match expression directly to the variable.
for item in files {
let file = match item {
Ok(de) => de,
Err(_) => {
println!("{:?} cannot be accessed", item);
continue;
}
};
// do something with file
file;
}

Resources