DSInternals/Src/Microsoft.Database.Isam/config/DatabaseConfig.cs

390 lines
16 KiB
C#

//-----------------------------------------------------------------------
// <copyright file="DatabaseConfig.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation.
// </copyright>
//-----------------------------------------------------------------------
namespace Microsoft.Database.Isam.Config
{
using System;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Reflection;
using System.Threading;
using Microsoft.Database.Isam.Win32;
using Microsoft.Isam.Esent.Interop;
using Microsoft.Isam.Esent.Interop.Vista;
using Microsoft.Isam.Esent.Interop.Windows10;
/// <summary>
/// Represents a subset of JET_param definitions required for config classes.
/// </summary>
internal struct ParamDef
{
/// <summary>
/// JET_param Id.
/// </summary>
public int Id;
/// <summary>
/// Can be set after global init?
/// </summary>
public bool StaticAfterGlobalInit;
/// <summary>
/// Managed type of the parameter.
/// </summary>
public Type ParamType;
/// <summary>
/// Initializes a new instance of the ParamDef struct.
/// </summary>
/// <param name="id"><see cref="JET_param"/> Id.</param>
/// <param name="staticAfterGlobalInit">Can be set after global init?</param>
/// <param name="paramType">Managed type of the param.</param>
public ParamDef(int id, bool staticAfterGlobalInit, Type paramType)
{
this.Id = id;
this.StaticAfterGlobalInit = staticAfterGlobalInit;
this.ParamType = paramType;
}
}
/// <summary>
/// A class that contains all engine wide Ese parameters.
/// </summary>
public sealed partial class DatabaseConfig : ConfigSetBase
{
/// <summary>
/// A table of JET_param definitions used by config classes.
/// </summary>
internal static readonly ParamDef[] ParamTable;
/// <summary>
/// Passed to JetGetSystemParameter for retrieving string parameters.
/// </summary>
private const int MaxStringParamSize = 260;
/// <summary>
/// A per-process lock to synchronize calls to SetGlobalParams()
/// </summary>
private static readonly Mutex GlobalParamsMutex;
/// <summary>
/// Initializes static members of the <see cref="DatabaseConfig" /> class.
/// Used to initialize the param table.
/// </summary>
static DatabaseConfig()
{
// Create a per-process named mutex. C# locks are per app-domain, which could potentially screw up Ese global params that are process wide.
DatabaseConfig.GlobalParamsMutex = new Mutex(false, "Microsoft.Isam.Esent.Interop.DatabaseConfig.globalParamsMutex_" + NativeMethods.GetCurrentProcessId());
DatabaseConfig.ParamTable = new ParamDef[DatabaseConfig.ParamMaxValueInvalid];
FillParamTable();
UnpublishedFillParamTable();
}
/// <summary>
/// Gets or sets a string identifier that uniquely identifies an instance of Database.
/// </summary>
public string Identifier
{
get { return this.GetEngineParam<string>(DatabaseParams.Identifier); }
set { this.SetEngineParam(DatabaseParams.Identifier, value); }
}
/// <summary>
/// Gets or sets a user-friendly name that is used to identify an instance of Database in system diagnostics (event log, etc).
/// </summary>
public string DisplayName
{
get { return this.GetEngineParam<string>(DatabaseParams.DisplayName); }
set { this.SetEngineParam(DatabaseParams.DisplayName, value); }
}
/// <summary>
/// Gets or sets flags used to select optional Engine behaviour at initialization. See <see cref ="CreateInstanceGrbit"/> and <see cref="Api.JetCreateInstance2"/>
/// </summary>
public CreateInstanceGrbit EngineFlags
{
get { return this.GetEngineParam<CreateInstanceGrbit>(DatabaseParams.EngineFlags); }
set { this.SetEngineParam(DatabaseParams.EngineFlags, value); }
}
/// <summary>
/// Gets or sets flags used to select optional behaviour while initializing an instance in the Engine and recovering the database from the log stream. See <see cref ="InitGrbit"/> and <see cref="Api.JetInit2"/>
/// </summary>
public InitGrbit DatabaseRecoveryFlags
{
get { return this.GetEngineParam<InitGrbit>(DatabaseParams.DatabaseRecoveryFlags); }
set { this.SetEngineParam(DatabaseParams.DatabaseRecoveryFlags, value); }
}
/// <summary>
/// Gets or sets a path to use for creating or opening the database file.
/// </summary>
public string DatabaseFilename
{
get { return this.GetEngineParam<string>(DatabaseParams.DatabaseFilename); }
set { this.SetEngineParam(DatabaseParams.DatabaseFilename, value); }
}
/// <summary>
/// Gets or sets flags used to select optional behaviour while creating a new database file. See <see cref ="CreateDatabaseGrbit"/> and <see cref="Api.JetCreateDatabase2"/>
/// </summary>
public CreateDatabaseGrbit DatabaseCreationFlags
{
get { return this.GetEngineParam<CreateDatabaseGrbit>(DatabaseParams.DatabaseCreationFlags); }
set { this.SetEngineParam(DatabaseParams.DatabaseCreationFlags, value); }
}
/// <summary>
/// Gets or sets flags used to select optional behaviour while attaching a database file to the Engine. See <see cref ="AttachDatabaseGrbit"/> and <see cref="Api.JetAttachDatabase2"/>
/// </summary>
public AttachDatabaseGrbit DatabaseAttachFlags
{
get { return this.GetEngineParam<AttachDatabaseGrbit>(DatabaseParams.DatabaseAttachFlags); }
set { this.SetEngineParam(DatabaseParams.DatabaseAttachFlags, value); }
}
/// <summary>
/// Gets or sets flags used to select optional behaviour while terminating an instance in the Engine. See <see cref ="TermGrbit"/> and <see cref="Api.JetTerm2"/>
/// </summary>
public TermGrbit DatabaseStopFlags
{
get { return this.GetEngineParam<TermGrbit>(DatabaseParams.DatabaseStopFlags); }
set { this.SetEngineParam(DatabaseParams.DatabaseStopFlags, value); }
}
/// <summary>
/// Gets or sets the maximum size of the database in pages. Zero means there is no maximum. See maxPages parameter for <see cref ="Api.JetCreateDatabase2"/> or <see cref="Api.JetAttachDatabase2"/>
/// </summary>
public int DatabaseMaxPages
{
get { return this.GetEngineParam<int>(DatabaseParams.DatabaseMaxPages); }
set { this.SetEngineParam(DatabaseParams.DatabaseMaxPages, value); }
}
/// <summary>
/// Sets global parameters before an Ese instance is created.
/// </summary>
public void SetGlobalParams()
{
// Lock this section using a process-wide lock, so in case of a race between two threads, a config set is either completely set or completely over-written
DatabaseConfig.GlobalParamsMutex.WaitOne();
try
{
for (int i = 0; i < DatabaseConfig.ParamTable.Length; i++)
{
// This check ensures that only global params that must be set before global init are set here
// This protects instance params from being set before instance init. Setting them here would change the defaults for these params
// for all future instances. That would be really bad because the settings contained in the current config set would 'spill over'
// into future config sets.
if (DatabaseConfig.ParamTable[i].StaticAfterGlobalInit)
{
object valueToSet;
if (this.ParamStore.TryGetValue(i, out valueToSet))
{
object currValue;
DatabaseConfig.TryGetParamFromInstance(JET_INSTANCE.Nil, i, out currValue);
if (!currValue.Equals(valueToSet))
{
DatabaseConfig.SetParamOnInstance(JET_INSTANCE.Nil, i, valueToSet);
}
}
}
}
}
finally
{
DatabaseConfig.GlobalParamsMutex.ReleaseMutex();
}
}
/// <summary>
/// Sets instance specific parameters after an Ese instance has been created.
/// </summary>
/// <param name="instance">The instance handle to use.</param>
public void SetInstanceParams(JET_INSTANCE instance)
{
Action<DatabaseConfig, JET_INSTANCE, int> setParamIfExists = (engineConfig, inst, param) =>
{
object value;
if (engineConfig.ParamStore.TryGetValue(param, out value))
{
DatabaseConfig.SetParamOnInstance(inst, param, value);
}
};
// First, set parameters that require to be set in a particular order
#if !ESENT && !MANAGEDESENT_ON_WSA
setParamIfExists(this, instance, (int)Windows10Param.ConfigStoreSpec);
#endif
setParamIfExists(this, instance, (int)VistaParam.EnableAdvanced);
setParamIfExists(this, instance, (int)VistaParam.Configuration);
for (int i = 0; i < DatabaseConfig.ParamTable.Length; i++)
{
if (!DatabaseConfig.ParamTable[i].StaticAfterGlobalInit
#if !ESENT && !MANAGEDESENT_ON_WSA
&& i != (int)Windows10Param.ConfigStoreSpec
#endif
&& i != (int)VistaParam.EnableAdvanced
&& i != (int)VistaParam.Configuration)
{
setParamIfExists(this, instance, i);
}
}
}
/// <summary>
/// Returns a <see cref="T:System.String"/> that represents the current <see cref="DatabaseConfig"/>.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> that represents the current <see cref="DatabaseConfig"/>.
/// </returns>
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "DatabaseConfig( {0} ) [Id: {1}]", this.DisplayName, this.Identifier);
}
/// <summary>
/// Get a <see cref="JET_param"/> from a particular instance.
/// </summary>
/// <param name="instance">The instance handle to use.</param>
/// <param name="param">The param id.</param>
/// <param name="value">The param value.</param>
/// <returns>True for all instance params. False otherwise.</returns>
internal static bool TryGetParamFromInstance(JET_INSTANCE instance, int param, out object value)
{
value = null;
if (param >= DatabaseConfig.ParamMaxValueInvalid)
{
return false;
}
JET_wrn wrn;
var paramDef = DatabaseConfig.ParamTable[param];
if (paramDef.ParamType == typeof(string))
{
string strValue;
var intValue = new IntPtr(0);
wrn = Api.JetGetSystemParameter(instance, JET_SESID.Nil, (JET_param)param, ref intValue, out strValue, DatabaseConfig.MaxStringParamSize);
value = strValue;
}
else
{
string strValue;
var intValue = new IntPtr(0);
wrn = Api.JetGetSystemParameter(instance, JET_SESID.Nil, (JET_param)param, ref intValue, out strValue, 0);
if (paramDef.ParamType == typeof(int))
{
value = intValue.ToInt32();
}
else if (paramDef.ParamType == typeof(bool))
{
value = Convert.ToBoolean(intValue.ToInt32());
}
else if (paramDef.ParamType.IsEnum)
{
value = Enum.ToObject(paramDef.ParamType, intValue.ToInt32());
}
else if (paramDef.ParamType == typeof(IntPtr))
{
value = intValue;
}
else
{
throw new NotSupportedException(string.Format("Can't get param {0}. Type {1} isn't supported.", paramDef.Id, paramDef.ParamType));
}
}
Contract.Ensures(wrn == JET_wrn.Success);
return true;
}
/// <summary>
/// Sets a <see cref="JET_param"/> on a particular instance.
/// </summary>
/// <param name="instance">The instance handle to use.</param>
/// <param name="param">The param id.</param>
/// <param name="value">The param value.</param>
internal static void SetParamOnInstance(JET_INSTANCE instance, int param, object value)
{
Contract.Requires(param < DatabaseConfig.ParamMaxValueInvalid);
JET_wrn wrn;
var paramDef = DatabaseConfig.ParamTable[param];
Contract.Requires(paramDef.ParamType == value.GetType());
if (paramDef.ParamType == typeof(string))
{
wrn = Api.JetSetSystemParameter(instance, JET_SESID.Nil, (JET_param)param, 0, (string)value);
}
else
{
IntPtr intValue;
if (paramDef.ParamType == typeof(int) || paramDef.ParamType.IsEnum)
{
intValue = new IntPtr((int)value);
}
else if (paramDef.ParamType == typeof(bool))
{
intValue = new IntPtr(Convert.ToInt32((bool)value));
}
else if (paramDef.ParamType == typeof(IntPtr))
{
intValue = (IntPtr)value;
}
else
{
throw new NotSupportedException(string.Format("Can't set param {0}. Type {1} isn't supported.", paramDef.Id, paramDef.ParamType));
}
wrn = Api.JetSetSystemParameter(instance, JET_SESID.Nil, (JET_param)param, intValue, null);
}
Contract.Ensures(wrn == JET_wrn.Success);
}
/// <summary>
/// Partial method for filling the param table from auto-generated code.
/// </summary>
static partial void FillParamTable();
/// <summary>
/// Partial method for filling the param table from auto-generated code.
/// </summary>
static partial void UnpublishedFillParamTable();
/// <summary>
/// Helper method to get a custom defined engine param.
/// </summary>
/// <typeparam name="T">The type of the engine param.</typeparam>
/// <param name="param">The engine param to get.</param>
/// <returns>The engine param value.</returns>
private T GetEngineParam<T>(DatabaseParams param)
{
// Engine params are always accessed from the config store dictionary (even if the config set is live)
return ConfigSetBase.GetValueOrDefault<T>(this.ParamStore.TryGetValue, (int)param);
}
/// <summary>
/// Helper method to set a custom defined engine param.
/// </summary>
/// <param name="param">The engine param to set.</param>
/// <param name="value">The engine param value.</param>
private void SetEngineParam(DatabaseParams param, object value)
{
// Engine params can't be set once the config set goes live
if (this.SetParamDelegate == null)
{
this.ParamStore[(int)param] = value;
}
else
{
throw new EsentAlreadyInitializedException();
}
}
}
}