/* Copyright 2010-2011 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. */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Driver.Builders; using MongoDB.Driver.GridFS; using MongoDB.Driver.Internal; namespace MongoDB.Driver { /// /// Represents a MongoDB database and the settings used to access it. This class is thread-safe. /// public class MongoDatabase { #region private fields private object databaseLock = new object(); private MongoServer server; private MongoDatabaseSettings settings; private string name; private Dictionary collections = new Dictionary(); private MongoCollection commandCollection; private MongoGridFS gridFS; #endregion #region constructors /// /// Creates a new instance of MongoDatabase. Normally you would call one of the indexers or GetDatabase methods /// of MongoServer instead. /// /// The server that contains this database. /// The settings to use to access this database. public MongoDatabase( MongoServer server, MongoDatabaseSettings settings ) { ValidateDatabaseName(settings.DatabaseName); this.server = server; this.settings = settings.Freeze(); this.name = settings.DatabaseName; // make sure commands get routed to the primary server by using slaveOk false var commandCollectionSettings = CreateCollectionSettings("$cmd"); commandCollectionSettings.AssignIdOnInsert = false; if (server.Settings.ConnectionMode == ConnectionMode.ReplicaSet) { commandCollectionSettings.SlaveOk = false; } commandCollection = GetCollection(commandCollectionSettings); } #endregion #region factory methods /// /// Creates a new instance or returns an existing instance of MongoDatabase. Only one instance /// is created for each combination of database settings. Automatically creates an instance /// of MongoServer if needed. /// /// Server and database settings in the form of a MongoConnectionStringBuilder. /// /// A new or existing instance of MongoDatabase. /// public static MongoDatabase Create( MongoConnectionStringBuilder builder ) { var serverSettings = builder.ToServerSettings(); var databaseName = builder.DatabaseName; return Create(serverSettings, databaseName); } /// /// Creates a new instance or returns an existing instance of MongoDatabase. Only one instance /// is created for each combination of database settings. Automatically creates an instance /// of MongoServer if needed. /// /// The server settings for the server that contains this database. /// The name of this database (will be accessed using default settings). /// /// A new or existing instance of MongoDatabase. /// public static MongoDatabase Create( MongoServerSettings serverSettings, string databaseName ) { if (databaseName == null) { throw new ArgumentException("Database name is missing."); } var server = MongoServer.Create(serverSettings); return server.GetDatabase(databaseName); } /// /// Creates a new instance or returns an existing instance of MongoDatabase. Only one instance /// is created for each combination of database settings. Automatically creates an instance /// of MongoServer if needed. /// /// Server and database settings in the form of a MongoUrl. /// /// A new or existing instance of MongoDatabase. /// public static MongoDatabase Create( MongoUrl url ) { var serverSettings = url.ToServerSettings(); var databaseName = url.DatabaseName; return Create(serverSettings, databaseName); } /// /// Creates a new instance or returns an existing instance of MongoDatabase. Only one instance /// is created for each combination of database settings. Automatically creates an instance /// of MongoServer if needed. /// /// Server and database settings in the form of a connection string. /// /// A new or existing instance of MongoDatabase. /// public static MongoDatabase Create( string connectionString ) { if (connectionString.StartsWith("mongodb://")) { MongoUrl url = MongoUrl.Create(connectionString); return Create(url); } else { MongoConnectionStringBuilder builder = new MongoConnectionStringBuilder(connectionString); return Create(builder); } } /// /// Creates a new instance or returns an existing instance of MongoDatabase. Only one instance /// is created for each combination of database settings. Automatically creates an instance /// of MongoServer if needed. /// /// Server and database settings in the form of a Uri. /// /// A new or existing instance of MongoDatabase. /// public static MongoDatabase Create( Uri uri ) { return Create(MongoUrl.Create(uri.ToString())); } #endregion #region public properties /// /// Gets the command collection for this database. /// public virtual MongoCollection CommandCollection { get { return commandCollection; } } /// /// Gets the credentials being used to access this database. /// public virtual MongoCredentials Credentials { get { return settings.Credentials; } } /// /// Gets the default GridFS instance for this database. The default GridFS instance uses default GridFS /// settings. See also GetGridFS if you need to use GridFS with custom settings. /// public virtual MongoGridFS GridFS { get { lock (databaseLock) { if (gridFS == null) { gridFS = new MongoGridFS(this); } return gridFS; } } } /// /// Gets the name of this database. /// public virtual string Name { get { return name; } } /// /// Gets the server that contains this database. /// public virtual MongoServer Server { get { return server; } } /// /// Gets the settings being used to access this database. /// public virtual MongoDatabaseSettings Settings { get { return settings; } } #endregion #region public indexers /// /// Gets a MongoCollection instance representing a collection on this database /// with a default document type of BsonDocument. /// /// The name of the collection. /// An instance of MongoCollection. public virtual MongoCollection this[ string collectionName ] { get { return GetCollection(collectionName); } } /// /// Gets a MongoCollection instance representing a collection on this database /// with a default document type of BsonDocument. /// /// The name of the collection. /// The safe mode to use when accessing this collection. /// An instance of MongoCollection. public virtual MongoCollection this[ string collectionName, SafeMode safeMode ] { get { return GetCollection(collectionName, safeMode); } } #endregion #region public methods /// /// Adds a user to this database. /// /// The user's credentials. public virtual void AddUser( MongoCredentials credentials ) { AddUser(credentials, false); } /// /// Adds a user to this database. /// /// The user's credentials. /// True if the user is a read-only user. public virtual void AddUser( MongoCredentials credentials, bool readOnly ) { var users = GetCollection("system.users"); var user = users.FindOne(Query.EQ("user", credentials.Username)); if (user == null) { user = new BsonDocument("user", credentials.Username); } user["readOnly"] = readOnly; user["pwd"] = MongoUtils.Hash(credentials.Username + ":mongo:" + credentials.Password); users.Save(user); } /// /// Tests whether a collection exists on this database. /// /// The name of the collection. /// True if the collection exists. public virtual bool CollectionExists( string collectionName ) { return GetCollectionNames().Contains(collectionName); } /// /// Creates a collection. MongoDB creates collections automatically when they are first used, so /// you only need to call this method if you want to provide non-default options. /// /// The name of the collection. /// Options for creating this collection (usually a CollectionOptionsDocument or constructed using the CollectionOptions builder). /// A CommandResult. public virtual CommandResult CreateCollection( string collectionName, IMongoCollectionOptions options ) { var command = new CommandDocument("create", collectionName); if (options != null) { command.Merge(options.ToBsonDocument()); } return RunCommand(command); } /// /// Creates an instance of MongoCollectionSettings for the named collection with the rest of the settings inherited. /// You can override some of these settings before calling GetCollection. /// /// The default document type for this collection. /// The name of this collection. /// A MongoCollectionSettings. public virtual MongoCollectionSettings CreateCollectionSettings( string collectionName ) { return new MongoCollectionSettings( collectionName, MongoDefaults.AssignIdOnInsert, settings.GuidByteOrder, settings.SafeMode, settings.SlaveOk ); } /// /// Creates an instance of MongoCollectionSettings for the named collection with the rest of the settings inherited. /// You can override some of these settings before calling GetCollection. /// /// The default document type for this collection. /// The name of this collection. /// A MongoCollectionSettings. public virtual MongoCollectionSettings CreateCollectionSettings( Type defaultDocumentType, string collectionName ) { var settingsDefinition = typeof(MongoCollectionSettings<>); var settingsType = settingsDefinition.MakeGenericType(defaultDocumentType); var constructorInfo = settingsType.GetConstructor(new Type[] { typeof(string), typeof(bool), typeof(GuidByteOrder), typeof(SafeMode), typeof(bool) }); return (MongoCollectionSettings) constructorInfo.Invoke( new object[] { collectionName, MongoDefaults.AssignIdOnInsert, settings.GuidByteOrder, settings.SafeMode, settings.SlaveOk } ); } /// /// Drops a database. /// public virtual void Drop() { server.DropDatabase(name); } /// /// Drops a collection. /// /// The name of the collection to drop. /// A CommandResult. public virtual CommandResult DropCollection( string collectionName ) { try { var command = new CommandDocument("drop", collectionName); var result = RunCommand(command); server.IndexCache.Reset(name, collectionName); return result; } catch (MongoCommandException ex) { if (ex.CommandResult.ErrorMessage == "ns not found") { return ex.CommandResult; } throw; } } /// /// Evaluates JavaScript code at the server. /// /// The code to evaluate. /// Optional arguments (only used when the code is a function with parameters). /// The result of evaluating the code. public virtual BsonValue Eval( string code, params object[] args ) { var command = new CommandDocument { { "$eval", code }, { "args", new BsonArray(args) } }; var result = RunCommand(command); return result.Response["retval"]; } /// /// Fetches the document referred to by the DBRef. /// /// The to fetch. /// A BsonDocument (or null if the document was not found). public virtual BsonDocument FetchDBRef( MongoDBRef dbRef ) { return FetchDBRefAs(dbRef); } /// /// Fetches the document referred to by the DBRef, deserialized as a . /// /// The nominal type of the document to fetch. /// The to fetch. /// A (or null if the document was not found). public virtual TDocument FetchDBRefAs( MongoDBRef dbRef ) { return (TDocument) FetchDBRefAs(typeof(TDocument), dbRef); } /// /// Fetches the document referred to by the DBRef. /// /// The nominal type of the document to fetch. /// The to fetch. /// An instance of nominalType (or null if the document was not found). public virtual object FetchDBRefAs( Type documentType, MongoDBRef dbRef ) { if (dbRef.DatabaseName != null && dbRef.DatabaseName != name) { return server.FetchDBRefAs(documentType, dbRef); } var collection = GetCollection(dbRef.CollectionName); var query = Query.EQ("_id", BsonValue.Create(dbRef.Id)); return collection.FindOneAs(documentType, query); } /// /// Gets a MongoCollection instance representing a collection on this database /// with a default document type of TDefaultDocument. /// /// The default document type for this collection. /// The settings to use when accessing this collection. /// An instance of MongoCollection. public virtual MongoCollection GetCollection( MongoCollectionSettings collectionSettings ) { lock (databaseLock) { MongoCollection collection; if (!collections.TryGetValue(collectionSettings, out collection)) { collection = new MongoCollection(this, collectionSettings); collections.Add(collectionSettings, collection); } return (MongoCollection) collection; } } /// /// Gets a MongoCollection instance representing a collection on this database /// with a default document type of TDefaultDocument. /// /// The name of the collection. /// An instance of MongoCollection. public virtual MongoCollection GetCollection( string collectionName ) { var collectionSettings = CreateCollectionSettings(collectionName); return GetCollection(collectionSettings); } /// /// Gets a MongoCollection instance representing a collection on this database /// with a default document type of TDefaultDocument. /// /// The name of the collection. /// The safe mode to use when accessing this collection. /// An instance of MongoCollection. public virtual MongoCollection GetCollection( string collectionName, SafeMode safeMode ) { var collectionSettings = CreateCollectionSettings(collectionName); collectionSettings.SafeMode = safeMode; return GetCollection(collectionSettings); } /// /// Gets a MongoCollection instance representing a collection on this database /// with a default document type of TDefaultDocument. /// /// The settings to use when accessing this collection. /// An instance of MongoCollection. public virtual MongoCollection GetCollection( MongoCollectionSettings collectionSettings ) { lock (databaseLock) { MongoCollection collection; if (!collections.TryGetValue(collectionSettings, out collection)) { var collectionDefinition = typeof(MongoCollection<>); var collectionType = collectionDefinition.MakeGenericType(collectionSettings.DefaultDocumentType); var constructorInfo = collectionType.GetConstructor(new Type[] { typeof(MongoDatabase), collectionSettings.GetType() }); collection = (MongoCollection) constructorInfo.Invoke(new object[] { this, collectionSettings }); collections.Add(collectionSettings, collection); } return collection; } } /// /// Gets a MongoCollection instance representing a collection on this database /// with a default document type of BsonDocument. /// /// The name of the collection. /// An instance of MongoCollection. public virtual MongoCollection GetCollection( string collectionName ) { var collectionSettings = CreateCollectionSettings(collectionName); return GetCollection(collectionSettings); } /// /// Gets a MongoCollection instance representing a collection on this database /// with a default document type of BsonDocument. /// /// The name of the collection. /// The safe mode to use when accessing this collection. /// An instance of MongoCollection. public virtual MongoCollection GetCollection( string collectionName, SafeMode safeMode ) { var collectionSettings = CreateCollectionSettings(collectionName); collectionSettings.SafeMode = safeMode; return GetCollection(collectionSettings); } /// /// Gets a MongoCollection instance representing a collection on this database /// with a default document type of BsonDocument. /// /// The default document type. /// The name of the collection. /// An instance of MongoCollection. public virtual MongoCollection GetCollection( Type defaultDocumentType, string collectionName ) { var collectionSettings = CreateCollectionSettings(defaultDocumentType, collectionName); return GetCollection(collectionSettings); } /// /// Gets a MongoCollection instance representing a collection on this database /// with a default document type of BsonDocument. /// /// The default document type. /// The name of the collection. /// The safe mode to use when accessing this collection. /// An instance of MongoCollection. public virtual MongoCollection GetCollection( Type defaultDocumentType, string collectionName, SafeMode safeMode ) { var collectionSettings = CreateCollectionSettings(defaultDocumentType, collectionName); collectionSettings.SafeMode = safeMode; return GetCollection(collectionSettings); } /// /// Gets a list of the names of all the collections in this database. /// /// A list of collection names. public virtual IEnumerable GetCollectionNames() { List collectionNames = new List(); var namespaces = GetCollection("system.namespaces"); var prefix = name + "."; foreach (var @namespace in namespaces.FindAll()) { string collectionName = @namespace["name"].AsString; if (!collectionName.StartsWith(prefix)) { continue; } if (collectionName.Contains('$')) { continue; } collectionNames.Add(collectionName.Substring(prefix.Length)); } collectionNames.Sort(); return collectionNames; } /// /// Gets the current operation. /// /// The current operation. public virtual BsonDocument GetCurrentOp() { var collection = GetCollection("$cmd.sys.inprog"); return collection.FindOne(); } /// /// Gets an instance of MongoGridFS for this database using custom GridFS settings. /// /// The GridFS settings to use. /// An instance of MongoGridFS. public virtual MongoGridFS GetGridFS( MongoGridFSSettings gridFSSettings ) { return new MongoGridFS(this, gridFSSettings); } // TODO: mongo shell has GetPrevError at the database level? // TODO: mongo shell has GetProfilingLevel at the database level? // TODO: mongo shell has GetReplicationInfo at the database level? /// /// Gets a sister database on the same server. /// /// The name of the sister database. /// An instance of MongoDatabase. public virtual MongoDatabase GetSisterDatabase( string databaseName ) { return server.GetDatabase(databaseName); } /// /// Gets the current database stats. /// /// An instance of DatabaseStatsResult. public virtual DatabaseStatsResult GetStats() { return RunCommandAs("dbstats"); } // TODO: mongo shell has IsMaster at database level? /// /// Removes a user from this database. /// /// The username to remove. public virtual void RemoveUser( string username ) { var users = GetCollection("system.users"); users.Remove(Query.EQ("user", username)); } /// /// Renames a collection on this database. /// /// The old name for the collection. /// The new name for the collection. /// A CommandResult. public virtual CommandResult RenameCollection( string oldCollectionName, string newCollectionName ) { var command = new CommandDocument { { "renameCollection", string.Format("{0}.{1}", name, oldCollectionName) }, { "to", string.Format("{0}.{1}", name, newCollectionName) } }; return server.RunAdminCommand(command); } /// /// Lets the server know that this thread is done with a series of related operations. Instead of calling this method it is better /// to put the return value of RequestStart in a using statement. /// public virtual void RequestDone() { server.RequestDone(); } /// /// Lets the server know that this thread is about to begin a series of related operations that must all occur /// on the same connection. The return value of this method implements IDisposable and can be placed in a /// using statement (in which case RequestDone will be called automatically when leaving the using statement). /// /// A helper object that implements IDisposable and calls from the Dispose method. public virtual IDisposable RequestStart() { return server.RequestStart(this); } // TODO: mongo shell has ResetError at the database level /// /// Removes all entries for this database in the index cache used by EnsureIndex. Call this method /// when you know (or suspect) that a process other than this one may have dropped one or /// more indexes. /// public virtual void ResetIndexCache() { server.IndexCache.Reset(this); } /// /// Runs a command on this database. /// /// The command object. /// A CommandResult public virtual CommandResult RunCommand( IMongoCommand command ) { return RunCommandAs(command); } /// /// Runs a command on this database. /// /// The name of the command. /// A CommandResult public virtual CommandResult RunCommand( string commandName ) { return RunCommandAs(commandName); } /// /// Runs a command on this database and returns the result as a TCommandResult. /// /// The command object. /// A TCommandResult public virtual TCommandResult RunCommandAs( IMongoCommand command ) where TCommandResult : CommandResult, new() { return (TCommandResult) RunCommandAs(typeof(TCommandResult), command); } /// /// Runs a command on this database and returns the result as a TCommandResult. /// /// The name of the command. /// A TCommandResult public virtual TCommandResult RunCommandAs( string commandName ) where TCommandResult : CommandResult, new() { return (TCommandResult) RunCommandAs(typeof(TCommandResult), commandName); } /// /// Runs a command on this database and returns the result as a TCommandResult. /// /// The command result type. /// The command object. /// A TCommandResult public virtual CommandResult RunCommandAs( Type commandResultType, IMongoCommand command ) { var response = CommandCollection.FindOne(command); if (response == null) { var commandName = command.ToBsonDocument().GetElement(0).Name; var message = string.Format("Command '{0}' failed. No response returned.", commandName); throw new MongoCommandException(message); } var commandResult = (CommandResult) Activator.CreateInstance(commandResultType); // constructor can't have arguments commandResult.Initialize(command, response); // so two phase construction required if (!commandResult.Ok) { if (commandResult.ErrorMessage == "not master") { server.Disconnect(); } throw new MongoCommandException(commandResult); } return commandResult; } /// /// Runs a command on this database and returns the result as a TCommandResult. /// /// The command result type. /// The name of the command. /// A TCommandResult public virtual CommandResult RunCommandAs( Type commandResultType, string commandName ) { var command = new CommandDocument(commandName, true); return RunCommandAs(commandResultType, command); } /// /// Gets a canonical string representation for this database. /// /// A canonical string representation for this database. public override string ToString() { return name; } #endregion #region private methods private void ValidateDatabaseName( string name ) { if (name == null) { throw new ArgumentNullException("name"); } if (name == "") { throw new ArgumentException("Database name is empty."); } if (name.IndexOfAny(new char[] { '\0', ' ', '.', '$', '/', '\\' }) != -1) { throw new ArgumentException("Database name cannot contain the following special characters: null, space, period, $, / or \\."); } if (Encoding.UTF8.GetBytes(name).Length > 64) { throw new ArgumentException("Database name cannot exceed 64 bytes (after encoding to UTF8)."); } } #endregion } }