Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

SERVER-6416 Compute field ranges for singleton $or clauses.

  • Loading branch information...
commit 6bea062f70ef5593a9510e17bc1dbdda6f631ce5 1 parent ab4fd6e
@astaple astaple authored
View
4 jstests/andor.js
@@ -101,5 +101,5 @@ test();
t.drop();
t.ensureIndex( {a:1} );
var e = t.find( {$and:[{a:1}]} ).explain();
-// nested $or clauses currently ignored for indexing
-assert.eq( e.indexBounds, t.find( {$and:[{a:1,$or:[{a:2}]}]} ).explain().indexBounds );
+// nested non singleton $or clauses currently ignored for indexing
+assert.eq( e.indexBounds, t.find( {$and:[{a:1,$or:[{a:2},{a:3}]}]} ).explain().indexBounds );
View
4 jstests/or4.js
@@ -93,12 +93,12 @@ assert.eq.automsg( "1", "t.count( {$or:[{a:2},{a:1}]} )" );
t.remove();
-assert.eq.automsg( "'BtreeCursor b_1'", "t.find( {$or:[{a:1}]} ).sort( {b:1} ).explain().cursor" );
+assert.eq.automsg( "'BtreeCursor a_1'", "t.find( {$or:[{a:1}]} ).sort( {b:1} ).explain().cursor" );
assert.eq.automsg( "'BtreeCursor b_1'", "t.find( {$or:[{}]} ).sort( {b:1} ).explain().cursor" );
assert.eq.automsg( "'BtreeCursor b_1'", "t.find( {$or:[{a:1},{a:3}]} ).sort( {b:1} ).explain().cursor" );
assert.eq.automsg( "'BtreeCursor b_1'", "t.find( {$or:[{a:1},{b:3}]} ).sort( {b:1} ).explain().cursor" );
assert.eq.automsg( "'BtreeCursor b_1'", "t.find( {$or:[{b:1}]} ).sort( {b:1} ).explain().cursor" );
-assert.eq.automsg( "1", "t.find( {$or:[{b:1}]} ).sort( {b:1} ).explain().indexBounds.b[ 0 ][ 0 ].$minElement" );
+assert.eq.automsg( "1", "t.find( {$or:[{b:1},{b:2}]} ).sort( {b:1} ).explain().indexBounds.b[ 0 ][ 0 ].$minElement" );
assert.eq.automsg( "'BtreeCursor b_1'", "t.find( {$or:[{a:1}]} ).hint( {b:1} ).explain().cursor" );
assert.eq.automsg( "'BtreeCursor b_1'", "t.find( {$or:[{}]} ).hint( {b:1} ).explain().cursor" );
View
35 jstests/orr.js
@@ -0,0 +1,35 @@
+// Index bounds within a singleton $or expression can be used with sorting. SERVER-6416 SERVER-1205
+
+t = db.jstests_orr;
+t.drop();
+
+function assertIndexBounds( bounds, query, sort, hint ) {
+ cursor = t.find( query ).sort( sort );
+ if ( hint ) {
+ cursor.hint( hint );
+ }
+ assert.eq( bounds, cursor.explain().indexBounds.a );
+}
+
+t.ensureIndex( { a:1 } );
+
+// Tight index bounds for a singleton $or expression with an indexed sort.
+assertIndexBounds( [[ 1, 1 ]], { $or:[ { a:1 } ] }, { a:1 } );
+
+// Tight index bounds for a nested singleton $or expression with an indexed sort.
+assertIndexBounds( [[ 1, 1 ]], { $or:[ { $or:[ { a:1 } ] } ] }, { a:1 } );
+
+// No index bounds computed for a non singleton $or expression with an indexed sort.
+assertIndexBounds( [[ { $minElement:1 }, { $maxElement:1 } ]], { $or:[ { a:1 }, { a:2 } ] },
+ { a:1 } );
+
+// Tight index bounds for a singleton $or expression with an unindexed sort.
+assertIndexBounds( [[ 1, 1 ]], { $or:[ { a:1 } ] }, { b:1 } );
+
+// No index bounds computed for a non singleton $or expression and an unindexed sort, so a $natural
+// plan is used.
+assertIndexBounds( undefined, { $or:[ { a:1 }, { a:2 } ] }, { b:1 } );
+
+// No index bounds computed for a non singleton $or expression with an unindexed sort.
+assertIndexBounds( [[ { $minElement:1 }, { $maxElement:1 } ]], { $or:[ { a:1 }, { a:2 } ] },
+ { b:1 }, { a:1 } );
View
40 src/mongo/db/queryutil.cpp
@@ -950,6 +950,19 @@ namespace mongo {
}
}
+ void FieldRangeSet::handleConjunctionClauses( const BSONObj& clauses, bool optimize ) {
+ BSONObjIterator clausesIterator( clauses );
+ while( clausesIterator.more() ) {
+ BSONElement clause = clausesIterator.next();
+ uassert( 14817, "$and/$or elements must be objects", clause.type() == Object );
+ BSONObjIterator clauseIterator( clause.embeddedObject() );
+ while( clauseIterator.more() ) {
+ BSONElement matchElement = clauseIterator.next();
+ handleMatchField( matchElement, optimize );
+ }
+ }
+ }
+
void FieldRangeSet::handleMatchField( const BSONElement& matchElement, bool optimize ) {
const char* matchFieldName = matchElement.fieldName();
if ( matchFieldName[ 0 ] == '$' ) {
@@ -957,32 +970,31 @@ namespace mongo {
uassert( 14816, "$and expression must be a nonempty array",
matchElement.type() == Array &&
matchElement.embeddedObject().nFields() > 0 );
- BSONObjIterator andExpressionIterator( matchElement.embeddedObject() );
- while( andExpressionIterator.more() ) {
- BSONElement andClause = andExpressionIterator.next();
- uassert( 14817, "$and elements must be objects", andClause.type() == Object );
- BSONObjIterator andClauseIterator( andClause.embeddedObject() );
- while( andClauseIterator.more() ) {
- BSONElement andMatchElement = andClauseIterator.next();
- handleMatchField( andMatchElement, optimize );
- }
- }
+ handleConjunctionClauses( matchElement.embeddedObject(), optimize );
return;
}
adjustMatchField();
- if ( str::equals( matchFieldName, "$where" ) ) {
- return;
- }
-
if ( str::equals( matchFieldName, "$or" ) ) {
+ // Check for a singleton $or expression.
+ if ( matchElement.type() == Array &&
+ matchElement.embeddedObject().nFields() == 1 ) {
+ // Compute field bounds for a singleton $or expression as if it is a $and
+ // expression. With only one clause, the matching semantics are the same.
+ // SERVER-6416
+ handleConjunctionClauses( matchElement.embeddedObject(), optimize );
+ }
return;
}
if ( str::equals( matchFieldName, "$nor" ) ) {
return;
}
+
+ if ( str::equals( matchFieldName, "$where" ) ) {
+ return;
+ }
}
bool equality =
View
1  src/mongo/db/queryutil.h
@@ -518,6 +518,7 @@ namespace mongo {
* TODO integrate these with an external query parser shared by the matcher. SERVER-1009
*/
void handleMatchField( const BSONElement& matchElement, bool optimize );
+ void handleConjunctionClauses( const BSONObj& clauses, bool optimize );
void handleOp( const char* matchFieldName, const BSONElement& op, bool isNot,
bool optimize );
void handleNotOp( const char* matchFieldName, const BSONElement& notOp, bool optimize );
View
4 src/mongo/dbtests/queryoptimizertests.cpp
@@ -1517,7 +1517,7 @@ namespace QueryOptimizerTests {
{
shared_ptr<MultiPlanScanner> mps =
- makeMps( fromjson( "{$or:[{a:1}]}" ), BSON( "c" << 1 ) );
+ makeMps( fromjson( "{$or:[{a:1},{a:2}]}" ), BSON( "c" << 1 ) );
ASSERT_EQUALS( 1, mps->currentNPlans() );
ASSERT( !mps->possibleInOrderPlan() );
ASSERT( !mps->haveInOrderPlan() );
@@ -1527,7 +1527,7 @@ namespace QueryOptimizerTests {
{
shared_ptr<MultiPlanScanner> mps =
- makeMps( fromjson( "{$or:[{a:1,b:1}]}" ), BSONObj() );
+ makeMps( fromjson( "{$or:[{a:1,b:1},{a:2,b:2}]}" ), BSONObj() );
ASSERT_EQUALS( 3, mps->currentNPlans() );
ASSERT( mps->possibleInOrderPlan() );
ASSERT( mps->haveInOrderPlan() );
View
74 src/mongo/dbtests/queryutiltests.cpp
@@ -263,6 +263,72 @@ namespace QueryUtilTests {
virtual BSONElement upper() { return _o2.firstElement(); }
BSONObj _o1, _o2;
};
+
+ /**
+ * Field bounds of a query clause within a singleton $or expression are intersected with
+ * those of the overall query. SERVER-6416
+ */
+ class SingletonOr : public Base {
+ public:
+ SingletonOr() :
+ _obj( BSON( "" << 5 ) ) {
+ }
+ void run() {
+ Base::run();
+ const FieldRangeSet s( "ns", query(), true, true );
+ // There should not be an index constraint recorded for the $or field.
+ ASSERT( s.range( "$or" ).universal() );
+ }
+ private:
+ virtual BSONObj query() {
+ // There is a single $or clause.
+ return fromjson( "{$or:[{a:5}]}" );
+ }
+ virtual bool mustBeExactMatchRepresentation() {
+ // The 'a' FieldRange is an exact match representation (it is an equality), though
+ // not the overall FieldRangeSet.
+ return true;
+ }
+ virtual bool isPointIntervalSet() { return true; }
+ virtual BSONElement lower() { return _obj.firstElement(); }
+ virtual BSONElement upper() { return _obj.firstElement(); }
+ BSONObj _obj;
+ };
+
+ /**
+ * Field bounds of a query clause within a nested singleton $or expression are intersected
+ * with those of the overall query. SERVER-6416
+ */
+ class NestedSingletonOr : public SingletonOr {
+ virtual BSONObj query() {
+ // There is a single $or clause.
+ return fromjson( "{$or:[{$or:[{a:5}]}]}" );
+ }
+ };
+
+ /**
+ * Field bounds of a query clause within a non singleton $or expression are not intersected
+ * with those of the overall query.
+ */
+ class NonSingletonOr : public Base {
+ public:
+ void run() {
+ Base::run();
+ const FieldRangeSet s( "ns", query(), true, true );
+ // There should not be an index constraint recorded for the $or field.
+ ASSERT( s.range( "$or" ).universal() );
+ }
+ private:
+ virtual BSONObj query() {
+ // There is more than one $or clause.
+ return fromjson( "{$or:[{a:5},{a:6}]}" );
+ }
+ virtual bool mustBeExactMatchRepresentation() {
+ // The 'a' FieldRange is an exact match representation (the default universal range
+ // matches anything), though not the overall FieldRangeSet.
+ return true;
+ }
+ };
class Empty {
public:
@@ -1379,6 +1445,10 @@ namespace QueryUtilTests {
And() : ExactMatchRepresentation( fromjson( "{$and:[{a:{$in:[0,1]}}]}" ) ) {}
};
+ struct Or : public NotExactMatchRepresentation {
+ Or() : NotExactMatchRepresentation( fromjson( "{$or:[{a:{$in:[0,1]}}]}" ) ) {}
+ };
+
struct All : public NotExactMatchRepresentation {
All() : NotExactMatchRepresentation( fromjson( "{a:{$all:[0]}}" ) ) {}
};
@@ -2414,6 +2484,9 @@ namespace QueryUtilTests {
add<FieldRangeTests::UnhelpfulRegex>();
add<FieldRangeTests::In>();
add<FieldRangeTests::And>();
+ add<FieldRangeTests::SingletonOr>();
+ add<FieldRangeTests::NestedSingletonOr>();
+ add<FieldRangeTests::NonSingletonOr>();
add<FieldRangeTests::Empty>();
add<FieldRangeTests::Equality>();
add<FieldRangeTests::SimplifiedQuery>();
@@ -2537,6 +2610,7 @@ namespace QueryUtilTests {
add<FieldRangeSetTests::ExactMatchRepresentation::Regex>();
add<FieldRangeSetTests::ExactMatchRepresentation::UntypedRegex>();
add<FieldRangeSetTests::ExactMatchRepresentation::And>();
+ add<FieldRangeSetTests::ExactMatchRepresentation::Or>();
add<FieldRangeSetTests::ExactMatchRepresentation::All>();
add<FieldRangeSetTests::ExactMatchRepresentation::ElemMatch>();
add<FieldRangeSetTests::ExactMatchRepresentation::AllElemMatch>();
Please sign in to comment.
Something went wrong with that request. Please try again.