TypeORM returns empty array when querying MongoDB for date - mongodb

I have the following query that is supposed to get the objects that have a future hospitalizedUntil date from my MongoDB:
return await this.hospitalRepository.find({
where: {
hospitalizedUntil: MoreThanDate(new Date(), EDateType.Datetime)
}
});
Where MoreThanDate:
const MoreThanDate = (date: Date, type: EDateType) => MoreThan(format(date, type));
However, this query doesn't return any objects, even though there is an object in the Mongo where hospitalizedUntil is in the future:
(Current datetime: 2020-08-28T17:11:09.888Z)
[
Hospital {
_id: 5f492efd50e81f63dc60c5d3,
discordId: 'xxxxxxxxxxxxxxxxx',
hospitalizedUntil: 2020-08-28T18:51:17.627Z,
reason: 'Was robbed',
hospitalizedBy: 'xxxxxxxxxxxxxxxxx'
}
]
I've also tried to use the Between operator:
where: {
hospitalizedUntil: Between(new Date(), new Date('9999-12-31'))
}
But this also returned an empty array.

Had the same issue, and solved it by using the native mongoDB query.
hospitalizedUntil: { $gte: format(new Date(), EDateType.Datetime) }
I think those functions just don't work on mongoDB (although I've not found anything related to that issue)

Related

MongoDB update in array fails: Updating the path 'companies.$.updatedAt' would create a conflict at 'companies.$'

