All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mongodb.DBCursor Maven / Gradle / Ivy

Go to download

The MongoDB Java Driver uber-artifact, containing mongodb-driver, mongodb-driver-core, and bson

There is a newer version: 3.1.0
Show newest version
/*
 * Copyright (c) 2008-2014 MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.mongodb;

import org.bson.util.annotations.NotThreadSafe;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import static java.util.concurrent.TimeUnit.MILLISECONDS;

/**
 * 

An iterator over database results. Doing a {@code find()} query on a collection returns a {@code DBCursor} thus

*
 * DBCursor cursor = collection.find( query );
 *    if(cursor.hasNext()) {
 *     DBObject obj = cursor.next();
 *    }
 * 
*

Warning: Calling {@code toArray} or {@code length} on a DBCursor will irrevocably turn it into an array. This means that, if * the cursor was iterating over ten million results (which it was lazily fetching from the database), suddenly there will be a ten-million * element array in memory. Before converting to an array, make sure that there are a reasonable number of results using {@code skip()} and * {@code limit()}. * *

For example, to get an array of the 1000-1100th elements of a cursor, use

* *
{@code
 * List obj = collection.find( query ).skip( 1000 ).limit( 100 ).toArray();
 * }
* * @mongodb.driver.manual core/read-operations Read Operations */ @NotThreadSafe public class DBCursor implements Cursor, Iterable { /** * Initializes a new database cursor. * * @param collection collection to use * @param q query to perform * @param k keys to return from the query * @param preference the Read Preference for this query */ public DBCursor( DBCollection collection , DBObject q , DBObject k, ReadPreference preference ){ if (collection == null) { throw new IllegalArgumentException("collection is null"); } _collection = collection; _query = q == null ? new BasicDBObject() : q; _keysWanted = k; _options = _collection.getOptions(); _readPref = preference; _decoderFact = collection.getDBDecoderFactory(); } /** * Adds a comment to the query to identify queries in the database profiler output. * * @param comment the comment that is to appear in the profiler output * @return {@code this} so calls can be chained * @mongodb.driver.manual reference/operator/meta/comment/ $comment * @since 2.12 */ public DBCursor comment(final String comment) { addSpecial(QueryOperators.COMMENT, comment); return this; } /** * Limits the number of documents a cursor will return for a query. * * @param max the maximum number of documents to return * @return {@code this} so calls can be chained * @mongodb.driver.manual reference/operator/meta/maxScan/ $maxScan * @see #limit(int) * @since 2.12 */ public DBCursor maxScan(final int max) { addSpecial(QueryOperators.MAX_SCAN, max); return this; } /** * Specifies an exclusive upper limit for the index to use in a query. * * @param max a document specifying the fields, and the upper bound values for those fields * @return {@code this} so calls can be chained * @mongodb.driver.manual reference/operator/meta/max/ $max * @since 2.12 */ public DBCursor max(final DBObject max) { addSpecial(QueryOperators.MAX, max); return this; } /** * Specifies an inclusive lower limit for the index to use in a query. * * @param min a document specifying the fields, and the lower bound values for those fields * @return {@code this} so calls can be chained * @mongodb.driver.manual reference/operator/meta/min/ $min * @since 2.12 */ public DBCursor min(final DBObject min) { addSpecial(QueryOperators.MIN, min); return this; } /** * Forces the cursor to only return fields included in the index. * * @return {@code this} so calls can be chained * @mongodb.driver.manual reference/operator/meta/returnKey/ $returnKey * @since 2.12 */ public DBCursor returnKey() { addSpecial(QueryOperators.RETURN_KEY, true); return this; } /** * Modifies the documents returned to include references to the on-disk location of each document. The location will be returned in a * property named {@code $diskLoc} * * @return {@code this} so calls can be chained * @mongodb.driver.manual reference/operator/meta/showDiskLoc/ $showDiskLoc * @since 2.12 */ public DBCursor showDiskLoc() { addSpecial(QueryOperators.SHOW_DISK_LOC, true); return this; } /** * Types of cursors: iterator or array. */ static enum CursorType { ITERATOR , ARRAY } /** * Creates a copy of an existing database cursor. The new cursor is an iterator, even if the original was an array. * * @return the new cursor */ public DBCursor copy() { DBCursor c = new DBCursor(_collection, _query, _keysWanted, _readPref); c._orderBy = _orderBy; c._hint = _hint; c._hintDBObj = _hintDBObj; c._limit = _limit; c._skip = _skip; c._options = _options; c._batchSize = _batchSize; c._snapshot = _snapshot; c._explain = _explain; c._maxTimeMS = _maxTimeMS; c._disableBatchSizeTracking = _disableBatchSizeTracking; if ( _specialFields != null ) c._specialFields = new BasicDBObject( _specialFields.toMap() ); return c; } /** *

Creates a copy of this cursor object that can be iterated. Note: - you can iterate the DBCursor itself without calling this method * - no actual data is getting copied.

* *

Note that use of this method does not let you call close the underlying cursor in the case of either an exception or an early * break. The preferred method of iteration is to use DBCursor as an Iterator, so that you can call close() on it in a finally * block.

* * @return an iterator */ @Override public Iterator iterator(){ return this.copy(); } // ---- querty modifiers -------- /** * Sorts this cursor's elements. This method must be called before getting any object from the cursor. * * @param orderBy the fields by which to sort * @return a cursor pointing to the first element of the sorted results */ public DBCursor sort( DBObject orderBy ){ if ( _it != null ) throw new IllegalStateException( "can't sort after executing query" ); _orderBy = orderBy; return this; } /** * Adds a special operator like $maxScan or $returnKey. For example: *
     *    addSpecial("$returnKey", 1)
     *    addSpecial("$maxScan", 100)
     * 
* * @param name the name of the special query operator * @param o the value of the special query operator * @return {@code this} so calls can be chained * @mongodb.driver.manual reference/operator Special Operators */ public DBCursor addSpecial(String name, Object o) { if ( _specialFields == null ) _specialFields = new BasicDBObject(); _specialFields.put( name , o ); return this; } /** * Informs the database of indexed fields of the collection in order to improve performance. * * @param indexKeys a {@code DBObject} with fields and direction * @return same DBCursor for chaining operations * @mongodb.driver.manual reference/operator/meta/hint/ $hint */ public DBCursor hint(final DBObject indexKeys) { if ( _it != null ) throw new IllegalStateException( "can't hint after executing query" ); _hintDBObj = indexKeys; return this; } /** * Informs the database of an indexed field of the collection in order to improve performance. * * @param indexName the name of an index * @return same DBCursor for chaining operations * @mongodb.driver.manual reference/operator/meta/hint/ $hint */ public DBCursor hint(final String indexName) { if ( _it != null ) throw new IllegalStateException( "can't hint after executing query" ); _hint = indexName; return this; } /** * Set the maximum execution time for operations on this cursor. * * @param maxTime the maximum time that the server will allow the query to run, before killing the operation. A non-zero value requires * a server version >= 2.6 * @param timeUnit the time unit * @return same DBCursor for chaining operations * @mongodb.server.release 2.6 * @mongodb.driver.manual reference/operator/meta/maxTimeMS/ $maxTimeMS * @since 2.12.0 */ public DBCursor maxTime(final long maxTime, final TimeUnit timeUnit) { _maxTimeMS = MILLISECONDS.convert(maxTime, timeUnit); return this; } /** * Use snapshot mode for the query. Snapshot mode prevents the cursor from returning a document more than once because an intervening * write operation results in a move of the document. Even in snapshot mode, documents inserted or deleted during the lifetime of the * cursor may or may not be returned. Currently, snapshot mode may not be used with sorting or explicit hints. * * @return {@code this} so calls can be chained * * @see com.mongodb.DBCursor#sort(DBObject) * @see com.mongodb.DBCursor#hint(DBObject) * @mongodb.driver.manual reference/operator/meta/snapshot/ $snapshot */ public DBCursor snapshot() { if (_it != null) throw new IllegalStateException("can't snapshot after executing the query"); _snapshot = true; return this; } /** * Returns an object containing basic information about the execution of the query that created this cursor. This creates a {@code * DBObject} with a number of fields, including but not limited to: *
    *
  • cursor: cursor type
  • *
  • nScanned: number of records examined by the database for this query
  • *
  • n: the number of records that the database returned
  • *
  • millis: how long it took the database to execute the query
  • *
* * @return a {@code DBObject} containing the explain output for this DBCursor's query * @throws MongoException * @mongodb.driver.manual reference/explain Explain Output */ public DBObject explain(){ DBCursor c = copy(); c._explain = true; if (c._limit > 0) { // need to pass a negative batchSize as limit for explain c._batchSize = c._limit * -1; c._limit = 0; } return c.next(); } /** * Limits the number of elements returned. Note: parameter {@code limit} should be positive, although a negative value is * supported for legacy reason. Passing a negative value will call {@link DBCursor#batchSize(int)} which is the preferred method. * * @param limit the number of elements to return * @return a cursor to iterate the results * @mongodb.driver.manual reference/method/cursor.limit Limit */ public DBCursor limit(final int limit) { if ( _it != null ) throw new IllegalStateException( "can't set limit after executing query" ); if (limit > 0) _limit = limit; else if (limit < 0) batchSize(limit); return this; } /** *

Limits the number of elements returned in one batch. A cursor typically fetches a batch of result objects and store them * locally.

* *

If {@code batchSize} is positive, it represents the size of each batch of objects retrieved. It can be adjusted to optimize * performance and limit data transfer.

* *

If {@code batchSize} is negative, it will limit of number objects returned, that fit within the max batch size limit (usually * 4MB), and cursor will be closed. For example if {@code batchSize} is -10, then the server will return a maximum of 10 documents and * as many as can fit in 4MB, then close the cursor. Note that this feature is different from limit() in that documents must fit within * a maximum size, and it removes the need to send a request to close the cursor server-side.

* * @param numberOfElements the number of elements to return in a batch * @return {@code this} so calls can be chained */ public DBCursor batchSize(int numberOfElements) { // check for special case, used to have server bug with 1 if ( numberOfElements == 1 ) numberOfElements = 2; if ( _it != null ) { _it.setBatchSize(numberOfElements); } _batchSize = numberOfElements; return this; } /** * Discards a given number of elements at the beginning of the cursor. * * @param numberOfElements the number of elements to skip * @return a cursor pointing to the new first element of the results * @throws IllegalStateException if the cursor has started to be iterated through */ public DBCursor skip(final int numberOfElements) { if ( _it != null ) throw new IllegalStateException( "can't set skip after executing query" ); _skip = numberOfElements; return this; } @Override public long getCursorId() { return _it == null ? 0 : _it.getCursorId(); } @Override public void close() { if (_it != null) _it.close(); } /** * Declare that this query can run on a secondary server. * * @return a copy of the same cursor (for chaining) * @see ReadPreference#secondaryPreferred() * @deprecated Replaced with {@link com.mongodb.ReadPreference#secondaryPreferred()} * @see ReadPreference#secondaryPreferred() */ @Deprecated public DBCursor slaveOk(){ return addOption( Bytes.QUERYOPTION_SLAVEOK ); } /** * Adds a query option. See Bytes.QUERYOPTION_* for list. * * @param option the option to be added * @return {@code this} so calls can be chained * @see Bytes * @mongodb.driver.manual ../meta-driver/latest/legacy/mongodb-wire-protocol/#op-query Query Flags */ public DBCursor addOption(final int option) { setOptions(_options |= option); return this; } /** * Sets the query option - see Bytes.QUERYOPTION_* for list. * * @param options the bitmask of options * @return {@code this} so calls can be chained * @see Bytes * @mongodb.driver.manual ../meta-driver/latest/legacy/mongodb-wire-protocol/#op-query Query Flags */ public DBCursor setOptions( int options ){ if ((options & Bytes.QUERYOPTION_EXHAUST) != 0) { throw new IllegalArgumentException("The exhaust option is not user settable."); } _options = options; return this; } /** * Resets the query options. * * @return {@code this} so calls can be chained * @mongodb.driver.manual ../meta-driver/latest/legacy/mongodb-wire-protocol/#op-query Query Flags */ public DBCursor resetOptions(){ _options = 0; return this; } /** * Gets the query options. * * @return the bitmask of options * @mongodb.driver.manual ../meta-driver/latest/legacy/mongodb-wire-protocol/#op-query Query Flags */ public int getOptions(){ return _options; } /** * Gets the query limit. * * @return the limit, or 0 if no limit is set */ public int getLimit() { return _limit; } /** * Gets the batch size. * * @return the batch size */ public int getBatchSize() { return _batchSize; } // ---- internal stuff ------ private void _check() { if (_it != null) return; _lookForHints(); QueryOpBuilder builder = new QueryOpBuilder() .addQuery(_query) .addOrderBy(_orderBy) .addHint(_hintDBObj) .addHint(_hint) .addExplain(_explain) .addSnapshot(_snapshot) .addSpecialFields(_specialFields) .addMaxTimeMS(_maxTimeMS); if (_collection.getDB().getMongo().isMongosConnection()) { builder.addReadPreference(_readPref); } _it = _collection.find(builder.get(), _keysWanted, _skip, _batchSize, _limit, _options, _readPref, getDecoder()); if (_disableBatchSizeTracking) { _it.disableBatchSizeTracking(); } } // Only create a new decoder if there is a decoder factory explicitly set on the collection. Otherwise return null // so that the collection can use a cached decoder private DBDecoder getDecoder() { return _decoderFact != null ? _decoderFact.create() : null; } /** * if there is a hint to use, use it */ private void _lookForHints(){ if ( _hint != null ) // if someone set a hint, then don't do this return; if ( _collection._hintFields == null ) return; Set mykeys = _query.keySet(); for ( DBObject o : _collection._hintFields ){ Set hintKeys = o.keySet(); if ( ! mykeys.containsAll( hintKeys ) ) continue; hint( o ); return; } } void _checkType( CursorType type ){ if ( _cursorType == null ){ _cursorType = type; return; } if ( type == _cursorType ) return; throw new IllegalArgumentException( "can't switch cursor access methods" ); } private DBObject _next() { if ( _cursorType == null ) _checkType( CursorType.ITERATOR ); _check(); _cur = _it.next(); _num++; if ( _keysWanted != null && _keysWanted.keySet().size() > 0 ){ _cur.markAsPartialObject(); //throw new UnsupportedOperationException( "need to figure out partial" ); } if ( _cursorType == CursorType.ARRAY ){ _all.add( _cur ); } return _cur; } /** * Gets the number of times, so far, that the cursor retrieved a batch from the database * * @return The number of times OP_GET_MORE has been called * @deprecated there is no replacement for this method */ @Deprecated public int numGetMores() { return _it == null ? 0 : _it.numGetMores(); } /** * Gets a list containing the number of items received in each batch * * @return a list containing the number of items received in each batch * @deprecated there is no replacement for this method */ @Deprecated public List getSizes() { return _it == null ? Collections.emptyList() : _it.getSizes(); } /** * Disable tracking of batch sizes in order to reduce memory consumption with high-frequency tailable cursors. This method must be * called before iterating the cursor. * * @return this * @see #getSizes() * @deprecated This method is being added temporarily, and will be removed along with {@link #getSizes()} in the next major release */ @Deprecated public DBCursor disableBatchSizeTracking() { if ( _it != null ) throw new IllegalStateException( "can't disable batch size tracking after executing query" ); _disableBatchSizeTracking = true; return this; } /** * Returns true if tracking of batch sizes is disabled in order to reduce memory consumption with high-frequency tailable cursors. * * @return true if tracking of batch sizes is disabled * @see #getSizes() * @see #disableBatchSizeTracking() * @deprecated This method is being added temporarily, and will be removed along with {@link #getSizes()} in the next major release */ @Deprecated public boolean isBatchSizeTrackingDisabled() { return _disableBatchSizeTracking; } private boolean _hasNext() { _check(); if ( _limit > 0 && _num >= _limit ) return false; return _it.hasNext(); } /** * Returns the number of objects through which the cursor has iterated. * * @return the number of objects seen */ public int numSeen(){ return _num; } // ----- iterator api ----- /** * Checks if there is another object available. * *

Note: Automatically adds the {@link Bytes#QUERYOPTION_AWAITDATA} option to any cursors with the * {@link Bytes#QUERYOPTION_TAILABLE} option set. For non blocking tailable cursors see {@link #tryNext }.

* * @return true if there is another object available * @mongodb.driver.manual /core/cursors/#cursor-batches Cursor Batches * @throws MongoException */ public boolean hasNext() { _checkType(CursorType.ITERATOR); if ((getOptions() & Bytes.QUERYOPTION_TAILABLE) != 0) { addOption(Bytes.QUERYOPTION_AWAITDATA); } return _hasNext(); } /** * Non blocking check for tailable cursors to see if another object is available. * *

Returns the object the cursor is at and moves the cursor ahead by one or * return null if no documents is available.

* * @return the next element or null * @throws MongoException * @mongodb.driver.manual /core/cursors/#cursor-batches Cursor Batches */ public DBObject tryNext() { _checkType( CursorType.ITERATOR ); if ((getOptions() & Bytes.QUERYOPTION_TAILABLE) != Bytes.QUERYOPTION_TAILABLE) { throw new IllegalArgumentException("Can only be used with a tailable cursor"); } _check(); if (!_it.tryHasNext()) { return null; } return _next(); } /** * Returns the object the cursor is at and moves the cursor ahead by one. * *

Note: Automatically adds the {@link Bytes#QUERYOPTION_AWAITDATA} option to any cursors with the * {@link Bytes#QUERYOPTION_TAILABLE} option set. For non blocking tailable cursors see {@link #tryNext }.

* * @return the next element * @mongodb.driver.manual /core/cursors/#cursor-batches Cursor Batches */ public DBObject next() { _checkType( CursorType.ITERATOR ); if ((getOptions() & Bytes.QUERYOPTION_TAILABLE) != 0) { addOption(Bytes.QUERYOPTION_AWAITDATA); } return _next(); } /** * Returns the element the cursor is at. * * @return the current element */ public DBObject curr(){ _checkType(CursorType.ITERATOR); return _cur; } /** * Not implemented. */ public void remove(){ throw new UnsupportedOperationException( "can't remove from a cursor" ); } // ---- array api ----- void _fill( int n ){ _checkType( CursorType.ARRAY ); while ( n >= _all.size() && _hasNext() ) _next(); } /** * Pulls back all items into an array and returns the number of objects. Note: this can be resource intensive. * * @return the number of elements in the array * @throws MongoException * @see #count() * @see #size() */ public int length() { _checkType( CursorType.ARRAY ); _fill( Integer.MAX_VALUE ); return _all.size(); } /** * Converts this cursor to an array. * * @return an array of elements * @throws MongoException */ public List toArray(){ return toArray( Integer.MAX_VALUE ); } /** * Converts this cursor to an array. * * @param max the maximum number of objects to return * @return an array of objects * @throws MongoException */ public List toArray( int max ) { _checkType( CursorType.ARRAY ); _fill( max - 1 ); return _all; } /** * For testing only! Iterates cursor and counts objects * * @return num objects * @throws MongoException * @see #count() */ public int itcount(){ int n = 0; while ( this.hasNext() ){ this.next(); n++; } return n; } /** * Counts the number of objects matching the query. This does not take limit/skip into consideration, and does initiate a call to the * server. * * @return the number of objects * @throws MongoException * @see DBCursor#size */ public int count() { Object hint = _hint != null ? _hint : _hintDBObj; if (hint == null && _specialFields != null && _specialFields.containsField("$hint")) { hint = _specialFields.get("$hint"); } return (int) _collection.getCount(this._query, this._keysWanted, 0, 0, getReadPreference(), _maxTimeMS, MILLISECONDS, hint); } /** * Returns the first document that matches the query. * * @return the first matching document * @since 2.12 */ public DBObject one() { return _collection.findOne(_query, _keysWanted, _orderBy, getReadPreference(), _maxTimeMS, MILLISECONDS); } /** * Counts the number of objects matching the query this does take limit/skip into consideration * * @return the number of objects * @throws MongoException * @see #count() */ public int size() { return (int)_collection.getCount(this._query, this._keysWanted, this._limit, this._skip, getReadPreference(), _maxTimeMS, MILLISECONDS); } /** * Gets the fields to be returned. * * @return the field selector that cursor used */ public DBObject getKeysWanted(){ return _keysWanted; } /** * Gets the query. * * @return the query that cursor used */ public DBObject getQuery(){ return _query; } /** * Gets the collection. * * @return the collection that data is pulled from */ public DBCollection getCollection(){ return _collection; } /** * Gets the Server Address of the server that data is pulled from. Note that this information may not be available until hasNext() or * next() is called. * * @return the address of the server */ public ServerAddress getServerAddress() { return _it == null ? null : _it.getServerAddress(); } /** * Sets the read preference for this cursor. See the documentation for {@link ReadPreference} for more information. * * @param readPreference read preference to use * @return {@code this} so calls can be chained */ public DBCursor setReadPreference(final ReadPreference readPreference) { _readPref = readPreference; return this; } /** * Gets the default read preference. * * @return the readPreference used by this cursor */ public ReadPreference getReadPreference(){ return _readPref; } /** * Sets the factory that will be used create a {@code DBDecoder} that will be used to decode BSON documents into DBObject instances. * * @param fact the DBDecoderFactory * @return {@code this} so calls can be chained */ public DBCursor setDecoderFactory(final DBDecoderFactory fact) { _decoderFact = fact; return this; } /** * Gets the decoder factory that creates the decoder this cursor will use to decode objects from MongoDB. * * @return the decoder factory. */ public DBDecoderFactory getDecoderFactory(){ return _decoderFact; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Cursor id=").append(getCursorId()); sb.append(", ns=").append(getCollection().getFullName()); sb.append(", query=").append(getQuery()); if (getKeysWanted() != null) sb.append(", fields=").append(getKeysWanted()); sb.append(", numIterated=").append(_num); if (_skip != 0) sb.append(", skip=").append(_skip); if (_limit != 0) sb.append(", limit=").append(_limit); if (_batchSize != 0) sb.append(", batchSize=").append(_batchSize); ServerAddress addr = getServerAddress(); if (addr != null) sb.append(", addr=").append(addr); if (_readPref != null) sb.append(", readPreference=").append( _readPref.toString() ); return sb.toString(); } boolean hasFinalizer() { if (_it == null) { return false; } return _it.hasFinalizer(); } // ---- query setup ---- private final DBCollection _collection; private final DBObject _query; private final DBObject _keysWanted; private DBObject _orderBy = null; private String _hint = null; private DBObject _hintDBObj = null; private boolean _explain = false; private int _limit = 0; private int _batchSize = 0; private int _skip = 0; private boolean _snapshot = false; private int _options = 0; private long _maxTimeMS; private ReadPreference _readPref; private DBDecoderFactory _decoderFact; private DBObject _specialFields; // ---- result info ---- private QueryResultIterator _it = null; private CursorType _cursorType = null; private DBObject _cur = null; private int _num = 0; private boolean _disableBatchSizeTracking; private final ArrayList _all = new ArrayList(); }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy