Skip to content

Commit

Permalink
fix(mongodb): use $or + $eq instead of $in (#4848)
Browse files Browse the repository at this point in the history
  • Loading branch information
laplab committed May 3, 2024
1 parent 5446571 commit 8853577
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 24 deletions.
Expand Up @@ -11,7 +11,7 @@ mod mongodb {
id String @id @default(auto()) @map("_id") @test.ObjectId
leagueId Int
teamId Int
awayLosses Int
awayLosses Int
}
"#
};
Expand Down
65 changes: 42 additions & 23 deletions query-engine/connectors/mongodb-query-connector/src/filter.rs
Expand Up @@ -188,42 +188,61 @@ impl MongoFilterVisitor {
// Todo: The nested list unpack looks like a bug somewhere.
// Likely join code mistakenly repacks a list into a list of PrismaValue somewhere in the core.
ScalarCondition::In(vals) => match vals {
ConditionListValue::List(vals) => match vals.split_first() {
// List is list of lists, we need to flatten.
Some((PrismaValue::List(_), _)) => {
let mut bson_values = Vec::with_capacity(vals.len());

for pv in vals {
if let PrismaValue::List(inner) = pv {
bson_values.extend(
inner
.into_iter()
.map(|val| self.coerce_to_bson_for_filter(field, val))
.collect::<crate::Result<Vec<_>>>()?,
)
}
ConditionListValue::List(values) => {
let mut equalities = Vec::with_capacity(values.len());

for value in values {
// List is list of lists, we need to flatten.
// This flattening behaviour does not affect user queries because Prisma does
// not support storing arrays as values inside a field. It is possible to have
// a 1-dimensional array field, but not 2 dimensional. Thus, we never have
// user queries which have arrays in the argument of `in` operator. If we
// encounter such case, then this query was produced internally and we can
// safely flatten it.
if let PrismaValue::List(list) = value {
equalities.extend(
list.into_iter()
.map(|value| {
let value = self.coerce_to_bson_for_filter(field, value)?;
Ok(doc! { "$eq": [&field_name, value] })
})
.collect::<crate::Result<Vec<_>>>()?,
);
} else {
let value = self.coerce_to_bson_for_filter(field, value)?;
equalities.push(doc! { "$eq": [&field_name, value] })
}

doc! { "$in": [&field_name, bson_values] }
}
_ => {
doc! { "$in": [&field_name, self.coerce_to_bson_for_filter(field, PrismaValue::List(vals))?] }
}
},

// Previously, `$in` operator was used instead of a tree of `$or` + `$eq` operators.
// At the moment of writing, MongoDB does not optimise aggregation version of `$in`
// operator to use indexes, leading to significant performance problems. Until this
// is fixed, we rely on `$eq` operator which does have index optimisation implemented.
doc! { "$or": equalities }
}
ConditionListValue::FieldRef(field_ref) => {
// In this context, `field_ref` refers to an array field, so we actually need an `$in` operator.
doc! { "$in": [&field_name, coerce_as_array(self.prefixed_field_ref(&field_ref)?)] }
}
},
ScalarCondition::NotIn(vals) => match vals {
ConditionListValue::List(vals) => {
let bson_values = vals
let equalities = vals
.into_iter()
.map(|val| self.coerce_to_bson_for_filter(field, val))
.map(|value| {
let value = self.coerce_to_bson_for_filter(field, value)?;
Ok(doc! { "$ne": [&field_name, value] })
})
.collect::<crate::Result<Vec<_>>>()?;

doc! { "$not": { "$in": [&field_name, bson_values] } }
// Previously, `$not` + `$in` operators were used instead of a tree of `$and` + `$ne` operators.
// At the moment of writing, MongoDB does not optimise aggregation version of `$in`
// operator to use indexes, leading to significant performance problems. Until this
// is fixed, we rely on `$ne` operator which does have index optimisation implemented.
doc! { "$and": equalities }
}
ConditionListValue::FieldRef(field_ref) => {
// In this context, `field_ref` refers to an array field, so we actually need an `$in` operator.
doc! { "$not": { "$in": [&field_name, coerce_as_array(self.prefixed_field_ref(&field_ref)?)] } }
}
},
Expand Down

0 comments on commit 8853577

Please sign in to comment.