`find_one` does not give ObjectId in proper format - mongodb

As the title states, here is the following code.
let users_coll = db
.database("foo")
.collection::<bson::oid::ObjectId>("users");
let user_id = users_coll
.find_one(
doc! { "email": &account.email },
mongodb::options::FindOneOptions::builder()
.projection(doc! { "_id": 1i32 })
.build(),
)
.await?
.unwrap();
But it fails at ? operator with the following mongodb::error::Error,
Error { kind: BsonDeserialization(DeserializationError { message: "expected map containing extended-JSON formatted ObjectId, instead found { \"_id\": ObjectId(\"62af199df4a16d3ea6056536\") }" }), labels: {}, wire_version: None, source: None }
And it is right. Given ObjectId should be in this format,
{
"_id": {
"$oid": "62af199df4a16d3ea6056536"
}
}
But I do not know how to handle this. Any help is appreciated.
Have a good day!

Your users collection isn't a collection of ObjectIds, it's actually a collection of documents which each contain an ObjectId. To let Rust know what to do with those, you should create a struct which represents the document, or at least the parts which you care about getting back from your query, and tell your collection to de-serialize into that struct:
use mongodb::bson::oid::ObjectId;
use serde::{Serialize, Deserialize};
#[derive(Debug, Default, Serialize, Deserialize)]
struct User {
_id: ObjectId,
}
#[tokio::main]
async fn main() {
let users_coll = db
.database("foo")
.collection::<User>("users");
let user_id: ObjectId = users_coll
.find_one(
doc! { "email": &account.email },
mongodb::options::FindOneOptions::builder()
.projection(doc! { "_id": 1i32 })
.build(),
)
.await?
.unwrap()
._id;
}
By default, the BSON fields have to match the struct fields exactly (_id in this case), but I'm pretty sure serde has a way to change that if you don't like the leading underscore.

Related

Vapor 4: how to include siblings including extra properties from the pivot table?

