/// <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>
		/// 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>
		/// Cuts the recent changes if necessary.
		/// </summary>
		private void CutRecentChangesIfNecessary() {
			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);
			DbTransaction transaction = BeginTransaction(connection);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectCountFrom("RecentChange");

			DbCommand command = builder.GetCommand(transaction, query, new List<Parameter>());

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

			int maxChanges = int.Parse(host.GetSettingValue(SettingName.MaxRecentChanges));

			if(rows > maxChanges) {
				// Remove 10% of old changes to avoid 1-by-1 deletion every time a change is made
				int entriesToDelete = maxChanges / 10;
				if(entriesToDelete > rows) entriesToDelete = rows;
				//entriesToDelete += entriesToDelete / 10;

				// This code is not optimized, but it surely works in most DBMS
				query = queryBuilder.SelectFrom("RecentChange", new string[] { "Id" });
				query = queryBuilder.OrderBy(query, new string[] { "Id" }, new Ordering[] { Ordering.Asc });

				command = builder.GetCommand(transaction, query, new List<Parameter>());

				DbDataReader reader = ExecuteReader(command);

				List<int> ids = new List<int>(entriesToDelete);

				if(reader != null) {
					while(reader.Read() && ids.Count < entriesToDelete) {
						ids.Add((int)Convert.ChangeType(reader["Id"],typeof(int)));
					}

					CloseReader(reader);
				}

				if(ids.Count > 0) {
					// Given that the IDs to delete can be many, the query is split in many chunks, each one deleting 50 items
					// This works-around the problem of too many parameters in a RPC call of Oracle
					// See also CutLog

					for(int chunk = 0; chunk <= ids.Count / MaxParametersInQuery; chunk++) {
						query = queryBuilder.DeleteFrom("RecentChange");
						List<string> parms = new List<string>(MaxParametersInQuery);
						List<Parameter> parameters = new List<Parameter>(MaxParametersInQuery);

						for(int i = chunk * MaxParametersInQuery; i < Math.Min(ids.Count, (chunk + 1) * MaxParametersInQuery); i++) {
							parms.Add("P" + i.ToString());
							parameters.Add(new Parameter(ParameterType.Int32, parms[parms.Count - 1], ids[i]));
						}

						query = queryBuilder.WhereIn(query, "Id", parms.ToArray());

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

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

			CommitTransaction(transaction);
		}
		/// <summary>
		/// Gets the total number of Messages in a Page Discussion.
		/// </summary>
		/// <param name="page">The Page.</param>
		/// <returns>The number of messages.</returns>
		/// <exception cref="ArgumentNullException">If <paramref name="page"/> is <c>null</c>.</exception>
		public int GetMessageCount(PageInfo page) {
			if(page == null) throw new ArgumentNullException("page");

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

			if(GetPage(connection, page.FullName) == null) {
				CloseConnection(connection);
				return -1;
			}

			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.SelectCountFrom("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(connection, query, parameters);

			int count = ExecuteScalar<int>(command, 0);

			return count;
		}
		/// <summary>
		/// Gets the number of elements in the index.
		/// </summary>
		/// <param name="element">The type of elements.</param>
		/// <returns>The number of elements.</returns>
		private int GetCount(IndexElementType element) {
			ICommandBuilder builder = GetCommandBuilder();
			DbConnection connection = builder.GetConnection(connString);

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			int count = 0;

			string elemName = "";
			if(element == IndexElementType.Documents) elemName = "IndexDocument";
			else if(element == IndexElementType.Words) elemName = "IndexWord";
			else if(element == IndexElementType.Occurrences) elemName = "IndexWordMapping";
			else throw new NotSupportedException("Unsupported element type");

			string query = queryBuilder.SelectCountFrom(elemName);

			DbCommand command = builder.GetCommand(connection, query, new List<Parameter>());
			count = ExecuteScalar<int>(command, -1, true);

			return count;
		}
		/// <summary>
		/// Gets the approximate size, in bytes, of the search engine index.
		/// </summary>
		private long GetSize() {
			// 1. Size of documents: 8 + 2*20 + 2*30 + 2*1 + 8 = 118 bytes
			// 2. Size of words: 8 + 2*8 = 24 bytes
			// 3. Size of mappings: 8 + 8 + 2 + 2 + 1 = 21 bytes
			// 4. Size = Size * 2

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

			QueryBuilder queryBuilder = new QueryBuilder(builder);

			long size = 0;

			string query = queryBuilder.SelectCountFrom("IndexDocument");
			DbCommand command = builder.GetCommand(connection, query, new List<Parameter>());
			int rows = ExecuteScalar<int>(command, -1, false);

			if(rows == -1) return 0;
			size += rows * 118;

			query = queryBuilder.SelectCountFrom("IndexWord");
			command = builder.GetCommand(connection, query, new List<Parameter>());
			rows = ExecuteScalar<int>(command, -1, false);

			if(rows == -1) return 0;
			size += rows * 24;

			query = queryBuilder.SelectCountFrom("IndexWordMapping");
			command = builder.GetCommand(connection, query, new List<Parameter>());
			rows = ExecuteScalar<int>(command, -1, false);

			if(rows == -1) return 0;
			size += rows * 21;

			CloseConnection(connection);

			return size * 2;
		}
		/// <summary>
		/// Determines whether a page attachment exists.
		/// </summary>
		/// <param name="transaction">A database transaction.</param>
		/// <param name="page">The page.</param>
		/// <param name="name">The attachment.</param>
		/// <returns><c>true</c> if the attachment exists, <c>false</c> otherwise.</returns>
		private bool AttachmentExists(DbTransaction transaction, PageInfo page, string name) {
			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectCountFrom("Attachment");
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
			query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name", name));
			parameters.Add(new Parameter(ParameterType.String, "Page", page.FullName));

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

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

			return count == 1;
		}
		/// <summary>
		/// Determines whether a file exists.
		/// </summary>
		/// <param name="transaction">A database transaction.</param>
		/// <param name="fullName">The file full name, for example "/file.txt" or "/directory/file.txt".</param>
		/// <returns><c>true</c> if the file exists, <c>false</c> otherwise.</returns>
		private bool FileExists(DbTransaction transaction, string fullName) {
			string directory, file;
			SplitFileFullName(fullName, out directory, out file);

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

			string query = queryBuilder.SelectCountFrom("File");
			query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name");
			query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory");

			List<Parameter> parameters = new List<Parameter>(2);
			parameters.Add(new Parameter(ParameterType.String, "Name", file));
			parameters.Add(new Parameter(ParameterType.String, "Directory", directory));

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

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

			return count == 1;
		}
		/// <summary>
		/// Verifies that a user exists.
		/// </summary>
		/// <param name="connection">A database connection.</param>
		/// <param name="username">The username.</param>
		/// <returns><c>true</c> if the user exists, <c>false</c> otherwise.</returns>
		private bool UserExists(DbConnection connection, string username) {
			ICommandBuilder builder = GetCommandBuilder();
			QueryBuilder queryBuilder = new QueryBuilder(builder);

			string query = queryBuilder.SelectCountFrom("User");
			query = queryBuilder.Where(query, "Username", WhereOperator.Equals, "Username");

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

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

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

			return count == 1;
		}