Matching structure to mgo result - mongodb

I have the following document in my local mongodb:
_id 25dd9d29-efd5-4b4e-8af0-360c49fdba31
name Reykjavik
initialDiseaseColouring blue
In my code I set up a city structure as the following:
type City struct {
ID bson.ObjectId `bson:"_id,omitempty"`
Name string
InitialDiseaseColouring string
}
I'm querying it using
result := City{}
collection.Find(bson.M{"name":"Reykjavik"}).One(&result)
When I try to access the initialDiseaseColouring attribute it's not there
This is result when I print it:
{ObjectIdHex("32356464396432392d656664352d346234652d386166302d333630633439666462613331") Reykjavik }
Does anyone know why?
I was following the example on https://gist.github.com/border/3489566

By default, the bson codec uses the lowercased field name as the key. Use the field tag to specify a different key:
type City struct {
ID bson.ObjectId `bson:"_id,omitempty"`
Name string
InitialDiseaseColouring string `bson:"initialDiseaseColouring"`
}
The addition of the field tag changes the key from "initialdiseasecolouring" to "initialDiseaseColouring".

Related

How to scan into nested structs with sqlx?

Let's assume that I have two models,
type Customer struct {
Id int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Address Address `json:"adress"`
}
type Address struct {
Street string `json:"street" db:"street"`
City string `json:"city" db:"city"`
}
// ...
customer := models.Customer{}
err := db.Get(&customer , `select * from users where id=$1 and name=$2`, id, name)
But this scan throws an error as: missing destination name street in *models.Customer
Am I doing something wrong? As you can see I already updated the db corresponding of the value. I doubled check so case sensitivity shouldn't be a problem.
Or is it not possible using https://github.com/jmoiron/sqlx?
I can see it in the documentation but still couldn't figure out how to solve it.
http://jmoiron.github.io/sqlx/#advancedScanning
The users table is declared as:
CREATE TABLE `users` (
`id` varchar(256) NOT NULL,
`name` varchar(150) NOT NULL,
`street` varchar(150) NOT NULL,
`city` varchar(150) NOT NULL,
)
The very link you posted gives you an hint about how to do this:
StructScan is deceptively sophisticated. It supports embedded structs, and assigns to fields using the same precedence rules that Go uses for embedded attribute and method access
So given your DB schema, you can simply embed Address into Customer:
type Customer struct {
Id int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Address
}
In your original code, Address was a field with its own db tag. This is not correct, and by the way your schema has no address column at all. (it appears you edited it out of your code snippet)
By embedding the struct into Customer instead, Address fields including tags are promoted into Customer and sqlx will be able to populate them from your query result.
Warning: embedding the field will also flatten the output of any JSON marshalling. It will become:
{
"id": 1,
"name": "foo",
"street": "bar",
"city": "baz"
}
If you want to place street and city into a JSON address object as based on your original struct tags, the easiest way is probably to remap the DB struct to your original type.
You could also scan the query result into a map[string]interface{} but then you have to be careful about how Postgres data types are represented in Go.
I had the same problem and came up with a slightly more elegant solution than #blackgreen's.
He's right, the easiest way is to embed the objects, but I do it in a temporary object instead of making the original messier.
You then add a function to convert your temp (flat) object into your real (nested) one.
type Customer struct {
Id int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Address Address `json:"adress"`
}
type Address struct {
Street string `json:"street" db:"street"`
City string `json:"city" db:"city"`
}
type tempCustomer struct {
Customer
Address
}
func (c *tempCustomer) ToCustomer() Customer {
customer := c.Customer
customer.Address = c.Address
return customer
}
Now you can scan into tempCustomer and simply call tempCustomer.ToCustomer before you return. This keeps your JSON clean and doesn't require a custom scan function.

Golang official MongoDB driver ObjectID weird behavior