I am struggling a lot with how to return a model that contains a many-to-many relationship via a pivot table that contains extra fields. Basically, I want to return the full pivot table with the extra fields, but I can't figure how to do this.
Let's consider the following 3 models: Course, User, and the pivot between them, a Student. The Student model contains the extra field progress.
final class Course: Model, Content {
static let schema = "courses"
#ID(key: .id)
var id: UUID?
#Field(key: "name")
var name: String
init() { }
}
final class Student: Model {
static let schema = "students"
#ID(key: .id)
var id: UUID?
#Parent(key: "course_id")
var course: Course
#Parent(key: "user_id")
var user: User
#Field(key: "progress")
var progress: Int
init() { }
}
final class User: Model, Content {
static let schema = "users"
#ID(key: .id)
var id: UUID?
#Field(key: "name")
var name: String
#Field(key: "private")
var somePrivateField: String
init() { }
}
I have a route like this, which returns an array of courses:
func list(req: Request) throws -> EventLoopFuture<[Course]> {
return Course
.query(on: req.db)
.all()
.get()
}
The resulting JSON looks something like this:
[
{
"id": 1,
"name": "Course 1"
}
]
How can I also include the array of students, so that the end result is something like this?
[
{
"id": 1,
"name": "Course 1",
"students": [
{
"user": {
"id": 1,
"name": "User 1"
},
"progress": 0
},
{
"user": {
"id": 2,
"name": "User 2"
},
"progress": 100
},
]
]
I can add the users to the Course model like this:
#Siblings(through: Student.self, from: \.$course, to: \.$user)
public var users: [User]
And then change my route like this:
func list(req: Request) throws -> EventLoopFuture<[Course]> {
return Course
.query(on: req.db)
.with(\.$user)
.all()
.get()
}
But that only adds the user info to the result, NOT the extra properties on the pivot table (namely, progress). It kinda seems to me that even though pivot tables can have extra properties and the docs even specifically point that out, there are no good ways of actually dealing with this scenario since #Siblings don't point to the pivot at all.
Bonus question: I'd want the User model be mapped to a PublicUser model, so that private/internal fields are not part of the JSON result. See this question for what I mean. I want to do that same thing, but with the Student pivot's User model. Complicated, I know 😬
I encountered the same issue about accessing additional fields in the pivot table, but there is a fairly tidy way of accomplishing this. In addition to your siblings relationship, define a #Children relation from Course to Student. Then, in your query, do a nested with.
Put this in your Course model:
#Children(for:\.$course) var students: [Student]
Query:
let query = Course.query(on: req.db).with(\.$students){ $0.with(\.$user) }.all()
The first with gets the additional fields of the pivot table and then the nested with get the User model.
You can query and use the extra properties on the pivot table directly using the pivots property on the Sibling relation which eagerly loads the pivot table objects through the sibling relation for easy access.
Eg:
Course.query(on: db)
.with(\.$user)
.with(\.$user.$pivots).all().map { course in
// you can now access the loaded students using:
let students = course.$user.pivots
...
}

Typing of return type after mongodb projection

I am making a graphql resolver in rust, and am only fetching the fields from the graphql query in my mongodb database. However Rust complains that the fetched data, of course, is now not of the same type as the specified return type. What is the right way to do something like this.
I guess I could do #[serde(default)], but that doesn't work exactly as expected (I will explain later)
use async_graphql::*;
use serde::{Deserialize, Serialize};
use mongodb::{bson::doc, bson::oid::ObjectId, options::FindOptions, Collection};
#[derive(SimpleObject, Serialize, Deserialize, Debug)]
#[graphql(complex)]
struct Post {
#[serde(rename = "_id")]
pub id: ObjectId,
pub title: String,
// I could do something like
// #[serde(default)]
pub body: String,
}
#[ComplexObject]
impl Post {
async fn text_snippet(&self) -> &str {
let length = self.body.len();
let end = min(length, 5);
&self.body[0..end]
}
}
struct Query;
#[Object]
impl Query {
// fetching posts
async fn posts<'ctx>(&self, ctx: &Context<'ctx>) -> Vec<Post> {
let posts = ctx.data_unchecked::<Collection<Post>>();
let projection = // getting the projection doc here based on graphql fields, lets say doc! {"title": 1}
let options = FindOptions::builder().limit(10).projection(projection).build();
let cursor = posts.find(None, options).await.unwrap();
cursor.try_collect().await.unwrap_or_else(|_| vec![])
}
}
But when I run the query
{
posts {
id
title
textSnippet
}
}
i get
thread 'actix-rt:worker:0' panicked at 'called `Result::unwrap()` on an `Err` value: Error { kind: BsonDecode(DeserializationError { message: "missing field `body`" }), labels: [] }', server/src/schema/post.rs:20:46
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
and when i do the #[serde(default)] stuff on body, and I then query textSnippet and not body, the textSnippet is an empty string.
How do i fix this?
Could you wrap every field in Post with an Option and let the try_collect fill the returned fields for you?
You can create a struct with those fileds you need and use a collection of the new struct.
use async_graphql::*;
use serde::{Deserialize, Serialize};
use mongodb::{bson::doc, bson::oid::ObjectId, options::FindOptions, Collection};
#[derive(SimpleObject, Serialize, Deserialize, Debug)]
#[graphql(complex)]
struct Post {
#[serde(rename = "_id")]
pub id: ObjectId,
pub title: String,
// I could do something like
// #[serde(default)]
pub body: String,
}
#[derive(SimpleObject, Serialize, Deserialize, Debug)]
#[graphql(complex)]
struct PostTitle {
#[serde(rename = "_id")]
pub id: ObjectId,
pub title: String,
}
struct Query;
#[Object]
impl Query {
// fetching posts
async fn posts<'ctx>(&self, ctx: &Context<'ctx>) -> Vec<PostTitle> {
let posts = ctx.data_unchecked::<Collection<PostTitle>>();
let projection = doc! {"title": 1}
let options = FindOptions::builder().limit(10).projection(projection).build();
let cursor = posts.find(None, options).await.unwrap();
cursor.try_collect().await.unwrap_or_else(|_| vec![])
}
}

Using MongoDB Projection

I have the following structure in my database:
{
"_id": {
"$oid": "5fc4fc68fcd604bac9f61f71"
},
"init": {
"fullname": "Besikta Anibula",
"parts": [
"Besikta",
"Anibula"
],
"alt": "Besikta Ani."
},
"industry": "E-Commerce"
}
I´m trying to just access the init object and write the results to a structured variable in Go:
var InputData struct {
Fullname string `bson:"fullname" json:"fullname"`
Parts []string`bson:"parts" json:"parts"`
Alt string `bson:"alt" json:"alt"`
}
collectionRESULTS.FindOne(ctx, options.FindOne().SetProjection(bson.M{"init": 1})).Decode(&InputData)
js, _ := json.Marshal(InputData)
fmt.Fprint(writer, string(js))
But the result is empty:
{"fullname":"","parts":null,"alt":""}
It is working when not using a projection like:
var InputData struct {
Ident primitive.ObjectID `bson:"_id" json:"id"`
}
collectionRESULTS.FindOne(ctx, bson.M{}).Decode(&InputData)
js, _ := json.Marshal(InputData)
fmt.Fprint(writer, string(js))
Result as expected:
{"id":"5fc4fc68fcd604bac9f61f71"}
You set the projection to only retrieve the init property of the result documents, and then you try to unmarshal into a struct value that does not have any matching field for the init value, so nothing will be set in that struct value.
You must use a value that has an init field, like this wrapper Result struct:
type Result struct {
Init InputData `bson:"init"`
}
type InputData struct {
Fullname string `bson:"fullname" json:"fullname"`
Parts []string `bson:"parts" json:"parts"`
Alt string `bson:"alt" json:"alt"`
}
Use it like this:
var result Result
err := collectionRESULTS.FindOne(ctx, bson.M{}, options.FindOne().
SetProjection(bson.M{"init": 1})).Decode(&result)
if err != nil {
// handle error
}

