diff options
-rw-r--r-- | jstests/core/always_true_false.js | 54 | ||||
-rw-r--r-- | jstests/core/or_always_false.js | 16 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder_filter.cpp | 30 |
3 files changed, 76 insertions, 24 deletions
diff --git a/jstests/core/always_true_false.js b/jstests/core/always_true_false.js new file mode 100644 index 00000000000..0b32ac22a16 --- /dev/null +++ b/jstests/core/always_true_false.js @@ -0,0 +1,54 @@ +// Tests $alwaysTrue and $alwaysFalse behavior for match expressions. +(function() { +"use strict"; + +const coll = db.always_true_false; +coll.drop(); + +assert.commandWorked( + coll.insert([{a: []}, {a: false}, {a: null}, {}, {a: false, b: 2}, {a: false, b: 1}])); + +// Check alwaysFalse. +assert.eq(0, coll.find({$alwaysFalse: 1}).itcount()); +assert.eq(0, coll.find({$alwaysFalse: 1, a: false}).itcount()); +assert.eq(0, coll.find({$alwaysFalse: 1, b: 1}).itcount()); + +// Check alwaysFalse with $and, $or. +assert.eq(1, coll.find({$or: [{b: 1}, {$alwaysFalse: 1}]}).itcount()); +assert.eq(0, coll.find({$or: [{$alwaysFalse: 1}]}).itcount()); +assert.eq(0, coll.find({$or: [{$alwaysFalse: 1}, {$alwaysFalse: 1}]}).itcount()); +assert.eq(0, coll.find({$or: [{$alwaysFalse: 1}, {a: {$all: []}}, {$alwaysFalse: 1}]}).itcount()); +assert.eq(0, coll.find({$and: [{b: 1}, {$alwaysFalse: 1}]}).itcount()); +assert.eq(0, coll.find({$and: [{a: false}, {$alwaysFalse: 1}, {$alwaysFalse: 1}]}).itcount()); + +// Check alwaysTrue. +assert.eq(6, coll.find({$alwaysTrue: 1}).itcount()); +assert.eq(3, coll.find({$alwaysTrue: 1, a: false}).itcount()); +assert.eq(1, coll.find({$alwaysTrue: 1, b: 1}).itcount()); + +// Check alwaysTrue with $and, $or. +assert.eq(3, coll.find({$and: [{a: false}, {$alwaysTrue: 1}, {$alwaysTrue: 1}]}).itcount()); +assert.eq(0, coll.find({$and: [{a: false}, {$alwaysTrue: 1}, {$alwaysFalse: 1}]}).itcount()); +assert.eq(6, coll.find({$or: [{b: 1}, {$alwaysTrue: 1}]}).itcount()); +assert.eq(6, coll.find({$or: [{b: 1}, {$alwaysFalse: 1}, {$alwaysTrue: 1}]}).itcount()); + +assert(coll.drop()); + +// Check that a rooted-$or query with each clause false will not return any results. +assert.commandWorked(coll.insert([{}, {}, {}])); +const emptyOrError = assert.throws(() => coll.find({$or: []}).itcount()); +assert.eq(emptyOrError.code, ErrorCodes.BadValue); + +assert.eq(coll.find({$or: [{$alwaysFalse: 1}]}).itcount(), 0); +assert.eq(coll.find({$or: [{a: {$all: []}}]}).itcount(), 0); +assert.eq(coll.find({$or: [{$alwaysFalse: 1}, {$alwaysFalse: 1}]}).itcount(), 0); +assert.eq(coll.find({$or: [{$alwaysFalse: 1}, {a: {$all: []}}, {$alwaysFalse: 1}]}).itcount(), 0); + +// Check failure cases. +assert.commandFailedWithCode(db.runCommand({find: coll.getName(), filter: {$alwaysTrue: 0}}), + ErrorCodes.FailedToParse); +assert.commandFailedWithCode(db.runCommand({find: coll.getName(), filter: {$alwaysFalse: 0}}), + ErrorCodes.FailedToParse); +assert.commandFailedWithCode(db.runCommand({find: coll.getName(), filter: {a: {$alwaysFalse: 1}}}), + ErrorCodes.BadValue); +}()); diff --git a/jstests/core/or_always_false.js b/jstests/core/or_always_false.js deleted file mode 100644 index 0766806a223..00000000000 --- a/jstests/core/or_always_false.js +++ /dev/null @@ -1,16 +0,0 @@ -// Tests that a rooted-$or query with each clause provably false will not return any results. -(function() { -"use strict"; - -const coll = db.or_always_false; -coll.drop(); - -assert.commandWorked(coll.insert([{}, {}, {}])); -const emptyOrError = assert.throws(() => coll.find({$or: []}).itcount()); -assert.eq(emptyOrError.code, ErrorCodes.BadValue); - -assert.eq(coll.find({$or: [{$alwaysFalse: 1}]}).itcount(), 0); -assert.eq(coll.find({$or: [{a: {$all: []}}]}).itcount(), 0); -assert.eq(coll.find({$or: [{$alwaysFalse: 1}, {$alwaysFalse: 1}]}).itcount(), 0); -assert.eq(coll.find({$or: [{$alwaysFalse: 1}, {a: {$all: []}}, {$alwaysFalse: 1}]}).itcount(), 0); -}()); diff --git a/src/mongo/db/query/sbe_stage_builder_filter.cpp b/src/mongo/db/query/sbe_stage_builder_filter.cpp index 550dafaff31..3d9550279d0 100644 --- a/src/mongo/db/query/sbe_stage_builder_filter.cpp +++ b/src/mongo/db/query/sbe_stage_builder_filter.cpp @@ -508,6 +508,20 @@ void generateLogicalAnd(MatchExpressionVisitorContext* context, const AndMatchEx } /** + * Generates and pushes a constant boolean expression for either alwaysTrue or alwaysFalse. + */ +void generateAlwaysBoolean(MatchExpressionVisitorContext* context, bool value) { + context->predicateVars.push(context->slotIdGenerator->generate()); + context->inputStage = + sbe::makeProjectStage(std::move(context->inputStage), + context->predicateVars.top(), + sbe::makeE<sbe::EConstant>(sbe::value::TypeTags::Boolean, value)); + + // Check if can bail out early from the $and predicate if this expression is part of branch. + checkForShortCircuitFromLogicalAnd(context); +} + +/** * A match expression pre-visitor used for maintaining nested logical expressions while traversing * the match expression tree. */ @@ -515,12 +529,8 @@ class MatchExpressionPreVisitor final : public MatchExpressionConstVisitor { public: MatchExpressionPreVisitor(MatchExpressionVisitorContext* context) : _context(context) {} - void visit(const AlwaysFalseMatchExpression* expr) final { - unsupportedExpression(expr); - } - void visit(const AlwaysTrueMatchExpression* expr) final { - unsupportedExpression(expr); - } + void visit(const AlwaysFalseMatchExpression* expr) final {} + void visit(const AlwaysTrueMatchExpression* expr) final {} void visit(const AndMatchExpression* expr) final { _context->nestedLogicalExprs.push({expr, expr->numChildren()}); } @@ -669,8 +679,12 @@ class MatchExpressionPostVisitor final : public MatchExpressionConstVisitor { public: MatchExpressionPostVisitor(MatchExpressionVisitorContext* context) : _context(context) {} - void visit(const AlwaysFalseMatchExpression* expr) final {} - void visit(const AlwaysTrueMatchExpression* expr) final {} + void visit(const AlwaysFalseMatchExpression* expr) final { + generateAlwaysBoolean(_context, false); + } + void visit(const AlwaysTrueMatchExpression* expr) final { + generateAlwaysBoolean(_context, true); + } void visit(const AndMatchExpression* expr) final { _context->nestedLogicalExprs.pop(); generateLogicalAnd(_context, expr); |