chore: initial git load of code space
This commit is contained in:
@@ -0,0 +1,474 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Interfaces.Core;
|
||||
|
||||
using System.Collections;
|
||||
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)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
if (!data.ContainsKey(key))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"The specified key is not available. Requested key: [{key}] Available keys: [{string.Join(", ", data.Keys)}]",
|
||||
nameof(key));
|
||||
}
|
||||
|
||||
var rawValue = data[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; }
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user