/Sources/Core/Microsoft.StreamProcessing/StreamableAPI/SelectExtensions.cs

https://github.com/Microsoft/Trill · C# · 229 lines · 134 code · 28 blank · 67 comment · 19 complexity · ad17540c275128b87942fad89d30b88e MD5 · raw file

  1. // *********************************************************************
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. // Licensed under the MIT License
  4. // *********************************************************************
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. using System.Linq.Expressions;
  9. using System.Reflection;
  10. namespace Microsoft.StreamProcessing
  11. {
  12. /// <summary>
  13. /// Static class for transformations to Streamables
  14. /// </summary>
  15. public static partial class Streamable
  16. {
  17. /// <summary>
  18. /// Performs a project over a streamable.
  19. /// </summary>
  20. /// <param name="source">Source streamable for the operation.</param>
  21. /// <param name="selector">Expression over Payload that returns the new Payload.</param>
  22. public static IStreamable<TKey, TResult> Select<TKey, TPayload, TResult>(
  23. this IStreamable<TKey, TPayload> source,
  24. Expression<Func<TPayload, TResult>> selector)
  25. {
  26. Invariant.IsNotNull(source, nameof(source));
  27. Invariant.IsNotNull(selector, nameof(selector));
  28. return source is IFusibleStreamable<TKey, TPayload> s && s.CanFuseSelect(selector, false, false)
  29. ? s.FuseSelect(selector)
  30. : (IStreamable<TKey, TResult>)new SelectStreamable<TKey, TPayload, TResult>(source, selector);
  31. }
  32. /// <summary>
  33. /// Performs a project over a streamable.
  34. /// </summary>
  35. /// <param name="source">Source streamable for the operation.</param>
  36. /// <param name="selector">Expression over StartTime and Payload that returns the new Payload.</param>
  37. public static IStreamable<TKey, TResult> Select<TKey, TPayload, TResult>(
  38. this IStreamable<TKey, TPayload> source,
  39. Expression<Func<long, TPayload, TResult>> selector)
  40. {
  41. Invariant.IsNotNull(source, nameof(source));
  42. Invariant.IsNotNull(selector, nameof(selector));
  43. return source is IFusibleStreamable<TKey, TPayload> s && s.CanFuseSelect(selector, true, false)
  44. ? s.FuseSelect(selector)
  45. : (IStreamable<TKey, TResult>)new SelectStreamable<TKey, TPayload, TResult>(source, selector, hasStartEdge: true);
  46. }
  47. /// <summary>
  48. /// Performs a project over a streamable, relative to the grouping key.
  49. /// </summary>
  50. /// <param name="source">Source streamable for the operation.</param>
  51. /// <param name="selector">Expression over Key and Payload, that returns the new Payload.</param>
  52. public static IStreamable<TKey, TResult> SelectByKey<TKey, TPayload, TResult>(
  53. this IStreamable<TKey, TPayload> source,
  54. Expression<Func<TKey, TPayload, TResult>> selector)
  55. {
  56. Invariant.IsNotNull(source, nameof(source));
  57. Invariant.IsNotNull(selector, nameof(selector));
  58. return source is IFusibleStreamable<TKey, TPayload> s && s.CanFuseSelect(selector, false, true)
  59. ? s.FuseSelectWithKey(selector)
  60. : (IStreamable<TKey, TResult>)new SelectStreamable<TKey, TPayload, TResult>(source, selector, hasKey: true);
  61. }
  62. /// <summary>
  63. /// Performs a project over a streamable, relative to the grouping key.
  64. /// </summary>
  65. /// <param name="source">Source streamable for the operation.</param>
  66. /// <param name="selector">Expression over StartTime, Key, and Payload, that returns the new Payload.</param>
  67. public static IStreamable<TKey, TResult> SelectByKey<TKey, TPayload, TResult>(
  68. this IStreamable<TKey, TPayload> source,
  69. Expression<Func<long, TKey, TPayload, TResult>> selector)
  70. {
  71. Invariant.IsNotNull(source, nameof(source));
  72. Invariant.IsNotNull(selector, nameof(selector));
  73. return source is IFusibleStreamable<TKey, TPayload> s && s.CanFuseSelect(selector, true, true)
  74. ? s.FuseSelectWithKey(selector)
  75. : (IStreamable<TKey, TResult>)new SelectStreamable<TKey, TPayload, TResult>(source, selector, hasStartEdge: true, hasKey: true);
  76. }
  77. /// <summary>
  78. ///
  79. /// </summary>
  80. /// <typeparam name="TKey"></typeparam>
  81. /// <typeparam name="TOld"></typeparam>
  82. /// <typeparam name="TNew"></typeparam>
  83. /// <param name="source"></param>
  84. /// <param name="initializer"></param>
  85. /// <param name="newColumnFormulas"></param>
  86. /// <returns></returns>
  87. public static IStreamable<TKey, TNew> Select<TKey, TOld, TNew>(
  88. this IStreamable<TKey, TOld> source,
  89. Expression<Func<TNew>> initializer,
  90. IDictionary<string, Expression<Func<TOld, object>>> newColumnFormulas = null) where TNew : new()
  91. {
  92. Invariant.IsNotNull(source, nameof(source));
  93. Invariant.IsNotNull(initializer, nameof(initializer));
  94. // Validate that the dictionary parameter references proper fields
  95. if (newColumnFormulas == null) newColumnFormulas = new Dictionary<string, Expression<Func<TOld, object>>>();
  96. Type newType = typeof(TNew);
  97. foreach (var pair in newColumnFormulas)
  98. {
  99. if (newType.GetTypeInfo().GetMember(pair.Key).Length == 0) throw new ArgumentException("Dictionary keys must refer to valid members of the destination type", nameof(newColumnFormulas));
  100. }
  101. if (!(initializer.Body is NewExpression newExpression)) throw new ArgumentException("Initializer must return a constructor expression.", nameof(initializer));
  102. return source.Select(CreateAdjustColumnsExpression<TOld, TNew>(
  103. newColumnFormulas.ToDictionary(o => o.Key, o => (LambdaExpression)o.Value),
  104. newExpression));
  105. }
  106. /// <summary>
  107. ///
  108. /// </summary>
  109. /// <typeparam name="TKey"></typeparam>
  110. /// <typeparam name="TOld"></typeparam>
  111. /// <typeparam name="TNew"></typeparam>
  112. /// <typeparam name="TField1"></typeparam>
  113. /// <param name="source"></param>
  114. /// <param name="initializer"></param>
  115. /// <param name="fieldSelector1"></param>
  116. /// <param name="fieldInitializer1"></param>
  117. /// <returns></returns>
  118. public static IStreamable<TKey, TNew> Select<TKey, TOld, TNew, TField1>(
  119. this IStreamable<TKey, TOld> source,
  120. Expression<Func<TNew>> initializer,
  121. Expression<Func<TNew, TField1>> fieldSelector1,
  122. Expression<Func<TOld, TField1>> fieldInitializer1) where TNew : new()
  123. {
  124. Invariant.IsNotNull(source, nameof(source));
  125. Invariant.IsNotNull(initializer, nameof(initializer));
  126. // Validate that the field selector formulas are actually selector expressions
  127. if (fieldSelector1.Body.NodeType != ExpressionType.MemberAccess)
  128. throw new ArgumentException("Unable to determine destination field name from selector expression - expected a member selection expression.", nameof(fieldSelector1));
  129. if (!(initializer.Body is NewExpression newExpression)) throw new ArgumentException("Initializer must return a constructor expression.", nameof(initializer));
  130. return source.Select(CreateAdjustColumnsExpression<TOld, TNew>(
  131. new Dictionary<string, LambdaExpression>
  132. {
  133. { ((MemberExpression)(fieldSelector1.Body)).Member.Name, fieldInitializer1 },
  134. },
  135. newExpression));
  136. }
  137. /// <summary>
  138. ///
  139. /// </summary>
  140. /// <typeparam name="TKey"></typeparam>
  141. /// <typeparam name="TOld"></typeparam>
  142. /// <typeparam name="TNew"></typeparam>
  143. /// <typeparam name="TField1"></typeparam>
  144. /// <typeparam name="TField2"></typeparam>
  145. /// <param name="source"></param>
  146. /// <param name="initializer"></param>
  147. /// <param name="fieldSelector1"></param>
  148. /// <param name="fieldInitializer1"></param>
  149. /// <param name="fieldSelector2"></param>
  150. /// <param name="fieldInitializer2"></param>
  151. /// <returns></returns>
  152. public static IStreamable<TKey, TNew> Select<TKey, TOld, TNew, TField1, TField2>(
  153. this IStreamable<TKey, TOld> source,
  154. Expression<Func<TNew>> initializer,
  155. Expression<Func<TNew, TField1>> fieldSelector1,
  156. Expression<Func<TOld, TField1>> fieldInitializer1,
  157. Expression<Func<TNew, TField2>> fieldSelector2,
  158. Expression<Func<TOld, TField2>> fieldInitializer2) where TNew : new()
  159. {
  160. Invariant.IsNotNull(source, nameof(source));
  161. Invariant.IsNotNull(initializer, nameof(initializer));
  162. // Validate that the field selector formulas are actually selector expressions
  163. if (fieldSelector1.Body.NodeType != ExpressionType.MemberAccess)
  164. throw new ArgumentException("Unable to determine destination field name from selector expression - expected a member selection expression.", nameof(fieldSelector1));
  165. if (fieldSelector2.Body.NodeType != ExpressionType.MemberAccess)
  166. throw new ArgumentException("Unable to determine destination field name from selector expression - expected a member selection expression.", nameof(fieldSelector2));
  167. if (!(initializer.Body is NewExpression newExpression)) throw new ArgumentException("Initializer must return a constructor expression.", nameof(initializer));
  168. return source.Select(CreateAdjustColumnsExpression<TOld, TNew>(
  169. new Dictionary<string, LambdaExpression>
  170. {
  171. { ((MemberExpression)(fieldSelector1.Body)).Member.Name, fieldInitializer1 },
  172. { ((MemberExpression)(fieldSelector2.Body)).Member.Name, fieldInitializer2 },
  173. },
  174. newExpression));
  175. }
  176. private static Expression<Func<TOld, TNew>> CreateAdjustColumnsExpression<TOld, TNew>(
  177. IDictionary<string, LambdaExpression> newColumnFormulas,
  178. NewExpression newExpression) where TNew : new()
  179. {
  180. Type oldType = typeof(TOld);
  181. Type newType = typeof(TNew);
  182. var inputParameter = Expression.Parameter(oldType, "input");
  183. var oldPublicFields =
  184. oldType.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Select(o => o.Name)
  185. .Union(
  186. oldType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetIndexParameters().Length == 0).Select(o => o.Name));
  187. var newPublicFields =
  188. newType.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Select(o => o.Name)
  189. .Union(
  190. newType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetIndexParameters().Length == 0).Select(o => o.Name));
  191. var fieldsInCommon = oldPublicFields.Intersect(newPublicFields).Except(newColumnFormulas.Keys);
  192. var commonFieldAssignments = fieldsInCommon.Select(
  193. o => Expression.Bind(newType.GetTypeInfo().GetMember(o).Single(), Expression.PropertyOrField(inputParameter, o)));
  194. var newFieldAssignments = newColumnFormulas.Select(
  195. o => Expression.Bind(newType.GetTypeInfo().GetMember(o.Key).Single(), o.Value.RemoveCastToObject().ReplaceParametersInBody(inputParameter)));
  196. var member = Expression.MemberInit(newExpression, commonFieldAssignments.Concat(newFieldAssignments).ToArray());
  197. var lambda = Expression.Lambda<Func<TOld, TNew>>(member, new ParameterExpression[] { inputParameter });
  198. return lambda;
  199. }
  200. }
  201. }