How can I compare two bson.M data sets using Golang - mongodb

I have the following code which retrieves two data sets from two different collections in a MongoDB database
opts := options.Find()
opts.SetProjection(bson.M{
"productId": 1,
"_id": 0,
})
cursor, err := currentProductsCollection.Find(ctx, bson.M{}, opts)
var oldProducts []bson.M
err = cursor.All(ctx, &oldProducts)
cursor, err = newProductsCollection.Find(ctx, bson.M{}, opts)
var newProducts []bson.M
err = cursor.All(ctx, &newProducts)
I want to be able to compare oldProducts with newProducts to find out what new productId's have appeared and which old productId's have disappeared.
The two variables have both loaded fine and I can happily inspect them in the debugger, but I can't seem to find a way of comparing them. I had hoped to be able to range over each in turn doing a lookup on the other and getting a couple of slices of missing values but I can't find any way to do it.
I've been going round the houses with this for the last three hours so if anyone has any suggestions I would more than welcome them.
I am using the vanilla go.mongodb.org/mongo-driver drivers, not mgo

Create map for both old product and new product by product id
oldProductsMap = make(map[interface{}]bson.M)
for _,oldp := range oldProducts {
oldProductsMap[oldp["productId"]] = oldp
}
newProductsMap = make(map[interface{}]bson.M)
for _,newp :=range newProducts {
newProductsMap[newp["productId"]] = newp
}
Then for the disappeared product check old product is in newProductsMap. If not then the product disappeared
var disProducts []bson.M
for _,oldp := range oldProducts {
if _, ok := newProductsMap[oldp["productId"]]; !ok {
disProducts = append(disProducts, oldp)
}
}
For newly appeared product check new product is in oldProductsMap. If not then the product newly appeared.
var appProducts []bson.M
for _,newp := range newProducts {
if _, ok := oldProductsMap[newp["productId"]]; !ok {
appProducts = append(appProducts, oldp)
}
}
Note : You can do this portion when create map for new product also

If you are sure all entries have the productId field:
func exists(in []bson.M,id interface{}) bool {
for _,p:=range in {
if id==p["productId"] {
return true
}
}
return false
}
Then use this to scan both lists:
for _,oldp:=range oldProducts {
if !exists(newProducts,oldp["productId"]) {
// Removed
}
}
for _,newp:=range newProducts {
if !exists(oldProducts,newp["productId"]) {
// Added
}
}

Related

Inserting a document into MongoDB wthout _id field seems bugged