Algolia sorting by geolocation in Swift

I have a problem with Algolia geolocation sorting in iOS App.
I need to display all documents in a range of 100 km from the user location.
I have multiple documents in my Indice. The document looks like this:
"document": {
"price": 5000,
"unit": "cały projekt",
"_geoloc": {
"lat": 54.5,
"lng": 18.55
},
"title": "Test ogloszenia",
"range": 0,
"activeFrom": {
"_seconds": 1597042800,
"_nanoseconds": 0
}
}
In my Algolia Ranking and Sorting, I have default set GEO.
My Swift code for sorting locations looks like below:
func getAnnouncesLocation(location: CLLocationCoordinate2D, completion: #escaping ([Announcement]) -> ()) {
var announcementsArray = [Announcement]()
let query = Query(query: "")
query.aroundLatLng = LatLng(lat: location.latitude, lng: location.longitude)
query.aroundRadius = .explicit(100000) // 100 km
collectionIndex = searchClient.index(withName: "products_geolocation")
collectionIndex.search(query) { (content, error) in
guard let content = content else {
if let error = error {
print(error.localizedDescription)
}
return
}
print("HITS \(content)")
}
}
The code doesn't return any error but the content is empty.
Another sorting like by price works perfectly. The only problem is with geolocation.
If that can help to set sorting by the price I need to add a sort-by attribute like this: document.price in Dashboard.
I am saving data to Algolia from my server in Node.js and there I am creating _geoloc value.
The latitude and longitude are hardcoded for testing so there isn’t a problem with async.
Thank you for any kind of help.
Regards
Matt
I found a bug in my project.
While sending the document to Algolia I send like this:
const record = {
objectID: doc.id,
document: document
};
where document contained all properties and also _geoloc property. After some investigation, I separated _geoloc from the document I sent it like below:
const record = {
objectID: doc.id,
document: document,
_geoloc: coordinates
};
Now everything works fine.
Cheers

Nested field update using golang struct in mongoDB

I am facing a issue with update document using golang mongo driver.
Scenario: I want to update a field that is nested in a struct. For ex: StructOuter -> structInner -> field1, field2, field3. Now if I want to update the field3 and I have the corresponding value as another struct, how can i go ahead by just updating this field alone. I tried with code below but it updates the whole structInner leaving only field3:
conv, _ := bson.Marshal(prod)
bson.Unmarshal(conv, &updateFields)
update := bson.M{
"$set": updateFields,
}
model.SetUpdate(update).
Sample JSON:
{
"field_one": "value",
"data": {
"field_two": [
"data1",
"data2"
],
"field_three": "check",
"field_four": "abc",
"field_five": "work",
}
}
I want to avoid hard coded field query for updating.
Just want to know if this is supported, if yes can you help me with it and also point to some deep dive links on this.
If you have control over the code, you could try creating methods on the struct. These methods can help you construct the fields path to perform partial update. For example, if you have the following structs:
type Outer struct {
Data Inner `bson:"data"`
}
type Inner struct {
FieldThree string `bson:"field_three"`
FieldFour string `bson:"field_four"`
}
You can try adding methods as below to construct update statements. These are returned in the dot-notation format.
func (o *Outer) SetFieldThree(value string) bson.E {
return bson.E{"data.field_three", value}
}
func (o *Outer) SetFieldFour(value string) bson.E {
return bson.E{"data.field_four", value}
}
To update, you can construct the statements like below:
x := Outer{}
var updateFields bson.D
updateFields = append(updateFields, x.SetFieldThree("updated"))
updateFields = append(updateFields, x.SetFieldFour("updated"))
statement := bson.D{{"$set", updateFields}}
result, err := collection.UpdateOne(ctx, bson.M{}, statement)