/// <summary>
		/// Determines whether a directory exists.
		/// </summary>
		/// <param name="transaction">A database transaction.</param>
		/// <param name="directory">The directory, for example "/my/directory".</param>
		/// <returns><c>true</c> if the directory exists, <c>false</c> otherwise.</returns>
		/// <remarks>The root directory always exists.</remarks>
		private bool DirectoryExists(DbTransaction transaction, string directory) {
			directory = PrepareDirectory(directory);

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectCountFrom("Directory");
			query = queryBuilder.Where(query, "FullPath", WhereOperator.Equals, "FullPath");

			List<Parameter> parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.String, "FullPath", directory));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int count = ExecuteScalar<int>(command, -1, false);

			return count == 1;
		}
		/// <summary>
		/// Retrieves the value of a Setting.
		/// </summary>
		/// <param name="name">The name of the Setting.</param>
		/// <returns>The value of the Setting, or null.</returns>
		/// <exception cref="ArgumentNullException">If <b>name</b> is <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <b>name</b> is empty.</exception>
		public string GetSetting(string name) {
			if(name == null) throw new ArgumentNullException("name");
			if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectFrom("Setting");
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");

			List<Parameter> parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.String, "Name", name));

			DbCommand command = builder.GetCommand(connString, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			if(reader != null) {
				string result = null;

				if(reader.Read()) {
					result = reader["Value"] as string;
				}

				CloseReader(command, reader);

				// HACK: this allows to correctly initialize a fully Oracle-based wiki instance without any user intervention
				if(string.IsNullOrEmpty(result)) {
					if(name == "DefaultUsersProvider") result = DefaultUsersStorageProvider;
					if(name == "DefaultPagesProvider") result = DefaultPagesStorageProvider;
					if(name == "DefaultFilesProvider") result = DefaultFilesStorageProvider;
				}

				return result;
			}
			else return null;
		}
		/// <summary>
		/// Removes all messages for a page and stores the new messages.
		/// </summary>
		/// <param name="page">The page.</param>
		/// <param name="messages">The new messages to store.</param>
		/// <returns><c>true</c> if the messages are stored, <c>false</c> otherwise.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="page"/> or <paramref name="messages"/> are <c>null</c>.</exception>
		public bool BulkStoreMessages(PageInfo page, Message[] messages) {
			if(page == null) throw new ArgumentNullException("page");
			if(messages == null) throw new ArgumentNullException("messages");

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			if(GetPage(transaction, page.FullName) == null) {
				RollbackTransaction(transaction);
				return false;
			}

			foreach(Message msg in GetMessages(transaction, page)) {
				UnindexMessageTree(page, msg, transaction);
			}

			string nspace, name;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("Message");
			query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			ExecuteNonQuery(command, false);

			List<Message> allMessages;
			List<int> parents;

			UnTreeMessages(messages, out allMessages, out parents, -1);

			string finalQuery = "";
			int count = 1;
			string countString;
			parameters = new List<Parameter>(MaxStatementsInBatch * 8);

			int rowsDone = 0;

			for(int i = 0; i < allMessages.Count; i++) {
				// Execute the batch in smaller chunks

				Message msg = allMessages[i];
				int parent = parents[i];

				countString = count.ToString();

				query = queryBuilder.InsertInto("Message", new string[] { "Page", "Namespace", "Id", "Parent", "Username", "Subject", "DateTime", "Body" },
					new string[] { "Page" + countString, "Namespace" + countString, "Id" + countString, "Parent" + countString, "Username" + countString, "Subject" + countString, "DateTime" + countString, "Body" + countString });

				parameters.Add(new Parameter(ParameterType.String, "Page" + countString, name));
				parameters.Add(new Parameter(ParameterType.String, "Namespace" + countString, nspace));
				parameters.Add(new Parameter(ParameterType.Int16, "Id" + countString, (short)msg.ID));
				if(parent != -1) parameters.Add(new Parameter(ParameterType.Int16, "Parent" + countString, parent));
				else parameters.Add(new Parameter(ParameterType.Int16, "Parent" + countString, DBNull.Value));
				parameters.Add(new Parameter(ParameterType.String, "Username" + countString, msg.Username));
				parameters.Add(new Parameter(ParameterType.String, "Subject" + countString, msg.Subject));
				parameters.Add(new Parameter(ParameterType.DateTime, "DateTime" + countString, msg.DateTime));
				parameters.Add(new Parameter(ParameterType.String, "Body" + countString, msg.Body));

				finalQuery = queryBuilder.AppendForBatch(finalQuery, query);

				count++;

				if(count == MaxStatementsInBatch) {
                    finalQuery = queryBuilder.BuildBatchCommand(finalQuery);
					command = builder.GetCommand(transaction, finalQuery, parameters);

                    rowsDone += ExecuteNonQuery(command, false, true, count); 

					finalQuery = "";
					count = 1;
					parameters.Clear();
				}
			}

			if(finalQuery.Length > 0) {
				command = builder.GetCommand(transaction, finalQuery, parameters);

                rowsDone += ExecuteNonQuery(command, false, true, count); 
			}

			if(rowsDone == allMessages.Count) {
				foreach(Message msg in messages) {
					IndexMessageTree(page, msg, transaction);
				}
				CommitTransaction(transaction);
				return true;
			}
			else {
				RollbackTransaction(transaction);
				return false;
			}
		}
		/// <summary>
		/// Sets the status of a plugin.
		/// </summary>
		/// <param name="typeName">The Type name of the plugin.</param>
		/// <param name="enabled">The plugin status.</param>
		/// <returns><c>true</c> if the status is stored, <c>false</c> otherwise.</returns>
		/// <exception cref="ArgumentNullException">If <b>typeName</b> is <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <b>typeName</b> is empty.</exception>
		public bool SetPluginStatus(string typeName, bool enabled) {
			if(typeName == null) throw new ArgumentNullException("typeName");
			if(typeName.Length == 0) throw new ArgumentException("Type Name cannot be empty", "typeName");

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			PreparePluginStatusRow(transaction, typeName);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("PluginStatus", new string[] { "Enabled" }, new string[] { "Enabled" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.Int32, "Enabled", enabled?1:0));
			parameters.Add(new Parameter(ParameterType.String, "Name", typeName));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows == 1) CommitTransaction(transaction);
			else RollbackTransaction(transaction);

			return rows == 1;
		}
		/// <summary>
		/// Removes a plugin's assembly.
		/// </summary>
		/// <param name="connection">A database connection.</param>
		/// <param name="filename">The file name of the assembly to remove, such as "Assembly.dll".</param>
		/// <returns><c>true</c> if the assembly is removed, <c>false</c> otherwise.</returns>
		private bool DeletePluginAssembly(DbConnection connection, string filename) {
			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("PluginAssembly");
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");

			List<Parameter> parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.String, "Name", filename));

			DbCommand command = builder.GetCommand(connection, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			return rows == 1;
		}
		/// <summary>
		/// Gets a meta-data item's content.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <param name="tag">The tag that specifies the context (usually the namespace).</param>
		/// <returns>The content.</returns>
		public string GetMetaDataItem(MetaDataItem item, string tag) {
			if(string.IsNullOrEmpty(tag)) tag = " ";

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectFrom("MetaDataItem", new string[] { "Data" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
			query = queryBuilder.AndWhere(query, "Tag", WhereOperator.Equals, "Tag");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name", item.ToString()));
			parameters.Add(new Parameter(ParameterType.String, "Tag", tag));

			DbCommand command = builder.GetCommand(connString, query, parameters);

			string value = ExecuteScalar<string>(command, " ");

			return value;
		}
		/// <summary>
		/// Deletes some ACL entries.
		/// </summary>
		/// <param name="entries">The entries to delete.</param>
		/// <returns><c>true</c> if one or more entries were deleted, <c>false</c> otherwise.</returns>
		private bool DeleteEntries(AclEntry[] entries) {
			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			foreach(AclEntry entry in entries) {
				string query = queryBuilder.DeleteFrom("AclEntry");
				query = queryBuilder.Where(query, "Resource", WhereOperator.Equals, "Resource");
				query = queryBuilder.AndWhere(query, "Action", WhereOperator.Equals, "Action");
				query = queryBuilder.AndWhere(query, "Subject", WhereOperator.Equals, "Subject");

				List<Parameter> parameters = new List<Parameter>(3);
				parameters.Add(new Parameter(ParameterType.String, "Resource", entry.Resource));
				parameters.Add(new Parameter(ParameterType.String, "Action", entry.Action));
				parameters.Add(new Parameter(ParameterType.String, "Subject", entry.Subject));

				DbCommand command = builder.GetCommand(transaction, query, parameters);

				if(ExecuteNonQuery(command, false) <= 0) {
					RollbackTransaction(transaction);
					return false;
				}
			}

			CommitTransaction(transaction);

			return true;
		}
		/// <summary>
		/// Updates all outgoing links data for a page rename.
		/// </summary>
		/// <param name="oldName">The old page name.</param>
		/// <param name="newName">The new page name.</param>
		/// <returns><c>true</c> if the data is updated, <c>false</c> otherwise.</returns>
		/// <exception cref="ArgumentNullException">If <b>oldName</b> or <b>newName</b> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <b>oldName</b> or <b>newName</b> are empty.</exception>
		public bool UpdateOutgoingLinksForRename(string oldName, string newName) {
			if(oldName == null) throw new ArgumentNullException("oldName");
			if(oldName.Length == 0) throw new ArgumentException("Old Name cannot be empty", "oldName");
			if(newName == null) throw new ArgumentNullException("newName");
			if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName");

			// 1. Rename sources
			// 2. Rename destinations

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("OutgoingLink", new string[] { "Source" }, new string[] { "NewSource" });
			query = queryBuilder.Where(query, "Source", WhereOperator.Equals, "OldSource");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "NewSource", newName));
			parameters.Add(new Parameter(ParameterType.String, "OldSource", oldName));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows == -1) {
				RollbackTransaction(transaction);
				return false;
			}

			bool somethingUpdated = rows > 0;

			query = queryBuilder.Update("OutgoingLink", new string[] { "Destination" }, new string[] { "NewDestination" });
			query = queryBuilder.Where(query, "Destination", WhereOperator.Equals, "OldDestination");

			parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "NewDestination", newName));
			parameters.Add(new Parameter(ParameterType.String, "OldDestination", oldName));

			command = builder.GetCommand(transaction, query, parameters);

			rows = ExecuteNonQuery(command, false);

			if(rows >= 0) CommitTransaction(transaction);
			else RollbackTransaction(transaction);

			return somethingUpdated || rows > 0;
		}
		/// <summary>
		/// Tries to load all data related to a word from the database.
		/// </summary>
		/// <param name="text">The word text.</param>
		/// <param name="word">The returned word.</param>
		/// <param name="connection">An open database connection.</param>
		/// <returns><c>true</c> if the word is found, <c>false</c> otherwise.</returns>
		private bool TryFindWord(string text, out Word word, DbConnection connection) {
			// 1. Find word - if not found, return
			// 2. Read all raw word mappings
			// 3. Read all documents (unique)
			// 4. Build result data structure

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectFrom("IndexWord", new string[] { "Id" });
			query = queryBuilder.Where(query, "Text", WhereOperator.Equals, "Text");

			List<Parameter> parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.String, "Text", text));

			DbCommand command = builder.GetCommand(connection, query, parameters);

			int wordId = ExecuteScalar<int>(command, -1, false);

			if(wordId == -1) {
				word = null;
				return false;
			}

			// Read all raw mappings
			query = queryBuilder.SelectFrom("IndexWordMapping");
			query = queryBuilder.Where(query, "Word", WhereOperator.Equals, "WordId");

			parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.Int32, "WordId", wordId));

			command = builder.GetCommand(connection, query, parameters);

			DbDataReader reader = ExecuteReader(command, false);

			List<DumpedWordMapping> mappings = new List<DumpedWordMapping>(2048);
            while (reader != null && reader.Read())
            {
                mappings.Add(new DumpedWordMapping((uint)wordId,
                    (uint)(int)Convert.ChangeType(reader["Document"], typeof(int)),
                    (ushort)(short)Convert.ChangeType(reader["FirstCharIndex"], typeof(short)),
                    (ushort)(short)Convert.ChangeType(reader["WordIndex"], typeof(short)),
                    (byte)Convert.ChangeType(reader["Location"], typeof(byte))));
            }
			CloseReader(reader);

			if(mappings.Count == 0) {
				word = null;
				return false;
			}

			// Find all documents
			query = queryBuilder.SelectFrom("IndexDocument");
			query = queryBuilder.Where(query, "Id", WhereOperator.Equals, "DocId");

			parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.Int32, "DocId", 0));

			Dictionary<uint, IDocument> documents = new Dictionary<uint, IDocument>(64);
			foreach(DumpedWordMapping map in mappings) {
				uint docId = map.DocumentID;
				if(documents.ContainsKey(docId)) continue;

				parameters[0].Value = (int)docId;
				command = builder.GetCommand(connection, query, parameters);

				reader = ExecuteReader(command, false);

				if(reader != null && reader.Read()) {
					DumpedDocument dumpedDoc = new DumpedDocument(docId,
						reader["Name"] as string, reader["Title"] as string,
						reader["TypeTag"] as string,
						(DateTime)reader["DateTime"]);

					IDocument document = BuildDocument(dumpedDoc);

					if(document != null) documents.Add(docId, document);
				}
				CloseReader(reader);
			}

			OccurrenceDictionary occurrences = new OccurrenceDictionary(mappings.Count);
			foreach(DumpedWordMapping map in mappings) {
				if(!occurrences.ContainsKey(documents[map.DocumentID])) {
					occurrences.Add(documents[map.DocumentID], new SortedBasicWordInfoSet(2));
				}

				occurrences[documents[map.DocumentID]].Add(new BasicWordInfo(
					map.FirstCharIndex, map.WordIndex, WordLocation.GetInstance(map.Location)));
			}

			word = new Word((uint)wordId, text, occurrences);
			return true;
		}
		/// <summary>
		/// Removes a content template.
		/// </summary>
		/// <param name="transaction">A database transaction.</param>
		/// <param name="name">The name of the template to remove.</param>
		/// <returns><c>true</c> if the template is removed, <c>false</c> otherwise.</returns>
		private bool RemoveContentTemplate(DbTransaction transaction, string name) {
			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("ContentTemplate");
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");

			List<Parameter> parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.String, "Name", name));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			return rows == 1;
		}
		/// <summary>
		/// Removes a Navigation Path.
		/// </summary>
		/// <param name="connection">A database connection.</param>
		/// <param name="path">The navigation path to remove.</param>
		/// <returns><c>true</c> if the path is removed, <c>false</c> otherwise.</returns>
		private bool RemoveNavigationPath(DbConnection connection, NavigationPath path) {
			string nspace, name;
			NameTools.ExpandFullName(path.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("NavigationPath");
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));

			DbCommand command = builder.GetCommand(connection, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			return rows > 0;
		}
		/// <summary>
		/// Gets all the Navigation Paths in a Namespace.
		/// </summary>
		/// <param name="nspace">The Namespace.</param>
		/// <returns>All the Navigation Paths, sorted by name.</returns>
		public NavigationPath[] GetNavigationPaths(NamespaceInfo nspace) {
			string nspaceName = nspace != null&&!string.IsNullOrEmpty(nspace.Name) ? nspace.Name : " ";

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectFrom("NavigationPath", new string[] { "Name", "Namespace", "Page" });
			query = queryBuilder.Where(query, "Namespace", WhereOperator.Equals, "Namespace");
			query = queryBuilder.OrderBy(query, new string[] { "Namespace", "Name", "Number" }, new Ordering[] { Ordering.Asc, Ordering.Asc, Ordering.Asc });

			List<Parameter> parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspaceName));

			DbCommand command = builder.GetCommand(connString, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			if(reader != null) {
				List<NavigationPath> result = new List<NavigationPath>(10);

				string prevName = "|||";
				string name;
				string actualNamespace = " ";
				List<string> pages = new List<string>(10);

				while(reader.Read()) {
					name = reader["Name"] as string;

					if(name != prevName) {
						actualNamespace = (reader["Namespace"] as string).Trim();
                        actualNamespace = actualNamespace.Trim();

						if(prevName != "|||") {
							result[result.Count - 1].Pages = pages.ToArray();
							pages.Clear();
						}

						result.Add(new NavigationPath(NameTools.GetFullName(actualNamespace, name), this));
					}

					prevName = name;
					pages.Add(NameTools.GetFullName(actualNamespace, reader["Page"] as string));
				}

				if(result.Count > 0) {
					result[result.Count - 1].Pages = pages.ToArray();
				}

				CloseReader(command, reader);

				return result.ToArray();
			}
			else return null;
		}
		/// <summary>
		/// Modifies a Message.
		/// </summary>
		/// <param name="page">The Page.</param>
		/// <param name="id">The ID of the Message to modify.</param>
		/// <param name="username">The Username.</param>
		/// <param name="subject">The Subject.</param>
		/// <param name="dateTime">The Date/Time.</param>
		/// <param name="body">The Body.</param>
		/// <returns>True if the Message is modified successfully.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="page"/>, <paramref name="username"/>, <paramref name="subject"/> or <paramref name="body"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentOutOfRangeException">If <paramref name="id"/> is less than zero.</exception>
		/// <exception cref="ArgumentException">If <paramref name="username"/> or <paramref name="subject"/> are empty.</exception>
		public bool ModifyMessage(PageInfo page, int id, string username, string subject, DateTime dateTime, string body) {
			if(page == null) throw new ArgumentNullException("page");
			if(id < 0) throw new ArgumentOutOfRangeException("id", "Invalid Message ID");
			if(username == null) throw new ArgumentNullException("username");
			if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username");
			if(subject == null) throw new ArgumentNullException("subject");
			if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject");
			if(body == null) throw new ArgumentNullException("body"); // body can be empty

			string nspace, name;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			Message[] messages = GetMessages(transaction, page);
			if(messages == null) {
				RollbackTransaction(transaction);
				return false;
			}
			Message oldMessage = FindMessage(messages, id);

			if(oldMessage == null) {
				RollbackTransaction(transaction);
				return false;
			}

			UnindexMessage(page, oldMessage.ID, oldMessage.Subject, oldMessage.DateTime, oldMessage.Body, transaction);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("Message", new string[] { "Username", "Subject", "DateTime", "Body" }, new string[] { "Username", "Subject", "DateTime", "Body" });
			query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
			query = queryBuilder.AndWhere(query, "Id", WhereOperator.Equals, "Id");

			List<Parameter> parameters = new List<Parameter>(7);
			parameters.Add(new Parameter(ParameterType.String, "Username", username));
			parameters.Add(new Parameter(ParameterType.String, "Subject", subject));
			parameters.Add(new Parameter(ParameterType.DateTime, "DateTime", dateTime));
			parameters.Add(new Parameter(ParameterType.String, "Body", body));
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
			parameters.Add(new Parameter(ParameterType.Int16, "Id", id));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows == 1) {
				IndexMessage(page, id, subject, dateTime, body, transaction);
				CommitTransaction(transaction);
				return true;
			}
			else {
				RollbackTransaction(transaction);
				return false;
			}
		}
		/// <summary>
		/// Removes a Message.
		/// </summary>
		/// <param name="transaction">A database transaction.</param>
		/// <param name="page">The Page.</param>
		/// <param name="id">The ID of the Message to remove.</param>
		/// <param name="removeReplies">A value specifying whether or not to remove the replies.</param>
		/// <returns>True if the Message is removed successfully.</returns>
		private bool RemoveMessage(DbTransaction transaction, PageInfo page, int id, bool removeReplies) {
			string nspace, name;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			Message[] messages = GetMessages(transaction, page);
			if(messages == null) return false;
			Message message = FindMessage(messages, id);
			if(message == null) return false;
			Message parent = FindAnchestor(messages, id);
			int parentId = parent != null ? parent.ID : -1;

			UnindexMessage(page, message.ID, message.Subject, message.DateTime, message.Body, transaction);

			if(removeReplies) {
				// Recursively remove all replies BEFORE removing parent (depth-first)
				foreach(Message reply in message.Replies) {
					if(!RemoveMessage(transaction, page, reply.ID, true)) return false;
				}
			}

			// Remove this message
			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("Message");
			query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
			query = queryBuilder.AndWhere(query, "Id", WhereOperator.Equals, "Id");

			List<Parameter> parameters = new List<Parameter>(3);
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
			parameters.Add(new Parameter(ParameterType.Int16, "Id", (short)id));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(!removeReplies && rows == 1) {
				// Update replies' parent id

				query = queryBuilder.Update("Message", new string[] { "Parent" }, new string[] { "NewParent" });
				query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
				query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
				query = queryBuilder.AndWhere(query, "Parent", WhereOperator.Equals, "OldParent");

				parameters = new List<Parameter>(4);
				if(parentId != -1) parameters.Add(new Parameter(ParameterType.Int16, "NewParent", parentId));
				else parameters.Add(new Parameter(ParameterType.Int16, "NewParent", DBNull.Value));
				parameters.Add(new Parameter(ParameterType.String, "Page", name));
				parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
				parameters.Add(new Parameter(ParameterType.Int16, "OldParent", (short)id));

				command = builder.GetCommand(transaction, query, parameters);

				rows = ExecuteNonQuery(command, false);
			}

			return rows > 0;
		}
		/// <summary>
		/// Adds a new Message to a Page.
		/// </summary>
		/// <param name="page">The Page.</param>
		/// <param name="username">The Username.</param>
		/// <param name="subject">The Subject.</param>
		/// <param name="dateTime">The Date/Time.</param>
		/// <param name="body">The Body.</param>
		/// <param name="parent">The Parent Message ID, or -1.</param>
		/// <returns>True if the Message is added successfully.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="page"/>, <paramref name="username"/>, <paramref name="subject"/> or <paramref name="body"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <paramref name="username"/> or <paramref name="subject"/> are empty.</exception>
		/// <exception cref="ArgumentOutOfRangeException">If <paramref name="parent"/> is less than -1.</exception>
		public bool AddMessage(PageInfo page, string username, string subject, DateTime dateTime, string body, int parent) {
			if(page == null) throw new ArgumentNullException("page");
			if(username == null) throw new ArgumentNullException("username");
			if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username");
			if(subject == null) throw new ArgumentNullException("subject");
			if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject");
			if(body == null) throw new ArgumentNullException("body"); // body can be empty
			if(parent < -1) throw new ArgumentOutOfRangeException("parent", "Invalid Parent Message ID");

			string nspace, name;
			NameTools.ExpandFullName(page.FullName, out nspace, out name);
			if( string.IsNullOrEmpty(nspace)||string.IsNullOrEmpty(nspace.Trim())) nspace = " ";

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			if(parent != -1 && FindMessage(GetMessages(transaction, page), parent) == null) {
				RollbackTransaction(transaction);
				return false;
			}

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			short freeId = -1;

			string query = queryBuilder.SelectFrom("Message", new string[] { "Id" });
			query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page");
			query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace");
			query = queryBuilder.OrderBy(query, new string[] { "Id" }, new Ordering[] { Ordering.Desc });

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			freeId = ExecuteScalar<short>(command, -1, false);

			if(freeId == -1) freeId = 0;
			else freeId++;

			query = queryBuilder.InsertInto("Message", new string[] { "Page", "Namespace", "Id", "Parent", "Username", "Subject", "DateTime", "Body" },
				new string[] { "Page", "Namespace", "Id", "Parent", "Username", "Subject", "DateTime", "Body" });

			parameters = new List<Parameter>(8);
			parameters.Add(new Parameter(ParameterType.String, "Page", name));
			parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace));
			parameters.Add(new Parameter(ParameterType.Int16, "Id", freeId));
			if(parent != -1) parameters.Add(new Parameter(ParameterType.Int16, "Parent", parent));
			else parameters.Add(new Parameter(ParameterType.Int16, "Parent", DBNull.Value));
			parameters.Add(new Parameter(ParameterType.String, "Username", username));
			parameters.Add(new Parameter(ParameterType.String, "Subject", subject));
			parameters.Add(new Parameter(ParameterType.DateTime, "DateTime", dateTime));
			parameters.Add(new Parameter(ParameterType.String, "Body", body));

			command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows == 1) {
				IndexMessage(page, freeId, subject, dateTime, body, transaction);

				CommitTransaction(transaction);
				return true;
			}
			else {
				RollbackTransaction(transaction);
				return false;
			}
		}
		/// <summary>
		/// Gets the outgoing links of a page.
		/// </summary>
		/// <param name="page">The full name of the page.</param>
		/// <returns>The outgoing links.</returns>
		/// <exception cref="ArgumentNullException">If <b>page</b> is <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <b>page</b> is empty.</exception>
		public string[] GetOutgoingLinks(string page) {
			if(page == null) throw new ArgumentNullException("page");
			if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page");

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectFrom("OutgoingLink", new string[] { "Destination" });
			query = queryBuilder.Where(query, "Source", WhereOperator.Equals, "Source");
			query = queryBuilder.OrderBy(query, new[] { "Destination" }, new[] { Ordering.Asc });

			List<Parameter> parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.String, "Source", page));

			DbCommand command = builder.GetCommand(connString, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			if(reader != null) {
				List<string> result = new List<string>(20);

				while(reader.Read()) {
					result.Add(reader["Destination"] as string);
				}

				CloseReader(command, reader);

				return result.ToArray();
			}
			else return null;
		}
		/// <summary>
		/// Deletes the outgoing links of a page and all the target links that include the page.
		/// </summary>
		/// <param name="page">The full name of the page.</param>
		/// <returns><c>true</c> if the links are deleted, <c>false</c> otherwise.</returns>
		/// <exception cref="ArgumentNullException">If <b>page</b> is <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <b>page</b> is empty.</exception>
		public bool DeleteOutgoingLinks(string page) {
			if(page == null) throw new ArgumentNullException("page");
			if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page");

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("OutgoingLink");
			query = queryBuilder.Where(query, "Source", WhereOperator.Equals, "Source");
			query = queryBuilder.OrWhere(query, "Destination", WhereOperator.Equals, "Destination");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Source", page));
			parameters.Add(new Parameter(ParameterType.String, "Destination", page));

			DbCommand command = builder.GetCommand(connString, query, parameters);

			int rows = ExecuteNonQuery(command);

			return rows > 0;
		}
		/// <summary>
		/// Gets a namespace.
		/// </summary>
		/// <param name="connection">A database connection.</param>
		/// <param name="name">The name of the namespace (cannot be <c>null</c> or empty).</param>
		/// <returns>The <see cref="T:NamespaceInfo" />, or <c>null</c> if no namespace is found.</returns>
		private NamespaceInfo GetNamespace(DbConnection connection, string name) {
			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			// select ... from Namespace left join Page on Namespace.DefaultPage = Page.Name where Namespace.Name = <name> and (Namespace.DefaultPage is null or Page.Namespace = <name>)
			string query = queryBuilder.SelectFrom("Namespace", "Page", "DefaultPage", "Name", Join.LeftJoin, new string[] { "Name", "DefaultPage" }, new string[] { "CreationDateTime" });
			query = queryBuilder.Where(query, "Namespace", "Name", WhereOperator.Equals, "Name1");
			query = queryBuilder.AndWhere(query, "Namespace", "DefaultPage", WhereOperator.IsNull, null, true, false);
			query = queryBuilder.OrWhere(query, "Page", "Namespace", WhereOperator.Equals, "Name2", false, true);

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name1", name));
			parameters.Add(new Parameter(ParameterType.String, "Name2", name));

			DbCommand command = builder.GetCommand(connection, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			if(reader != null) {
				NamespaceInfo result = null;

				if(reader.Read()) {
					string realName = reader["Namespace_Name"] as string;
					string page = GetNullableColumn<string>(reader, "Namespace_DefaultPage", null);
					PageInfo defaultPage = string.IsNullOrEmpty(page) ? null :
						new PageInfo(NameTools.GetFullName(realName, page), this, (DateTime)reader["Page_CreationDateTime"]);

					result = new NamespaceInfo(realName, this, defaultPage);
				}

				CloseReader(reader);

				return result;
			}
			else return null;
		}
		/// <summary>
		/// Retrieves all ACL entries for a subject.
		/// </summary>
		/// <param name="subject">The subject.</param>
		/// <returns>The ACL entries for the subject.</returns>
		private AclEntry[] RetrieveAclEntriesForSubject(string subject) {
			ICommandBuilder builder = GetCommandBuilder();

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			// Sort order is not relevant
			string query = queryBuilder.SelectFrom("AclEntry");
			query = queryBuilder.Where(query, "Subject", WhereOperator.Equals, "Subject");

			List<Parameter> parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.String, "Subject", subject));

			DbCommand command = builder.GetCommand(connString, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			if(reader != null) {
				List<AclEntry> result = new List<AclEntry>(50);

				while(reader.Read()) {
					result.Add(new AclEntry(reader["Resource"] as string, reader["Action"] as string, reader["Subject"] as string,
						AclEntryValueFromChar(((string)reader["Value"])[0])));
				}

				CloseReader(command, reader);

				return result.ToArray();
			}
			else return null;
		}
		/// <summary>
		/// Gets all the sub-namespaces.
		/// </summary>
		/// <returns>The sub-namespaces, sorted by name.</returns>
		public NamespaceInfo[] GetNamespaces() {
			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			// select ... from Namespace left join Page on Namespace.DefaultPage = Page.Name where Namespace.Name <> '' and (Namespace.DefaultPage is null or Page.Namespace <> '')
			string query = queryBuilder.SelectFrom("Namespace", "Page", "DefaultPage", "Name", Join.LeftJoin, new string[] { "Name", "DefaultPage" }, new string[] { "CreationDateTime" });
			query = queryBuilder.Where(query, "Namespace", "Name", WhereOperator.NotEquals, "Empty1");
			query = queryBuilder.AndWhere(query, "Namespace", "DefaultPage", WhereOperator.IsNull, null, true, false);
			query = queryBuilder.OrWhere(query, "Page", "Namespace", WhereOperator.NotEquals, "Empty2", false, true);
			query = queryBuilder.OrderBy(query, new[] { "Namespace_Name" }, new[] { Ordering.Asc });

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Empty1", " "));
			parameters.Add(new Parameter(ParameterType.String, "Empty2", " "));

			DbCommand command = builder.GetCommand(connString, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			if(reader != null) {
				List<NamespaceInfo> result = new List<NamespaceInfo>(10);

				while(reader.Read()) {
					string realName = reader["Namespace_Name"] as string;
					string page = GetNullableColumn<string>(reader, "Namespace_DefaultPage", null);
					PageInfo defaultPage = string.IsNullOrEmpty(page) ? null :
						new PageInfo(NameTools.GetFullName(realName, page), this, (DateTime)reader["Page_CreationDateTime"]);

					// The query returns duplicate entries if the main page of two or more namespaces have the same name
					if(result.Find(n => { return n.Name.Equals(realName); }) == null) {
						result.Add(new NamespaceInfo(realName, this, defaultPage));
					}
				}

				CloseReader(command, reader);

				return result.ToArray();
			}
			else return null;
		}
		/// <summary>
		/// Renames a ACL resource.
		/// </summary>
		/// <param name="resource">The resource to rename.</param>
		/// <param name="newName">The new name of the resource.</param>
		/// <returns><c>true</c> if one or more entries weere updated, <c>false</c> otherwise.</returns>
		private bool RenameAclResource(string resource, string newName) {
			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("AclEntry", new[] { "Resource" }, new[] { "ResourceNew" });
			query = queryBuilder.Where(query, "Resource", WhereOperator.Equals, "ResourceOld");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "ResourceNew", newName));
			parameters.Add(new Parameter(ParameterType.String, "ResourceOld", resource));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			if(ExecuteNonQuery(command, false) <= 0) {
				RollbackTransaction(transaction);
				return false;
			}

			CommitTransaction(transaction);

			return true;
		}
		/// <summary>
		/// Renames a namespace.
		/// </summary>
		/// <param name="nspace">The namespace to rename.</param>
		/// <param name="newName">The new name of the namespace.</param>
		/// <returns>The correct <see cref="T:NamespaceInfo"/> object.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="nspace"/> or <paramref name="newName"/> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <paramref name="newName"/> is empty.</exception>
		public NamespaceInfo RenameNamespace(NamespaceInfo nspace, string newName) {
			if(nspace==null) throw new ArgumentNullException("nspace");
			if(newName == null) throw new ArgumentNullException("newName");
			if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName");

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			if(GetNamespace(transaction, nspace.Name) == null) {
				RollbackTransaction(transaction);
				return null;
			}

			foreach(PageInfo page in GetPages(transaction, nspace)) {
				PageContent content = GetContent(transaction, page, CurrentRevision);
				if(content != null) {
					UnindexPage(content, transaction);
				}
				Message[] messages = GetMessages(transaction, page);
				if(messages != null) {
					foreach(Message msg in messages) {
						UnindexMessageTree(page, msg, transaction);
					}
				}
			}

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("Namespace", new string[] { "Name" }, new string[] { "NewName" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "OldName");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "NewName", newName));
			parameters.Add(new Parameter(ParameterType.String, "OldName", nspace.Name));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows > 0) {
				NamespaceInfo result = GetNamespace(transaction, newName);

				foreach(PageInfo page in GetPages(transaction, result)) {
					PageContent content = GetContent(transaction, page, CurrentRevision);
					if(content != null) {
						IndexPage(content, transaction);
					}
					Message[] messages = GetMessages(transaction, page);
					if(messages != null) {
						foreach(Message msg in messages) {
							IndexMessageTree(page, msg, transaction);
						}
					}
				}

				CommitTransaction(transaction);

				return result;
			}
			else {
				RollbackTransaction(transaction);
				return null;
			}
		}
		/// <summary>
		/// Sets a meta-data items' content.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <param name="tag">The tag that specifies the context (usually the namespace).</param>
		/// <param name="content">The content.</param>
		/// <returns><c>true</c> if the content is set, <c>false</c> otherwise.</returns>
		public bool SetMetaDataItem(MetaDataItem item, string tag, string content) {
			if(string.IsNullOrEmpty(tag)) tag = " ";
			if(string.IsNullOrEmpty(content)) content = " ";

			// 1. Delete old value, if any
			// 2. Store new value

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("MetaDataItem");
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
			query = queryBuilder.AndWhere(query, "Tag", WhereOperator.Equals, "Tag");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name", item.ToString()));
			parameters.Add(new Parameter(ParameterType.String, "Tag", tag));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows == -1) {
				RollbackTransaction(transaction);
				return false;
			}

			query = queryBuilder.InsertInto("MetaDataItem", new string[] { "Name", "Tag", "Data" }, new string[] { "Name", "Tag", "Content" });

			parameters = new List<Parameter>(3);
			parameters.Add(new Parameter(ParameterType.String, "Name", item.ToString()));
			parameters.Add(new Parameter(ParameterType.String, "Tag", tag));
			parameters.Add(new Parameter(ParameterType.String, "Content", content));

			command = builder.GetCommand(transaction, query, parameters);

			rows = ExecuteNonQuery(command, false);

			if(rows == 1) CommitTransaction(transaction);
			else RollbackTransaction(transaction);

			return rows == 1;
		}
		/// <summary>
		/// Sets the default page of a namespace.
		/// </summary>
		/// <param name="nspace">The namespace of which to set the default page.</param>
		/// <param name="page">The page to use as default page, or <c>null</c>.</param>
		/// <returns>The correct <see cref="T:NamespaceInfo"/> object.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="nspace"/> is <c>null</c>.</exception>
		public NamespaceInfo SetNamespaceDefaultPage(NamespaceInfo nspace, PageInfo page) {
			if( nspace==null) throw new ArgumentNullException("nspace");

			// Namespace existence is verified by the affected rows (should be 1)

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			if(page != null && GetPage(transaction, page.FullName) == null) {
				RollbackTransaction(transaction);
				return null;
			}

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.Update("Namespace", new string[] { "DefaultPage" }, new string[] { "DefaultPage" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");

			List<Parameter> parameters = new List<Parameter>(2);
			if(page == null) parameters.Add(new Parameter(ParameterType.String, "DefaultPage", DBNull.Value));
			else parameters.Add(new Parameter(ParameterType.String, "DefaultPage", NameTools.GetLocalName(page.FullName)));
			parameters.Add(new Parameter(ParameterType.String, "Name", nspace.Name));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows == 1) {
				CommitTransaction(transaction);
				return new NamespaceInfo(nspace.Name, this, page);
			}
			else {
				RollbackTransaction(transaction);
				return null;
			}
		}
		/// <summary>
		/// Retrieves a plugin's assembly.
		/// </summary>
		/// <param name="filename">The file name of the assembly.</param>
		/// <returns>The assembly content, or <c>null</c>.</returns>
		/// <exception cref="ArgumentNullException">If <b>filename</b> is <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <b>filename</b> is empty.</exception>
		public byte[] RetrievePluginAssembly(string filename) {
			if(filename == null) throw new ArgumentNullException("filename");
			if(filename.Length == 0) throw new ArgumentException("Filename cannot be empty", "filename");

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectFrom("PluginAssembly", new string[] { "Assembly" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");

			List<Parameter> parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.String, "Name", filename));

			DbCommand command = builder.GetCommand(connString, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			if(reader != null) {
				byte[] result = null;

				if(reader.Read()) {
					result = GetBinaryColumn(reader, "Assembly", MaxAssemblySize);
				}

				CloseReader(command, reader);

				return result;
			}
			else return null;
		}
		/// <summary>
		/// Gets the configuration of a plugin.
		/// </summary>
		/// <param name="typeName">The Type name of the plugin.</param>
		/// <returns>The plugin configuration, or <b>String.Empty</b>.</returns>
		/// <exception cref="ArgumentNullException">If <b>typeName</b> is <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <b>typeName</b> is empty.</exception>
		public string GetPluginConfiguration(string typeName) {
			if(typeName == null) throw new ArgumentNullException("typeName");
			if(typeName.Length == 0) throw new ArgumentException("Type Name cannot be empty", "typeName");

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectFrom("PluginStatus", new string[] { "Configuration" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");

			List<Parameter> parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.String, "Name", typeName));

			DbCommand command = builder.GetCommand(connString, query, parameters);

			string result = ExecuteScalar<string>(command, "");

            return result.Trim() ;
		}
		/// <summary>
		/// Prepares the plugin status row, if necessary.
		/// </summary>
		/// <param name="transaction">A database transaction.</param>
		/// <param name="typeName">The Type name of the plugin.</param>
		private void PreparePluginStatusRow(DbTransaction transaction, string typeName) {
			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectCountFrom("PluginStatus");
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");

			List<Parameter> parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.String, "Name", typeName));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteScalar<int>(command, -1, false);

			if(rows == -1) return;

			if(rows == 0) {
				// Insert a neutral row (enabled, empty config)

				query = queryBuilder.InsertInto("PluginStatus", new string[] { "Name", "Enabled", "Configuration" }, new string[] { "Name", "Enabled", "Configuration" });

				parameters = new List<Parameter>(3);
				parameters.Add(new Parameter(ParameterType.String, "Name", typeName));
				parameters.Add(new Parameter(ParameterType.Int32, "Enabled", 1));
				parameters.Add(new Parameter(ParameterType.String, "Configuration", " "));

				command = builder.GetCommand(transaction, query, parameters);

				ExecuteNonQuery(command, false);
			}
		}
		/// <summary>
		/// Stores the value of a Setting.
		/// </summary>
		/// <param name="name">The name of the Setting.</param>
		/// <param name="value">The value of the Setting. Value cannot contain CR and LF characters, which will be removed.</param>
		/// <returns>True if the Setting is stored, false otherwise.</returns>
		/// <remarks>This method stores the Value immediately.</remarks>
		public bool SetSetting(string name, string value) {
			if(name == null) throw new ArgumentNullException("name");
			if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");

			// 1. Delete old value, if any
			// 2. Store new value

            // Nulls and empty strings are converted to " "
			if(string.IsNullOrEmpty(value)) value = " ";

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("Setting");
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");

			List<Parameter> parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.String, "Name", name));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			int rows = ExecuteNonQuery(command, false);

			if(rows == -1) {
				RollbackTransaction(transaction);
				return false; // Deletion command failed (0-1 are OK)
			}

			query = queryBuilder.InsertInto("Setting",
				new string[] { "Name", "Value" }, new string[] { "Name", "Value" });
			parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name", name));
			parameters.Add(new Parameter(ParameterType.String, "Value", value));

			command = builder.GetCommand(transaction, query, parameters);

			rows = ExecuteNonQuery(command, false);

			if(rows == 1) CommitTransaction(transaction);
			else RollbackTransaction(transaction);

			return rows == 1;
		}
		/// <summary>
		/// Gets the status of a plugin.
		/// </summary>
		/// <param name="typeName">The Type name of the plugin.</param>
		/// <returns>The status (<c>false</c> for disabled, <c>true</c> for enabled), or <c>true</c> if no status is found.</returns>
		/// <exception cref="ArgumentNullException">If <b>typeName</b> is <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <b>typeName</b> is empty.</exception>
		public bool GetPluginStatus(string typeName) {
			if(typeName == null) throw new ArgumentNullException("typeName");
			if(typeName.Length == 0) throw new ArgumentException("Type Name cannot be empty", "typeName");

			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectFrom("PluginStatus", new string[] { "Enabled" });
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");

			List<Parameter> parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.String, "Name", typeName));

			DbCommand command = builder.GetCommand(connString, query, parameters);

			DbDataReader reader = ExecuteReader(command);

			bool? enabled = null;

			if(reader != null && reader.Read()) {
				if(!IsDBNull(reader, "Enabled")) enabled = Convert.ToBoolean(reader["Enabled"]);
			}
			CloseReader(command, reader);

			if(enabled.HasValue) return enabled.Value;
			else {
				if(typeName == "ScrewTurn.Wiki.UsersStorageProvider" ||
					typeName == "ScrewTurn.Wiki.PagesStorageProvider" ||
					typeName == "ScrewTurn.Wiki.FilesStorageProvider") return false;
				else return true;
			}
		}
		/// <summary>
		/// Stores the outgoing links of a page, overwriting existing data.
		/// </summary>
		/// <param name="page">The full name of the page.</param>
		/// <param name="outgoingLinks">The full names of the pages that <b>page</b> links to.</param>
		/// <returns><c>true</c> if the outgoing links are stored, <c>false</c> otherwise.</returns>
		/// <exception cref="ArgumentNullException">If <b>page</b> or <b>outgoingLinks</b> are <c>null</c>.</exception>
		/// <exception cref="ArgumentException">If <b>page</b> or <b>outgoingLinks</b> are empty.</exception>
		public bool StoreOutgoingLinks(string page, string[] outgoingLinks) {
			if(page == null) throw new ArgumentNullException("page");
			if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page");
			if(outgoingLinks == null) throw new ArgumentNullException("outgoingLinks");

			foreach(string link in outgoingLinks) {
				if(link == null) throw new ArgumentNullException("outgoingLinks");
				if(link.Length == 0) throw new ArgumentException("Link cannot be empty", "outgoingLinks");
			}

			// 1. Delete old values, if any
			// 2. Store new values

			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.DeleteFrom("OutgoingLink");
			query = queryBuilder.Where(query, "Source", WhereOperator.Equals, "Source");

			List<Parameter> parameters = new List<Parameter>(1);
			parameters.Add(new Parameter(ParameterType.String, "Source", page));

			DbCommand command = builder.GetCommand(transaction, query, parameters);

			if(ExecuteNonQuery(command, false) < 0) {
				RollbackTransaction(transaction);
				return false;
			}

			foreach(string link in outgoingLinks) {
				query = queryBuilder.InsertInto("OutgoingLink", new string[] { "Source", "Destination" }, new string[] { "Source", "Destination" });

				parameters = new List<Parameter>(2);
				parameters.Add(new Parameter(ParameterType.String, "Source", page));
				parameters.Add(new Parameter(ParameterType.String, "Destination", link));

				command = builder.GetCommand(transaction, query, parameters);

				int rows = ExecuteNonQuery(command, false);

				if(rows != 1) {
					RollbackTransaction(transaction);
					return false;
				}
			}

			CommitTransaction(transaction);
			return true;
		}