You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
397 lines
13 KiB
397 lines
13 KiB
/* Copyright 2010 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.Driver.GridFS;
|
|
using MongoDB.Driver.Internal;
|
|
|
|
namespace MongoDB.Driver {
|
|
public class MongoDatabase {
|
|
#region private fields
|
|
private object databaseLock = new object();
|
|
private MongoServer server;
|
|
private string name;
|
|
private MongoCredentials credentials;
|
|
private SafeMode safeMode;
|
|
private Dictionary<string, MongoCollection> collections = new Dictionary<string, MongoCollection>();
|
|
private MongoGridFS gridFS;
|
|
#endregion
|
|
|
|
#region constructors
|
|
public MongoDatabase(
|
|
MongoServer server,
|
|
string name
|
|
)
|
|
: this(server, name, null) {
|
|
}
|
|
|
|
public MongoDatabase(
|
|
MongoServer server,
|
|
string name,
|
|
MongoCredentials credentials
|
|
) {
|
|
ValidateDatabaseName(name);
|
|
this.server = server;
|
|
this.name = name;
|
|
this.credentials = credentials;
|
|
this.safeMode = server.SafeMode;
|
|
}
|
|
#endregion
|
|
|
|
#region factory methods
|
|
public static MongoDatabase Create(
|
|
MongoConnectionStringBuilder builder
|
|
) {
|
|
return Create(builder.ToMongoUrl());
|
|
}
|
|
|
|
public static MongoDatabase Create(
|
|
MongoUrl url
|
|
) {
|
|
if (url.DatabaseName == null) {
|
|
throw new ArgumentException("Connection string must have database name");
|
|
}
|
|
MongoServer server = MongoServer.Create(url);
|
|
return server.GetDatabase(url.DatabaseName, url.Credentials);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
public static MongoDatabase Create(
|
|
Uri uri
|
|
) {
|
|
return Create(MongoUrl.Create(uri.ToString()));
|
|
}
|
|
#endregion
|
|
|
|
#region public properties
|
|
public MongoCollection<BsonDocument> CommandCollection {
|
|
get { return GetCollection<BsonDocument>("$cmd"); }
|
|
}
|
|
|
|
public MongoCredentials Credentials {
|
|
get { return credentials; }
|
|
}
|
|
|
|
public MongoGridFS GridFS {
|
|
get {
|
|
lock (databaseLock) {
|
|
if (gridFS == null) {
|
|
gridFS = new MongoGridFS(this, MongoGridFSSettings.Defaults.Clone());
|
|
}
|
|
return gridFS;
|
|
}
|
|
}
|
|
}
|
|
|
|
public string Name {
|
|
get { return name; }
|
|
}
|
|
|
|
public SafeMode SafeMode {
|
|
get { return safeMode; }
|
|
set { safeMode = value; }
|
|
}
|
|
|
|
public MongoServer Server {
|
|
get { return server; }
|
|
}
|
|
#endregion
|
|
|
|
#region public indexers
|
|
public MongoCollection<BsonDocument> this[
|
|
string collectionName
|
|
] {
|
|
get { return GetCollection(collectionName); }
|
|
}
|
|
#endregion
|
|
|
|
#region public methods
|
|
public void AddUser(
|
|
MongoCredentials credentials
|
|
) {
|
|
AddUser(credentials, false);
|
|
}
|
|
|
|
public void AddUser(
|
|
MongoCredentials credentials,
|
|
bool readOnly
|
|
) {
|
|
var users = GetCollection("system.users");
|
|
var user = users.FindOne(new BsonDocument("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);
|
|
}
|
|
|
|
public bool CollectionExists(
|
|
string collectionName
|
|
) {
|
|
return GetCollectionNames().Contains(collectionName);
|
|
}
|
|
|
|
public BsonDocument CreateCollection(
|
|
string collectionName,
|
|
BsonDocument options
|
|
) {
|
|
BsonDocument command = new BsonDocument("create", collectionName);
|
|
command.Merge(options);
|
|
return RunCommand(command);
|
|
}
|
|
|
|
public BsonDocument CurrentOp() {
|
|
var collection = GetCollection("$cmd.sys.inprog");
|
|
return collection.FindOne();
|
|
}
|
|
|
|
public BsonDocument DropCollection(
|
|
string collectionName
|
|
) {
|
|
BsonDocument command = new BsonDocument("drop", collectionName);
|
|
return RunCommand(command);
|
|
}
|
|
|
|
public BsonValue Eval(
|
|
string code,
|
|
params object[] args
|
|
) {
|
|
BsonDocument command = new BsonDocument {
|
|
{ "$eval", code },
|
|
{ "args", new BsonArray(args) }
|
|
};
|
|
var result = RunCommand(command);
|
|
return result["retval"];
|
|
}
|
|
|
|
public BsonDocument FetchDBRef(
|
|
MongoDBRef dbRef
|
|
) {
|
|
return FetchDBRefAs<BsonDocument>(dbRef);
|
|
}
|
|
|
|
public TDocument FetchDBRefAs<TDocument>(
|
|
MongoDBRef dbRef
|
|
) {
|
|
if (dbRef.DatabaseName != null && dbRef.DatabaseName != name) {
|
|
return server.FetchDBRefAs<TDocument>(dbRef);
|
|
}
|
|
|
|
var collection = GetCollection(dbRef.CollectionName);
|
|
var query = new BsonDocument("_id", BsonValue.Create(dbRef.Id));
|
|
return collection.FindOneAs<TDocument>(query);
|
|
}
|
|
|
|
public MongoCollection<BsonDocument> GetCollection(
|
|
string collectionName
|
|
) {
|
|
return GetCollection<BsonDocument>(collectionName);
|
|
}
|
|
|
|
public MongoCollection<TDefaultDocument> GetCollection<TDefaultDocument>(
|
|
string collectionName
|
|
) {
|
|
lock (databaseLock) {
|
|
MongoCollection collection;
|
|
string key = string.Format("{0}<{1}>", collectionName, typeof(TDefaultDocument).FullName);
|
|
if (!collections.TryGetValue(key, out collection)) {
|
|
collection = new MongoCollection<TDefaultDocument>(this, collectionName);
|
|
collections.Add(key, collection);
|
|
}
|
|
return (MongoCollection<TDefaultDocument>) collection;
|
|
}
|
|
}
|
|
|
|
public IEnumerable<string> GetCollectionNames() {
|
|
List<string> collectionNames = new List<string>();
|
|
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;
|
|
}
|
|
|
|
public BsonDocument GetLastError() {
|
|
var connectionPool = server.GetConnectionPool();
|
|
if (connectionPool.RequestNestingLevel == 0) {
|
|
throw new InvalidOperationException("GetLastError can only be called if RequestStart has been called first");
|
|
}
|
|
return RunCommand("getlasterror"); // use all lowercase for backward compatibility
|
|
}
|
|
|
|
// 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?
|
|
|
|
public MongoDatabase GetSisterDatabase(
|
|
string databaseName
|
|
) {
|
|
return server.GetDatabase(databaseName);
|
|
}
|
|
|
|
public BsonDocument GetStats() {
|
|
return RunCommand("dbstats");
|
|
}
|
|
|
|
// TODO: mongo shell has IsMaster at database level?
|
|
|
|
public void RemoveUser(
|
|
string username
|
|
) {
|
|
var users = GetCollection("system.users");
|
|
users.Remove(new BsonDocument("user", username));
|
|
}
|
|
|
|
public BsonDocument RenameCollection(
|
|
MongoCredentials adminCredentials,
|
|
string oldCollectionName,
|
|
string newCollectionName
|
|
) {
|
|
var command = new BsonDocument {
|
|
{ "renameCollection", string.Format("{0}.{1}", name, oldCollectionName) },
|
|
{ "to", string.Format("{0}.{1}", name, newCollectionName) }
|
|
};
|
|
return server.RunAdminCommand(adminCredentials, command);
|
|
}
|
|
|
|
public BsonDocument RenameCollection(
|
|
string oldCollectionName,
|
|
string newCollectionName
|
|
) {
|
|
return RenameCollection(server.AdminCredentials, oldCollectionName, newCollectionName);
|
|
}
|
|
|
|
public void RequestDone() {
|
|
var connectionPool = server.GetConnectionPool();
|
|
connectionPool.RequestDone();
|
|
}
|
|
|
|
// the result of RequestStart is IDisposable so you can use RequestStart in a using statment
|
|
// and then RequestDone will be called automatically when leaving the using statement
|
|
public IDisposable RequestStart() {
|
|
var connectionPool = server.GetConnectionPool();
|
|
connectionPool.RequestStart(this);
|
|
return new RequestStartResult(this);
|
|
}
|
|
|
|
// TODO: mongo shell has ResetError at the database level
|
|
|
|
public BsonDocument RunCommand<TCommand>(
|
|
TCommand command
|
|
) {
|
|
BsonDocument result = CommandCollection.FindOne(command);
|
|
if (!result.Contains("ok")) {
|
|
throw new MongoCommandException("ok element is missing");
|
|
}
|
|
if (!result["ok"].ToBoolean()) {
|
|
string errmsg = result["errmsg"].AsString;
|
|
string errorMessage = string.Format("Command failed: {0}", errmsg);
|
|
throw new MongoCommandException(errorMessage);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public BsonDocument RunCommand(
|
|
string commandName
|
|
) {
|
|
BsonDocument command = new BsonDocument(commandName, true);
|
|
return RunCommand(command);
|
|
}
|
|
|
|
public override string ToString() {
|
|
return name;
|
|
}
|
|
#endregion
|
|
|
|
#region internal methods
|
|
internal MongoConnection GetConnection() {
|
|
MongoConnectionPool connectionPool;
|
|
lock (databaseLock) {
|
|
connectionPool = server.GetConnectionPool();
|
|
}
|
|
return connectionPool.GetConnection(this);
|
|
}
|
|
|
|
internal void ReleaseConnection(
|
|
MongoConnection connection
|
|
) {
|
|
connection.ConnectionPool.ReleaseConnection(connection);
|
|
}
|
|
#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
|
|
|
|
#region private nested classes
|
|
private class RequestStartResult : IDisposable {
|
|
#region private fields
|
|
private MongoDatabase database;
|
|
#endregion
|
|
|
|
#region constructors
|
|
public RequestStartResult(
|
|
MongoDatabase database
|
|
) {
|
|
this.database = database;
|
|
}
|
|
#endregion
|
|
|
|
#region public methods
|
|
public void Dispose() {
|
|
database.RequestDone();
|
|
}
|
|
#endregion
|
|
}
|
|
#endregion
|
|
}
|
|
}
|