mongo query - does property exist? - node.js

Within a collection document there is a 'sub' property that is an object containing 1 or more properties. The collection looks like: (I've removes extraneous other properties)
"properties": {
"source": {
"a/name": 12837,
"a/different/name": 76129
}
}
I need to do a find on the collection where a/name and a/different/name are variables.
The variables have embedded front slashes because they are mqtt topic names, in case you wonder.
(node.js)
I've tried:
collection.find({'properties.source[variable containing name]': {$exists: true}}).toArray(function...
Doesn't work, nothing returned
I've also tried setting a query string as in:
var q = util.format('properties.source.%s', variable containing name);
collection.find({q: {$exists: true}}).toArray(function...
Fails with query error
I could replace the front slashes with some other character if they are the problem but I suspect they are ok. I have tried escaping the front slashes and it makes no difference.
Any suggestions?

You can't directly use a variable as an object key, so you need to change it to something like:
var name = 'a/name';
var query = {};
query['properties.source.' + name] = {$exists: true};
collection.find(query).toArray(function...

As you have properties an object and source is also an object, You should use $exist in a query like following :
db.collection.find({"properties.source.a/name":{$exists:true}})

Related

Mongoose model deleteOne() only works with hard coded string

I have a strange error with mongoose deleteOne() function. Today I wanted to work on my project and got an error while deleting an item from a collection. It simply doesn't delete the document until I use a hardcoded parameter for the options object like this:
const { deletedCount } = await Model.deleteOne({symbol: 'hardcoded'})
// results in deletedCount = 1
But if I try to use a dynamic string like:
const test = 'dynamic'
const { deletedCount } = await Model.deleteOne({symbol: test})
// results in deletedCount = 0
It does no longer delete the document from my collection. The strange thing is yesterday it worked fine and deleted the item.
I tried one other thing I read regarding errors with deleteOne():
const { deletedCount } = await Model.deleteOne({symbol: JSON.stringifiy(symbol)})
But this doesn't work, too.
Does anyone have an idea what's going wrong?
I always default to using ids whenever possible to make sure there's no mistake in the data I am targeting with a given operation.
So in this case that would mean using findByIdAndDelete() instead.
If I don't know the id of the document I'm trying to delete, then only I'd use findOneAndDelete() or deleteOne(), as you have, with something other than an id to identify the document I'm looking for.
Are you certain that the key-value pair you're passing to the function exists in your database?
Problem solved. I accidentally added an additional space character at the end of the string. This is very strange because the error was there since the beginning of my project and yesterday it worked.
So for everyone who might have a similar problem:
I have a ejs template file where I render a html element like this:
<div id="<%= symbol %> ">
Then in my event handler for requesting the server to deleting one item from my list I use the id attribute as a request parameter in the body. In the route handler this parameter is passed to mongoose:
const { symbol } = req.body
const { deletedCount } = await Model.deleteOne({ symbol })
As I mentioned. In the template file after the last ejs seperator there is an addional space character that caused the error. I spotted this issue by making a copy of the monoogse query and than logged it to the console. There I could see the wrong condition parameter.

How to get the id from the given lines of code?

{
acknowledged: true,
insertedId: new ObjectId("612d9d08db9c20f031033478")
}
This was the JSON format I got while I added a file and some other things to MongoDB and I want to get this id separately to save the file to another folder.
Can anyone please explain this?
I believe this question was answered by Vikas in this thread How to get value from specific key in NodeJS JSON [duplicate]
Edited The Answer. Now Its working for above object in question
You can use following function to access the keys of JSON. I have
returned 'mm' key specifically.
function jsonParser(stringValue) {
var string = JSON.stringify(stringValue);
var objectValue = JSON.parse(string);
return objectValue['mm'];
}
if this is a JSON String, at first of all you have to parse it to object liertal, then you can access the specified property you mentioned, so you can do like the following:
function parseJsonString(jsonString) {
// first parse it to object
var obj = JSON.parse(jsonString);
// now access your object property/key
var id = obj.insertedId;
}
If your doing it on mongodb shell or Robo3T then, the line of code would be:
db.collection_name.find({},{insertedId:1})
This is related to projection. 1 represents you want to display it as your output.
In your case as you want only the value of your object id to get displayed, you have to use .valueOf() ; that is ObjectId().valueOf().
-> insertedId: new ObjectId("612d9d08db9c20f031033478")
-> ObjectId("612d9d08db9c20f031033478").valueOf()
->
-> 612d9d08db9c20f031033478
Your can refer this : https://docs.mongodb.com/manual/reference/method/ObjectId.valueOf/ site for your reference.
So you can use this .valueOf() in your code and get the id saved in the document

session.queryObjects does not support secondary types

Reading this https://chemistry.apache.org/docs/cmis-samples/samples/properties/index.html#retrieving-properties, I thought it would be possible to retrieve secondary types using queryObjects method, but it does not. For example, I'm trying to get cm:author from Alfresco, it returns null. Here is my piece of code:
OperationContext oc = OperationContextUtils.createMaximumOperationContext();
ItemIterable<CmisObject> results = session.queryObjects(task.getCmisType(), where, false, oc);
...
Object value = cmisObject.getPropertyValue("cm:author");
Am I missing something?
P.S: I'm using Chemistry 1.0.0, CMIS 1.1, Binding: Browser
UPDATE:
Okay I found something interesting, In order to retrieve cm:author, I have to reload the cmisObject to make it work:
results = session.queryObjects("cmis:document", "IN_FOLDER('" + folder.getId() + "')", false, oc);
results.each { it ->
object = session.getObject(it.getId());
author = object.getPropertyValue("cm:author");
if(author != null) {
println object.getId() + " => " + author;
}
Bug?
First make sure cm:author is what you want. That is not the person who created the document node in Alfresco. That is an editable property that anyone can set to anything, and by default it is null.
If what you want is the actual username of the person who created the document node, you should use cmis:createdBy which is mapped to alfresco's cm:creator property.
Assuming cm:author is definitely what you want, you have two choices regarding how to get it. First, you can get it from the object. But in order to get it from the object you must first fetch the object. Your query returns QueryResult objects, not CmisObjects.
So you should do something like:
ItemIterable<QueryResult> results = session.query(queryString, false);
for (QueryResult qResult : results) {
String objectId = "";
PropertyData<?> propData = qResult.getPropertyById("cmis:objectId");
if (propData != null) {
objectId = (String) propData.getFirstValue();
}
CmisObject obj = session.getObject(session.createObjectId(objectId));
// Dump the object here
System.out.println("Author: " + obj.getPropertyValue("cm:author");
}
Your second option would be to get the property value from the query result. Your ability to do this depends on the query you ran. The author property is defined on an aspect, so you must do a join in order to get it back. The query might look something like:
queryString = "select content.cmis:name, content.cmis:objectId, author.cm:author from cmis:document content JOIN cm:author author ON content.cmis:objectId = author.cmis:objectId WHERE content.cmis:objectId is not null AND author.cm:author = 'Jeff'";
If you use that query, then you can grab the author using the QueryResult, like this:
System.out.println("Author: " + qResult.getPropertyValueByQueryName("author.cm:author"));
Hopefully that explains the difference between fetching the value from a query result and fetching a property value from the object itself.

MongoDB update object and remove properties?

I have been searching for hours, but I cannot find anything about this.
Situation:
Backend, existing of NodeJS + Express + Mongoose (+ MongoDB ofcourse).
Frontend retrieves object from the Backend.
Frontend makes some changes (adds/updates/removes some attributes).
Now I use mongoose: PersonModel.findByIdAndUpdate(id, updatedPersonObject);
Result: added properties are added. Updated properties are updated. Removed properties... are still there!
Now I've been searching for an elegant way to solve this, but the best I could come up with is something like:
var properties = Object.keys(PersonModel.schema.paths);
for (var i = 0, len = properties.length; i < len; i++) {
// explicitly remove values that are not in the update
var property = properties[i];
if (typeof(updatedPersonObject[property]) === 'undefined') {
// Mongoose does not like it if I remove the _id property
if (property !== '_id') {
oldPersonDocument[property] = undefined;
}
}
}
oldPersonDocument.save(function() {
PersonModel.findByIdAndUpdate(id, updatedPersonObject);
});
(I did not even include trivial code to fetch the old document).
I have to write this for every Object I want to update. I find it hard to believe that this is the best way to handle this. Any suggestions anyone?
Edit:
Another workaround I found: to unset a value in MongoDB you have to set it to undefined.
If I set this value in the frontend, it is lost in the REST-call. So I set it to null in the frontend, and then in the backend I convert all null-values to undefined.
Still ugly though. There must be a better way.
You could use replaceOne() if you want to know how many documents matched your filter condition and how many were changed (I believe it only changes one document, so this may not be useful to know). Docs: https://mongoosejs.com/docs/api/model.html#model_Model.replaceOne
Or you could use findOneAndReplace if you want to see the document. I don't know if it is the old doc or the new doc that is passed to the callback; the docs say Finds a matching document, replaces it with the provided doc, and passes the returned doc to the callback., but you could test that on your own. Docs: https://mongoosejs.com/docs/api.html#model_Model.findOneAndReplace
So, instead of:
PersonModel.findByIdAndUpdate(id, updatedPersonObject);, you could do:
PersonModel.replaceOne({ _id: id }, updatedPersonObject);
As long as you have all the properties you want on the object you will use to replace the old doc, you should be good to go.
Also really struggling with this but I don't think your solution is too bad. Our setup is frontend -> update function backend -> sanitize users input -> save in db. For the sanitization part, we use a helper function where we integrate your approach.
private static patchModel(dbDocToUpdate: IModel, dataFromUser: Record<string, any>): IModel {
const sanitized = {};
const properties = Object.keys(PersonModel.schema.paths);
for (const key of properties) {
if (key in dbDocToUpdate) {
sanitized[key] = data[key];
}
}
Object.assign(dbDocToUpdate, sanitized);
return dbDocToUpdate;
}
That works smoothly and sets the values to undefined. Hence, they get removed from the document in the db.
The only problem that remains for us is that we wanted to allow partial updates. With that solution that's not possible and you always have to send everything to the backend.
EDIT
Another workaround we found is setting the property to an empty string in the frontend. Mongo then also removes the property in the database

What type of GUID on Mongoose

Currently, I used MongoVUE to import from current SQL Server database but all PK with uniqueidentifier were converted to something like "Binary - 3:UuidLegacy
My question is how do is create schema for this structure on Mongoose? I can't see Guid/UUID datatype on Mongoose docs http://mongoosejs.com/docs/api.html#schema_Schema.Types
And for more, I get issue when query with ValidationID something like
db.Validations.find({ValidationID: '1389AB5E-56BD-46FD-9A8A-258C7BDE4251'});
It returns nothing although this Guid is exactly same with SQL Server record.
Thanks.
MongoVUE is obscuring things a bit here, but in a nice way that makes it easier to read. Here's what your example ValidationID of '1389AB5E-56BD-46FD-9A8A-258C7BDE4251' actually looks like - it's type 3 BinData:
{"ValidationID" : BinData(3,"E4mrXla9Rv2aiiWMe95CUQ==")}
The viewer is converting that to a more readable format for you. It's doing that by converting to hex and adding dashes. For proof:
> var bar = BinData(3,"E4mrXla9Rv2aiiWMe95CUQ==")
> bar.hex()
1389ab5e56bd46fd9a8a258c7bde4251
If you want to find that ID, then strip the dashes and pass that into the find as follows (I inserted a sample doc):
> db.foo.find({ValidationID: UUID('1389AB5E56BD46FD9A8A258C7BDE4251')})
{ "_id" : ObjectId("544fd7ddbb4f50c77c61f367"), "ValidationID" : BinData(3,"E4mrXla9Rv2aiiWMe95CUQ==") }
I don't have mongoose set up to test, but have done the leg work in another answer similar to this in terms of converting in javascript.
This drove me crazy for several hours, as a solution I ended up having to install
npm install mongodb --save
npm install slugid --save
and code it as follows
var mongo = require('mongodb');
var slugid = require('slugid');
...
var guidb64 = slugid.encode(guid); // guid is something like '8440d561-1127-4fd8-aca9-54de19465d0b'
guidb64 = guidb64.replace(/_/g, '/'); // for whatever reason slug uses '_' instead of '/' I have in db
guidb64 += '=='; // adding missing trailing '==' I have in db
var GUID = new mongo.Binary(new Buffer(guidb64, 'base64'), 3);
var query = MySchemaType.findOne({ Guid: GUID });
query.exec(function(err, entity) {
// process
})

Resources