Simpl-Schema - update error while using $lte operator when calling "Update" by the field of type "Date"

I’m trying to update a document in the collection, so here is query:

let todayStart = moment().startOf('day').toDate();
let todayEnd = moment().endOf('day').toDate();
let query = { 'vendors.ptsxNfxL4uRxq8pT3': 5, vendorTotal: 5 };
SystemInfo.update({createdAt: {$gte: todayStart, $lte: todayEnd}}, {$inc: query}, {upsert: true});

Schema for the collection looks like this:

vendors: {
    type: Object,
    blackbox: true,
    optional: true
},
companies: {
    type: Object,
    blackbox: true,
    optional: true
},
vendorTotal: {
    type: Number,
    optional: true
},
companyTotal: {
    type: Number,
    optional: true
},
createdAt: {
    type: Date,
    defaultValue: new Date()
}

But update query fails. Here is short log of error:

error: 400,
reason: 'Created at must be of type Date in system_info update',
details: '[
    {
        "name":"createdAt",
        "value":{
            "$gte":{
                "$date":1570737600000
            },
            "$lte":{
                "$date":1570823999999
            }
        },
        "type":"expectedType",
        "dataType":"Date"
    },
    {
        "name":"createdAt.$gte",
        "type":"keyNotInSchema",
        "value":{
            "$date":1570737600000
        }
    },{
        "name":"createdAt.$lte",
        "type":"keyNotInSchema",
        "value":{
            "$date":1570823999999
        }
    }
]'

I’m using simpl-schema@1.5.6 node module and aldeed:simple-schema@1.5.4.

Is there any solution of this problem (Without removing schema for this collection)?

This won’t fix your main issue, but I noticed that the default value here is static (defined at app start (different on server and client)) instead of generated as needed.
You should probably use autoValue to set it when the document is cleaned / created

1 Like

Having a look at the error proper, it looks like it’s caused by this section in collection2, which applies to upserts:

Unfortunately the prediction about being error prone in the comment came true.

You can see that it’s cloning in the selector fields to the object being validated. The comment asserts that in an upsert with plain fields in the selector, those fields will get merged with the result of the modifier to be the new document. Not sure if that’s accurate, but it’s why simple-schema is doing this.

Thinking about it, I’m not sure an upsert makes sense when the selector is a range of dates. What would date would the inserted document use if none are found? :thinking:
Actually, That’s probably why you have a defaultValue in the schema! :smiley:

Problem is that these all happen at different layers, and simple-schema and collection2 can’t tell what the result of the query will be before it does it’s defaultValue / autoValue processing, so even if the upsert bug didn’t exist, it wouldn’t end up receiving the defaultValue and then mongo would have to deal with it.

So I think you’ll need to move the logic you want into user code, check if a document already exists and decide if you’re going to update or insert yourself


Okay I was curious what mongo does with an upsert with a range query and and $inc modifier, and it doesn’t set anything for the query key createdAt and sets the $inc keys with the values in the $inc

meteor:PRIMARY> db.test.update(
  { createdAt: { $gte: new Date(), $lte: new Date('2019-10-15') } }, 
  { $inc: { one: 1, two: 2 } },
  { upsert: true }
)
WriteResult({
	"nMatched" : 0,
	"nUpserted" : 1,
	"nModified" : 0,
	"_id" : ObjectId("5da3c6dbf4eb9c83785f8baa")
})
meteor:PRIMARY> db.test.find()
{ "_id" : ObjectId("5da3c6dbf4eb9c83785f8baa"), "one" : 1, "two" : 2 }
meteor:PRIMARY>

And it does in fact merge the query fields with the result of the modifier in an upsert normally:

meteor:PRIMARY> db.test.update(
  { createdAt: new Date() }, 
  { $inc: {one: 1, two: 2 } }, 
  {upsert: true}
)
WriteResult({
	"nMatched" : 0,
	"nUpserted" : 1,
	"nModified" : 0,
	"_id" : ObjectId("5da3c7f0f4eb9c83785f8da9")
})
meteor:PRIMARY> db.test.find()
{ 
  "_id" : ObjectId("5da3c7f0f4eb9c83785f8da9"), 
  "createdAt" : ISODate("2019-10-14T00:57:20.706Z"), 
  "one" : 1, 
  "two" : 2 
}

So collection2’s behaviour would be more correct if they just stripped out $ fields before validating


EDIT2: I went ahead and had a go at a fix: Fix bug where upserts with query operators fail by coagmano · Pull Request #398 · Meteor-Community-Packages/meteor-collection2 · GitHub
Might need to wait a bit till Aldeed either has time to review or hands maintainer rights to Meteor Community Packages

3 Likes

Thanks for your detailed explanation! We fixed the bug by dividing “upsert” query to “insert” and “update” queries.

1 Like

You’re lucky it was a slow day at work. I had fun diving into collection2’s internals to figure this one out :laughing:

1 Like