// DBCollection.java
/**
* Copyright (C) 2008 10gen 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;
// Mongo
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.bson.types.ObjectId;
/** This class provides a skeleton implementation of a database collection.
* <p>A typical invocation sequence is thus
* <blockquote><pre>
* Mongo mongo = new Mongo( new DBAddress( "localhost", 127017 ) );
* DB db = mongo.getDB( "mydb" );
* DBCollection collection = db.getCollection( "test" );
* </pre></blockquote>
* @dochub collections
*/
@SuppressWarnings("unchecked")
public abstract class DBCollection {
/**
* Saves document(s) to the database.
* if doc doesn't have an _id, one will be added
* you can get the _id that was added from doc after the insert
*
* @param arr array of documents to save
* @param concern the write concern
* @return
* @throws MongoException
* @dochub insert
*/
public WriteResult insert(DBObject[] arr , WriteConcern concern ) throws MongoException {
return insert( arr, concern, getDBEncoder());
}
/**
* Saves document(s) to the database.
* if doc doesn't have an _id, one will be added
* you can get the _id that was added from doc after the insert
*
* @param arr array of documents to save
* @param concern the write concern
* @param encoder the DBEncoder to use
* @return
* @throws MongoException
* @dochub insert
*/
public abstract WriteResult insert(DBObject[] arr , WriteConcern concern, DBEncoder encoder) throws MongoException;
/**
* Inserts a document into the database.
* if doc doesn't have an _id, one will be added
* you can get the _id that was added from doc after the insert
*
* @param o
* @param concern the write concern
* @return
* @throws MongoException
* @dochub insert
*/
public WriteResult insert(DBObject o , WriteConcern concern )
throws MongoException {
return insert( new DBObject[]{ o } , concern );
}
/**
* Saves document(s) to the database.
* if doc doesn't have an _id, one will be added
* you can get the _id that was added from doc after the insert
*
* @param arr array of documents to save
* @return
* @throws MongoException
* @dochub insert
*/
public WriteResult insert(DBObject ... arr)
throws MongoException {
return insert( arr , getWriteConcern() );
}
/**
* Saves document(s) to the database.
* if doc doesn't have an _id, one will be added
* you can get the _id that was added from doc after the insert
*
* @param arr array of documents to save
* @return
* @throws MongoException
* @dochub insert
*/
public WriteResult insert(WriteConcern concern, DBObject ... arr)
throws MongoException {
return insert( arr, concern );
}
/**
* Saves document(s) to the database.
* if doc doesn't have an _id, one will be added
* you can get the _id that was added from doc after the insert
*
* @param list list of documents to save
* @return
* @throws MongoException
* @dochub insert
*/
public WriteResult insert(List<DBObject> list )
throws MongoException {
return insert( list, getWriteConcern() );
}
/**
* Saves document(s) to the database.
* if doc doesn't have an _id, one will be added
* you can get the _id that was added from doc after the insert
*
* @param list list of documents to save
* @param concern the write concern
* @return
* @throws MongoException
* @dochub insert
*/
public WriteResult insert(List<DBObject> list, WriteConcern concern )
throws MongoException {
return insert( list.toArray( new DBObject[list.size()] ) , concern );
}
/**
* Performs an update operation.
* @param q search query for old object to update
* @param o object with which to update <tt>q</tt>
* @param upsert if the database should create the element if it does not exist
* @param multi if the update should be applied to all objects matching (db version 1.1.3 and above). An object will
* not be inserted if it does not exist in the collection and upsert=true and multi=true.
* See <a href="http://www.mongodb.org/display/DOCS/Atomic+Operations">http://www.mongodb.org/display/DOCS/Atomic+Operations</a>
* @param concern the write concern
* @return
* @throws MongoException
* @dochub update
*/
public WriteResult update( DBObject q , DBObject o , boolean upsert , boolean multi , WriteConcern concern ) throws MongoException {
return update( q, o, upsert, multi, concern, getDBEncoder());
}
/**
* Performs an update operation.
* @param q search query for old object to update
* @param o object with which to update <tt>q</tt>
* @param upsert if the database should create the element if it does not exist
* @param multi if the update should be applied to all objects matching (db version 1.1.3 and above). An object will
* not be inserted if it does not exist in the collection and upsert=true and multi=true.
* See <a href="http://www.mongodb.org/display/DOCS/Atomic+Operations">http://www.mongodb.org/display/DOCS/Atomic+Operations</a>
* @param concern the write concern
* @param encoder the DBEncoder to use
* @return
* @throws MongoException
* @dochub update
*/
public abstract WriteResult update( DBObject q , DBObject o , boolean upsert , boolean multi , WriteConcern concern, DBEncoder encoder ) throws MongoException ;
/**
* calls {@link DBCollection#update(com.mongodb.DBObject, com.mongodb.DBObject, boolean, boolean, com.mongodb.WriteConcern)} with default WriteConcern.
* @param q search query for old object to update
* @param o object with which to update <tt>q</tt>
* @param upsert if the database should create the element if it does not exist
* @param multi if the update should be applied to all objects matching (db version 1.1.3 and above)
* See http://www.mongodb.org/display/DOCS/Atomic+Operations
* @return
* @throws MongoException
* @dochub update
*/
public WriteResult update( DBObject q , DBObject o , boolean upsert , boolean multi )
throws MongoException {
return update( q , o , upsert , multi , getWriteConcern() );
}
/**
* calls {@link DBCollection#update(com.mongodb.DBObject, com.mongodb.DBObject, boolean, boolean)} with upsert=false and multi=false
* @param q search query for old object to update
* @param o object with which to update <tt>q</tt>
* @return
* @throws MongoException
* @dochub update
*/
public WriteResult update( DBObject q , DBObject o ) throws MongoException {
return update( q , o , false , false );
}
/**
* calls {@link DBCollection#update(com.mongodb.DBObject, com.mongodb.DBObject, boolean, boolean)} with upsert=false and multi=true
* @param q search query for old object to update
* @param o object with which to update <tt>q</tt>
* @return
* @throws MongoException
* @dochub update
*/
public WriteResult updateMulti( DBObject q , DBObject o ) throws MongoException {
return update( q , o , false , true );
}
/**
* Adds any necessary fields to a given object before saving it to the collection.
* @param o object to which to add the fields
*/
protected abstract void doapply( DBObject o );
/**
* Removes objects from the database collection.
* @param o the object that documents to be removed must match
* @param concern WriteConcern for this operation
* @return
* @throws MongoException
* @dochub remove
*/
public WriteResult remove( DBObject o , WriteConcern concern ) throws MongoException {
return remove( o, concern, getDBEncoder());
}
/**
* Removes objects from the database collection.
* @param o the object that documents to be removed must match
* @param concern WriteConcern for this operation
* @param encoder the DBEncoder to use
* @return
* @throws MongoException
* @dochub remove
*/
public abstract WriteResult remove( DBObject o , WriteConcern concern, DBEncoder encoder ) throws MongoException ;
/**
* calls {@link DBCollection#remove(com.mongodb.DBObject, com.mongodb.WriteConcern)} with the default WriteConcern
* @param o the object that documents to be removed must match
* @return
* @throws MongoException
* @dochub remove
*/
public WriteResult remove( DBObject o )
throws MongoException {
return remove( o , getWriteConcern() );
}
/**
* Finds objects
*/
abstract Iterator<DBObject> __find( DBObject ref , DBObject fields , int numToSkip , int batchSize , int limit, int options, ReadPreference readPref, DBDecoder decoder ) throws MongoException ;
abstract Iterator<DBObject> __find( DBObject ref , DBObject fields , int numToSkip , int batchSize , int limit, int options,
ReadPreference readPref, DBDecoder decoder, DBEncoder encoder ) throws MongoException ;
/**
* Calls {@link DBCollection#find(com.mongodb.DBObject, com.mongodb.DBObject, int, int)} and applies the query options
* @param query query used to search
* @param fields the fields of matching objects to return
* @param numToSkip number of objects to skip
* @param batchSize the batch size. This option has a complex behavior, see {@link DBCursor#batchSize(int) }
* @param options - see Bytes QUERYOPTION_*
* @return the cursor
* @throws MongoException
* @dochub find
*/
@Deprecated
public DBCursor find( DBObject query , DBObject fields , int numToSkip , int batchSize , int options ) throws MongoException{
return find(query, fields, numToSkip, batchSize).addOption(options);
}
/**
* Finds objects from the database that match a query.
* A DBCursor object is returned, that can be iterated to go through the results.
*
* @param query query used to search
* @param fields the fields of matching objects to return
* @param numToSkip number of objects to skip
* @param batchSize the batch size. This option has a complex behavior, see {@link DBCursor#batchSize(int) }
* @return the cursor
* @throws MongoException
* @dochub find
*/
@Deprecated
public DBCursor find( DBObject query , DBObject fields , int numToSkip , int batchSize ) {
DBCursor cursor = find(query, fields).skip(numToSkip).batchSize(batchSize);
return cursor;
}
// ------
/**
* Finds an object by its id.
* This compares the passed in value to the _id field of the document
*
* @param obj any valid object
* @return the object, if found, otherwise <code>null</code>
* @throws MongoException
*/
public DBObject findOne( Object obj )
throws MongoException {
return findOne(obj, null);
}
/**
* Finds an object by its id.
* This compares the passed in value to the _id field of the document
*
* @param obj any valid object
* @param fields fields to return
* @return the object, if found, otherwise <code>null</code>
* @dochub find
*/
public DBObject findOne( Object obj, DBObject fields ) {
Iterator<DBObject> iterator = __find( new BasicDBObject("_id", obj), fields, 0, -1, 0, getOptions(), getReadPreference(), getDecoder() );
return (iterator.hasNext() ? iterator.next() : null);
}
/**
* Finds the first document in the query and updates it.
* @param query query to match
* @param fields fields to be returned
* @param sort sort to apply before picking first document
* @param remove if true, document found will be removed
* @param update update to apply
* @param returnNew if true, the updated document is returned, otherwise the old document is returned (or it would be lost forever)
* @param upsert do upsert (insert if document not present)
* @return the document
*/
public DBObject findAndModify(DBObject query, DBObject fields, DBObject sort, boolean remove, DBObject update, boolean returnNew, boolean upsert) {
BasicDBObject cmd = new BasicDBObject( "findandmodify", _name);
if (query != null && !query.keySet().isEmpty())
cmd.append( "query", query );
if (fields != null && !fields.keySet().isEmpty())
cmd.append( "fields", fields );
if (sort != null && !sort.keySet().isEmpty())
cmd.append( "sort", sort );
if (remove)
cmd.append( "remove", remove );
else {
if (update != null && !update.keySet().isEmpty()) {
// if 1st key doesnt start with $, then object will be inserted as is, need to check it
String key = update.keySet().iterator().next();
if (key.charAt(0) != '$')
_checkObject(update, false, false);
cmd.append( "update", update );
}
if (returnNew)
cmd.append( "new", returnNew );
if (upsert)
cmd.append( "upsert", upsert );
}
if (remove && !(update == null || update.keySet().isEmpty() || returnNew))
throw new MongoException("FindAndModify: Remove cannot be mixed with the Update, or returnNew params!");
CommandResult res = this._db.command( cmd );
if (res.ok() || res.getErrorMessage().equals( "No matching object found" ))
return (DBObject) res.get( "value" );
res.throwOnError();
return null;
}
/**
* calls {@link DBCollection#findAndModify(com.mongodb.DBObject, com.mongodb.DBObject, com.mongodb.DBObject, boolean, com.mongodb.DBObject, boolean, boolean)}
* with fields=null, remove=false, returnNew=false, upsert=false
* @param query
* @param sort
* @param update
* @return the old document
*/
public DBObject findAndModify( DBObject query , DBObject sort , DBObject update){
return findAndModify( query, null, sort, false, update, false, false);
}
/**
* calls {@link DBCollection#findAndModify(com.mongodb.DBObject, com.mongodb.DBObject, com.mongodb.DBObject, boolean, com.mongodb.DBObject, boolean, boolean)}
* with fields=null, sort=null, remove=false, returnNew=false, upsert=false
* @param query
* @param update
* @return the old document
*/
public DBObject findAndModify( DBObject query , DBObject update ) {
return findAndModify( query, null, null, false, update, false, false );
}
/**
* calls {@link DBCollection#findAndModify(com.mongodb.DBObject, com.mongodb.DBObject, com.mongodb.DBObject, boolean, com.mongodb.DBObject, boolean, boolean)}
* with fields=null, sort=null, remove=true, returnNew=false, upsert=false
* @param query
* @return the removed document
*/
public DBObject findAndRemove( DBObject query ) {
return findAndModify( query, null, null, true, null, false, false );
}
// --- START INDEX CODE ---
/**
* calls {@link DBCollection#createIndex(com.mongodb.DBObject, com.mongodb.DBObject)} with default index options
* @param keys an object with a key set of the fields desired for the index
* @throws MongoException
*/
public void createIndex( final DBObject keys )
throws MongoException {
createIndex( keys , defaultOptions( keys ) );
}
/**
* Forces creation of an index on a set of fields, if one does not already exist.
* @param keys
* @param options
* @throws MongoException
*/
public void createIndex( DBObject keys , DBObject options ) throws MongoException {
createIndex( keys, options, getDBEncoder());
}
/**
* Forces creation of an index on a set of fields, if one does not already exist.
* @param keys
* @param options
* @param encoder the DBEncoder to use
* @throws MongoException
*/
public abstract void createIndex( DBObject keys , DBObject options, DBEncoder encoder ) throws MongoException;
/**
* Creates an ascending index on a field with default options, if one does not already exist.
* @param name name of field to index on
*/
public void ensureIndex( final String name ){
ensureIndex( new BasicDBObject( name , 1 ) );
}
/**
* calls {@link DBCollection#ensureIndex(com.mongodb.DBObject, com.mongodb.DBObject)} with default options
* @param keys an object with a key set of the fields desired for the index
* @throws MongoException
*/
public void ensureIndex( final DBObject keys )
throws MongoException {
ensureIndex( keys , defaultOptions( keys ) );
}
/**
* calls {@link DBCollection#ensureIndex(com.mongodb.DBObject, java.lang.String, boolean)} with unique=false
* @param keys fields to use for index
* @param name an identifier for the index
* @throws MongoException
* @dochub indexes
*/
public void ensureIndex( DBObject keys , String name )
throws MongoException {
ensureIndex( keys , name , false );
}
/**
* Ensures an index on this collection (that is, the index will be created if it does not exist).
* @param keys fields to use for index
* @param name an identifier for the index. If null or empty, the default name will be used.
* @param unique if the index should be unique
* @throws MongoException
*/
public void ensureIndex( DBObject keys , String name , boolean unique )
throws MongoException {
DBObject options = defaultOptions( keys );
if (name != null && name.length()>0)
options.put( "name" , name );
if ( unique )
options.put( "unique" , Boolean.TRUE );
ensureIndex( keys , options );
}
/**
* Creates an index on a set of fields, if one does not already exist.
* @param keys an object with a key set of the fields desired for the index
* @param optionsIN options for the index (name, unique, etc)
* @throws MongoException
*/
public void ensureIndex( final DBObject keys , final DBObject optionsIN )
throws MongoException {
if ( checkReadOnly( false ) ) return;
final DBObject options = defaultOptions( keys );
for ( String k : optionsIN.keySet() )
options.put( k , optionsIN.get( k ) );
final String name = options.get( "name" ).toString();
if ( _createdIndexes.contains( name ) )
return;
createIndex( keys , options );
_createdIndexes.add( name );
}
/**
* Clears all indices that have not yet been applied to this collection.
*/
public void resetIndexCache(){
_createdIndexes.clear();
}
DBObject defaultOptions( DBObject keys ){
DBObject o = new BasicDBObject();
o.put( "name" , genIndexName( keys ) );
o.put( "ns" , _fullName );
return o;
}
/**
* Convenience method to generate an index name from the set of fields it is over.
* @param keys the names of the fields used in this index
* @return a string representation of this index's fields
*/
public static String genIndexName( DBObject keys ){
StringBuilder name = new StringBuilder();
for ( String s : keys.keySet() ){
if ( name.length() > 0 )
name.append( '_' );
name.append( s ).append( '_' );
Object val = keys.get( s );
if ( val instanceof Number || val instanceof String )
name.append( val.toString().replace( ' ', '_' ) );
}
return name.toString();
}
// --- END INDEX CODE ---
/**
* Set hint fields for this collection (to optimize queries).
* @param lst a list of <code>DBObject</code>s to be used as hints
*/
public void setHintFields( List<DBObject> lst ){
_hintFields = lst;
}
/**
* Queries for an object in this collection.
* @param ref object for which to search
* @return an iterator over the results
* @dochub find
*/
public DBCursor find( DBObject ref ){
return new DBCursor( this, ref, null, getReadPreference());
}
/**
* Queries for an object in this collection.
*
* <p>
* An empty DBObject will match every document in the collection.
* Regardless of fields specified, the _id fields are always returned.
* </p>
* <p>
* An example that returns the "x" and "_id" fields for every document
* in the collection that has an "x" field:
* </p>
* <blockquote><pre>
* BasicDBObject keys = new BasicDBObject();
* keys.put("x", 1);
*
* DBCursor cursor = collection.find(new BasicDBObject(), keys);
* </pre></blockquote>
*
* @param ref object for which to search
* @param keys fields to return
* @return a cursor to iterate over results
* @dochub find
*/
public DBCursor find( DBObject ref , DBObject keys ){
return new DBCursor( this, ref, keys, getReadPreference());
}
/**
* Queries for all objects in this collection.
* @return a cursor which will iterate over every object
* @dochub find
*/
public DBCursor find(){
return new DBCursor( this, null, null, getReadPreference());
}
/**
* Returns a single object from this collection.
* @return the object found, or <code>null</code> if the collection is empty
* @throws MongoException
*/
public DBObject findOne()
throws MongoException {
return findOne( new BasicDBObject() );
}
/**
* Returns a single object from this collection matching the query.
* @param o the query object
* @return the object found, or <code>null</code> if no such object exists
* @throws MongoException
*/
public DBObject findOne( DBObject o )
throws MongoException {
return findOne( o, null, getReadPreference());
}
/**
* Returns a single object from this collection matching the query.
* @param o the query object
* @param fields fields to return
* @return the object found, or <code>null</code> if no such object exists
* @dochub find
*/
public DBObject findOne( DBObject o, DBObject fields ) {
return findOne( o, fields, getReadPreference());
}
/**
* Returns a single object from this collection matching the query.
* @param o the query object
* @param fields fields to return
* @return the object found, or <code>null</code> if no such object exists
* @dochub find
*/
public DBObject findOne( DBObject o, DBObject fields, ReadPreference readPref ) {
Iterator<DBObject> i = __find( o , fields , 0 , -1 , 0, getOptions(), readPref, getDecoder() );
DBObject obj = (i.hasNext() ? i.next() : null);
if ( obj != null && ( fields != null && fields.keySet().size() > 0 ) ){
obj.markAsPartialObject();
}
return obj;
}
// Only create a new decoder if there is a decoder factory explicitly set on the collection. Otherwise return null
// so that DBPort will use a cached decoder from the default factory.
private DBDecoder getDecoder() {
return getDBDecoderFactory() != null ? getDBDecoderFactory().create() : null;
}
// Only create a new encoder if there is an encoder factory explicitly set on the collection. Otherwise return null
// to allow DB to create its own or use a cached one.
private DBEncoder getDBEncoder() {
return getDBEncoderFactory() != null ? getDBEncoderFactory().create() : null;
}
/**
* calls {@link DBCollection#apply(com.mongodb.DBObject, boolean)} with ensureID=true
* @param o <code>DBObject</code> to which to add fields
* @return the modified parameter object
*/
public Object apply( DBObject o ){
return apply( o , true );
}
/**
* calls {@link DBCollection#doapply(com.mongodb.DBObject)}, optionally adding an automatic _id field
* @param jo object to add fields to
* @param ensureID whether to add an <code>_id</code> field
* @return the modified object <code>o</code>
*/
public Object apply( DBObject jo , boolean ensureID ){
Object id = jo.get( "_id" );
if ( ensureID && id == null ){
id = ObjectId.get();
jo.put( "_id" , id );
}
doapply( jo );
return id;
}
/**
* calls {@link DBCollection#save(com.mongodb.DBObject, com.mongodb.WriteConcern)} with default WriteConcern
* @param jo the <code>DBObject</code> to save
* will add <code>_id</code> field to jo if needed
* @return
*/
public WriteResult save( DBObject jo ) {
return save(jo, getWriteConcern());
}
/**
* Saves an object to this collection (does insert or update based on the object _id).
* @param jo the <code>DBObject</code> to save
* @param concern the write concern
* @return
* @throws MongoException
*/
public WriteResult save( DBObject jo, WriteConcern concern )
throws MongoException {
if ( checkReadOnly( true ) )
return null;
_checkObject( jo , false , false );
Object id = jo.get( "_id" );
if ( id == null || ( id instanceof ObjectId && ((ObjectId)id).isNew() ) ){
if ( id != null && id instanceof ObjectId )
((ObjectId)id).notNew();
if ( concern == null )
return insert( jo );
else
return insert( jo, concern );
}
DBObject q = new BasicDBObject();
q.put( "_id" , id );
if ( concern == null )
return update( q , jo , true , false );
else
return update( q , jo , true , false , concern );
}
// ---- DB COMMANDS ----
/**
* Drops all indices from this collection
* @throws MongoException
*/
public void dropIndexes()
throws MongoException {
dropIndexes( "*" );
}
/**
* Drops an index from this collection
* @param name the index name
* @throws MongoException
*/
public void dropIndexes( String name )
throws MongoException {
DBObject cmd = BasicDBObjectBuilder.start()
.add( "deleteIndexes" , getName() )
.add( "index" , name )
.get();
resetIndexCache();
CommandResult res = _db.command( cmd );
if (res.ok() || res.getErrorMessage().equals( "ns not found" ))
return;
res.throwOnError();
}
/**
* Drops (deletes) this collection. Use with care.
* @throws MongoException
*/
public void drop()
throws MongoException {
resetIndexCache();
CommandResult res =_db.command( BasicDBObjectBuilder.start().add( "drop" , getName() ).get() );
if (res.ok() || res.getErrorMessage().equals( "ns not found" ))
return;
res.throwOnError();
}
/**
* returns the number of documents in this collection.
* @return
* @throws MongoException
*/
public long count()
throws MongoException {
return getCount(new BasicDBObject(), null);
}
/**
* returns the number of documents that match a query.
* @param query query to match
* @return
* @throws MongoException
*/
public long count(DBObject query)
throws MongoException {
return getCount(query, null);
}
/**
* calls {@link DBCollection#getCount(com.mongodb.DBObject, com.mongodb.DBObject)} with an empty query and null fields.
* @return number of documents that match query
* @throws MongoException
*/
public long getCount()
throws MongoException {
return getCount(new BasicDBObject(), null);
}
/**
* calls {@link DBCollection#getCount(com.mongodb.DBObject, com.mongodb.DBObject)} with null fields.
* @param query query to match
* @return
* @throws MongoException
*/
public long getCount(DBObject query)
throws MongoException {
return getCount(query, null);
}
/**
* calls {@link DBCollection#getCount(com.mongodb.DBObject, com.mongodb.DBObject, long, long)} with limit=0 and skip=0
* @param query query to match
* @param fields fields to return
* @return
* @throws MongoException
*/
public long getCount(DBObject query, DBObject fields)
throws MongoException {
return getCount( query , fields , 0 , 0 );
}
/**
* Returns the number of documents in the collection
* that match the specified query
*
* @param query query to select documents to count
* @param fields fields to return
* @param limit limit the count to this value
* @param skip number of entries to skip
* @return number of documents that match query and fields
* @throws MongoException
*/
public long getCount(DBObject query, DBObject fields, long limit, long skip )
throws MongoException {
BasicDBObject cmd = new BasicDBObject();
cmd.put("count", getName());
cmd.put("query", query);
if (fields != null) {
cmd.put("fields", fields);
}
if ( limit > 0 )
cmd.put( "limit" , limit );
if ( skip > 0 )
cmd.put( "skip" , skip );
CommandResult res = _db.command(cmd,getOptions());
if ( ! res.ok() ){
String errmsg = res.getErrorMessage();
if ( errmsg.equals("ns does not exist") ||
errmsg.equals("ns missing" ) ){
// for now, return 0 - lets pretend it does exist
return 0;
}
res.throwOnError();
}
return res.getLong("n");
}
/**
* Calls {@link DBCollection#rename(java.lang.String, boolean)} with dropTarget=false
* @param newName new collection name (not a full namespace)
* @return the new collection
* @throws MongoException
*/
public DBCollection rename( String newName )
throws MongoException {
return rename(newName, false);
}
/**
* renames of this collection to newName
* @param newName new collection name (not a full namespace)
* @param dropTarget if a collection with the new name exists, whether or not to drop it
* @return the new collection
* @throws MongoException
*/
public DBCollection rename( String newName, boolean dropTarget )
throws MongoException {
CommandResult ret =
_db.getSisterDB( "admin" )
.command( BasicDBObjectBuilder.start()
.add( "renameCollection" , _fullName )
.add( "to" , _db._name + "." + newName )
.add( "dropTarget" , dropTarget )
.get() );
ret.throwOnError();
resetIndexCache();
return _db.getCollection( newName );
}
/**
* calls {@link DBCollection#group(com.mongodb.DBObject, com.mongodb.DBObject, com.mongodb.DBObject, java.lang.String, java.lang.String)} with finalize=null
* @param key - { a : true }
* @param cond - optional condition on query
* @param reduce javascript reduce function
* @param initial initial value for first match on a key
* @return
* @throws MongoException
* @see <a href="http://www.mongodb.org/display/DOCS/Aggregation">http://www.mongodb.org/display/DOCS/Aggregation</a>
*/
public DBObject group( DBObject key , DBObject cond , DBObject initial , String reduce )
throws MongoException {
return group( key , cond , initial , reduce , null );
}
/**
* Applies a group operation
* @param key - { a : true }
* @param cond - optional condition on query
* @param reduce javascript reduce function
* @param initial initial value for first match on a key
* @param finalize An optional function that can operate on the result(s) of the reduce function.
* @return
* @throws MongoException
* @see <a href="http://www.mongodb.org/display/DOCS/Aggregation">http://www.mongodb.org/display/DOCS/Aggregation</a>
*/
public DBObject group( DBObject key , DBObject cond , DBObject initial , String reduce , String finalize )
throws MongoException {
GroupCommand cmd = new GroupCommand(this, key, cond, initial, reduce, finalize);
return group( cmd );
}
/**
* Applies a group operation
* @param cmd the group command
* @return
* @throws MongoException
* @see <a href="http://www.mongodb.org/display/DOCS/Aggregation">http://www.mongodb.org/display/DOCS/Aggregation</a>
*/
public DBObject group( GroupCommand cmd ) {
CommandResult res = _db.command( cmd.toDBObject(), getOptions() );
res.throwOnError();
return (DBObject)res.get( "retval" );
}
/**
* @deprecated prefer the {@link DBCollection#group(com.mongodb.GroupCommand)} which is more standard
* Applies a group operation
* @param args object representing the arguments to the group function
* @return
* @throws MongoException
* @see <a href="http://www.mongodb.org/display/DOCS/Aggregation">http://www.mongodb.org/display/DOCS/Aggregation</a>
*/
@Deprecated
public DBObject group( DBObject args )
throws MongoException {
args.put( "ns" , getName() );
CommandResult res = _db.command( new BasicDBObject( "group" , args ), getOptions() );
res.throwOnError();
return (DBObject)res.get( "retval" );
}
/**
* find distinct values for a key
* @param key
* @return
*/
public List distinct( String key ){
return distinct( key , new BasicDBObject() );
}
/**
* find distinct values for a key
* @param key
* @param query query to match
* @return
*/
public List distinct( String key , DBObject query ){
DBObject c = BasicDBObjectBuilder.start()
.add( "distinct" , getName() )
.add( "key" , key )
.add( "query" , query )
.get();
CommandResult res = _db.command( c, getOptions() );
res.throwOnError();
return (List)(res.get( "values" ));
}
/**
* performs a map reduce operation
* Runs the command in REPLACE output mode (saves to named collection)
*
* @param map
* map function in javascript code
* @param outputTarget
* optional - leave null if want to use temp collection
* @param reduce
* reduce function in javascript code
* @param query
* to match
* @return
* @throws MongoException
* @dochub mapreduce
*/
public MapReduceOutput mapReduce( String map , String reduce , String outputTarget , DBObject query ) throws MongoException{
return mapReduce( new MapReduceCommand( this , map , reduce , outputTarget , MapReduceCommand.OutputType.REPLACE, query ) );
}
/**
* performs a map reduce operation
* Specify an outputType to control job execution
* * INLINE - Return results inline
* * REPLACE - Replace the output collection with the job output
* * MERGE - Merge the job output with the existing contents of outputTarget
* * REDUCE - Reduce the job output with the existing contents of
* outputTarget
*
* @param map
* map function in javascript code
* @param outputTarget
* optional - leave null if want to use temp collection
* @param outputType
* set the type of job output
* @param reduce
* reduce function in javascript code
* @param query
* to match
* @return
* @throws MongoException
* @dochub mapreduce
*/
public MapReduceOutput mapReduce( String map , String reduce , String outputTarget , MapReduceCommand.OutputType outputType , DBObject query )
throws MongoException{
return mapReduce( new MapReduceCommand( this , map , reduce , outputTarget , outputType , query ) );
}
/**
* performs a map reduce operation
*
* @param command
* object representing the parameters
* @return
* @throws MongoException
*/
public MapReduceOutput mapReduce( MapReduceCommand command ) throws MongoException{
DBObject cmd = command.toDBObject();
// if type in inline, then query options like slaveOk is fine
CommandResult res = null;
if (command.getOutputType() == MapReduceCommand.OutputType.INLINE)
res = _db.command( cmd, getOptions(), command.getReadPreference() != null ? command.getReadPreference() : getReadPreference() );
else
res = _db.command( cmd );
res.throwOnError();
return new MapReduceOutput( this , cmd, res );
}
/**
* performs a map reduce operation
*
* @param command
* object representing the parameters
* @return
* @throws MongoException
*/
public MapReduceOutput mapReduce( DBObject command ) throws MongoException{
if ( command.get( "mapreduce" ) == null && command.get( "mapReduce" ) == null )
throw new IllegalArgumentException( "need mapreduce arg" );
CommandResult res = _db.command( command );
res.throwOnError();
return new MapReduceOutput( this , command, res );
}
/**
* Return a list of the indexes for this collection. Each object
* in the list is the "info document" from MongoDB
*
* @return list of index documents
*/
public List<DBObject> getIndexInfo() {
BasicDBObject cmd = new BasicDBObject();
cmd.put("ns", getFullName());
DBCursor cur = _db.getCollection("system.indexes").find(cmd);
List<DBObject> list = new ArrayList<DBObject>();
while(cur.hasNext()) {
list.add(cur.next());
}
return list;
}
/**
* Drops an index from this collection
* @param keys keys of the index
* @throws MongoException
*/
public void dropIndex( DBObject keys )
throws MongoException {
dropIndexes( genIndexName( keys ) );
}
/**
* Drops an index from this collection
* @param name name of index to drop
* @throws MongoException
*/
public void dropIndex( String name )
throws MongoException {
dropIndexes( name );
}
/**
* gets the collections statistics ("collstats" command)
* @return
*/
public CommandResult getStats() {
return getDB().command(new BasicDBObject("collstats", getName()), getOptions());
}
/**
* returns whether or not this is a capped collection
* @return
*/
public boolean isCapped() {
CommandResult stats = getStats();
Object capped = stats.get("capped");
return(capped != null && (Integer)capped == 1);
}
// ------
/**
* Initializes a new collection. No operation is actually performed on the database.
* @param base database in which to create the collection
* @param name the name of the collection
*/
protected DBCollection( DB base , String name ){
_db = base;
_name = name;
_fullName = _db.getName() + "." + name;
_options = new Bytes.OptionHolder( _db._options );
}
protected DBObject _checkObject( DBObject o , boolean canBeNull , boolean query ){
if ( o == null ){
if ( canBeNull )
return null;
throw new IllegalArgumentException( "can't be null" );
}
if ( o.isPartialObject() && ! query )
throw new IllegalArgumentException( "can't save partial objects" );
if ( ! query ){
_checkKeys(o);
}
return o;
}
/**
* Checks key strings for invalid characters.
*/
private void _checkKeys( DBObject o ) {
for ( String s : o.keySet() ){
validateKey ( s );
Object inner = o.get( s );
if ( inner instanceof DBObject ) {
_checkKeys( (DBObject)inner );
} else if ( inner instanceof Map ) {
_checkKeys( (Map<String, Object>)inner );
}
}
}
/**
* Checks key strings for invalid characters.
*/
private void _checkKeys( Map<String, Object> o ) {
for ( String s : o.keySet() ){
validateKey ( s );
Object inner = o.get( s );
if ( inner instanceof DBObject ) {
_checkKeys( (DBObject)inner );
} else if ( inner instanceof Map ) {
_checkKeys( (Map<String, Object>)inner );
}
}
}
/**
* Check for invalid key names
* @param s the string field/key to check
* @exception IllegalArgumentException if the key is not valid.
*/
private void validateKey(String s ) {
if ( s.contains( "." ) )
throw new IllegalArgumentException( "fields stored in the db can't have . in them. (Bad Key: '" + s + "')" );
if ( s.startsWith( "$" ) )
throw new IllegalArgumentException( "fields stored in the db can't start with '$' (Bad Key: '" + s + "')" );
}
/**
* Finds a collection that is prefixed with this collection's name.
* A typical use of this might be
* <blockquote><pre>
* DBCollection users = mongo.getCollection( "wiki" ).getCollection( "users" );
* </pre></blockquote>
* Which is equivalent to
* <pre><blockquote>
* DBCollection users = mongo.getCollection( "wiki.users" );
* </pre></blockquote>
* @param n the name of the collection to find
* @return the matching collection
*/
public DBCollection getCollection( String n ){
return _db.getCollection( _name + "." + n );
}
/**
* Returns the name of this collection.
* @return the name of this collection
*/
public String getName(){
return _name;
}
/**
* Returns the full name of this collection, with the database name as a prefix.
* @return the name of this collection
*/
public String getFullName(){
return _fullName;
}
/**
* Returns the database this collection is a member of.
* @return this collection's database
*/
public DB getDB(){
return _db;
}
/**
* Returns if this collection's database is read-only
* @param strict if an exception should be thrown if the database is read-only
* @return if this collection's database is read-only
* @throws RuntimeException if the database is read-only and <code>strict</code> is set
*/
protected boolean checkReadOnly( boolean strict ){
if ( ! _db._readOnly )
return false;
if ( ! strict )
return true;
throw new IllegalStateException( "db is read only" );
}
@Override
public int hashCode(){
return _fullName.hashCode();
}
@Override
public boolean equals( Object o ){
return o == this;
}
@Override
public String toString(){
return _name;
}
/**
* Sets a default class for objects in this collection; null resets the class to nothing.
* @param c the class
* @throws IllegalArgumentException if <code>c</code> is not a DBObject
*/
public void setObjectClass( Class c ){
if ( c == null ){
// reset
_wrapper = null;
_objectClass = null;
return;
}
if ( ! DBObject.class.isAssignableFrom( c ) )
throw new IllegalArgumentException( c.getName() + " is not a DBObject" );
_objectClass = c;
if ( ReflectionDBObject.class.isAssignableFrom( c ) )
_wrapper = ReflectionDBObject.getWrapper( c );
else
_wrapper = null;
}
/**
* Gets the default class for objects in the collection
* @return the class
*/
public Class getObjectClass(){
return _objectClass;
}
/**
* sets the internal class
* @param path
* @param c
*/
public void setInternalClass( String path , Class c ){
_internalClass.put( path , c );
}
/**
* gets the internal class
* @param path
* @return
*/
protected Class getInternalClass( String path ){
Class c = _internalClass.get( path );
if ( c != null )
return c;
if ( _wrapper == null )
return null;
return _wrapper.getInternalClass( path );
}
/**
* Set the write concern for this collection. Will be used for
* writes to this collection. Overrides any setting of write
* concern at the DB level. See the documentation for
* {@link WriteConcern} for more information.
*
* @param concern write concern to use
*/
public void setWriteConcern( WriteConcern concern ){
_concern = concern;
}
/**
* Get the write concern for this collection.
* @return
*/
public WriteConcern getWriteConcern(){
if ( _concern != null )
return _concern;
return _db.getWriteConcern();
}
/**
* Sets the read preference for this collection. Will be used as default
* for reads from this collection; overrides DB & Connection level settings.
* See the * documentation for {@link ReadPreference} for more information.
*
* @param preference Read Preference to use
*/
public void setReadPreference( ReadPreference preference ){
_readPref = preference;
}
/**
* Gets the read preference
* @return
*/
public ReadPreference getReadPreference(){
if ( _readPref != null )
return _readPref;
return _db.getReadPreference();
}
/**
* makes this query ok to run on a slave node
*
* @deprecated Replaced with ReadPreference.SECONDARY
* @see com.mongodb.ReadPreference.SECONDARY
*/
@Deprecated
public void slaveOk(){
addOption( Bytes.QUERYOPTION_SLAVEOK );
}
/**
* adds a default query option
* @param option
*/
public void addOption( int option ){
_options.add(option);
}
/**
* sets the default query options
* @param options
*/
public void setOptions( int options ){
_options.set(options);
}
/**
* resets the default query options
*/
public void resetOptions(){
_options.reset();
}
/**
* gets the default query options
* @return
*/
public int getOptions(){
return _options.get();
}
/**
* Set a customer decoder factory for this collection. Set to null to use the default from MongoOptions.
* @param fact the factory to set.
*/
public synchronized void setDBDecoderFactory(DBDecoderFactory fact) {
_decoderFactory = fact;
}
/**
* Get the decoder factory for this collection. A null return value means that the default from MongoOptions
* is being used.
* @return the factory
*/
public synchronized DBDecoderFactory getDBDecoderFactory() {
return _decoderFactory;
}
/**
* Set a customer encoder factory for this collection. Set to null to use the default from MongoOptions.
* @param fact the factory to set.
*/
public synchronized void setDBEncoderFactory(DBEncoderFactory fact) {
_encoderFactory = fact;
}
/**
* Get the encoder factory for this collection. A null return value means that the default from MongoOptions
* is being used.
* @return the factory
*/
public synchronized DBEncoderFactory getDBEncoderFactory() {
return _encoderFactory;
}
final DB _db;
final protected String _name;
final protected String _fullName;
protected List<DBObject> _hintFields;
private WriteConcern _concern = null;
private ReadPreference _readPref = null;
private DBDecoderFactory _decoderFactory;
private DBEncoderFactory _encoderFactory;
final Bytes.OptionHolder _options;
protected Class _objectClass = null;
private Map<String,Class> _internalClass = Collections.synchronizedMap( new HashMap<String,Class>() );
private ReflectionDBObject.JavaWrapper _wrapper = null;
final private Set<String> _createdIndexes = new HashSet<String>();
}