ObjectID automatically set to "0...0" in go with official mongoDB driver - mongodb

I'm trying to save user entries in a MongoDB database with Go. Users should get an ID automatically. I'm using the offical MongoDB Go driver.
My sources were especially https://vkt.sh/go-mongodb-driver-cookbook/ and https://www.mongodb.com/blog/post/mongodb-go-driver-tutorial.
Struct looks like this:
type User struct {
ID primitive.ObjectID `json:"_id" bson:"_id"`
Fname string `json:"fname" bson:"fname"`
Lname string `json:"lname" bson:"lname"`
Mail string `json:"mail" bson:"mail"`
Password string `json:"password" bson:"password"`
Street string `json:"street" bson:"street"`
Zip string `json:"zip" bson:"zip"`
City string `json:"city" bson:"city"`
Country string `json:"country" bson:"country"`
}
Setting up the database (connection works) and signing up users (based on an HTTP-Request r with a user in it's body):
ctx := context.Background()
uriDB := "someURI"
clientOptions := options.Client().ApplyURI(uriDB)
client, err := mongo.Connect(ctx, clientOptions)
collection := client.Database("guDB").Collection("users")
...
var user User
err := json.NewDecoder(r.Body).Decode(&user)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
result, err := collection.InsertOne(ctx, user)
...
When I enter the first user, it is added to the collection, but the ID looks like this:
_id:ObjectID(000000000000000000000000)
If I now want to enter another user, I get the following error:
multiple write errors: [{write errors: [{E11000 duplicate key error collection: guDB.users index: _id_ dup key: { : ObjectId('000000000000000000000000') }}]}, {<nil>}]
So it seems like again ObjectID 000000000000000000000000 is assigned.
I expected the ID to be automatically set to a unique value for each entry.
Do I have to manually set the ID or how can I assign unique IDs to users?

Per the documentation you linked, you must set the object ID yourself when using structs:
_, err := col.InsertOne(ctx, &Post{
ID: primitive.NewObjectID(), // <-- this line right here
Title: "post",
Tags: []string{"mongodb"},
Body: `blog post`,
CreatedAt: time.Now(),
})
The examples before that using bson.M don't need to specify an ID because they don't send the _id field at all; with a struct, the field is being sent with its zero value (as you've seen).

If you set a document _id, mongodb will use that _id for the document during insertion and will not generate. You have to either omit it, or set it manually with primitive.NewObjectID().

Related

GORM unable to update data in one to many relationship

I have two tables users and documents. They are related in such a way that each document must belong to a user using a one to many relationship. When I try updating a document I get the following error
ERROR: insert or update on table "documents" violates foreign key
constraint "fk_users_documents" (SQLSTATE 23503)
Here are my structs definition and update function
type User struct {
gorm.Model
Name string
Email string
Password string
Documents []Document
}
type Document struct {
gorm.Model
Name string
UserID uint
}
//Update document by id
func (h handler)UpdateDocument(w http.ResponseWriter, r *http.Request) {
// once again, we will need to parse the path parameters
var updatedDoc Document
reqBody, _ := ioutil.ReadAll(r.Body)
json.Unmarshal(reqBody, &updatedDoc)
var document Document
vars := mux.Vars(r)
id := vars["id"]
if result := Db.First(&updatedDoc, id); result.Error != nil {
fmt.Println(result.Error)
}
document.Name=updatedDoc.Name
Db.Save(&document)
json.NewEncoder(w).Encode(&updatedDoc)
}
You are calling Db.Save(&document) but document has only its Name field populated. This means that the UserID is set to 0. I'm guessing you don't have any user with ID 0 present in the User table, therefore this violates the foreign key constraint.
The UserID field shall always be set to an existing user when updating a document otherwise the query will fail.
Regardless of this, I'd suggest you to study a bit of database and golang basics because the code you posted is quite messy.

How to use Find().Select().One() in go mongo-driver library

This code is working fine in go mgo library
result interface{}
err = getCollection.Find(bson.M{}).Select(bson.M{"_id": 1}).One(&result)
but I want to perform this using go mongo-driver library
I have tried below code but it is not working as the above one
err = getCollection.FindOne(ctx, bson.M{}, options.FindOne().SetProjection(bson.M{"_id": 1})).Decode(&result)
My test collection data is
example{
"_id":ObjectId(),
"Name":"qwert"
}
Anyone suggest me how can we achieve this in mongo-driver?
i can't comment on your question because i am new contributor here, i am using mongo-driver in my project now, i have tried to fetch only projection only some fields to be show up,
can you specific the argument on second for filtering ?
var (
opt options.FindOneOptions
modelStruct model.Person
)
filter := bson.M{"email": "hello#test.com"}
opt.SetProjection(bson.M{"name": 1})
err := collection.findOne(context.Background(), filter, opt).Decode(&modelStruct)
if that doesn't work, then you should limit the struct , make sure in your model.Person has data like this
type Person struct {
Name string `json:"name" bson:"name"`
Gender string `json:"gender" bson:"gender"`
}
or you can just make own model for limit the fields:
var personLimitOnlyGetName struct {
Name string `json:"name" bson:"name"`
}
// please look carefully in your collection field for camelCase
opt.SetProjection(bson.M{"name": 1})

How to insert a document with mgo and get the value returned

For the record, I'm learning Go. I'm trying to use and the mgo package and I'd like to insert a new document and return this newly created document to user (I'm trying to write a basic API). I've wrote the following code:
EDIT: Here's the struct for the model:
type Book struct {
ISBN string `json:"isbn"`
Title string `json:"title"`
Authors []string `json:"authors"`
Price string `json:"price"`
}
session := s.Copy()
defer session.Close()
var book Book
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&book)
if err != nil {
ErrorWithJSON(w, "Incorrect body", http.StatusBadRequest)
return
}
c := session.DB("store").C("books")
info, err := c.Upsert(nil, book)
if err != nil {
ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed insert book: ", err)
return
}
respBody, err := json.MarshalIndent(info, "", " ")
if err != nil {
log.Fatal(err)
}
ResponseWithJSON(w, respBody, http.StatusOK)
Please note that Book is a struct I have created earlier. The above code does work but what it returns is the upsert result like so:
{
"Updated": 1,
"Removed": 0,
"Matched": 1,
"UpsertedId": null
}
Which is not the recently created object. How can I get the the recently created object to return as a response (please note that ideally I'd like the confirmation that the document was successfully inserted. I have seen other questions where the ID is generated beforehand but for what I've seen it doesn't confirm that the document was created was it?)
Let's clear the concepts first. In MongoDB, each document must have an _id property which acts as its unique document identifier inside a collection. Either you provide the value of this _id or it is assigned automatically by MongoDB.
So it would be ideal (or it's strongly recommended) for your model types to include a field for the _id document identifier. Since we're talking about books here, books already have a unique identifier called ISBN, which you may opt to use as the value of the _id field.
The mapping between MongoDB fields and Go struct fields must be specified using the bson tag (not json). So you should provide bson tag values along with json tags.
So change your model to:
type Book struct {
ISBN string `json:"isbn" bson:"_id"`
Title string `json:"title" bson:"title"`
Authors []string `json:"authors" bson:"authors"`
Price string `json:"price" bson:"price"`
}
If you want to insert a new document (a new book), you should always use Collection.Insert().
And what will be the ID of the newly inserted document? The field you set to the Book.ISBN field as we declared it to be the document ID with the bson:"_id" tag.
You should only use Collection.Upsert() if you are not sure whether the document already exists, but either way you want it to be the document you have at hand. Collection.Upsert() will try to find a document to update, and if one is found, that will be updated. If no document is found, then an insert operation will be performed. The first parameter is the selector to find the document to be updated. Since you passed nil, that means any document may qualify, so one will be selected "randomly". So if you already have books saved, any may get selected and get overwritten. This is certainly not want you want.
Since now the ISBN is the ID, you should specify a selector that filters by ISBN, like this:
info, err := c.Upsert(bson.M{"_id": book.ISBN}, book)
Or since we're filtering by ID, use the more convenient Collection.UpsertId():
info, err := c.UpsertId(book.ISBN, book)
If you want to update an existing document, for that you may use Collection.Update(). This is similar to Collection.Upsert(), but the difference is that if no documents match the selector, an insert will not be performed. Updating a document matched by ID can also be done with the more convenient Collection.UpdateId() (which is analogue to Collection.UpsertId()).
For other documents which do not have a unique identifier naturally (like books having ISBN), you may use generated IDs. The mgo library provides the bson.NewObjectId() function for such purpose, which returns you a value of type bson.ObjectId. When saving new documents with Collection.Insert(), you can acquire a new unique ID with bson.NewObjectId() and assign it to the struct field that is mapped to the MongoDB _id property. If the insert succeeds, you can be sure the document's ID is what you just set before calling Collection.Insert(). This ID generation is designed to work even in a distributed environment, so it will generate unique IDs even if 2 of your nodes attempt to generate an ID at the same time.
So for example if you don't have the ISBN for a book when saving it, then you must have a separate, designated ID field in your Book type, for example:
type Book struct {
ID bson.ObjectId `bson:"_id"`
ISBN string `json:"isbn" bson:"isbn"`
Title string `json:"title" bson:"title"`
Authors []string `json:"authors" bson:"authors"`
Price string `json:"price" bson:"price"`
}
And when saving a new book:
var book Book
// Fill what you have about the book
book.ID = bson.NewObjectId()
c := session.DB("store").C("books")
err = c.Insert(book)
// check error
// If no error, you can refer to this document via book.ID
Banged my head for a while on this.
info, err := c.Upsert(nil, book)
The nil selector for the upsert will match everything so if your collection is empty the selector won't match and all will be fine, the info object will contain the ObjectId in the UpsertedId field, but on every following upsert with nil selector the collection will have records and the nil selector for the upsert will match, therefore it won't return you the UpsertedId and you will be updating the first matched record.
You could use never matching selector for the upsert (which was my solution), but note that this way you will only insert and get the inserted ObjectId and never update a record.
You can checkout the following test:
func TestFoo(t *testing.T) {
foo := struct {
Bar string `bson:"bar"`
}{
Bar: "barrr",
}
session, _ := mgo.DialWithInfo(&mgo.DialInfo{
Addrs: []string{"127.0.0.1:27017"},
})
coll := session.DB("foo").C("bar")
coll.DropCollection()
info, _ := coll.Upsert(nil, foo) // will insert
count, _ := coll.Count()
fmt.Printf("%+v, collecton records:%v\n", info, count) // &{Updated:0 Removed:0 Matched:0 UpsertedId:ObjectIdHex("5c35e8ee1f5b80c932b44afb")}, collection records:1
info, _ = coll.Upsert(bson.M{}, foo) // will update
count, _ = coll.Count()
fmt.Printf("%+v, collecton records:%v\n", info, count) // &{Updated:1 Removed:0 Matched:1 UpsertedId:<nil>}, collection records:1
info, _ = coll.Upsert(bson.M{"nonsense": -1}, foo) // will insert duplicate record (due to the selector that will never match anything)
count, _ = coll.Count()
fmt.Printf("%+v, collecton records:%v\n", info, count) // &{Updated:0 Removed:0 Matched:0 UpsertedId:ObjectIdHex("5c35ea2a1f5b80c932b44b1d")}, collection records:2
foo.Bar = "baz"
info, _ = coll.Upsert(nil, foo) // will update the first matched (since the selector matches everything)
count, _ = coll.Count()
fmt.Printf("%+v, collecton records:%v\n", info, count)
// after the test the collection will have the following records
//> use foo
//> db.bar.find()
//{ "_id" : ObjectId("5c35ebf41f5b80c932b44b81"), "bar" : "baz" } // the first matched on the last update with nil selector
//{ "_id" : ObjectId("5c35ebf41f5b80c932b44b86"), "bar" : "barrr" } // the duplicated record with the selector that never matches anything
}
EDIT: Note that you should have an index on the never matching field, or your insert query will take too long if you have many records, because the upsert on not indexed filed will scan all the documents in the collection.

How to update a sub-document array fields along with some other fields in Mongodb, Mgo?

Can we update sub-document array fields along with other document fields in Mgo?
If so, please help me with my query.
c := db.C("user")
colQuerier := bson.M{"email": *olduname}
change := bson.M{"$set":bson.M{"password":*pwd, "place":*place, "emails.$.received":*received,"emails.$.sent":*sent}}
err := c.Update(colQuerier, change)
My Database Structs are as follows:
type Emails struct{
Id bson.ObjectId `bson:"_id,omitempty"`
Received string
Sent string
}
type User struct {
Id bson.ObjectId `bson:"_id,omitempty"`
Email string
Password string
Place string
Emails
}
I am getting a run time error saying: The positional operator did not find the match needed from the query. Unexpanded update: emails.$.received
It should be emails.received as received is not an array, so you don't need the positional operator $:
c := db.C("user")
colQuerier := bson.M{"email": *olduname}
change := bson.M{"$set":bson.M{"password":*pwd, "place":*place, "emails.received":*received}}
err := c.Update(colQuerier, change)

Why does mgo not return the ID of inserted document?

According to the documentation (http://godoc.org/launchpad.net/mgo/v2) you can obtain the ID of your "Upserted" document if you use the Upsert method.
There is also an Insert method that does not provide this functionality.
Why is that? What if I want to perform an Insert instead of an Upsert? (or wouldn't ever be any valid reason to want to do that? I'm starting to wonder.)
You use bson.NewObjectId() to generate an ID to be inserted.
This is how you'd insert a new document:
i := bson.NewObjectId()
c.Insert(bson.M{"_id": i, "foo": "bar"})
Since you don't know if you're going to insert or update when you issue an Upsert, it would be superfluous to generate an ID just to drop it right after the query (in case an update happens). That's why it's generated db-side and returned to you when applicable.
This should not happen at all, the mgo should insert and return the Id, since, if we generated the ObjectId from the application itself, If the application is restarted, the Object Id generator will start from the beginning generating the same IDs again and again, thus updating existing records in the database.
That is wrong, MGO should rely on the database in generating those IDs and updating the object or returning the objectId of the inserted object immediately like what other languages binding to MongoDB does like in Python or Java.
You can always try the Upsert function to get the generated ID.
db := service.ConnectDb()
sessionCopy := db.Copy()
defer sessionCopy.Close() // clean up
collection := sessionCopy.DB(service.MongoDB.DTB).C(MessageCol.tbl)
log.Println("before to write: ", msg)
// Update record inserts and creates an ID if wasn't set (Returns created record with new Id)
info, err := collection.Upsert(nil, msg)
if err != nil {
log.Println("Error write message upsert collection: ", err)
return MessageMgo{}, err
}
if info.UpsertedId != nil {
msg.Id = info.UpsertedId.(bson.ObjectId)
}
// gets room from mongo
room, err := GetRoom(msg.Rid)
if err != nil {
return msg, err
}
// increments the msgcount and update it
room.MsgCount = room.MsgCount + 1
err = UpdateRoom(room)
if err != nil {
return msg, err
}
return msg, err
This is a sample code I have and works fine.....