Golang mongodb mgo driver Upsert / UpsertId documentation

16,437

Solution 1

I found the documentation of the MongoDB was right. The correct way to do this is to wrap the struct to insert into an update operator.

The sample code provided by Neil Lunn, would look like:

package main

import (
  "fmt"
  "gopkg.in/mgo.v2"
  "gopkg.in/mgo.v2/bson"
)

type Person struct {
  Code string
  Name  string
}

func main() {
  session, err := mgo.Dial("admin:admin@localhost");

  if err != nil {
        fmt.Println("Error: ", err)
        return
  }

  defer session.Close()

  session.SetMode(mgo.Monotonic, true)

  c := session.DB("test").C("people")

  var p = Person{
    Code: "1234",
    Name: "Bill",
  }
    upsertdata := bson.M{ "$set": p}

    info , err2 := c.UpsertId( p.Code, upsertdata )
    fmt.Println("UpsertId -> ", info, err2)
  result := Person{}
  err = c.FindId(p.Code).One(&result)
  if err != nil {
        fmt.Println("FindId Error: ", err)
        return
  }

  fmt.Println("Person", result)

}

Thank you very much for your interest and help Neil.

Solution 2

You seem to be talking about assigning a struct with a custom _id field here. This really comes down to how you define your struct. Here is a quick example:

package main

import (
  "fmt"
  "gopkg.in/mgo.v2"
  "gopkg.in/mgo.v2/bson"
)

type Person struct {
  ID    string `bson:"_id"`
  Name  string
}

func main() {
  session, err := mgo.Dial("127.0.0.1");

  if err != nil {
    panic(err)
  }

  defer session.Close()

  session.SetMode(mgo.Monotonic, true)

  c := session.DB("test").C("people")

  var p = Person{
    ID: "1",
    Name: "Bill",
  }

  _, err = c.UpsertId( p.ID, &p )

  result := Person{}
  err = c.Find(bson.M{"_id": p.ID}).One(&result)
  if err != nil {
    panic(err)
  }

  fmt.Println("Person", result)

}

So in the custom definition here I am mapping the ID field to bson _id and defining it's type as string. As shown in the example this is exactly what happens when serialized via UpsertId and then retrieved.


Now you have elaborated I'll point to the difference on the struct definition.

What I have produces this:

{ "_id": 1, "name": "Bill" }

What you have ( without the same mapping on the struct ) does this:

{ "_id": ObjectId("53cfa557e248860d16e1f7e0"), "code": 1, "name": "Bill" }

As you see, the _id given in the upsert will never match because none of your fields in the struct are mapped to _id. You need the same as I have:

type Person struct {
    Code string `bson:"_id"`
    Name string
}

That maps a field to the mandatory _id field, otherwise one is automatically produced for you.

Share:
16,437
Admin
Author by

Admin

Updated on June 11, 2022

Comments

  • Admin
    Admin almost 2 years

    The mongodb documentation says:

    The fields and values of both the and parameters if the parameter contains only update operator expressions. The update creates a base document from the equality clauses in the parameter, and then applies the update expressions from the parameter.

    And the mgo documentation says:

    Upsert finds a single document matching the provided selector document and modifies it according to the update document. If no document matching the selector is found, the update document is applied to the selector document and the result is inserted in the collection.

    But if i do an upsert like this:

    session.UpsertId(data.Code, data)
    

    I end up with an entry which have an ObjectID generated automatically by mongodb, instead of data.Code.

    this means that UpsertId expect data to be formated with update operators and you can't use a an arbitrary struct? Or what i'm missing here?

    Pd. Mongo 2.4.9 mgo v2 golang go version devel +f613443bb13a

    EDIT:

    This is a sample of what i mean, using the sample code from Neil Lunn:

    package main
    
    import (
      "fmt"
      "gopkg.in/mgo.v2"
      // "gopkg.in/mgo.v2/bson"
    )
    
    type Person struct {
      Code string
      Name  string
    }
    
    func main() {
      session, err := mgo.Dial("admin:admin@localhost");
    
      if err != nil {
            fmt.Println("Error: ", err)
            return
        // panic(err)
      }
    
      defer session.Close()
    
      session.SetMode(mgo.Monotonic, true)
    
      c := session.DB("test").C("people")
    
      var p = Person{
        Code: "1234",
        Name: "Bill",
      }
    
      _, err = c.UpsertId( p.Code, &p )
    
      result := Person{}
      err = c.FindId(p.Code).One(&result)
      if err != nil {
            fmt.Println("FindId Error: ", err)
            return
        // panic(err)
      }
    
      fmt.Println("Person", result)
    
    }
    
  • Admin
    Admin almost 10 years
    The struct does not have an ID field, what's why UpsertId comes handy. But it does not do what the mgo documentation says. That's why i end up in mondogdb documentation. I want Data.Code to be the _id, but the struct does not have the bson:"_id" as it is from an external package.
  • Neil Lunn
    Neil Lunn almost 10 years
    @GabrielDíaz I am otherwise not sure what you are asking. It appears you want to provide something different as the _id. This is what this code does and works. If you mean something different, then perhaps you can make your question more clear. Upsert will use the _id value it is given unless there is some other cause that makes it not do so.
  • Admin
    Admin almost 10 years
    I have edited your sample with what I'm doing. Hope this helps to understand the question. Thanks!
  • Neil Lunn
    Neil Lunn almost 10 years
    @GabrielDíaz If I understand the tone of your question you are saying that upsert adds it's own _id value. From the differences between my listing and yours the reason is clear. Look at the mapping on the struct. This is the BSON serialization part which is exactly what I have said.
  • Admin
    Admin almost 10 years
    I understand what you're saying, but my question is different. Reading the mgo driver documentation "If no document matching the selector is found, the update document is applied to the selector document and the result is inserted in the collection", I expect Upsert to merge the provided Id and the provided struct as an object, and then insert that into the database. It does not work, so whats the meaning of that "is applied" in the documentation?
  • Neil Lunn
    Neil Lunn almost 10 years
    @GabrielDíaz It isn't different. I have added information to show you where you are doing this wrong. Take a look.
  • Admin
    Admin almost 10 years
    I understand that. I can't do that because the struct is from a third party package, could I?. As I understand either the documentation is not correctly written, or the UpsertId does not do what the documentation says. I know how to make it work, I just want to be sure, that is the documentation which is not as clear as it should be, or that there is a problem with Upsert. Thanks!
  • Neil Lunn
    Neil Lunn almost 10 years
    @GabrielDíaz There is nothing wrong with the documentation. For future reference you are almost certain to be shot down by suggesting "doesn't work like you say". But now you seem to be pointing to an actual question which is something like "How do I subclass a struct?". Which I think you can agree is what you want but not the question you asked. Am I correct?
  • Admin
    Admin almost 10 years
    If you look at the latest sample code, i'm not doing any subclassing to make it work, it appears that the mongodb documentation is also applicable to the mgo driver, and that the "is applied" needs the clause "if the update document is specified by update operators". If I'm wrong with this, I'll start taking english lessons again :)
  • Neil Lunn
    Neil Lunn almost 10 years
    @GabrielDíaz By my calculations you still have the ability to "vote up" the responses that are "helpful". You quoted me enough times to ensure that is true. I also believe that you are taking the wrong approach and that you should be doing what I say you should be doing. And that would not just be upvoting for "useful help" but also an accept of the answer you would never have derived to otherwise.