Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang mongodb mgo driver Upsert / UpsertId documentation

Tags:

mongodb

go

mgo

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)

}
like image 220
Gabriel Díaz Avatar asked Jul 23 '14 07:07

Gabriel Díaz


2 Answers

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.

like image 118
Gabriel Díaz Avatar answered Sep 19 '22 16:09

Gabriel Díaz


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.

like image 29
Neil Lunn Avatar answered Sep 18 '22 16:09

Neil Lunn