MongoDB (Mgo v2) Projection returns parent struct - mongodb

I have here a Building Object where inside sits an Array of Floor Objects.
When Projecting, my goal is to return or count the number of Floor Objects inside a Building Object after matching the elements accordingly. The code is as follows:
Objects:
type Floor struct {
// Binary JSON Identity
ID bson.ObjectId `bson:"_id,omitempty"`
// App-level Identity
FloorUUID string `bson:"f"`
// Floor Info
FloorNumber int `bson:"l"`
// Units
FloorUnits []string `bson:"u"`
// Statistics
Created time.Time `bson:"y"`
}
type Building struct {
// Binary JSON Identity
ID bson.ObjectId `bson:"_id,omitempty"`
// App-level Identity
BldgUUID string `bson:"b"`
// Address Info
BldgNumber string `bson:"i"` // Street Number
BldgStreet string `bson:"s"` // Street
BldgCity string `bson:"c"` // City
BldgState string `bson:"t"` // State
BldgCountry string `bson:"x"` // Country
// Building Info
BldgName string `bson:"w"`
BldgOwner string `bson:"o"`
BldgMaxTenant int `bson:"m"`
BldgNumTenant int `bson:"n"`
// Floors
BldgFloors []Floor `bson:"p"`
// Statistics
Created time.Time `bson:"z"`
}
Code:
func InsertFloor(database *mgo.Database, bldg_uuid string, fnum int) error {
fmt.Println(bldg_uuid)
fmt.Println(fnum) // Floor Number
var result Floor // result := Floor{}
database.C("buildings").Find(bson.M{"b": bldg_uuid}).Select(
bson.M{"p": bson.M{"$elemMatch": bson.M{"l": fnum}}}).One(&result)
fmt.Printf("AHA %s", result)
return errors.New("x")
}
It turns out, no matter how I try the query returns a Building Object, not a floor object? What changes do I need to make in order to have the query fetch and count Floors and not Buildings?
This is done so to check if a Floor inside Building already exists before insertion. If there's a better a approach then I'll replace mine with the better!
Thanks!

You are querying for a Building document so mongo returns that to you even though you try to mask some of its fields using projection.
I don't know of a way to count the number of elements in a mongo array in a find query, but you can use the aggregation framework, where you have the $size operator that does exactly this. So you should send a query like this to mongo :
db.buildings.aggregate([
{
"$match":
{
"_id": buildingID,
"p": {
"$elemMatch": {"l": fNum}
}
}
},
{
"$project":
{
nrOfFloors: {
"$size": "$p"
}
}
}])
Which in go it would look like
result := []bson.M{}
match := bson.M{"$match": bson.M{"b": bldg_uuid, "p": bson.M{"$elemMatch": bson.M{"l": fNum}}}}
count := bson.M{"$project": bson.M{"nrOfFloors": bson.M{"$size": "$p"}}}
operations := []bson.M{match, count}
pipe := sess.DB("mgodb").C("building").Pipe(operations)
pipe.All(&result)

Related

How to do multi-level push of array element in mgo?

So I want to do a multi level push for the struct below :
type Inspector_Pool struct{
Unique_Id string `json:"unique_id" form:"unique_id" query:"unique_id"`
Email string `json:"email" form:"email" query:"email"`
Branch []string `json:"branch" query:"branch"`
Date []Date `json:"date" query:"date"`
}
type Date struct {
Event_Timestamp string `json:"event_timestamp" query:"event_timestamp"`
Event []Event `json:"event" query:"event"`
}
type Event struct {
Event_Name string `json:"event_name" form:"event_name" query:"event_name"`
Event_Id string `json:"event_id" form:"event_id" query:"event_id"`
Status string `json:"status" query:"status"`
}
So what i want is to push data in Event array so what I have to achieve code below:
//push a new node in date array
query := bson.M{"branch":"400612", "unique_id":c.Unique_Id}
update := bson.M{"$push": bson.M{"date": bson.M{"event_timestamp": t, "event": []models.Event{
{
Event:models.INSPECTION,
Event_Id:"123456789",
Status:models.PENDING,
},
}}}}
err = look_up.Update(query, update)
if err != nil {
panic(err)
}
//push the data in the nested event
pushQuery := bson.M{"date.event": bson.M{"event": []models.Event{
{
Event_Name:models.INSPECTION,
Event_Id:"4984984198",
Status:models.PENDING,
},
}}}
err = look_up.Update(bson.M{"unique_id": "2549090", "date.event_timestamp":"08-05-2017"}, bson.M{"$push": pushQuery})
if err != nil {
//panic(err)
fmt.Print("error_2",err)
}
but it doesn'tpush it in the event object but instead create a new entry in date object heres the snapshot
You can utilise the $ positional operator to update. Which basically identifies an element in an array to update without explicitly specifying the position of the element in the array.
Alter your second push statement as below:
pushQuery := bson.M{"date.$.event": Event{
Event_Name:"foobar",
Event_Id:"4984984198",
}}
err = collection.Update(bson.M{"unique_id":"2549090",
"date.event_timestamp":"08-05-2017"},
bson.M{"$push": pushQuery})
The above will push event 'foobar' into the same date array matching event_timestamp '08-05-2017' i.e.
{"_id": ObjectId("5909287934cb838fe8f89b6e"),
"unique_id": "2549090",
"date": [
{
"event_timestamp": "08-05-2017",
"event": [
{
"event_name": "baz",
"event_id": "123456789"
},
{
"event_name": "foobar",
"event_id": "4984984198"
}
]
}
]}
Generally having an array of array makes querying or extracting data difficult/complex later on. Depending on your application's use case, I would also suggest to re-consider your document Data Model.

