// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using Microsoft.SqlTools.Hosting.Utility; namespace Microsoft.SqlTools.CoreServices.Connection.ReliableConnection { /// /// This class represents connection (and other) settings specified by called of the DacFx API. DacFx /// cannot rely on the registry to supply override values therefore setting overrides must be made /// by the top-of-the-stack /// internal sealed class AmbientSettings { private const string LogicalContextName = "__LocalContextConfigurationName"; internal enum StreamBackingStore { // MemoryStream Memory = 0, // FileStream File = 1 } // Internal for test purposes internal const string MasterReferenceFilePathIndex = "MasterReferenceFilePath"; internal const string DatabaseLockTimeoutIndex = "DatabaseLockTimeout"; internal const string QueryTimeoutIndex = "QueryTimeout"; internal const string LongRunningQueryTimeoutIndex = "LongRunningQueryTimeout"; internal const string AlwaysRetryOnTransientFailureIndex = "AlwaysRetryOnTransientFailure"; internal const string MaxDataReaderDegreeOfParallelismIndex = "MaxDataReaderDegreeOfParallelism"; internal const string ConnectionRetryHandlerIndex = "ConnectionRetryHandler"; internal const string TraceRowCountFailureIndex = "TraceRowCountFailure"; internal const string TableProgressUpdateIntervalIndex = "TableProgressUpdateInterval"; internal const string UseOfflineDataReaderIndex = "UseOfflineDataReader"; internal const string StreamBackingStoreForOfflineDataReadingIndex = "StreamBackingStoreForOfflineDataReading"; internal const string DisableIndexesForDataPhaseIndex = "DisableIndexesForDataPhase"; internal const string ReliableDdlEnabledIndex = "ReliableDdlEnabled"; internal const string ImportModelDatabaseIndex = "ImportModelDatabase"; internal const string SupportAlwaysEncryptedIndex = "SupportAlwaysEncrypted"; internal const string SkipObjectTypeBlockingIndex = "SkipObjectTypeBlocking"; internal const string DoNotSerializeQueryStoreSettingsIndex = "DoNotSerializeQueryStoreSettings"; internal const string AlwaysEncryptedWizardMigrationIndex = "AlwaysEncryptedWizardMigration"; internal static AmbientData _defaultSettings; static AmbientSettings() { _defaultSettings = new AmbientData(); } /// /// Access to the default ambient settings. Access to these settings is made available /// for SSDT scenarios where settings are read from the registry and not set explicitly through /// the API /// public static AmbientData DefaultSettings { get { return _defaultSettings; } } public static string MasterReferenceFilePath { get { return GetValue(MasterReferenceFilePathIndex); } } public static int LockTimeoutMilliSeconds { get { return GetValue(DatabaseLockTimeoutIndex); } } public static int QueryTimeoutSeconds { get { return GetValue(QueryTimeoutIndex); } } public static int LongRunningQueryTimeoutSeconds { get { return GetValue(LongRunningQueryTimeoutIndex); } } public static Action ConnectionRetryMessageHandler { get { return GetValue>(ConnectionRetryHandlerIndex); } } public static bool AlwaysRetryOnTransientFailure { get { return GetValue(AlwaysRetryOnTransientFailureIndex); } } public static int MaxDataReaderDegreeOfParallelism { get { return GetValue(MaxDataReaderDegreeOfParallelismIndex); } } public static int TableProgressUpdateInterval { // value of zero means do not fire 'heartbeat' progress events. Non-zero values will // fire a heartbeat progress event every n seconds. get { return GetValue(TableProgressUpdateIntervalIndex); } } public static bool TraceRowCountFailure { get { return GetValue(TraceRowCountFailureIndex); } } public static bool UseOfflineDataReader { get { return GetValue(UseOfflineDataReaderIndex); } } public static StreamBackingStore StreamBackingStoreForOfflineDataReading { get { return GetValue(StreamBackingStoreForOfflineDataReadingIndex); } } public static bool DisableIndexesForDataPhase { get { return GetValue(DisableIndexesForDataPhaseIndex); } } public static bool ReliableDdlEnabled { get { return GetValue(ReliableDdlEnabledIndex); } } public static bool ImportModelDatabase { get { return GetValue(ImportModelDatabaseIndex); } } /// /// Setting that shows whether Always Encrypted is supported. /// If false, then reverse engineering and script interpretation of a database with any Always Encrypted object will fail /// public static bool SupportAlwaysEncrypted { get { return GetValue(SupportAlwaysEncryptedIndex); } } public static bool AlwaysEncryptedWizardMigration { get { return GetValue(AlwaysEncryptedWizardMigrationIndex); } } /// /// Setting that determines whether checks for unsupported object types are performed. /// If false, unsupported object types will prevent extract from being performed. /// Default value is false. /// public static bool SkipObjectTypeBlocking { get { return GetValue(SkipObjectTypeBlockingIndex); } } /// /// Setting that determines whether the Database Options that store Query Store settings will be left out during package serialization. /// Default value is false. /// public static bool DoNotSerializeQueryStoreSettings { get { return GetValue(DoNotSerializeQueryStoreSettingsIndex); } } /// /// Called by top-of-stack API to setup/configure settings that should be used /// throughout the API (lower in the stack). The settings are reverted once the returned context /// has been disposed. /// public static IStackSettingsContext CreateSettingsContext() { return new StackConfiguration(); } private static T1 GetValue(string configIndex) { IAmbientDataDirectAccess config = _defaultSettings; return (T1)config.Data[configIndex].Value; } /// /// Data-transfer object that represents a specific configuration /// public class AmbientData : IAmbientDataDirectAccess { private readonly Dictionary _configuration; public AmbientData() { _configuration = new Dictionary(StringComparer.OrdinalIgnoreCase); _configuration[DatabaseLockTimeoutIndex] = new AmbientValue(5000); _configuration[QueryTimeoutIndex] = new AmbientValue(60); _configuration[LongRunningQueryTimeoutIndex] = new AmbientValue(0); _configuration[AlwaysRetryOnTransientFailureIndex] = new AmbientValue(false); _configuration[ConnectionRetryHandlerIndex] = new AmbientValue(typeof(Action), null); _configuration[MaxDataReaderDegreeOfParallelismIndex] = new AmbientValue(8); _configuration[TraceRowCountFailureIndex] = new AmbientValue(false); // default: throw DacException on rowcount mismatch during import/export data validation _configuration[TableProgressUpdateIntervalIndex] = new AmbientValue(300); // default: fire heartbeat progress update events every 5 minutes _configuration[UseOfflineDataReaderIndex] = new AmbientValue(false); _configuration[StreamBackingStoreForOfflineDataReadingIndex] = new AmbientValue(StreamBackingStore.File); //applicable only when UseOfflineDataReader is set to true _configuration[MasterReferenceFilePathIndex] = new AmbientValue(typeof(string), null); // Defect 1210884: Enable an option to allow secondary index, check and fk constraints to stay enabled during data upload with import in DACFX for IES _configuration[DisableIndexesForDataPhaseIndex] = new AmbientValue(true); _configuration[ReliableDdlEnabledIndex] = new AmbientValue(false); _configuration[ImportModelDatabaseIndex] = new AmbientValue(true); _configuration[SupportAlwaysEncryptedIndex] = new AmbientValue(false); _configuration[AlwaysEncryptedWizardMigrationIndex] = new AmbientValue(false); _configuration[SkipObjectTypeBlockingIndex] = new AmbientValue(false); _configuration[DoNotSerializeQueryStoreSettingsIndex] = new AmbientValue(false); } public string MasterReferenceFilePath { get { return (string)_configuration[MasterReferenceFilePathIndex].Value; } set { _configuration[MasterReferenceFilePathIndex].Value = value; } } public int LockTimeoutMilliSeconds { get { return (int)_configuration[DatabaseLockTimeoutIndex].Value; } set { _configuration[DatabaseLockTimeoutIndex].Value = value; } } public int QueryTimeoutSeconds { get { return (int)_configuration[QueryTimeoutIndex].Value; } set { _configuration[QueryTimeoutIndex].Value = value; } } public int LongRunningQueryTimeoutSeconds { get { return (int)_configuration[LongRunningQueryTimeoutIndex].Value; } set { _configuration[LongRunningQueryTimeoutIndex].Value = value; } } public bool AlwaysRetryOnTransientFailure { get { return (bool)_configuration[AlwaysRetryOnTransientFailureIndex].Value; } set { _configuration[AlwaysRetryOnTransientFailureIndex].Value = value; } } public Action ConnectionRetryMessageHandler { get { return (Action)_configuration[ConnectionRetryHandlerIndex].Value; } set { _configuration[ConnectionRetryHandlerIndex].Value = value; } } public bool TraceRowCountFailure { get { return (bool)_configuration[TraceRowCountFailureIndex].Value; } set { _configuration[TraceRowCountFailureIndex].Value = value; } } public int TableProgressUpdateInterval { get { return (int)_configuration[TableProgressUpdateIntervalIndex].Value; } set { _configuration[TableProgressUpdateIntervalIndex].Value = value; } } public bool UseOfflineDataReader { get { return (bool)_configuration[UseOfflineDataReaderIndex].Value; } set { _configuration[UseOfflineDataReaderIndex].Value = value; } } public StreamBackingStore StreamBackingStoreForOfflineDataReading { get { return (StreamBackingStore)_configuration[StreamBackingStoreForOfflineDataReadingIndex].Value; } set { _configuration[StreamBackingStoreForOfflineDataReadingIndex].Value = value; } } public bool DisableIndexesForDataPhase { get { return (bool)_configuration[DisableIndexesForDataPhaseIndex].Value; } set { _configuration[DisableIndexesForDataPhaseIndex].Value = value; } } public bool ReliableDdlEnabled { get { return (bool)_configuration[ReliableDdlEnabledIndex].Value; } set { _configuration[ReliableDdlEnabledIndex].Value = value; } } public bool ImportModelDatabase { get { return (bool)_configuration[ImportModelDatabaseIndex].Value; } set { _configuration[ImportModelDatabaseIndex].Value = value; } } internal bool SupportAlwaysEncrypted { get { return (bool)_configuration[SupportAlwaysEncryptedIndex].Value; } set { _configuration[SupportAlwaysEncryptedIndex].Value = value; } } internal bool AlwaysEncryptedWizardMigration { get { return (bool)_configuration[AlwaysEncryptedWizardMigrationIndex].Value; } set { _configuration[AlwaysEncryptedWizardMigrationIndex].Value = value; } } internal bool SkipObjectTypeBlocking { get { return (bool)_configuration[SkipObjectTypeBlockingIndex].Value; } set { _configuration[SkipObjectTypeBlockingIndex].Value = value; } } internal bool DoNotSerializeQueryStoreSettings { get { return (bool)_configuration[DoNotSerializeQueryStoreSettingsIndex].Value; } set { _configuration[DoNotSerializeQueryStoreSettingsIndex].Value = value; } } /// /// Provides a way to bulk populate settings from a dictionary /// public void PopulateSettings(IDictionary settingsCollection) { if (settingsCollection != null) { Dictionary newSettings = new Dictionary(); // We know all the values are set on the current configuration foreach (KeyValuePair potentialPair in settingsCollection) { AmbientValue currentValue; if (_configuration.TryGetValue(potentialPair.Key, out currentValue)) { object newValue = potentialPair.Value; newSettings[potentialPair.Key] = newValue; } } if (newSettings.Count > 0) { foreach (KeyValuePair newSetting in newSettings) { _configuration[newSetting.Key].Value = newSetting.Value; } } } } /// /// Logs the Ambient Settings /// public void TraceSettings() { // NOTE: logging as warning so we can get this data in the IEService DacFx logs Logger.Write(TraceEventType.Warning, Resources.LoggingAmbientSettings); foreach (KeyValuePair setting in _configuration) { // Log Ambient Settings Logger.Write( TraceEventType.Warning, string.Format( Resources.AmbientSettingFormat, setting.Key, setting.Value == null ? setting.Value : setting.Value.Value)); } } Dictionary IAmbientDataDirectAccess.Data { get { return _configuration; } } } /// /// This class is used as value in the dictionary to ensure that the type of value is correct. /// private class AmbientValue { private readonly Type _type; private readonly bool _isTypeNullable; private object _value; public AmbientValue(object value) : this(value == null ? null : value.GetType(), value) { } public AmbientValue(Type type, object value) { if (type == null) { throw new ArgumentNullException("type"); } _type = type; _isTypeNullable = !type.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(type) != null; Value = value; } public object Value { get { return _value; } set { if ((_isTypeNullable && value == null) || _type.GetTypeInfo().IsInstanceOfType(value)) { _value = value; } else { Logger.Write(TraceEventType.Error, string.Format(Resources.UnableToAssignValue, value.GetType().FullName, _type.FullName)); } } } } /// /// This private interface allows pass-through access directly to member data /// private interface IAmbientDataDirectAccess { Dictionary Data { get; } } /// /// This class encapsulated the concept of configuration that is set on the stack and /// flows across multiple threads as part of the logical call context /// private sealed class StackConfiguration : IStackSettingsContext { private readonly AmbientData _data; public StackConfiguration() { _data = new AmbientData(); //CallContext.LogicalSetData(LogicalContextName, _data); } public AmbientData Settings { get { return _data; } } public void Dispose() { Dispose(true); } private void Dispose(bool disposing) { //CallContext.LogicalSetData(LogicalContextName, null); } } } }