I am trying to use the official mongodb driver in golang and am seeing something unexpected.
If I have a struct like
type User struct {
ID primitive.ObjectID `json:"id" bson:"_id"`
Name string `json:"name" bson:"name"`
Email string `json:"email" bson:"email"`
}
I create a new instance of this with Name and Email but omit ID expecting that the DB will fill this with its value. Instead it uses all zeroes and so the second and so on inserts fail with
multiple write errors: [{write errors: [{E11000 duplicate key error collection: collection.name index: _id_ dup key: { : ObjectId('000000000000000000000000') }}]}, {<nil>}]
If I use a *primitive.ObjectID I get the same class of error only on null instead of zeroes
multiple write errors: [{write errors: [{E11000 duplicate key error collection: collection.name index: _id_ dup key: { : null }}]}, {<nil>}]
It doesn't matter if I use the omitempty directive or not, same result.
If I omit the ID field entirely, it works, but then my struct doesn't have that data on it.
Is there a way to have the DB handle the ID? Or MUST I explicitly call the NewObjectID() function on the struct?
It doesn't matter if I use the omitempty directive or not, same result.
omitempty tag on ID should work. For example:
type User struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
Name string `json:"name" bson:"name"`
Email string `json:"email" bson:"email"`
}
collection.InsertOne(context.Background(), User{Name:"Foo", Email:"Baz"})
If you don't specify the omitepmty tag, then the behaviour that you observed is just Go structs behaviour; whereby if any of struct fields are omitted it will be zero-valued. In this case because you have specified the field type to be primitive.ObjectID, ObjectId('000000000000000000000000') is the zero value.
This is the reason why you need to generate a value first before inserting, i.e.:
collection.InsertOne(context.Background(),
User{ ID: primitive.NewObjectID(),
Name: "Foo",
Email: "Bar"})
Is there a way to have the DB handle the ID?
Technically, it's the MongoDB driver that automatically generates the ObjectId if it's not supplied before sending to server.
You can try to use bson.M instead of a struct when inserting to leave out the _id field, i.e.
collection.InsertOne(context.Background(),
bson.M{"name":"Foo", "email":"Bar"})
Code snippet above is written using mongo-go-driver v1.3.x
The omitempty struct tag should work:
type User struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
Name string `json:"name" bson:"name"`
Email string `json:"email" bson:"email"`
}
The primitive.ObjectID type implements the bsoncodec.Zeroer interface, so it should be omitted from the document if it's the empty object ID (all 0's) and the driver will generate a new one for you. Can you try this and post the output?

Changing the default MongoDB ObjectID generator