What is wrong with this mongo $or query

This query works perfectly
{
$or:[{author:this.userId} , {somethingelse:true} ]
}
But when I try:
{
$or:[{author:this.userId} , {sharedwith[this.userId]:true} ]
}
I receive the message
Errors prevented startup:
While processing files with ecmascript (for target os.linux.x86_64): server/main.js:113:43: Unexpected token, expected
, (113:43)
=> Your application has errors. Waiting for file change.
And thats where the comma , in the $or statement is
Help
I guess that you are trying to retrieve all documents for which the current user is the author, or which have been shared with him/her? And therefore that you have structured your documents with a sharedWith field which is a hash map of userId as keys and boolean as value?
Document structure:
{
author: string,
sharedWith: {
<userId1>: boolean
// <userId2>…
}
}
In that case, your MongoDB query should use the dot notation to specify the value of a nested field within sharedWith field:
{
$or: [{
author: string
}, {
"sharedWith.<userId>": boolean
}]
}
To easily create the query with the interpolated value of <userId>, you can use a computed key in your object (ES6 syntax):
{
$or:[{
author: this.userId
} , {
// The query computed key must be in square brackets.
// Specify the field child key using the dot notation within your query.
["sharedwith." + this.userId]: true
}]
}
Or with good old ES5 syntax, similarly to #MichelFloyd's answer:
var query = {
$or: [{
author: this.userId
}]
};
var newCondition = {};
newCondition["sharedWith." + this.userId] = true;
query.$or.push(newCondition);
Note: the above described document structure could conveniently replace the sharedWith hash map by an array (since having a false value for the boolean could simply be replaced by removing the corresponding userId from the array):
{
author: string,
sharedWith: [
<userId1>
// <userId2>…
]
}
In which case the query would simply become:
{
$or:[{
author: this.userId
} , {
// In MongoDB query, the below selector matches either:
// - documents where `sharedWith` value is a string equal to
// the value of `this.userId`, or
// - documents where `sharedWith` value is an array which contains
// an element with the same value as `this.userId`.
sharedwith: this.userId
}]
}
Try building the query as a variable before running it.
let query = { $or: [{ author: this.userId }]};
const sw = sharedwith[this.userId];
query.$or.push({sw: true});
MyCollection.find(query);

Unable to map query result to variable

Below code is being used many a times in the mongo shell.
db.CourseContent.find({Actor: a, Object: obj})
So, I have pushed the query results in a variable for further use. However, it seems the query is being changed or modified which cannot be used further.
OLD CODE: (THIS IS WORKING FINE)
var bag={}
bag.$sri=[]; bag.$idz=[]
bag.$sri =db.CourseContent.find({Actor: a, Object: obj}).map( function(sri) {return sri.StatementRefId});
bag.$idz =db.CourseContent.find({Actor: a, Object: obj}).map( function(sri) {return sri._id});
NEW CODE: (NOT WORKING WHEN USING VARIABLE TO STORE QUERY RESULTS) bag.$idz does not contain any value where has it succeeds in OLD CODE.
var bag={}
bag.$sri=[]; bag.$idz=[]
var qry = db.CourseContent.find({Actor: a, Object: obj})
bag.$sri =qry.map( function(sri) {return sri.StatementRefId});
bag.$idz = qry.map( function(sri) {return sri._id});
Can someone help me find where I have been doing wrong?
The problem is that the find() method and his companion in crime aggregate() return a Cursor which can be consumed only once. So you need to convert the query result using the toArray() method like this:
var qry = db.CourseContent.fin‌​d({ Actor: a, Object: obj }).toArray()
I suggest you use the aggregation framework to return those values:
db.CourseContent.aggregate([
{ "$match": { Actor: a, Object: obj } },
{ "group": {
"_id": null,
"sri": { "$push": "$StatementRefId" },
"idz": { "$push": "$_id" }
}}
])
The resulted Cursor object contains a single document. If you are in the shell, it yield that document but using some driver, you need to iterate the cursor.

select function does't work to only get the array i want

i have a document like this
{
"_id": {
"$oid": "570bc73da8ebd9005dd54de3"
},
"title": "dota",
"imgurl": "asd.com",
"description": "",
"hints": [
{
"date": "2016-04-26 22:50:12.6069011 +0430 IRDT",
"description": "narinin"
},
{
"date": "2016-04-26 22:50:12.6069011 +0430 IRDT",
"description": "doros shod"
}
]
}
the script i execute is
hints := hints{}
err := db.C("games").Find(bson.M{"title": game}).Select(bson.M{"hints": 0}).One(&hints)
my 2 structs are
type Game struct {
Id bson.ObjectId `bson:"_id,omitempty"`
Title string `json:"title"`
Imgurl string `json:"imgurl"`
Description string `json:"desc"`
Hints []*hint `bson:"hints", json:"hints"`
}
type hint struct {
Description string `json:"desc"`
Date time.Time `json:"date"`
}
when i use the script all i get is a none sense date string witch is not even in document
how can i get just the slice of hints from a game
You have too keep using Game struct to receive the result, even for only hints column. Also your select query should be .Select(bson.M{"hints": 1}).
I fixed your code, and tried in my local, this one is working.
game := Game{}
err = db.C("games").Find(bson.M{"title": "dota"})
.Select(bson.M{"hints": 1}).One(&game)
if err != nil {
panic(err)
}
for _, hint := range game.Hints {
fmt.Printf("%#v\n", *hint)
}
All properties of game is empty, except Hints.
EDITED 1
To get the first 10 rows on hints, the easiest way would be by playing the slice, but this is bad because it need to fetch all the rows first.
for _, hint := range game.Hints[:10] { // 10 rows
fmt.Printf("%#v\n", *hint)
}
The other solution (which is better), is by using $slice on the .Select() query.
selector := bson.M{"hints": bson.M{"$slice": 10}} // 10 rows
err = db.C("so").Find(bson.M{"title": "dota"})
.Select(selector).One(&game)
EDITED 2
Use []int{skip, limit} on the $slice, to support skip and limit.
selector := bson.M{"hints": bson.M{"$slice": []int{0, 10}}}

Create a custom ID with Mgo

I'm currently starting with GoLang and MongoDB.I'm writing a small web application, a blog to be more specific (which is like the first webapp I write when I try new languages). Everything works fine with MGO even if I had some troubles at first. But now I'd like to access each blog entry (articles will be referred as entries to stick with my models) separately. I could use the ObjectID in the url. But that's damn ugly. For example :
mydomain.com/entries/543fd8940e82533995000002/
That's not user friendly. I did a lot of research on the internet to find a suitable solution, because using any other database engine I could just use the id (and that would be fine).
Could someone help me with the creation of a custom (public) id which would auto-increment when I insert a new entry and that I could use in the url ?
Here is the code of my model for now :
package models
import (
"time"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
)
type (
Entries []Entry
Entry struct {
ID bson.ObjectId `bson:"_id,omitempty"`
Title string `bson:"title"`
Short string `bson:"short"`
Content string `bson:"content"`
Posted time.Time `bson:"posted"`
}
)
// Insert an entry to the database
func InsertEntry(database *mgo.Database, entry *Entry) error {
entry.ID = bson.NewObjectId()
return database.C("entries").Insert(entry)
}
// Find an entry by id
func GetEntryByID(database *mgo.Database, id string) (entry Entry, err error) {
bid := bson.ObjectIdHex(id)
err = database.C("entries").FindId(bid).One(&entry)
return
}
// Retrieves all the entries
func AllEntries(database *mgo.Database) (entries Entries, err error) {
err = database.C("entries").Find(nil).All(&entries)
return
}
// Retrieve all the entries sorted by date.
func AllEntriesByDate(database *mgo.Database) (entries Entries, err error) {
err = database.C("entries").Find(nil).Sort("-posted").All(&entries)
return
}
// Counts all the entries.
func CountAllEntries(database *mgo.Database) (count int, err error) {
count, err = database.C("entries").Find(nil).Count()
return
}
As you know the _id is a mandatory field, that is automatically fill by the driver when you do not set it. This is the behavior that you have in your current application/code. You can find information about this type and its generation here: http://docs.mongodb.org/manual/reference/object-id/
However, you can create your own _id and set the value to anything that makes sense for your business.
This is why I am do not understand the following statement:
I did a lot of research on the internet to find a suitable solution, because using any other database engine I could just use the id (and that would be fine).
You can use any value you want as soon as it is unique for your collection.
About the auto-increment, MongoDB does not provide any auto increment field, so you have to implement it yourself, and call the increment from your application.
For example you create a new collection that contains your "sequences/counters": (showing shell commands not go)
{
_id : "entry",
sequence : 0
}
Then when you want an new id for your document you have first to update, with a findand modify the document you have created with a simple $inc operation
var ret = db.counters.findAndModify(
{
query: { _id: "entry" },
update: { $inc: { seq: 1 } },
new: true
}
);
You can then use the returned value into you new document as an _id.
This pattern is documented here:
http://docs.mongodb.org/manual/tutorial/create-an-auto-incrementing-field/