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.12.14
Show newest version
/*
 * Copyright (c) 2008-2015 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 com.mongodb.annotations.NotThreadSafe;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.model.FindOptions;
import com.mongodb.operation.FindOperation;
import com.mongodb.operation.OperationExecutor;
import org.bson.codecs.Decoder;

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

import static com.mongodb.DBObjects.toDBObject;
import static com.mongodb.assertions.Assertions.notNull;
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();
 * }
* * See {@link Mongo#getDB(String)} for further information about the effective deprecation of this class. * * @mongodb.driver.manual core/read-operations Read Operations */ @NotThreadSafe public class DBCursor implements Cursor, Iterable { private final DBCollection collection; private final OperationExecutor executor; private final DBObject filter; private final DBObject modifiers; private DBObject projection; private DBObject sort; private final FindOptions findOptions; private int options; private ReadPreference readPreference; private Decoder resultDecoder; private DBDecoderFactory decoderFactory; private IteratorOrArray iteratorOrArray; private DBObject currentObject; private int numSeen; private boolean closed; private final List all = new ArrayList(); private MongoCursor cursor; // This allows us to easily enable/disable finalizer for cleaning up un-closed cursors @SuppressWarnings("UnusedDeclaration")// IDEs will say it can be converted to a local variable, resist the urge private OptionalFinalizer optionalFinalizer; /** * Initializes a new database cursor. * * @param collection collection to use * @param query the query filter to apply * @param fields keys to return from the query * @param readPreference the read preference for this query */ public DBCursor(final DBCollection collection, final DBObject query, final DBObject fields, final ReadPreference readPreference) { this(collection, collection.getExecutor(), query, new BasicDBObject(), fields, null, new FindOptions(), readPreference); addOption(collection.getOptions()); DBObject indexKeys = lookupSuitableHints(query, collection.getHintFields()); if (indexKeys != null) { hint(indexKeys); } } private DBCursor(final DBCollection collection, final OperationExecutor executor, final DBObject filter, final DBObject modifiers, final DBObject fields, final DBObject sort, final FindOptions findOptions, final ReadPreference readPreference) { if (collection == null) { throw new IllegalArgumentException("Collection can't be null"); } this.collection = collection; this.executor = executor; this.filter = filter; this.modifiers = modifiers; this.projection = fields; this.sort = sort; this.findOptions = findOptions; this.readPreference = readPreference; this.resultDecoder = collection.getObjectCodec(); this.decoderFactory = collection.getDBDecoderFactory(); } /** * 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() { return new DBCursor(collection, executor, filter, modifiers, projection, sort, new FindOptions(findOptions), readPreference); } /** * 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 */ @Override public boolean hasNext() { if (closed) { throw new IllegalStateException("Cursor has been closed"); } if (cursor == null) { FindOperation operation = getQueryOperation(findOptions, resultDecoder); if (operation.getCursorType() == CursorType.Tailable) { operation.cursorType(CursorType.TailableAwait); } initializeCursor(operation); } boolean hasNext = cursor.hasNext(); setServerCursorOnFinalizer(cursor.getServerCursor()); return hasNext; } /** * 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 */ @Override public DBObject next() { checkIteratorOrArray(IteratorOrArray.ITERATOR); if (!hasNext()) { throw new NoSuchElementException(); } return nextInternal(); } /** * 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 if failed * @throws IllegalArgumentException if the cursor is not tailable * @mongodb.driver.manual /core/cursors/#cursor-batches Cursor Batches */ public DBObject tryNext() { if (cursor == null) { FindOperation operation = getQueryOperation(findOptions, resultDecoder); if (!operation.getCursorType().isTailable()) { throw new IllegalArgumentException("Can only be used with a tailable cursor"); } initializeCursor(operation); } DBObject next = cursor.tryNext(); setServerCursorOnFinalizer(cursor.getServerCursor()); return currentObject(next); } /** * Returns the element the cursor is at. * * @return the current element */ public DBObject curr() { return currentObject; } @Override public void remove() { throw new UnsupportedOperationException(); } /** * 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(this.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(final int options) { if ((options & Bytes.QUERYOPTION_EXHAUST) != 0) { throw new UnsupportedOperationException("exhaust query option is not supported"); } this.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() { this.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 findOptions.getLimit(); } /** * Gets the batch size. * * @return the batch size */ public int getBatchSize() { return findOptions.getBatchSize(); } /** * 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 value 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(final String name, final Object value) { if (name == null || value == null) { return this; } if ("$comment".equals(name)) { comment(value.toString()); } else if ("$explain".equals(name)) { modifiers.put("$explain", true); } else if ("$hint".equals(name)) { if (value instanceof String) { hint((String) value); } else { hint((DBObject) value); } } else if ("$maxScan".equals(name)) { maxScan(((Number) value).intValue()); } else if ("$maxTimeMS".equals(name)) { maxTime(((Number) value).longValue(), MILLISECONDS); } else if ("$max".equals(name)) { max((DBObject) value); } else if ("$min".equals(name)) { min((DBObject) value); } else if ("$orderby".equals(name)) { sort((DBObject) value); } else if ("$returnKey".equals(name)) { returnKey(); } else if ("$showDiskLoc".equals(name)) { showDiskLoc(); } else if ("$snapshot".equals(name)) { snapshot(); } else if ("$natural".equals(name)) { sort(new BasicDBObject("$natural", ((Number) value).intValue())); } else { throw new IllegalArgumentException(name + "is not a supported modifier"); } return this; } /** * 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) { modifiers.put("$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) { modifiers.put("$maxScan", 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) { modifiers.put("$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) { modifiers.put("$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() { modifiers.put("$returnKey", 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() { modifiers.put("$showDiskLoc", true); 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) { modifiers.put("$hint", 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) { modifiers.put("$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) { findOptions.maxTime(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() { modifiers.put("$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 if the operation failed * @mongodb.driver.manual reference/explain Explain Output */ public DBObject explain() { return toDBObject(executor.execute(getQueryOperation(findOptions, collection.getObjectCodec()) .asExplainableOperation(ExplainVerbosity.QUERY_PLANNER), getReadPreference())); } private FindOperation getQueryOperation(final FindOptions options, final Decoder decoder) { FindOperation operation = new FindOperation(collection.getNamespace(), decoder) .filter(collection.wrapAllowNull(filter)) .batchSize(options.getBatchSize()) .skip(options.getSkip()) .limit(options.getLimit()) .maxTime(options.getMaxTime(MILLISECONDS), MILLISECONDS) .modifiers(collection.wrap(modifiers)) .projection(collection.wrapAllowNull(projection)) .sort(collection.wrapAllowNull(sort)) .noCursorTimeout(options.isNoCursorTimeout()) .oplogReplay(options.isOplogReplay()) .partial(options.isPartial()); if ((this.options & Bytes.QUERYOPTION_TAILABLE) != 0) { if ((this.options & Bytes.QUERYOPTION_AWAITDATA) != 0) { operation.cursorType(CursorType.TailableAwait); } else { operation.cursorType(CursorType.Tailable); } } if ((this.options & Bytes.QUERYOPTION_OPLOGREPLAY) != 0) { operation.oplogReplay(true); } if ((this.options & Bytes.QUERYOPTION_NOTIMEOUT) != 0) { operation.noCursorTimeout(true); } if ((this.options & Bytes.QUERYOPTION_PARTIAL) != 0) { operation.partial(true); } return operation; } /** * 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(final DBObject orderBy) { this.sort = orderBy; return this; } /** * 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) { findOptions.limit(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(final int numberOfElements) { findOptions.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) { findOptions.skip(numberOfElements); return this; } @Override public long getCursorId() { if (cursor != null && cursor.getServerCursor() != null) { return cursor.getServerCursor().getId(); } else { return 0; } } /** * Returns the number of objects through which the cursor has iterated. * * @return the number of objects seen */ public int numSeen() { return numSeen; } @Override public void close() { closed = true; if (cursor != null) { cursor.close(); cursor = null; setServerCursorOnFinalizer(null); } currentObject = null; } /** * 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()} */ @Deprecated public DBCursor slaveOk() { return addOption(Bytes.QUERYOPTION_SLAVEOK); } /** *

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(); } /** * Converts this cursor to an array. * * @return an array of elements * @throws MongoException if failed */ 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 if failed */ public List toArray(final int max) { checkIteratorOrArray(IteratorOrArray.ARRAY); fillArray(max - 1); return all; } /** * 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 if the operation failed * @see DBCursor#size */ public int count() { return (int) collection.getCount(getQuery(), getKeysWanted(), 0, 0, getReadPreferenceForCursor(), findOptions.getMaxTime(MILLISECONDS), MILLISECONDS, collection.wrap(modifiers).get("$hint")); } /** * Returns the first document that matches the query. * * @return the first matching document * @since 2.12 */ public DBObject one() { return collection.findOne(getQuery(), getKeysWanted(), sort, getReadPreferenceForCursor(), findOptions.getMaxTime(MILLISECONDS), MILLISECONDS); } /** * 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 if failed * @see #count() * @see #size() */ public int length() { checkIteratorOrArray(IteratorOrArray.ARRAY); fillArray(Integer.MAX_VALUE); return all.size(); } /** * For testing only! Iterates cursor and counts objects * * @return num objects * @throws MongoException if failed * @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 take limit/skip into consideration * * @return the number of objects * @throws MongoException if the operation failed * @see #count() */ public int size() { return (int) collection.getCount(getQuery(), getKeysWanted(), findOptions.getLimit(), findOptions.getSkip(), getReadPreference(), findOptions.getMaxTime(MILLISECONDS), MILLISECONDS); } /** * Gets the fields to be returned. * * @return the field selector that cursor used */ public DBObject getKeysWanted() { return projection; } /** * Gets the query. * * @return the query that cursor used */ public DBObject getQuery() { return filter; } /** * Gets the collection. * * @return the collection that data is pulled from */ public DBCollection getCollection() { return collection; } @Override public ServerAddress getServerAddress() { if (cursor != null) { return cursor.getServerAddress(); } else { return null; } } /** * 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) { this.readPreference = readPreference; return this; } /** * Gets the default read preference. * * @return the readPreference used by this cursor */ public ReadPreference getReadPreference() { return readPreference; } /** * Sets the factory that will be used create a {@code DBDecoder} that will be used to decode BSON documents into DBObject instances. * * @param factory the DBDecoderFactory * @return {@code this} so calls can be chained */ public DBCursor setDecoderFactory(final DBDecoderFactory factory) { this.decoderFactory = factory; //Not creating new CompoundDBObjectCodec because we don't care about encoder. this.resultDecoder = new DBDecoderAdapter(factory.create(), collection, getCollection().getBufferPool()); 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 decoderFactory; } @Override public String toString() { return "DBCursor{" + "collection=" + collection + ", find=" + findOptions + (cursor != null ? (", cursor=" + cursor.getServerCursor()) : "") + '}'; } private void initializeCursor(final FindOperation operation) { cursor = new MongoBatchCursorAdapter(executor.execute(operation, getReadPreferenceForCursor())); if (isCursorFinalizerEnabled() && cursor.getServerCursor() != null) { optionalFinalizer = new OptionalFinalizer(collection.getDB().getMongo(), collection.getNamespace()); } } private boolean isCursorFinalizerEnabled() { return collection.getDB().getMongo().getMongoClientOptions().isCursorFinalizerEnabled(); } private void setServerCursorOnFinalizer(final ServerCursor serverCursor) { if (optionalFinalizer != null) { optionalFinalizer.setServerCursor(serverCursor); } } private void checkIteratorOrArray(final IteratorOrArray expected) { if (iteratorOrArray == null) { iteratorOrArray = expected; return; } if (expected == iteratorOrArray) { return; } throw new IllegalArgumentException("Can't switch cursor access methods"); } private ReadPreference getReadPreferenceForCursor() { ReadPreference readPreference = getReadPreference(); if ((options & Bytes.QUERYOPTION_SLAVEOK) != 0 && !readPreference.isSlaveOk()) { readPreference = ReadPreference.secondaryPreferred(); } return readPreference; } private void fillArray(final int n) { checkIteratorOrArray(IteratorOrArray.ARRAY); while (n >= all.size() && hasNext()) { all.add(nextInternal()); } } private DBObject nextInternal() { if (iteratorOrArray == null) { checkIteratorOrArray(IteratorOrArray.ITERATOR); } DBObject next = cursor.next(); setServerCursorOnFinalizer(cursor.getServerCursor()); return currentObject(next); } private DBObject currentObject(final DBObject newCurrentObject){ if (newCurrentObject != null) { currentObject = newCurrentObject; numSeen++; if (projection != null && !(projection.keySet().isEmpty())) { currentObject.markAsPartialObject(); } } return newCurrentObject; } private static DBObject lookupSuitableHints(final DBObject query, final List hints) { if (hints == null) { return null; } Set keys = query.keySet(); for (final DBObject hint : hints) { if (keys.containsAll(hint.keySet())) { return hint; } } return null; } private static enum IteratorOrArray { ITERATOR, ARRAY } private static class OptionalFinalizer { private final Mongo mongo; private final MongoNamespace namespace; private volatile ServerCursor serverCursor; private OptionalFinalizer(final Mongo mongo, final MongoNamespace namespace) { this.namespace = notNull("namespace", namespace); this.mongo = notNull("mongo", mongo); } private void setServerCursor(final ServerCursor serverCursor) { this.serverCursor = serverCursor; } @Override protected void finalize() { if (serverCursor != null) { mongo.addOrphanedCursor(serverCursor, namespace); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy