Applied via `dotnet format analyzers --diagnostics CA1854 --severity info`. Eliminates the duplicate hash lookup in the `if (d.ContainsKey(k)) d[k]++ else d[k] = 1` pattern. The fixer rewrites the conditional to `if (d.TryGetValue(k, out var value)) d[k] = ++value;` which is semantically identical but does the lookup once. 4 files touched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
472 lines
15 KiB
C#
472 lines
15 KiB
C#
using Strata.SqlTools.SqlBreakdown.Interfaces.Core;
|
|
|
|
using System.Collections;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace Strata.SqlTools.SqlBreakdown.Expressions;
|
|
|
|
/// <summary>
|
|
/// Represents a reference to a property from an input data source, typically used for
|
|
/// dynamic data binding in query construction. Used when building queries that reference
|
|
/// external data sources by GUID identifiers.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This expression type is used in scenarios where data comes from registered data
|
|
/// sources (like data tables or dimensions) that are identified by GUIDs. The
|
|
/// expression can reference a primary data source and optionally a secondary data source
|
|
/// for hierarchical or relational lookups.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// <code language="csharp">
|
|
/// // Reference a simple property from a data source
|
|
/// var patientName = new InputPropertyExpression(
|
|
/// dataSourceGuid: Guid.Parse("41639c8f-fecf-4449-b6e6-53f796c0c3e4"),
|
|
/// dataKeyLookup: "PatientName"
|
|
/// );
|
|
///
|
|
/// // Reference a property with a secondary data source (dimension lookup)
|
|
/// var patientType = new InputPropertyExpression(
|
|
/// dataSourceGuid: Guid.Parse("41639c8f-fecf-4449-b6e6-53f796c0c3e4"),
|
|
/// secondaryDataSource: Guid.Parse("6ef6b1f7-a50c-4198-8866-140bb82e2dda"),
|
|
/// dataKeyLookup: "PatientTypeRollupName"
|
|
/// );
|
|
/// </code>
|
|
/// </example>
|
|
public class InputPropertyExpression : Expression
|
|
{
|
|
/// <summary>
|
|
/// Gets the GUID identifier of the primary data source.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The data source GUID, typically representing a data table or primary data entity.
|
|
/// </value>
|
|
/// <example>
|
|
/// 41639c8f-fecf-4449-b6e6-53f796c0c3e4 - the data table id of PES
|
|
/// </example>
|
|
public Guid DataSourceGuid { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the optional GUID identifier of a secondary data source.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The secondary data source GUID, typically representing a dimension or lookup table.
|
|
/// Null if no secondary source is required.
|
|
/// </value>
|
|
/// <example>
|
|
/// 6ef6b1f7-a50c-4198-8866-140bb82e2dda - the dimension id of the Patient Type
|
|
/// Rollup dimension
|
|
/// </example>
|
|
public Guid? SecondaryDataSource { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the key name used to look up the data value.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The property or column name within the data source.
|
|
/// </value>
|
|
/// <example>"PatientTypeRollupName"</example>
|
|
public string DataKeyLookup { get; }
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="InputPropertyExpression"/> class
|
|
/// with a primary data source.
|
|
/// </summary>
|
|
/// <param name="dataSourceGuid">The GUID of the primary data source.</param>
|
|
/// <param name="dataKeyLookup">The key name for data lookup.</param>
|
|
/// <remarks>
|
|
/// Example:
|
|
/// <code language="csharp">
|
|
/// var property = new InputPropertyExpression(
|
|
/// Guid.Parse("41639c8f-fecf-4449-b6e6-53f796c0c3e4"),
|
|
/// "PatientName");
|
|
/// </code>
|
|
/// </remarks>
|
|
public InputPropertyExpression(Guid dataSourceGuid, string dataKeyLookup) : this(dataSourceGuid, null, dataKeyLookup)
|
|
{
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="InputPropertyExpression"/> class
|
|
/// with primary and optional secondary data sources.
|
|
/// </summary>
|
|
/// <param name="dataSourceGuid">The GUID of the primary data source.</param>
|
|
/// <param name="secondaryDataSource">
|
|
/// The optional GUID of the secondary data source.
|
|
/// </param>
|
|
/// <param name="dataKeyLookup">The key name for data lookup.</param>
|
|
/// <remarks>
|
|
/// Example with secondary data source:
|
|
/// <code language="csharp">
|
|
/// var property = new InputPropertyExpression(
|
|
/// Guid.Parse("41639c8f-fecf-4449-b6e6-53f796c0c3e4"),
|
|
/// Guid.Parse("6ef6b1f7-a50c-4198-8866-140bb82e2dda"),
|
|
/// "PatientTypeRollupName");
|
|
/// </code>
|
|
/// </remarks>
|
|
public InputPropertyExpression(Guid dataSourceGuid, Guid? secondaryDataSource, string dataKeyLookup)
|
|
{
|
|
DataSourceGuid = dataSourceGuid;
|
|
SecondaryDataSource = secondaryDataSource;
|
|
DataKeyLookup = dataKeyLookup;
|
|
}
|
|
|
|
public override T Accept<T>(IVisitor<T> visitor)
|
|
{
|
|
return visitor.VisitInputPropertyExpression(this);
|
|
}
|
|
}
|
|
|
|
public class CollectionInputPropertyExpression : InputPropertyExpression
|
|
{
|
|
public CollectionInputPropertyExpression(Guid dataSourceGuid, Guid secondaryDataSource, string dataKeyLookup)
|
|
: base(dataSourceGuid, secondaryDataSource, dataKeyLookup)
|
|
{
|
|
|
|
}
|
|
|
|
public InputPropertyExpression? ItemProperty { get; }
|
|
|
|
public override T Accept<T>(IVisitor<T> visitor)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
public interface IFlatData : IDictionary<string, object>
|
|
{
|
|
string RowIdKey { get; }
|
|
|
|
long RowId { get; }
|
|
}
|
|
|
|
public class MyFlatData : Dictionary<string, object>, IFlatData
|
|
{
|
|
private readonly Lazy<long> _rowId;
|
|
|
|
public MyFlatData() : this(new Dictionary<string, object>())
|
|
{
|
|
|
|
}
|
|
|
|
public MyFlatData(IDictionary<string, object> rawData) : this(rawData, "RowID")
|
|
{
|
|
|
|
}
|
|
|
|
public MyFlatData(IDictionary<string, object> rawData, string rowIdKey) : base(rawData)
|
|
{
|
|
RowIdKey = rowIdKey;
|
|
_rowId = new Lazy<long>(() => this.GetValue<long>(RowIdKey), false);
|
|
}
|
|
|
|
public string RowIdKey { get; }
|
|
|
|
public long RowId => _rowId.Value;
|
|
}
|
|
|
|
public class FlatData : IFlatData
|
|
{
|
|
private readonly IDictionary<string, object> _data;
|
|
|
|
private readonly Lazy<long> _rowId;
|
|
|
|
public FlatData() : this(new Dictionary<string, object>(), "RowID")
|
|
{
|
|
}
|
|
|
|
public FlatData(IDictionary<string, object> rawData, string rowIdKey)
|
|
{
|
|
_data = rawData;
|
|
RowIdKey = rowIdKey;
|
|
_rowId = new Lazy<long>(() => this.GetValue<long>(RowIdKey), false);
|
|
}
|
|
|
|
public string RowIdKey { get; }
|
|
|
|
public long RowId => _rowId.Value;
|
|
|
|
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
|
{
|
|
return _data.GetEnumerator();
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return ((IEnumerable)_data).GetEnumerator();
|
|
}
|
|
|
|
public void Add(KeyValuePair<string, object> item)
|
|
{
|
|
_data.Add(item);
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
_data.Clear();
|
|
}
|
|
|
|
public bool Contains(KeyValuePair<string, object> item)
|
|
{
|
|
return _data.Contains(item);
|
|
}
|
|
|
|
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
|
|
{
|
|
_data.CopyTo(array, arrayIndex);
|
|
}
|
|
|
|
public bool Remove(KeyValuePair<string, object> item)
|
|
{
|
|
return _data.Remove(item);
|
|
}
|
|
|
|
public int Count => _data.Count;
|
|
|
|
public bool IsReadOnly { get; }
|
|
|
|
public void Add(string key, object value)
|
|
{
|
|
_data.Add(key, value);
|
|
}
|
|
|
|
public bool ContainsKey(string key)
|
|
{
|
|
return _data.ContainsKey(key);
|
|
}
|
|
|
|
public bool Remove(string key)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public bool TryGetValue(string key, out object value)
|
|
{
|
|
return _data.TryGetValue(key, out value!);
|
|
}
|
|
|
|
public IEnumerable<string> Keys => _data.Keys;
|
|
|
|
ICollection<object> IDictionary<string, object>.Values => _data.Values;
|
|
|
|
ICollection<string> IDictionary<string, object>.Keys => _data.Keys;
|
|
|
|
public IEnumerable<object> Values => _data.Values;
|
|
|
|
public object this[string key]
|
|
{
|
|
get => _data[key];
|
|
set => _data[key] = value;
|
|
}
|
|
}
|
|
|
|
public static class FlatDataUtils
|
|
{
|
|
private static readonly IFormatProvider _culture = new CultureInfo("en-US");
|
|
|
|
public static object GetValue(this IFlatData data, string key)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(data);
|
|
|
|
if (!data.TryGetValue(key, out object? rawValue))
|
|
{
|
|
throw new ArgumentException(
|
|
$"The specified key is not available. Requested key: [{key}] Available keys: [{string.Join(", ", data.Keys)}]",
|
|
nameof(key));
|
|
}
|
|
|
|
return rawValue;
|
|
}
|
|
|
|
public static TValue GetValue<TValue>(this IFlatData data, string key)
|
|
{
|
|
var rawValue = data.GetValue(key);
|
|
|
|
var convertedValue = (TValue)Convert.ChangeType(rawValue, typeof(TValue), _culture);
|
|
|
|
return convertedValue;
|
|
}
|
|
|
|
public static IEnumerable<long> GetRowIds(this IEnumerable<IFlatData> data)
|
|
{
|
|
return data.Select(x => x.RowId);
|
|
}
|
|
}
|
|
|
|
public interface IHierarchicalData
|
|
{
|
|
/// <summary>
|
|
/// The DataSource for this level of the Hierarchy
|
|
/// </summary>
|
|
Guid DataSourceGuid { get; }
|
|
|
|
/// <summary>
|
|
/// The Data at this level of the Hierarchy
|
|
/// </summary>
|
|
IFlatData Data { get; set; }
|
|
|
|
/// <summary>
|
|
/// Returns all Child Data across all DataSources, or an empty collection if there is no Child Data
|
|
/// </summary>
|
|
IEnumerable<IHierarchicalData> AllChildData { get; set; }
|
|
|
|
/// <summary>
|
|
/// Checks if this record has Child Data for a particular DataSource
|
|
/// </summary>
|
|
/// <param name="dataSourceGuid">The DataSource to check</param>
|
|
/// <returns>True if any Child Data exists for the DataSource</returns>
|
|
bool HasChildData(Guid dataSourceGuid);
|
|
|
|
/// <summary>
|
|
/// Returns the Child Data for the given DataSource
|
|
/// </summary>
|
|
/// <param name="dataSourceGuid">DataSource identifier for the child data</param>
|
|
/// <returns>The Child Data or an empty collection if no child data is set for the DataSource</returns>
|
|
IEnumerable<IHierarchicalData> GetChildData(Guid dataSourceGuid);
|
|
|
|
/// <summary>
|
|
/// Attempts to add Child Data. Will check if child data exists before adding.
|
|
/// </summary>
|
|
/// <param name="datasourceGuid">DataSource identifier for the child data</param>
|
|
/// <param name="data">The Child Data to Add to the Record</param>
|
|
/// <returns>False if child data exists and was not overriden</returns>
|
|
bool TryAddChildData(Guid datasourceGuid, IEnumerable<IHierarchicalData> data);
|
|
|
|
/// <summary>
|
|
/// Add Child Data, will override any existing Child Data for the DataSource
|
|
/// </summary>
|
|
/// <param name="datasourceGuid">DataSource identifier for the child data</param>
|
|
/// <param name="data">The Child Data to Add to the Record</param>
|
|
void SetChildData(Guid datasourceGuid, IEnumerable<IHierarchicalData> data);
|
|
}
|
|
|
|
public class HierarchicalData : IHierarchicalData
|
|
{
|
|
private Dictionary<Guid, List<IHierarchicalData>> _childDataMap;
|
|
|
|
[JsonConstructor]
|
|
public HierarchicalData() : this(Guid.Empty, default!, new List<IHierarchicalData>())
|
|
{
|
|
|
|
}
|
|
|
|
public HierarchicalData(Guid dataSourceGuid, IFlatData rootData) : this(dataSourceGuid, rootData,
|
|
new List<IHierarchicalData>())
|
|
{
|
|
}
|
|
|
|
public HierarchicalData(Guid dataSourceGuid, IFlatData rootData, IEnumerable<IHierarchicalData> childData)
|
|
{
|
|
DataSourceGuid = dataSourceGuid;
|
|
Data = rootData;
|
|
_childDataMap = childData.GroupBy(x => x.DataSourceGuid).ToDictionary(x => x.Key, x => x.ToList());
|
|
}
|
|
|
|
public Guid DataSourceGuid { get; set; }
|
|
|
|
public IFlatData Data { get; set; }
|
|
|
|
[SuppressMessage("Major Code Smell", "S2365:Properties should not make collection or array copies", Justification = "AllChildData is part of the IHierarchicalData interface contract and is JSON-serialized (see Data.json). The flatten-on-get / group-on-set transformation is the deliberate purpose of the property — _childDataMap is the storage form, the property is the wire form.")]
|
|
public IEnumerable<IHierarchicalData> AllChildData
|
|
{
|
|
get => _childDataMap.SelectMany(x => x.Value).ToList();
|
|
set => _childDataMap = value.GroupBy(x => x.DataSourceGuid).ToDictionary(x => x.Key, x => x.ToList());
|
|
}
|
|
|
|
public bool HasChildData(Guid dataSourceGuid)
|
|
{
|
|
return _childDataMap.ContainsKey(dataSourceGuid);
|
|
}
|
|
|
|
public IEnumerable<IHierarchicalData> GetChildData(Guid dataSourceGuid)
|
|
{
|
|
if (HasChildData(dataSourceGuid))
|
|
{
|
|
return _childDataMap[dataSourceGuid];
|
|
}
|
|
|
|
return new List<IHierarchicalData>();
|
|
}
|
|
|
|
public bool TryAddChildData(Guid datasourceGuid, IEnumerable<IHierarchicalData> data)
|
|
{
|
|
if (_childDataMap.ContainsKey(datasourceGuid))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_childDataMap[datasourceGuid] = data.ToList();
|
|
|
|
return true;
|
|
}
|
|
|
|
public void SetChildData(Guid datasourceGuid, IEnumerable<IHierarchicalData> data)
|
|
{
|
|
_childDataMap[datasourceGuid] = data.ToList();
|
|
}
|
|
|
|
public override string? ToString()
|
|
{
|
|
if (Data?.ContainsKey("DimPatientEnEncounterID") ?? false)
|
|
{
|
|
return Data["DimPatientEnEncounterID"].ToString();
|
|
}
|
|
return base.ToString();
|
|
}
|
|
}
|
|
|
|
#region Json Converters
|
|
public class HierarchicalDataConverter : JsonConverter<IHierarchicalData>
|
|
{
|
|
public override IHierarchicalData? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
{
|
|
return JsonSerializer.Deserialize<HierarchicalData>(ref reader, options);
|
|
}
|
|
|
|
public override void Write(Utf8JsonWriter writer, IHierarchicalData value, JsonSerializerOptions options)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
public class FlatDataConverter : JsonConverter<IFlatData>
|
|
{
|
|
public override IFlatData? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
{
|
|
return JsonSerializer.Deserialize<MyFlatData>(ref reader, options);
|
|
}
|
|
|
|
public override void Write(Utf8JsonWriter writer, IFlatData value, JsonSerializerOptions options)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
public class ObjectToInferredTypesConverter : JsonConverter<object>
|
|
{
|
|
public override object Read(
|
|
ref Utf8JsonReader reader,
|
|
Type typeToConvert,
|
|
JsonSerializerOptions options) => reader.TokenType switch
|
|
{
|
|
JsonTokenType.True => true,
|
|
JsonTokenType.False => false,
|
|
JsonTokenType.Number when reader.TryGetInt64(out long l) => l,
|
|
JsonTokenType.Number => reader.GetDouble(),
|
|
JsonTokenType.String when reader.TryGetDateTime(out DateTime datetime) => datetime,
|
|
JsonTokenType.String => reader.GetString()!,
|
|
_ => JsonDocument.ParseValue(ref reader).RootElement.Clone()
|
|
};
|
|
|
|
public override void Write(
|
|
Utf8JsonWriter writer,
|
|
object objectToWrite,
|
|
JsonSerializerOptions options) =>
|
|
JsonSerializer.Serialize(writer, objectToWrite, objectToWrite.GetType(), options);
|
|
}
|
|
#endregion
|
|
|