How to collect mongodb::Cursor into a Vec in rust - mongodb

I'm trying to use the .find() method in mongodb. The output yeilds a mongodb::Cursor. I'm unable to convert the cursor into a vector so that I can wrap them in a json and send it to my front-end. This is the following idea I've tried
The following error message is:
the trait bound `Vec<user_model::User>: Extend<Result<user_model::User, mongodb::error::Error>>` is not satisfied\nthe following other types implement trait `Extend<A>`
I've already included and use futures::StreamExt; and use futures::TryFutureExt; and tried out .try_next() and .map() instead of .collect(), still cant parse it

Converting an element in the cursor to a User might fail. You may only collect into a Vec of Result with .collect().
let serial: Vec<Result<User, _>> = users.collect().await;
The easy way to get to Vec<User> here would be to use .try_collect(). Just make sure to handle the Err correctly and not just use unwrap as I've done here.
let serial: Vec<User> = users.try_collect().await.unwrap();

Here is how I managed this in one of my projects.
I simply iterate over the cursor calling next and push each item in a vector. I used the Document type to collect the results.
Using a match for each result allows to handle the errors properly, because extraction may fail.
let mut results: Vec<Document> = Vec::new();
while let Some(result) = cursor.next().await {
match result {
Ok(document) => {
results.push(document);
}
_ => {
return HttpResponse::InternalServerError().finish();
}
}
}

Related

What is the similar syntax of Swift's try? on Flutter?

On Swift, we have try? when the error handling is just little of importance, and we just want to silence the whole ordeal and just "give me null if it fails".
Is there such thing in Flutter? I tried to find it, but I can only find the usual try ... catch clause on the documentation. I mean, sure I can make it like this:
dynamic result = null;
try { result = json.decode(response.body); } catch (e) { }
But I'm trying to find something more like this if it exists:
var result = try? json.decode(response.body);
This will have the added value of not having to manually type the variable type beforehand and let the lint/editor/compiler do that by simply using var (though in this specific case the type result might be dynamic).
There isn't a Dart language feature to do it. I'm not familiar with how try? works in Swift, but one potential problem for such a construct in Dart is that without specifying what you catch, you could catch logical errors (such as failed assertions).
If you don't mind an extra level of function calls, you could write a function to make it (arguably) a bit more convenient (and this also would give you control over what to catch):
T? doOrDoNot<T>(T Function() closure) {
try {
return closure();
} on Exception {
return null;
}
}
var result = doOrDoNot(() => json.decode(response.body));

Iterating through an array of strings, fetched from MongoDB

I'm using the MongoKitten library to fetch documents from mongoDB.
I have the following document in my mongoDB:
{
...
foo: ["A", "B"]
...
}
I can query the db, but I can't loop through the foo array of the returned documents. For instance, let's say that I save the results of my query into mongoDocs.
for Doc in mongoDocs {
print(Doc["foo"] as Any) // prints ["A", "B"]
var tempFoos = [String]()
for foo in Doc["foo"] { // gives error: Type 'Value' does not conform to protocol "Sequence"
print("Foo: " + foo)
tempFoos.append(foo)
}
}
I understand the error. Basically, my foo array doesn't conform to the Sequence protocol that allows me to loop over it. But how do I fix that?
Edit - Here's the code I'm using to fetch the mongoDocs. I've printed the results and used other properties from them. I just can't seem to iterate through this array.
mongoDocs = try self.geographiesCollection!.find(matching: q, projecting: projection, limitedTo: 100)
Here's the relevant function in the MongoKitten source code. The function returns Cursor<Document>
Here you can check out how a framework dev explained handling this situation. MongoKitten closed issue 27
here are some quotes from his explanation incase the link becomes invalid.
"MongoKitten BSON library always returns an enum (Value) when subscripting a document."
"A BSON array is really just a document with keys from 0 to x, so the enum case for array has a document as it's associated value. Because Value can also be, say, a double or a date, it doesn't conform to the Sequence protocol.
The easiest way to iterate over the array is by using the document convenience accessor on Value. This returns the underlying document if Value is either an array or document, or an empty document if it's something else. You can then iterate like this:"
for (key, val) in doc["vals"].document {
print("Value is \(val)")
}
Convert it into an array:
for Doc in mongoDocs {
guard let arr = Doc["foo"] as? [String] else { continue }
for foo in arr {
// do your things
}
}
I think you're using BSON 3. Value is an enum with multiple cases, which you can see here. Here's my stab at it:
for doc in mongoDocs {
guard case Value.array(let fooArray)? = doc["foo"] {
fatalError("doc[\"foo\"] is not an array")
}
print(fooArray)
}

