stalniy/ucast

[mongo2js] Bug: field level operators must support arrays

J3m5 opened this issue · 7 comments

J3m5 commented

I'm currently testing mongo2js and noticed that gt and gt doesn't works on nested arrays.
I'm using sift to compare results.

const sift = require("sift");
const { filter } = require("@ucast/mongo2js");

const query = { "foo.bar.baz": { $lt: 0 } };

const uQuery = filter(query);
const siftQuery = sift(query);

const data = { foo: [{ bar: [{ baz: 1 }] }] };

const uResult = uQuery(data);
// true
// value returned by "getField" : [ 1 ] and comapred against query value "1"

const siftResult = siftQuery(data);
// false

This is because in @ucast/js, in this case, the comparison interpreters like lt compare array of values against a single value.
This is due to the getField function that returns an array of value.

I thought about a solution and because the actual value could be an array, we would need to know if values have been aggregated into an array or if the value is an array.
But there might be a simpler solution.

Thanks for the issue!

I think the only way to do this is to check, if it's an array then use some and if not use single value. A similar was added to eq operator.

J3m5 commented

Yes, but I'm afraid that doing that will break queries if we do something like that:

const query = filter({ a: { $gt: 1 } });

const uQuery = filter(query);
const siftQuery = sift(query);

const data = { a: [1, 2, 3] };

const uResult = uQuery(data);
// true because [1,2,3].some(val => val > 1) will return true

const siftResult = siftQuery(data);
// false [1,2,3] is not the same type as value

What sift do is compare the types of the values to compare

J3m5 commented

But uCast works differently than sift because it aggregate the values with the query and then compare them.
So we might find a way to know if we got an array of values or if the value is an array. (I don't know if it's clear)

For reference, the mongodb docs specify that the comparison operator like $gt only compare values of the same types.

For most data types, comparison operators only perform comparisons on fields where the BSON type matches the query value’s type.

This looks very strange to me. What you showed is actually a normal case and we shouldn't care about types. This is how MongoDB 4.0.5 works:

> db.articles.insert({ a: [1,2,3] })
WriteResult({ "nInserted" : 1 })
> db.articles.insert({ a: [4,5,6] })
WriteResult({ "nInserted" : 1 })

> db.articles.find({ a: { $gt: 3 } })
{ "_id" : ObjectId("5f368dd33b4354f9e35a3e2f"), "a" : [ 4, 5, 6 ] }
> db.articles.find({ a: { $gte: 3 } })
{ "_id" : ObjectId("5f368dcc3b4354f9e35a3e2e"), "a" : [ 1, 2, 3 ] }
{ "_id" : ObjectId("5f368dd33b4354f9e35a3e2f"), "a" : [ 4, 5, 6 ] }

The same about all field level operators, they support arrays

J3m5 commented

That's right, it's there.

Arrays

With arrays, a less-than comparison or an ascending sort compares the smallest element of arrays, and a greater-than comparison or a descending sort compares the largest element of the arrays. As such, when comparing a field whose value is a single-element array (e.g. [ 1 ]) with non-array fields (e.g. 2), the comparison is between 1 and 2. A comparison of an empty array (e.g. [ ]) treats the empty array as less than null or a missing field.

What I saw is this, but it's only mentioning that it doesn't works with objects.

So I think it's safe to do it this way.

fixed in @ucast/js@2.1.1

run npm update and it will fetch new deps for @ucast/mongo2js