/* 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
}
}