we upgraded (from MongoDB 3.4) to:
MongoDB: 4.2.8
Mongoose: 5.9.10
and now we receive those errors. For the smallest example the models are:
[company.js]
'use strict';
const Schema = require('mongoose').Schema;
module.exports = new Schema({
name: {type: String, required: true},
}, {timestamps: true});
and
[target_group.js]
'use strict';
const Schema = require('mongoose').Schema;
module.exports = new Schema({
title: {
type: String,
required: true,
index: true,
},
minAge: Number,
maxAge: Number,
companies: [Company],
}, {timestamps: true});
and when I try to update the company within a targetgroup
_updateTargetGroup(companyId, company) {
return this.targetGroup.update(
{'companies._id': companyId},
{$set: {'companies.$': company}},
{multi: true});
}
I receive
MongoError: Updating the path 'companies.$.updatedAt' would create a conflict at 'companies.$'
even if I prepend
delete company.updatedAt;
delete company.createdAt;
I get this error.
If I try similar a DB Tool (Robo3T) everything works fine:
db.getCollection('targetgroups').update(
{'companies.name': "Test 1"},
{$set: {'companies.$': {name: "Test 2"}}},
{multi: true});
Of course I could use the workaround
_updateTargetGroup(companyId, company) {
return this.targetGroup.update(
{'companies._id': companyId},
{$set: {'companies.$.name': company.name}},
{multi: true});
}
(this is working in deed), but I'd like to understand the problem and we have also bigger models in the project with same issue.
Is this a problem of the {timestamps: true}? I searched for an explanation but werenot able to find anything ... :-(
The issue originates from using the timestamps as you mentioned but I would not call it a "bug" as in this instance I could argue it's working as intended.
First let's understand what using timestamps does in code, here is a code sample of what mongoose does to an array (company array) with timestamps: (source)
for (let i = 0; i < len; ++i) {
if (updatedAt != null) {
arr[i][updatedAt] = now;
}
if (createdAt != null) {
arr[i][createdAt] = now;
}
}
This runs on every update/insert. As you can see it sets the updatedAt and createdAt of each object in the array meaning the update Object changes from:
{$set: {'companies.$.name': company.name}}
To:
{
"$set": {
"companies.$": company.name,
"updatedAt": "2020-09-22T06:02:11.228Z", //now
"companies.$.updatedAt": "2020-09-22T06:02:11.228Z" //now
},
"$setOnInsert": {
"createdAt": "2020-09-22T06:02:11.228Z" //now
}
}
Now the error occurs when you try to update the same field with two different values/operations, for example if you were to $set and $unset the same field in the same update Mongo does not what to do hence it throws the error.
In your case it happens due to the companies.$.updatedAt field. Because you're updating the entire object at companies.$, that means you are basically setting it to be {name: "Test 2"} this also means you are "deleting" the updatedAt field (amongst others) while mongoose is trying to set it to be it's own value thus causing the error. This is also why your change to companies.$.name works as you would only be setting the name field and not the entire object so there's no conflict created.

MongoDB Shell: Is it possible do an update query using a function?

I have a collection with data like this:
[{
_id: 1,
address: '1/23 Fake Street'
},
{
_id: 2,
address: '5/20 Whatever Lane'
},
{
_id: 3,
address: '10 Foo Avenue'
}]
I'd like to perform a Mongo bulk update query, which does the following:
Transforms the address field to lowercase
Creates a new field, 'buildingAddress', which splits an address at the slash (if present, as with the first two items) and uses the text after it to populate the new field
In Node, I'd do something like this:
const cursor = db.items.find({});
for await (const item of cursor) {
try {
await pageMapper(item);
} catch (e) {
console.error(e);
}
}
async function pageMapper(item){
const newAddress = item.address.toLowerCase()
const buildingAddress = newAddress.split('/ ')[1];
return db.items.updateOne(item._id, {
$set: {
address: newAddress,
buildingAddress
}
})
}
I'm wondering if there's a way to do this in the MongoDB shell itself, passing in a function to db.collection.update? Or should I stick to the node driver for doing more complex update operations?
If you are using MongoDB 4.2+, you can use aggregation or the pipeline form of update to accomplish that.
$toLower converts a string to lower case
$split to split the field
$slice or $arrayElemAt to pick the element(s) to keep
One possible way to do that with update:
db.items.updateMany({},[
{$addFields:{
address:{$toLower:"$address"}
}},
{$addFields:{
buildingAddress:{
$arrayElemAt:[
{$split:["$address","/"]},
-1
]
}
}}
])

Mongoose: MongoError: >1 field while trying to project out $elemMatch

I'm trying to project out only the matched element of an array, in the updated version. But I'm getting the error: "MongoError: >1 field in obj: { _id: 0, lotes.$: 1 }"
If I remove 'new: true', it works. But then I have the doc before the update. And I would really like the updated version.
What's wrong? How can I fix it?
The Offer doc is something like:
{
_id
series: [ Serie ]
}
Serie structure is something like:
{
_id
public.available: Number
public.expDate: Date
}
I'm using Mongoose:
var query = {
'_id': offerId,
'series': {
$elemMatch: {
'_id': serieId,
'public.available': {$gt:0},
'public.expDate': {$gt: now}
}
}
};
var update = {
$inc: { 'series.$.public.available' : -1 }
};
var options = { // project out just the element found, updated
new:true,
select: {
'_id': 0,
'series.$': 1
}
};
Offers.findOneAndUpdate(query, update, options)
.then( element => {
...
}
For anyone else experiencing this error, it is also the most common error when trying to perform an illegal action such as trying to update a database element inside of a findOne request.
Making sure your request is correct, such as findOneAndUpdate should be your first port of call when you get this error.
As Anthony Winzlet pointed out in the links, there seems to be an issue with Mongoose, in which if you use 'new:true', you can't project out the $elemMatch.
So my solution was to keep using 'new:true' only, without projections. And reduce the array later on to get the $elemMatch:
.then( (result) => {
var aux = result.series.reduce((acu, serie, index) => {
if (serie._id == req.params.serieId) return index;
});
var element = result.series[aux];
}

Using TTL in MongoDB [duplicate]

I have a very certain thing i want to accomplish, and I wanted to make sure it is not possible in mongoose/mongoDB before I go and code the whole thing myself.
I checked mongoose-ttl for nodejs and several forums and didn't find quite what I need.
here it is:
I have a schema with a date field createDate. Now i wish to place a TTL on that field, so far so good, i can do it like so (expiration in 5000 seconds):
createDate: {type: Date, default: Date.now, expires: 5000}
but I would like my users to be able to "up vote" documents they like so those documents will get a longer period of time to live, without changing the other documents in my collection.
So, Can i change a TTL of a SINGLE document somehow once a user tells me he likes that document using mongoose or other existing npm related modules?
thank you
It has been more than a year, but this may be useful for others, so here is my answer:
I was trying accomplish this same thing, in order to allow a grace period after an entry deletion, so the user can cancel the operation afterwards.
As stated by Mike Bennett, you can use a TTL index making documents expire at a specific clock time.
Yo have to create an index, setting the expireAfterSeconds to zero:
db.yourCollection.createIndex({ "expireAt": 1 }, { expireAfterSeconds: 0 });
This will not affect any of the documents in your collection, unless you set expireAfterSeconds on a particular document like so:
db.log_events.insert( {
"expireAt": new Date('July 22, 2013 14:00:00'),
"logEvent": 2,
"logMessage": "Success!"
} )
Example in mongoose
Model
var BeerSchema = new Schema({
name: {
type: String,
unique: true,
required: true
},
description: String,
alcohol: Number,
price: Number,
createdAt: { type: Date, default: Date.now }
expireAt: { type: Date, default: undefined } // you don't need to set this default, but I like it there for semantic clearness
});
BeerSchema.index({ "expireAt": 1 }, { expireAfterSeconds: 0 });
Deletion with grace period
Uses moment for date manipulation
exports.deleteBeer = function(id) {
var deferred = q.defer();
Beer.update(id, { expireAt: moment().add(10, 'seconds') }, function(err, data) {
if(err) {
deferred.reject(err);
} else {
deferred.resolve(data);
}
});
return deferred.promise;
};
Revert deletion
Uses moment for date manipulation
exports.undeleteBeer = function(id) {
var deferred = q.defer();
// Set expireAt to undefined
Beer.update(id, { $unset: { expireAt: 1 }}, function(err, data) {
if(err) {
deferred.reject(err);
} else {
deferred.resolve(data);
}
});
return deferred.promise;
};
You could use the expire at clock time feature in mongodb. You will have to update the expire time each time you want to extend the expiration of a document.
http://docs.mongodb.org/manual/tutorial/expire-data/#expire-documents-at-a-certain-clock-time

Meteor/Mongo DB $gte operator not working on find();

I am trying to make a query so that I only return the 'productos' that are on the current 'categoria', they also need to have the 'stock.cantidad' field greater or equal than 1 and the 'stock.idCedis' equal to a specific value, and this is how I am trying to do it:
return Productos.find(
{
idCategoria: Router.current().params._id,
"stock.cantidad":{$gte: 1},
"stock.idCedis":idCedis
});
I checked and the "stock.idCedis":idCedis is working just fine displaying the 'productos' that have that specific 'idCedis', but what I am having problems with is the "stock.cantidad":{$gte: 1}, part because I don't know why Meteor or Mongo DB for that matter are just ignoring it.
The schema for the stock part of 'productos' that I am currently using is this:
stock: {
type: [Object]
},
"stock.$.cantidad": {
type: Number,
label: "Cantidad de Stock",
min: 0
},
"stock.$.idCedis": {
type: String,
label: "Centro de DistribuciĆ³n"
},
So I would like to know if I am doing something wrong or any other way I could make this work.
Since stock.$.cantitad is an array try $elemMatch:
return Productos.find(
{
idCategoria: Router.current().params._id,
"stock.cantidad":{$elemMatch {$gte: 1}},
"stock.idCedis":idCedis
});
The mongo docs indicate that you shouldn't need to do this when there's only a single query condition but given how Meteor interacts with mongo I'd give it a try.
Muchas gracias Michael Floyd, what I needed was that $elemMatch query operator, but for some reason your code did not worked form me, what worked was this:
Productos.find(
{
idCategoria: Router.current().params._id,
stock: { $elemMatch: { idCedis: idCedis, cantidad: { $gte: 0 } } }
});