I'm using the mongo-go driver for Go to save some documents on mongoDb. Everyhting works fine, but i'm wondering if there is a way to change how the ID is auto-generated. Right now the document model in the code has the primitive.ObjectID type, is something like this
type Review struct {
ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`
Title string `json:"title"`
Text string `json:"text"`
Rate float64 `json:"rate"`
Date time.Time `json:"date"`
Product Product `json:"product"`
}
And the document created is something like this
{
"_id" : ObjectId("5d6f739a20d42db438016cb1"),
"title" : "test-id",
"text" : "rev-text",
"rate" : 5.0,
"date" : ISODate("2019-09-02T12:18:00.000+02:00"),
"status" : "pending"
}
So far so good. However i want the ID to be a UUID, not an ObjectId. I know i can change the struct ID type to UUID or string and set that field when i'm saving the document, however i want to know if there is a way to change the default ObjectID generator of mongoDb so it generates a UUID automatically when saving a new document
You don't have to use an ObjectID for _id:
ID string `json:"id,omitempty" bson:"_id,omitempty"`
Then you can insert any value you want for _id. However, you won't be able to auto-generate ID this way, you have to generate the ID yourself, and insert it with the generated ID.

How to work with multiple types in single collection with mgo

I'm very new to golang and try to write a simple event-sourcing user-management webapi using mongodb as backing database. Now i have User, which looks something like this:
type User struct {
Id bson.ObjectId `json:"id" bson:"_id"`
UserName string `json:"username" bson:"username"`
Email string `json:"email" bson:"email"`
PwdHash string `json:"pwd_hash" bson:"pwd_hash"`
FullName string `json:"fullname" bson:"fullname"`
}
and three events, happening to user, when somebody uses api:
type UserCreatedEvent struct {
UserId bson.ObjectId `json:"id" bson:"_id"`
//time when event was issued
CreatedEpoch time.Time `json:"created_epoch" bson:"created_epoch"`
//id of user that made a change
IssuedByUserId bson.ObjectId `json:"issuedby_userid" bson:"issuedby_userid"`
}
type UserDeletedEvent struct {
UserId bson.ObjectId `json:"id" bson:"_id"`
CreatedEpoch time.Time `json:"created_epoch" bson:"created_epoch"`
//id of user that made a change
IssuedByUserId bson.ObjectId `json:"issuedby_userid" bson:"issuedby_userid"`
}
type UserUpdatedEvent struct {
UserId bson.ObjectId `json:"id" bson:"_id"`
CreatedEpoch time.Time `json:"created_epoch" bson:"created_epoch"`
//id of user that made a change
IssuedByUserId bson.ObjectId `json:"issuedby_userid" bson:"issuedby_userid"`
ChangedFieldName string `json:"changed_field_name" bson:"changed_field_name"`
NewChangedFieldValue string `json:"new_changed_field_value" bson:"new_changed_field_value"`
}
Now i'm stuck on saving and retrieving events from db. The problem is i want to store them in a single collection, so that i have a full plain history of user modifications. But i can't find how to correctly store event type name as a mongo document field and then use it in searches. What is the idiomatic go-way to do this?
I'll be gratefull for any help.
What's nice about a non-relational database is that a little redundancy is OK and is faster for retrieval since you're not joining things together. You could just add your bottom three objects as properties on your User in whatever fashion makes sense for you and your data. Then you wouldn't need to store the UserId on those objects either.
If you need to search over the Events quickly, you could create another collection to hold them. You'd be inserting to two collections, but retrieval times/logic should be pretty good/easy to write.
Your new Event would be something like:
type UserEventType int
const (
Created UserEventType = iota
Deleted
Updated
)
type UserEvent struct {
UserId bson.ObjectId `json:"id" bson:"_id"`
CreatedEpoch time.Time `json:"created_epoch" bson:"created_epoch"`
//id of user that made a change
IssuedByUserId bson.ObjectId `json:"issuedby_userid" bson:"issuedby_userid"`
ChangedFieldName string `json:"changed_field_name,omitempty" bson:"changed_field_name,omitempty"`
NewChangedFieldValue string `json:"new_changed_field_value,omitempty" bson:"new_changed_field_value,omitempty"`
EventType UserEventType `json:"user_event_type" bson:"user_event_type"`
}
Notice the omitempty on the fields that are optional depending on the type of event.
You really aren't supposed to store different objects in the same collection. Here's a line from the Mongo documentation:
MongoDB stores documents in collections. Collections are analogous to tables in relational databases.
In case you aren't familiar with relational databases, a table would essentially represent one type of object.

Golang revel+mgo - no data returned when struct variables having lowercase names

This is my struct type
type Category struct {
Name string `bson:"listName"`
Slug string `bson:"slug"`
}
used with the following function to return all results from a mongo collection -
func GetCategories(s *mgo.Session) []Category {
var results []Category
Collection(s).Find(bson.M{}).All(&results)
return results
}
The problem is that the field names in my db have names starting in lowercase but the Golang struct returns null when I try to use variable names starting with lower case. For e.g. this returns a JSON with corresponding fields empty -
type Category struct {
listName string `bson:"listName"`
slug string `bson:"slug"`
}
I'm actually porting a Meteor based API to Golang and a lot of products currently using the API rely on those field names like they are in the db!
Is there a workaround?
You need to make your fields visible for mgos bson Unmarshall by naming them with a starting capital letter. You also need to map to the appropiates json/bson field names
type Category struct {
ListName string `json:"listName" bson:"listName"`
Slug string `json:"slug" bson:"slug"`
}