Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maintain Default Data Source Name from Original Config after Hot Reloading #2069

Merged
merged 23 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
899bd9d
pass in datasource to save in hot reload
aaronburtle Feb 28, 2024
7b1bfc4
add helper to update dictionaries in runtimeconfig
aaronburtle Feb 29, 2024
efd04ba
add testing, cleanup format
aaronburtle Mar 2, 2024
ecac30f
addressing comments, still need to remove GetDataSourceName()
aaronburtle Mar 11, 2024
2f57773
remove un-needed function for getting dataSourceName, cleanup test st…
aaronburtle Mar 11, 2024
58d418c
whitespace
aaronburtle Mar 11, 2024
732d32e
clarifying comments
aaronburtle Mar 12, 2024
81b2ecd
refactor update
aaronburtle Mar 12, 2024
b3c067b
use private DefaultDataSourceName
aaronburtle Mar 12, 2024
873445a
typos
aaronburtle Mar 12, 2024
1a9229d
push change to rivate
aaronburtle Mar 12, 2024
da0abcd
match mocked loader to new signature
aaronburtle Mar 12, 2024
f16ef6d
change get method to property
aaronburtle Mar 12, 2024
5e1b057
update references to property
aaronburtle Mar 12, 2024
579199f
add ignore for default data source name to unit test verifier
aaronburtle Mar 12, 2024
be78615
style fix
aaronburtle Mar 12, 2024
3d31d89
merging
aaronburtle Mar 12, 2024
7f316c3
merging main
aaronburtle Mar 12, 2024
9d1bd74
add ignore for other tests in verifier for Jsonignored DefaultDatasou…
aaronburtle Mar 12, 2024
f295edb
defensive coding, typo fix
aaronburtle Mar 12, 2024
5148832
format
aaronburtle Mar 13, 2024
be7bd5e
Merge branch 'main' into dev/aaronburtle/SaveDataSourceNameForHotReload
aaronburtle Mar 13, 2024
937b731
Merge branch 'main' into dev/aaronburtle/SaveDataSourceNameForHotReload
aaronburtle Mar 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Cli.Tests/ModuleInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public static void Init()
VerifierSettings.IgnoreMember<RuntimeConfig>(config => config.Schema);
// Ignore the message as that's not serialized in our config file anyway.
VerifierSettings.IgnoreMember<DataSource>(dataSource => dataSource.DatabaseTypeNotSupportedMessage);
// Ignore DefaultDataSourceName as that's not serialized in our config file.
VerifierSettings.IgnoreMember<RuntimeConfig>(config => config.DefaultDataSourceName);
// Customise the path where we store snapshots, so they are easier to locate in a PR review.
VerifyBase.DerivePathInfo(
(sourceFile, projectDirectory, type, method) => new(
Expand Down
24 changes: 20 additions & 4 deletions src/Config/FileSystemRuntimeConfigLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,32 @@ public string GetConfigFileName()
/// <param name="config">The loaded <c>RuntimeConfig</c>, or null if none was loaded.</param>
/// <param name="replaceEnvVar">Whether to replace environment variable with its
/// value or not while deserializing.</param>
/// <param name="dataSourceName">If provided and not empty, this is the data source name that will be used in the loaded config.</param>
/// <returns>True if the config was loaded, otherwise false.</returns>
public bool TryLoadConfig(
string path,
[NotNullWhen(true)] out RuntimeConfig? config,
bool replaceEnvVar = false)
bool replaceEnvVar = false,
string dataSourceName = "")
aaronburtle marked this conversation as resolved.
Show resolved Hide resolved
{
if (_fileSystem.File.Exists(path))
{
Console.WriteLine($"Loading config file from {path}.");
string json = _fileSystem.File.ReadAllText(path);
return TryParseConfig(json, out config, connectionString: _connectionString, replaceEnvVar: replaceEnvVar);

if (TryParseConfig(
json: json,
config: out config,
connectionString: _connectionString,
replaceEnvVar: replaceEnvVar))
{
if (!string.IsNullOrEmpty(dataSourceName))
{
config.UpdateDefaultDataSourceName(dataSourceName);
}

return true;
}
}
else
{
Expand All @@ -123,10 +138,11 @@ public bool TryLoadConfig(
/// <param name="config">The loaded <c>RuntimeConfig</c>, or null if none was loaded.</param>
/// <param name="replaceEnvVar">Whether to replace environment variable with its
/// value or not while deserializing.</param>
/// <param name="dataSourceName">The data source name to be used in the loaded config.</param>
/// <returns>True if the config was loaded, otherwise false.</returns>
public override bool TryLoadKnownConfig([NotNullWhen(true)] out RuntimeConfig? config, bool replaceEnvVar = false)
public override bool TryLoadKnownConfig([NotNullWhen(true)] out RuntimeConfig? config, bool replaceEnvVar = false, string dataSourceName = "")
{
return TryLoadConfig(ConfigFilePath, out config, replaceEnvVar);
return TryLoadConfig(ConfigFilePath, out config, replaceEnvVar, dataSourceName);
}

/// <summary>
Expand Down
46 changes: 30 additions & 16 deletions src/Config/ObjectModel/RuntimeConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ Runtime.GraphQL is null ||
}
}

private string _defaultDataSourceName;
[JsonIgnore]
public string DefaultDataSourceName { get; private set; }

private Dictionary<string, DataSource> _dataSourceNameToDataSource;

Expand Down Expand Up @@ -169,18 +170,18 @@ public RuntimeConfig(string? Schema, DataSource DataSource, RuntimeEntities Enti
this.DataSource = DataSource;
this.Runtime = Runtime;
this.Entities = Entities;
_defaultDataSourceName = Guid.NewGuid().ToString();
this.DefaultDataSourceName = Guid.NewGuid().ToString();

// we will set them up with default values
_dataSourceNameToDataSource = new Dictionary<string, DataSource>
{
{ _defaultDataSourceName, this.DataSource }
{ DefaultDataSourceName, this.DataSource }
};

_entityNameToDataSourceName = new Dictionary<string, string>();
foreach (KeyValuePair<string, Entity> entity in Entities)
{
_entityNameToDataSourceName.TryAdd(entity.Key, _defaultDataSourceName);
_entityNameToDataSourceName.TryAdd(entity.Key, DefaultDataSourceName);
}

// Process data source and entities information for each database in multiple database scenario.
Expand Down Expand Up @@ -241,7 +242,7 @@ public RuntimeConfig(string Schema, DataSource DataSource, RuntimeOptions Runtim
this.DataSource = DataSource;
this.Runtime = Runtime;
this.Entities = Entities;
_defaultDataSourceName = DefaultDataSourceName;
this.DefaultDataSourceName = DefaultDataSourceName;
_dataSourceNameToDataSource = DataSourceNameToDataSource;
_entityNameToDataSourceName = EntityNameToDataSourceName;
this.DataSourceFiles = DataSourceFiles;
Expand Down Expand Up @@ -273,6 +274,30 @@ public void UpdateDataSourceNameToDataSource(string dataSourceName, DataSource d
_dataSourceNameToDataSource[dataSourceName] = dataSource;
}

/// <summary>
/// In a Hot Reload scenario we should maintain the same default data source
/// name before the hot reload as after the hot reload. This is because we hold
/// references to the Data Source itself which depend on this data source name
/// for lookups. To correctly retrieve this information after a hot reload
/// we need the data source name to stay the same after hot reloading. This method takes
/// a default data source name, such as the one from before hot reload, and
/// replaces the current dictionary entries of this RuntimeConfig that were
/// built using a new, unique guid during the construction of this RuntimeConfig
/// with entries using the provided default data source name. We then update the DefaultDataSourceName.
/// </summary>
/// <param name="initialDefaultDataSourceName">The name used to update the dictionaries.</param>
public void UpdateDefaultDataSourceName(string initialDefaultDataSourceName)
{
_dataSourceNameToDataSource.Remove(DefaultDataSourceName);
_dataSourceNameToDataSource.Add(initialDefaultDataSourceName, this.DataSource);
aaronburtle marked this conversation as resolved.
Show resolved Hide resolved
foreach (KeyValuePair<string, Entity> entity in Entities)
{
_entityNameToDataSourceName[entity.Key] = initialDefaultDataSourceName;
}

DefaultDataSourceName = initialDefaultDataSourceName;
}

/// <summary>
/// Gets datasourceName from EntityNameToDatasourceName dictionary.
/// </summary>
Expand Down Expand Up @@ -303,17 +328,6 @@ public bool CheckDataSourceExists(string dataSourceName)
return _dataSourceNameToDataSource.ContainsKey(dataSourceName);
}

/// <summary>
/// Get the default datasource name.
/// </summary>
/// <returns>default datasourceName.</returns>
#pragma warning disable CA1024 // Use properties where appropriate. Reason: Do not want datasource serialized and want to keep it private to restrict set;
public string GetDefaultDataSourceName()
#pragma warning restore CA1024 // Use properties where appropriate
{
return _defaultDataSourceName;
}

/// <summary>
/// Serializes the RuntimeConfig object to JSON for writing to file.
/// </summary>
Expand Down
7 changes: 4 additions & 3 deletions src/Config/RuntimeConfigLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ public RuntimeConfigLoader(string? connectionString = null)
/// <param name="config">The loaded <c>RuntimeConfig</c>, or null if none was loaded.</param>
/// <param name="replaceEnvVar">Whether to replace environment variable with its
/// value or not while deserializing.</param>
/// <param name="dataSourceName">The data source name to be used in the loaded config.</param>
/// <returns>True if the config was loaded, otherwise false.</returns>
public abstract bool TryLoadKnownConfig([NotNullWhen(true)] out RuntimeConfig? config, bool replaceEnvVar = false);
public abstract bool TryLoadKnownConfig([NotNullWhen(true)] out RuntimeConfig? config, bool replaceEnvVar = false, string dataSourceName = "");
aaronburtle marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Returns the link to the published draft schema.
Expand Down Expand Up @@ -80,7 +81,7 @@ public static bool TryParseConfig(string json,
// set dataSourceName to default if not provided
if (string.IsNullOrEmpty(dataSourceName))
{
dataSourceName = config.GetDefaultDataSourceName();
dataSourceName = config.DefaultDataSourceName;
}

if (!string.IsNullOrEmpty(connectionString))
Expand Down Expand Up @@ -113,7 +114,7 @@ public static bool TryParseConfig(string json,
ds = ds with { ConnectionString = updatedConnection };
config.UpdateDataSourceNameToDataSource(dataSourceName, ds);

if (string.Equals(dataSourceKey, config.GetDefaultDataSourceName(), StringComparison.OrdinalIgnoreCase))
if (string.Equals(dataSourceKey, config.DefaultDataSourceName, StringComparison.OrdinalIgnoreCase))
{
config = config with { DataSource = ds };
}
Expand Down
1 change: 1 addition & 0 deletions src/Core/Configurations/ConfigFileWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ private void OnConfigFileChange(object sender, FileSystemEventArgs e)

if (!_configProvider.IsLateConfigured && _configProvider.GetConfig().IsDevelopmentMode())
{
// pass along the original default data source name for consistency within runtime config's data structures
_configProvider.HotReloadConfig();
}
}
Expand Down
13 changes: 7 additions & 6 deletions src/Core/Configurations/RuntimeConfigProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ public bool TryGetLoadedConfig([NotNullWhen(true)] out RuntimeConfig? runtimeCon
/// </summary>
public void HotReloadConfig()
{
ConfigLoader.TryLoadKnownConfig(out _runtimeConfig, replaceEnvVar: true);
// _runtimeconfig can not be null in a hot reload scenario
ConfigLoader.TryLoadKnownConfig(out _runtimeConfig, replaceEnvVar: true, _runtimeConfig!.DefaultDataSourceName);
}

/// <summary>
Expand Down Expand Up @@ -193,7 +194,7 @@ public async Task<bool> Initialize(
_runtimeConfig = HandleCosmosNoSqlConfiguration(schema, _runtimeConfig, _runtimeConfig.DataSource.ConnectionString);
}

ManagedIdentityAccessToken[_runtimeConfig.GetDefaultDataSourceName()] = accessToken;
ManagedIdentityAccessToken[_runtimeConfig.DefaultDataSourceName] = accessToken;
}

bool configLoadSucceeded = await InvokeConfigLoadedHandlersAsync();
Expand Down Expand Up @@ -268,8 +269,8 @@ public async Task<bool> Initialize(
DatabaseType.CosmosDB_NoSQL => HandleCosmosNoSqlConfiguration(graphQLSchema, runtimeConfig, connectionString),
_ => runtimeConfig with { DataSource = runtimeConfig.DataSource with { ConnectionString = connectionString } }
};
ManagedIdentityAccessToken[_runtimeConfig.GetDefaultDataSourceName()] = accessToken;
_runtimeConfig.UpdateDataSourceNameToDataSource(_runtimeConfig.GetDefaultDataSourceName(), _runtimeConfig.DataSource);
ManagedIdentityAccessToken[_runtimeConfig.DefaultDataSourceName] = accessToken;
_runtimeConfig.UpdateDataSourceNameToDataSource(_runtimeConfig.DefaultDataSourceName, _runtimeConfig.DataSource);

return await InvokeConfigLoadedHandlersAsync();
}
Expand Down Expand Up @@ -298,7 +299,7 @@ private static RuntimeConfig HandleCosmosNoSqlConfiguration(string? schema, Runt
{
if (string.IsNullOrEmpty(dataSourceName))
{
dataSourceName = runtimeConfig.GetDefaultDataSourceName();
dataSourceName = runtimeConfig.DefaultDataSourceName;
}

DbConnectionStringBuilder dbConnectionStringBuilder = new()
Expand Down Expand Up @@ -341,7 +342,7 @@ private static RuntimeConfig HandleCosmosNoSqlConfiguration(string? schema, Runt
// Update the connection string in the datasource with the one that was provided to the controller
dataSource = dataSource with { Options = options, ConnectionString = connectionString };

if (dataSourceName == runtimeConfig.GetDefaultDataSourceName())
if (dataSourceName == runtimeConfig.DefaultDataSourceName)
{
// update default db.
runtimeConfig = runtimeConfig with { DataSource = dataSource };
Expand Down
4 changes: 2 additions & 2 deletions src/Core/Resolvers/MsSqlQueryExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public override async Task SetManagedIdentityAccessTokenIfAnyAsync(DbConnection
// using default datasource name for first db - maintaining backward compatibility for single db scenario.
if (string.IsNullOrEmpty(dataSourceName))
{
dataSourceName = ConfigProvider.GetConfig().GetDefaultDataSourceName();
dataSourceName = ConfigProvider.GetConfig().DefaultDataSourceName;
}

_dataSourceAccessTokenUsage.TryGetValue(dataSourceName, out bool setAccessToken);
Expand Down Expand Up @@ -198,7 +198,7 @@ public override string GetSessionParamsQuery(HttpContext? httpContext, IDictiona
{
if (string.IsNullOrEmpty(dataSourceName))
{
dataSourceName = ConfigProvider.GetConfig().GetDefaultDataSourceName();
dataSourceName = ConfigProvider.GetConfig().DefaultDataSourceName;
}

if (httpContext is null || !_dataSourceToSessionContextUsage[dataSourceName])
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Resolvers/MySqlQueryExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public override async Task SetManagedIdentityAccessTokenIfAnyAsync(DbConnection
// using default datasource name for first db - maintaining backward compatibility for single db scenario.
if (string.IsNullOrEmpty(dataSourceName))
{
dataSourceName = ConfigProvider.GetConfig().GetDefaultDataSourceName();
dataSourceName = ConfigProvider.GetConfig().DefaultDataSourceName;
}

_dataSourceAccessTokenUsage.TryGetValue(dataSourceName, out bool setAccessToken);
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Resolvers/PostgreSqlExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public override async Task SetManagedIdentityAccessTokenIfAnyAsync(DbConnection
// using default datasource name for first db - maintaining backward compatibility for single db scenario.
if (string.IsNullOrEmpty(dataSourceName))
{
dataSourceName = ConfigProvider.GetConfig().GetDefaultDataSourceName();
dataSourceName = ConfigProvider.GetConfig().DefaultDataSourceName;
}

_dataSourceAccessTokenUsage.TryGetValue(dataSourceName, out bool setAccessToken);
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Resolvers/QueryExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public QueryExecutor(DbExceptionParser dbExceptionParser,
{
if (string.IsNullOrEmpty(dataSourceName))
{
dataSourceName = ConfigProvider.GetConfig().GetDefaultDataSourceName();
dataSourceName = ConfigProvider.GetConfig().DefaultDataSourceName;
}

if (!ConnectionStringBuilders.ContainsKey(dataSourceName))
Expand Down
4 changes: 2 additions & 2 deletions src/Core/Resolvers/SqlMutationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ await queryExecutor.ExecuteQueryAsync(
public async Task<IActionResult?> ExecuteAsync(RestRequestContext context)
{
// for REST API scenarios, use the default datasource
string dataSourceName = _runtimeConfigProvider.GetConfig().GetDefaultDataSourceName();
string dataSourceName = _runtimeConfigProvider.GetConfig().DefaultDataSourceName;

Dictionary<string, object?> parameters = PrepareParameters(context);
ISqlMetadataProvider sqlMetadataProvider = _sqlMetadataProviderFactory.GetMetadataProvider(dataSourceName);
Expand Down Expand Up @@ -1194,7 +1194,7 @@ private static TransactionScope ConstructTransactionScopeWithSpecifiedIsolationL
private string GetValidatedDataSourceName(string dataSourceName)
{
// For rest scenarios - no multiple db support. Hence to maintain backward compatibility, we will use the default db.
return string.IsNullOrEmpty(dataSourceName) ? _runtimeConfigProvider.GetConfig().GetDefaultDataSourceName() : dataSourceName;
return string.IsNullOrEmpty(dataSourceName) ? _runtimeConfigProvider.GetConfig().DefaultDataSourceName : dataSourceName;
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Resolvers/SqlQueryEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ await ExecuteAsync(structure, dataSourceName),
public async Task<JsonDocument?> ExecuteAsync(FindRequestContext context)
{
// for REST API scenarios, use the default datasource
string dataSourceName = _runtimeConfigProvider.GetConfig().GetDefaultDataSourceName();
string dataSourceName = _runtimeConfigProvider.GetConfig().DefaultDataSourceName;

ISqlMetadataProvider sqlMetadataProvider = _sqlMetadataProviderFactory.GetMetadataProvider(dataSourceName);
SqlQueryStructure structure = new(
Expand Down
4 changes: 2 additions & 2 deletions src/Core/Services/OpenAPI/OpenApiDocumentor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ private OpenApiPaths BuildPaths()
{
OpenApiPaths pathsCollection = new();

string defaultDataSourceName = _runtimeConfig.GetDefaultDataSourceName();
string defaultDataSourceName = _runtimeConfig.DefaultDataSourceName;
ISqlMetadataProvider metadataProvider = _metadataProviderFactory.GetMetadataProvider(defaultDataSourceName);
foreach (KeyValuePair<string, DatabaseObject> entityDbMetadataMap in metadataProvider.EntityToDatabaseObject)
{
Expand Down Expand Up @@ -952,7 +952,7 @@ private Dictionary<string, OpenApiSchema> CreateComponentSchemas()
Dictionary<string, OpenApiSchema> schemas = new();

// for rest scenario we need the default datasource name.
string defaultDataSourceName = _runtimeConfig.GetDefaultDataSourceName();
string defaultDataSourceName = _runtimeConfig.DefaultDataSourceName;
ISqlMetadataProvider metadataProvider = _metadataProviderFactory.GetMetadataProvider(defaultDataSourceName);

foreach (KeyValuePair<string, DatabaseObject> entityDbMetadataMap in metadataProvider.EntityToDatabaseObject)
Expand Down
6 changes: 3 additions & 3 deletions src/Core/Services/RestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ RequestValidator requestValidator
private async Task<IActionResult> DispatchQuery(RestRequestContext context, DatabaseType databaseType)
{
IQueryEngine queryEngine = _queryEngineFactory.GetQueryEngine(databaseType);
string defaultDataSourceName = _runtimeConfigProvider.GetConfig().GetDefaultDataSourceName();
string defaultDataSourceName = _runtimeConfigProvider.GetConfig().DefaultDataSourceName;

if (context is FindRequestContext findRequestContext)
{
Expand All @@ -237,7 +237,7 @@ private async Task<IActionResult> DispatchQuery(RestRequestContext context, Data
private Task<IActionResult?> DispatchMutation(RestRequestContext context, DatabaseType databaseType)
{
IMutationEngine mutationEngine = _mutationEngineFactory.GetMutationEngine(databaseType);
string defaultDataSourceName = _runtimeConfigProvider.GetConfig().GetDefaultDataSourceName();
string defaultDataSourceName = _runtimeConfigProvider.GetConfig().DefaultDataSourceName;
return context switch
{
StoredProcedureRequestContext => mutationEngine.ExecuteAsync((StoredProcedureRequestContext)context, defaultDataSourceName),
Expand Down Expand Up @@ -437,7 +437,7 @@ public bool TryGetRestRouteFromConfig([NotNullWhen(true)] out string? configured
public (string, string) GetEntityNameAndPrimaryKeyRouteFromRoute(string routeAfterPathBase)
{

string dataSourceName = _runtimeConfigProvider.GetConfig().GetDefaultDataSourceName();
string dataSourceName = _runtimeConfigProvider.GetConfig().DefaultDataSourceName;
ISqlMetadataProvider sqlMetadataProvider = _sqlMetadataProviderFactory.GetMetadataProvider(dataSourceName);

// Split routeAfterPath on the first occurrence of '/', if we get back 2 elements
Expand Down
Loading