I might have found a weird bug..
I do update once in a while my mongodb-driver package as I use for year now I layer I've made a long time ago.
However, today, for some personal reasons, I have refactored my tests and now have e2e full tests suite over my layer. The thing is, while doing my tests, I could reproduce a bug I had in the past, which is when I don't set an ID for my document (_id bson field)
Situation:
For obvious reason, some piece of code are missing but I am quite sure you will be able to guess really easily ;)
The commented out code part is the tests that are creating the weird behavior
If I create a document (really simple struct) and set the _id field before inserting, it works perfectly.
If I create a document (really simple struct) and don't set the _id field before inserting, it inserts something that results as "null" while observing the database using Robo3T.
Code:
To reproduce that bug, there is my tests and the source code of my layer
Tests:
type e2eMongoDBSuite struct {
suite.Suite
_mongodb mongodb.IMongoDB
}
type e2eTestDocument struct {
ID *primitive.ObjectID `json:"_id" bson:"_id"`
ValueStr string `json:"value_str" bson:"value_str"`
ValueInt32 int32 `json:"value_int32" bson:"value_int32"`
}
const (
//// ci
connectionString = "mongodb://localhost:27017"
databaseName = "mongodb-database-e2e-tests"
collection = "mongodb-collection-e2e-tests"
defaultTimeout = 5 * time.Second
)
func (e2eSuite *e2eMongoDBSuite) SetupSuite() {
var err error
if e2eSuite._mongodb, err = mongodb.New(&mongodb.Config{
ConnectionString: connectionString,
DatabaseName: databaseName,
DefaultTimeout: defaultTimeout,
}); err != nil {
panic(fmt.Errorf("couldn't setup suite: %w", err).Error())
}
}
func (e2eSuite *e2eMongoDBSuite) TestInsertOneWithID_OK() {
a := assert.New(e2eSuite.T())
// given
docID := e2eSuite._mongodb.GenerateNewObjectID()
doc := &e2eTestDocument{ID: docID}
// when
insertedID, err := e2eSuite._mongodb.InsertOne(collection, doc)
// expected
a.NotNil(insertedID, "the inserted document ID should be returned")
a.Equal(docID, insertedID, "the inserted document ID should be the same as the given one at creation")
a.Nil(err, "there should be no error for this insert")
// then
_ = e2eSuite._mongodb.DeleteMany(collection, bson.D{})
}
//func (e2eSuite *e2eMongoDBSuite) TestInsertOneWithNoID_OK() {
// a := assert.New(e2eSuite.T())
//
// // given
// doc := &e2eTestDocument{}
//
// // when
// insertedID, err := e2eSuite._mongodb.InsertOne(collection, doc)
//
// // expected
// a.NotNil(insertedID, "the inserted document ID should be returned")
// //a.Equal(docID, insertedID, "the inserted document ID should be the same as the given one at creation")
// a.Nil(err, "there should be no error for this insert")
//
// // then
// _ = e2eSuite._mongodb.DeleteMany(collection, bson.D{})
//}
func (e2eSuite *e2eMongoDBSuite) TestInsertManyWithIDs_OK() {
a := assert.New(e2eSuite.T())
// given
docID1 := e2eSuite._mongodb.GenerateNewObjectID()
docID2 := e2eSuite._mongodb.GenerateNewObjectID()
doc1 := &e2eTestDocument{ID: docID1}
doc2 := &e2eTestDocument{ID: docID2}
// when
insertedIDs, err := e2eSuite._mongodb.InsertMany(collection, []*e2eTestDocument{doc1, doc2})
// expected
a.NotNil(insertedIDs, "the inserted document IDs should be returned")
a.Equal(2, len(insertedIDs), "the inserted document IDs amount should be of two")
a.EqualValues([]*primitive.ObjectID{docID1, docID2}, insertedIDs, "the inserted document IDs should be the same as the given ones at creation")
a.Nil(err, "there should be no error for this insert")
// then
_ = e2eSuite._mongodb.DeleteMany(collection, bson.D{})
}
//func (e2eSuite *e2eMongoDBSuite) TestInsertManyWithNoIDs_OK() {
// a := assert.New(e2eSuite.T())
//
// // given
// doc1 := &e2eTestDocument{}
// doc2 := &e2eTestDocument{}
//
// // when
// insertedIDs, err := e2eSuite._mongodb.InsertMany(collection, []*e2eTestDocument{doc1, doc2})
//
// // expected
// a.NotNil(insertedIDs, "the inserted document IDs should be returned")
// a.Equal(2, len(insertedIDs), "the inserted document IDs amount should be of two")
// a.Nil(err, "there should be no error for this insert")
//
//// then
//_ = e2eSuite._mongodb.DeleteMany(collection, bson.D{})
//}
func (e2eSuite *e2eMongoDBSuite) TearDownSuite() {
_ = e2eSuite._mongodb.DropDatabase()
}
func TestE2eMongoDBSuite(t *testing.T) {
suite.Run(t, new(e2eMongoDBSuite))
}
The layer i've developed:
// InsertOne inserts a document in a given collection and returns the inserted ObjectID.
func (m *mongoDB) InsertOne(collectionName string, document interface{}) (*primitive.ObjectID, error) {
res, err := m.GetDatabase().Collection(collectionName).InsertOne(context.Background(), document)
if err != nil {
return nil, err
}
if oid, ok := res.InsertedID.(primitive.ObjectID); ok {
return &oid, nil
}
return nil, nil
}
// InsertMany inserts documents in a given collection and returns the inserted ObjectIDs.
func (m *mongoDB) InsertMany(collectionName string, documents interface{}) ([]*primitive.ObjectID, error) {
s := reflect.ValueOf(documents)
if s.Kind() != reflect.Slice {
panic("Documents given a non-slice type")
}
slice := make([]interface{}, s.Len())
for i := 0; i < s.Len(); i++ {
slice[i] = s.Index(i).Interface()
}
res, err := m.GetDatabase().Collection(collectionName).InsertMany(context.Background(), slice)
if err != nil {
return nil, err
}
var insertedIDs []*primitive.ObjectID
for _, insertedID := range res.InsertedIDs {
if oid, ok := insertedID.(primitive.ObjectID); ok {
insertedIDs = append(insertedIDs, &oid)
}
}
return insertedIDs, nil
}
Conclusion
I don't know if the behavior is logic or not but an id should be generated if my document as no ID :confused:
Note
A topic as been opened here: https://developer.mongodb.com/community/forums/t/inserting-a-document-in-go-with-no-id-set-results-into-a-weird-behavior/100359 (don't know if it's a bug or not so I thought I should post there too)
Thanks!
Max
If you insert a document containing the _id field, mongodb will use that as the document id, even if it is null. To have the _id auto-generated, you have to insert the document without the _id field. This should work:
type Doc struct {
ID *primitive.ObjectID `json:"_id" bson:"_id,omitempty"`
...
This will not marshal the _id field if it is nil, focing it to be auto-generated.

How to retrieve values in GoLang database/sql, when my structure in db(here postgres) is unknown?

I use Go with PostgreSQL using github.com/lib/pq and able to successfully fetch the records when my structure is known.
Now my query is how to fetch records when my structure changes dynamically?
By rows.columns I am able to fetch the column names, but could you help me with fetching the values of these columns for all the rows. I referred this link answered by #Luke, still, here the person has a structure defined.
Is it possible to retrieve a column value by name using GoLang database/sql
type Person struct {
Id int
Name string
}
Meanwhile I do not have a structure that is fixed, so how will I iterate through all the columns that too again for all rows. My approach would be a pointer to loop through all columns at first, then another one for going to next row.
Still not able to code this, Could you please help me with this, like how to proceed and get the values.
Since you don't know the structure up front you can return the rows as a two dimensional slice of empty interfaces. However for the row scan to work you'll need to pre-allocate the values to the appropriate type and to do this you can use the ColumnTypes method and the reflect package. Keep in mind that not every driver provides access to the columns' types so make sure the one you use does.
rows, err := db.Query("select * from foobar")
if err != nil {
return err
}
defer rows.Close()
// get column type info
columnTypes, err := rows.ColumnTypes()
if err != nil {
return err
}
// used for allocation & dereferencing
rowValues := make([]reflect.Value, len(columnTypes))
for i := 0; i < len(columnTypes); i++ {
// allocate reflect.Value representing a **T value
rowValues[i] = reflect.New(reflect.PtrTo(columnTypes[i].ScanType()))
}
resultList := [][]interface{}{}
for rows.Next() {
// initially will hold pointers for Scan, after scanning the
// pointers will be dereferenced so that the slice holds actual values
rowResult := make([]interface{}, len(columnTypes))
for i := 0; i < len(columnTypes); i++ {
// get the **T value from the reflect.Value
rowResult[i] = rowValues[i].Interface()
}
// scan each column value into the corresponding **T value
if err := rows.Scan(rowResult...); err != nil {
return err
}
// dereference pointers
for i := 0; i < len(rowValues); i++ {
// first pointer deref to get reflect.Value representing a *T value,
// if rv.IsNil it means column value was NULL
if rv := rowValues[i].Elem(); rv.IsNil() {
rowResult[i] = nil
} else {
// second deref to get reflect.Value representing the T value
// and call Interface to get T value from the reflect.Value
rowResult[i] = rv.Elem().Interface()
}
}
resultList = append(resultList, rowResult)
}
if err := rows.Err(); err != nil {
return err
}
fmt.Println(resultList)
This function prints the result of a query without knowing anything about the column types and count. It is a variant of the previous answer without using the reflect package.
func printQueryResult(db *sql.DB, query string) error {
rows, err := db.Query(query)
if err != nil {
return fmt.Errorf("canot run query %s: %w", query, err)
}
defer rows.Close()
cols, _ := rows.Columns()
row := make([]interface{}, len(cols))
rowPtr := make([]interface{}, len(cols))
for i := range row {
rowPtr[i] = &row[i]
}
fmt.Println(cols)
for rows.Next() {
err = rows.Scan(rowPtr...)
if err != nil {
fmt.Println("cannot scan row:", err)
}
fmt.Println(row...)
}
return rows.Err()
}
The trick is that rows.Scan can scan values into *interface{} but you have to wrap it in interface{} to be able to pass it to Scan using ....

How to search for documents in mongodb using GoLang mongodb driver where the value in document is a string and the filter has a slice of string?

I'm not able to frame my whole question in title so here it is:
I have a slice of strings var temp = []string{"abc","efg","xyz"}
Now I want to search documents in collection for every element in the above slice.
I know I can do something like this:
for _, str:=range temp{
collection.Find(context.background(), bson.M{"key":str})
}
but as you can see I will have to fire many queries.
So is there a solution where I can fire a single query to find all those documents
e.g:
err = collection.Find(context.Background(), bson.M{"key": MY_SLICE_OF_STRING})
You can use:
// I'm not sure what is your struct, so I use bson.Raw for this example
// but you can parse into your struct in the loop.
resultQuery := make([]bson.Raw, 0)
// you can use bson.M if you like,
// filter := bson.M{"key": bson.M{"$in": MY_SLICE_OF_STRING}}
filter := bson.D{
{
Key: "key",
Value: bson.E{
Key: "$in",
Value: MY_SLICE_OF_STRING,
},
},
}
ctx := context.background()
cursor, err := collection.Find(ctx, filter)
if err != nil {
//Handle your error.
}
if err == nil {
// you should put defer function to close your cursor,
defer func() {
cursor.Close(ctx)
}()
for cursor.Next(ctx) {
resultQuery = append(resultQuery, cursor.Current)
}
}

converting MongoDB function foreach into mgo (Golang) function

This is function that tries to update the code matching by its value
the res collection as having the code of Marque it will be compared with doc.Marque if it is the case it will be replaced by the value of the marque.
This code is working perfectly in mongoDB CLI, but as i am working with GO.
I tried to convert it into mgo as you may see below but it is not working , i did not find the foreach function in mgo , is there something to be replaced with in this case ? thanks for the help
db.res.find().forEach(function(doc){
var v = db.brands.findOne({code: doc.Marque});
if(v){
db.res.update({"Marque": doc.Marque},
{$set: {"Marque":v.value}}, {multi: true});
}
});
Here is what i tried :
result:=Results{}
pipe:=res.find(bson.M{}).Iter()
for pipe.Next(&result) {
brands:=brands.findOne({code: doc.Marque});
if(v){
pipe.update({"Marque": doc.Marque},
{$set: {"Marque": v.value}}, {multi: true});
}
}
Visit the mgo Godoc may help you understand how it works.
Second, exported types/functions in Golang are begin with a capital letter. So res.find, brands.findOne, ... should be res.Find, brands.FineOne respectively, if such functions exist.
// let's say you have a type like this
type myResult struct {
ID bson.ObjectId `bson:"_id"`
Marque string `bson:"Marque"`
// other fields...
}
// and another type like this
type myCode struct {
Code string `bson:"code"`
// other fields...
}
res := db.C("res")
brands := db.C("brands")
result := myResult{}
// iterate all documents
iter := res.Find(nil).Iter()
for iter.Next(&result) {
var v myCode
err := brands.Find(bson.M{"code": result.Marque}).One(&v)
if err != nil {
// maybe not found or other reason,
// it is recommend to have additional check
continue
}
query := bson.M{"_id": result.ID}
update := bson.M{"Marque": v.value}
if err = res.Update(query, update); err != nil {
// handle error
}
}
if err := iter.Close(); err != nil {
fmt.Println(err)
}

Is there a way to get slice as result of Find()?

Now I'm doing:
sess := mongodb.DB("mybase").C("mycollection")
var users []struct {
Username string `bson:"username"`
}
err = sess.Find(nil).Select(bson.M{"username": 1, "_id": 0}).All(&users)
if err != nil {
fmt.Println(err)
}
var myUsers []string
for _, user := range users{
myUsers = append(myUsers, user.Username)
}
Is there a more effective way to get slice with usernames from Find (or another search function) directly, without struct and range loop?
The result of a MongoDB find() is always a list of documents. So if you want a list of values, you have to convert it manually just as you did.
Using a custom type (derived from string)
Also note that if you would create your own type (derived from string), you could override its unmarshaling logic, and "extract" just the username from the document.
This is how it could look like:
type Username string
func (u *Username) SetBSON(raw bson.Raw) (err error) {
doc := bson.M{}
if err = raw.Unmarshal(&doc); err != nil {
return
}
*u = Username(doc["username"].(string))
return
}
And then querying the usernames into a slice:
c := mongodb.DB("mybase").C("mycollection") // Obtain collection
var uns []Username
err = c.Find(nil).Select(bson.M{"username": 1, "_id": 0}).All(&uns)
if err != nil {
fmt.Println(err)
}
fmt.Println(uns)
Note that []Username is not the same as []string, so this may or may not be sufficient to you. Should you need a user name as a value of string instead of Username when processing the result, you can simply convert a Username to string.
Using Query.Iter()
Another way to avoid the slice copying would be to call Query.Iter(), iterate over the results and extract and store the username manually, similarly how the above custom unmarshaling logic does.
This is how it could look like:
var uns []string
it := c.Find(nil).Select(bson.M{"username": 1, "_id": 0}).Iter()
defer it.Close()
for doc := (bson.M{}); it.Next(&doc); {
uns = append(uns, doc["username"].(string))
}
if err := it.Err(); err != nil {
fmt.Println(err)
}
fmt.Println(uns)
I don't see what could be more effective than a simple range loop with appends. Without all the Mongo stuff your code basically is this and that's exactly how I would do this.
package main
import (
"fmt"
)
type User struct {
Username string
}
func main() {
var users []User
users = append(users, User{"John"}, User{"Jane"}, User{"Jim"}, User{"Jean"})
fmt.Println(users)
// Interesting part starts here.
var myUsers []string
for _, user := range users {
myUsers = append(myUsers, user.Username)
}
// Interesting part ends here.
fmt.Println(myUsers)
}
https://play.golang.com/p/qCwENmemn-R