@@ -260,26 +260,76 @@ namespace CursorTests {
260260 }
261261 virtual BSONObj idx () const { return BSON ( " a" << 1 << " b" << 1 ); }
262262 };
263-
263+
264+ /* *
265+ * BtreeCursor::advance() may skip to new btree positions multiple times. A cutoff (tested
266+ * here) has been implemented to avoid excessive iteration in such cases. See SERVER-3448.
267+ */
264268 class AbortImplicitScan : public Base {
265269 public:
266270 void run () {
271+ // Set up a compound index with some data.
267272 IndexSpec idx ( BSON ( " a" << 1 << " b" << 1 ) );
268273 _c.ensureIndex ( ns (), idx.keyPattern );
269274 for ( int i = 0 ; i < 300 ; ++i ) {
270- _c.insert ( ns (), BSON ( " a" << i << " b" << 5 ) );
275+ _c.insert ( ns (), BSON ( " a" << i << " b" << i ) );
271276 }
272- FieldRangeSet frs ( ns (), BSON ( " b" << 3 ), true , true );
277+ _c.insert ( ns (), BSON ( " a" << 300 << " b" << 30 ) );
278+
279+ // Set up a cursor on the { a:1, b:1 } index, the same cursor that would be created
280+ // for the query { b:30 }. Because this query has no constraint on 'a' (the
281+ // first field of the compound index), the cursor will examine every distinct value
282+ // of 'a' in the index and check for an index key with that value for 'a' and 'b'
283+ // equal to 30.
284+ FieldRangeSet frs ( ns (), BSON ( " b" << 30 ), true , true );
273285 boost::shared_ptr<FieldRangeVector> frv ( new FieldRangeVector ( frs, idx, 1 ) );
274286 Client::WriteContext ctx ( ns () );
275- scoped_ptr<BtreeCursor> c ( BtreeCursor::make ( nsdetails ( ns () ), nsdetails ( ns () )->idx (1 ), frv, 1 ) );
276- long long initialNscanned = c->nscanned ();
277- ASSERT ( initialNscanned < 200 );
287+ scoped_ptr<BtreeCursor> c ( BtreeCursor::make ( nsdetails ( ns () ),
288+ nsdetails ( ns () )->idx (1 ),
289+ frv,
290+ 1 ) );
291+
292+ // BtreeCursor::init() and BtreeCursor::advance() attempt to advance the cursor to
293+ // the next matching key, which may entail examining many successive distinct values
294+ // of 'a' having no index key where b equals 30. To prevent excessive iteration
295+ // within init() and advance(), examining distinct 'a' values is aborted once an
296+ // nscanned cutoff is reached. We test here that this cutoff is applied, and that
297+ // if it is applied before a matching key is found, then
298+ // BtreeCursor::currentMatches() returns false appropriately.
299+
278300 ASSERT ( c->ok () );
279- c->advance ();
280- ASSERT ( c->nscanned () > initialNscanned );
301+ // The starting iterate found by BtreeCursor::init() does not match. This is a key
302+ // before the {'':30,'':30} key, because init() is aborted prematurely.
303+ ASSERT ( !c->currentMatches () );
304+ // And init() stopped iterating before scanning the whole btree (with ~300 keys).
281305 ASSERT ( c->nscanned () < 200 );
282- ASSERT ( c->ok () );
306+
307+ ASSERT ( c->advance () );
308+ // The next iterate matches (this is the {'':30,'':30} key).
309+ ASSERT ( c->currentMatches () );
310+
311+ int oldNscanned = c->nscanned ();
312+ ASSERT ( c->advance () );
313+ // Check that nscanned has increased ...
314+ ASSERT ( c->nscanned () > oldNscanned );
315+ // ... but that advance() stopped iterating before the whole btree (with ~300 keys)
316+ // was scanned.
317+ ASSERT ( c->nscanned () < 200 );
318+ // Because advance() is aborted prematurely, the current iterate does not match.
319+ ASSERT ( !c->currentMatches () );
320+
321+ // Iterate through the remainder of the btree.
322+ bool foundLastMatch = false ;
323+ while ( c->advance () ) {
324+ bool bMatches = ( c->current ()[ " b" ].number () == 30 );
325+ // The current iterate only matches if it has the proper 'b' value.
326+ ASSERT_EQUALS ( bMatches, c->currentMatches () );
327+ if ( bMatches ) {
328+ foundLastMatch = true ;
329+ }
330+ }
331+ // Check that the final match, on key {'':300,'':30}, is found.
332+ ASSERT ( foundLastMatch );
283333 }
284334 };
285335
0 commit comments