.prepare vs. .select

I have a working connection to a database in an iOS10 app, using SQLite.swift.
I want to select details for a specific university where I have an IHE_ID passed in from another view controller.
I would like to just select the row for that specific university, but I can't get a query to work. I can however loop through all the data with a prepare and then choose the one I need from that, which of course is more resource intensive than I need since I already have the specific IHE_ID passed in as anIHE Int from the sending view controller.
connection is working so omitting that code...
do {
let db = try Connection(destinationDBPath, readonly: true)
let IHEs = Table("IHE")
let IHE_ID = Expression<Int>("IHE_ID")
let IHE_Name = Expression<String>("IHE_Name")
let IHE_COE_Url = Expression<String>("IHE_COE_URL")
let IHE_Sector = Expression<Int>("IHE_Sector")
let IHE_COE_Name = Expression<String>("IHE_COE_Name")
for ihe in try db.prepare(IHEs){
if (ihe[IHE_ID] == anIHE){
// build this string, otherwise ignore rest of dataset (doing this because query isn't working)
buildIHE = "Name: \(ihe[IHE_Name])\n"
buildIHE.append("URL: \(ihe[IHE_COE_Url])\n")
// buildIHE.append("Sector: \(ihe[IHE_Sector])\n")
if (ihe[IHE_Sector] == 0) {
buildIHE.append("Sector: Public\n")
} else {
buildIHE.append("Sector: Private\n")
}
buildIHE.append("College of Education Name: \(ihe[IHE_COE_Name])\n")
}
}
print ("Got through building IHE portion of view")
What I'd like to do is use this instead of the for loop.
if let query = IHEs.select(IHE_ID,IHE_Name,IHE_COE_Url,IHE_Sector,IHE_COE_Name).filter(IHE_ID == anIHE){
print("Query successful for \(anIHE) with name \(query[IHE_Name])")
// more actions to build the string I need would then occur
} else {
print("Query has failed or returned nil")
}
Finally, I'll use the selected elements if I can get the query to work.
I think I probably just have something wrong with my syntax on the query, but any help is appreciated.
The line with the "if let query" has this error in Xcode:
Initializer for conditional binding must have Optional type, not 'Table'.
This leads me to think it's something with my use of the .select statement and just new to using SQLite.swift and swift in general.
Last thing is that anIHE comes into this function as an Int, and IHE_ID is Expression as shown in this code. I'm thinking this may be part of the problem.
The Initializer for conditional binding must have Optional type error means that the expression on the right of the if let v = expr statement is not an Optional: there is no point using if let, and the Swift compiler says that you should just write let v = expr.
And indeed, IHEs.select(...).filter(...) returns a non-optional value of type Table.
It is not the database row you expect, because the query has been defined, but not executed yet. After all, you weren't using db: where would the rows be loaded from?
The solution is to bring back the database connection, and load a single row. This is done with the pluck method.
if let ihe = try db.pluck(IHEs.select(...).filter(...)) {
print("Name \(ihe[IHE_Name])")
}

SequenceType / GeneratorType That Can Also Throw?

I'm in the process of learning Swift and as an exercise, I'm writing a wrapper around SQLite. As I was experimenting, I realized that for queries that return rows (like SELECT), I could implement the SequenceType / GeneratorType protocols so that I can return a set of data for each sqlite3_step that I perform.
In practice, sqlite3_step either returns a row or is done, but in theory, it could error out. I'm not doing anything crazy with SQLite. It's just a simple data store for me, so I'm not rewriting schemas on the fly or potentially ripping the database out from under itself, but the fact remains that IN THEORY sqlite3_step could fail.
The question then is, is there a proper way to handle errors in the SequenceType / GeneratorType pattern? GeneratorType's next method doesn't support a throws parameter and returning nil just dictates the end of a sequence. Would there be a good way to handle the error and propagate it up the chain?
You have a few options, depending on what you're looking for.
If you need the Sequence to be lazy, you could use a ResultType kind of thing:
enum SQLiteRow<T> {
case Success(T), FailureTypeOne, FailureTypeTwo
}
Then, your next() method would return a SQLiteRow<T>?, where T is the type of your row.
This fits in nicely with for-loops, as you can use it like this:
for case let .Success(row) in queries {...
so the successful queries are bound to the row variable. This is only if you want to filter out the failed queries. If you wanted to stop everything, you could switch within the for loop, or have a function like this:
func sanitize<
S : SequenceType, T where
S.Generator.Element == SQLiteRow<T>
>(queries: S) -> SQLiteRow<[T]> {
var result: [T] = []
result.reserveCapacity(queries.underestimateCount())
for query in queries {
switch query {
case let .Success(x): result.append(x)
case .FailureTypeOne: return .FailureTypeOne
case .FailureTypeTwo: return .FailureTypeTwo
}
}
return SQLiteRow.Success(result)
}
That will take a sequence of possibly-failed queries, and give back either a sequence of successful queries (if none failed), or a failure type representing the first failure it came across.
However, the second option there isn't lazy. Another eager way to do it would be to use map, which (as of the latest beta) can take a closure which throws:
func makeQuery(x: String) throws -> String {
return x
}
let queries = ["a", "b", "c"]
do {
let successful = try queries.map(makeQuery)
} catch {
// handle
}
Unfortunately the lazy version of map doesn't throw, so you have to evaluate the whole sequence if you want to throw like this.

What is the cleanest way to convert &str types to numeric types?

I find myself reading large CSV files and collecting the numerical elements into a Vec<&str>. Thereafter I have to convert them to numeric types and the simplest way I've found to do that is to implement a function like this:
fn to_u32(str: &str) -> u32
{
let str_num: Option<u32> = str.parse();
match str_num
{
Some(num) => num,
None => panic!("Failed to read number"),
}
}
This seems like a fairly common operation so I've sifted through the reference docs but haven't found anything that matches it. Is there a cleaner way to do this?
The Option type has a large variety of adapter methods which can be used to munge the data around more nicely than repeated matchs.
For example, there's unwrap and expect for just extracting the data out of a Some, panicking if the Option is None. The expect method is actually the closest to your written code: str.parse().expect("Failed to read number.").
However, it can often makes sense to use other the functions listed there, to propagate errors, avoiding the hard "crash" of a panic and allowing users (or yourself) to handle errors more centrally and with more control. It also often makes sense to use Result for this, which gives you the chance to pass along more information in the error case, and also allows one to use the try! macro, that said, one can easily define an equivalent of try! for Option:
macro_rules! option_try {
($e: expr) => {
match $e {
Some(x) => x,
None => return None
}
}
}
Well, you can use unwrap() to avoid pattern matching, but you should do it sparingly - with unwrap() you can't handle the actual parse error, so if the string does not represent a number, it'll panic:
let str_num: u32 = str.parse().unwrap();
if let is also an option:
if let Some(str_num) = str.parse::<u32>() {
// ...
}
You can also use unwrap_or() if you want to specify some default value:
let str_num: u32 = str.parse().unwrap_or(42);
Or you can use unwrap_or_default() which employs Default instance for u32:
let str_num: u32 = str.parse().unwrap_or_default();