S0DimValueKind.cs 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
  1. namespace Admin.NET.Plugin.AiDOP.DataPlatform.S0Dim;
  2. /// <summary>
  3. /// dim 列的取值方式,决定 <see cref="S0DimSqlBuilder"/> 为该列生成哪种 SQL 表达式。
  4. /// 除 <see cref="TenantIdColumn"/> 外全部作用于 staging 的 <c>raw_data</c> JSON,
  5. /// 底层统一走 <see cref="MdpJsonSql"/>(已处理 JSON null → 字符串 'null'、ISO-T 时间、STRICT CAST)。
  6. /// </summary>
  7. public enum S0DimValueKind
  8. {
  9. /// <summary>取 staging 的物理列 <c>s.tenant_id</c>,不走 raw_data。租户身份的唯一来源。</summary>
  10. TenantIdColumn,
  11. /// <summary>可空字符串。</summary>
  12. Str,
  13. /// <summary>
  14. /// 可空整数(SIGNED)。
  15. ///
  16. /// <para>🔴 <b>源列若是 MySQL 的 <c>bit(1)</c>,绝不能用本 Kind</b>(2026-09-07 实测):
  17. /// 对账两侧都走 <c>IFNULL(CAST(expr AS CHAR), 哨兵)</c>,而
  18. /// <c>CAST(b'1' AS CHAR)</c> 返回的是**裸字节** <c>0x01</c>,
  19. /// dim 侧的 <c>CAST(1 AS CHAR)</c> 是字符 <c>'1'</c> = <c>0x31</c> —— 两者不等,
  20. /// 于是 <b>source_chk ≠ dim_chk,每一轮刷新都会被阻断对账打回</b>,
  21. /// 而数据本身其实是对的,排查时极易怀疑错方向。</para>
  22. ///
  23. /// <para>正确做法:<c>bit(1)</c> 用 <see cref="BoolTrue"/>(其 Norm 是双层
  24. /// <c>CAST(CAST(… AS UNSIGNED) AS CHAR)</c>,能把 bit 正规化成 <c>'1'</c>/<c>'0'</c>)。
  25. /// 但要先确认该列**不会为 NULL** —— <c>BoolTrue</c> 的可辨识性守卫要求原值
  26. /// ∈ <c>{1,0,true,false}</c>,NULL 会让整行被过滤、行数对账 FAIL。
  27. /// 两个条件都不满足时,本阶段的结论是**不物化该列**。</para>
  28. /// </summary>
  29. Int,
  30. /// <summary>可空 DECIMAL(Precision, Scale)。</summary>
  31. Dec,
  32. /// <summary>秒级 datetime(源侧微秒被截断,与对账口径一致)。</summary>
  33. DateTimeSec,
  34. /// <summary>
  35. /// 布尔判真,产出 1/0。
  36. /// ⚠️ <see cref="MdpJsonSql.BoolTrue"/> 会把 JSON null / 无法识别的值**静默判为 0**。
  37. /// 为杜绝静默失真,<see cref="S0DimSqlBuilder"/> 对本类型**强制追加可辨识性守卫**
  38. /// (原始值必须是 1/0/true/false 之一),不满足的行被过滤掉 →
  39. /// 触发 <c>dim_count &lt; stg_count</c> → 对账 FAIL,问题被暴露而非被吞掉。
  40. /// </summary>
  41. BoolTrue
  42. }