dlin-cli 0.2.2__tar.gz → 0.2.4b1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/Cargo.lock +31 -35
  2. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/Cargo.toml +4 -4
  3. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/PKG-INFO +1 -1
  4. dlin_cli-0.2.4b1/crates/dlin-core/src/graph/column_lineage/single_model.rs +450 -0
  5. dlin_cli-0.2.4b1/crates/dlin-core/src/graph/column_lineage/tests/core_basic.rs +465 -0
  6. dlin_cli-0.2.4b1/crates/dlin-core/src/graph/column_lineage/tests/core_cross_model.rs +293 -0
  7. dlin_cli-0.2.2/crates/dlin-core/src/graph/column_lineage/tests/core.rs → dlin_cli-0.2.4b1/crates/dlin-core/src/graph/column_lineage/tests/core_star.rs +380 -795
  8. dlin_cli-0.2.4b1/crates/dlin-core/src/graph/column_lineage/tests/core_util.rs +33 -0
  9. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/tests/mod.rs +4 -1
  10. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/jinja.rs +107 -54
  11. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/sql.rs +299 -61
  12. dlin_cli-0.2.2/crates/dlin-core/src/graph/column_lineage/single_model.rs +0 -217
  13. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/LICENSE +0 -0
  14. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/README.md +0 -0
  15. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin/Cargo.toml +0 -0
  16. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin/README.md +0 -0
  17. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin/src/cli.rs +0 -0
  18. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin/src/main.rs +0 -0
  19. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin/src/mcp.rs +0 -0
  20. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin/tests/integration_test.rs +0 -0
  21. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/Cargo.toml +0 -0
  22. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/README.md +0 -0
  23. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/error.rs +0 -0
  24. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/builder/mod.rs +0 -0
  25. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/builder/tests/build_graph.rs +0 -0
  26. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/builder/tests/generic_tests.rs +0 -0
  27. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/builder/tests/mod.rs +0 -0
  28. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/builder/tests/ref_parsing.rs +0 -0
  29. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/builder/tests/versioned.rs +0 -0
  30. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/cache.rs +0 -0
  31. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/cross_model.rs +0 -0
  32. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/impact.rs +0 -0
  33. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/mod.rs +0 -0
  34. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/schema.rs +0 -0
  35. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/tests/cache.rs +0 -0
  36. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/tests/impact.rs +0 -0
  37. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/types.rs +0 -0
  38. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/mod.rs +0 -0
  39. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/collapse.rs +0 -0
  40. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/filter_graph.rs +0 -0
  41. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/mod.rs +0 -0
  42. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/output.rs +0 -0
  43. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/search.rs +0 -0
  44. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/selector.rs +0 -0
  45. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot.snap +0 -0
  46. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_bfs_pseudoendpoint.snap +0 -0
  47. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_endpoints_fan_out.snap +0 -0
  48. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_endpoints_leaf_model.snap +0 -0
  49. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_multiple_focus_models.snap +0 -0
  50. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_no_source_exposure.snap +0 -0
  51. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_preserve_focus.snap +0 -0
  52. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__snapshot_transitive_node_type_filter.snap +0 -0
  53. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__snapshot_transitive_select_filter.snap +0 -0
  54. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__snapshot_transitive_select_with_node_type.snap +0 -0
  55. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/transitive.rs +0 -0
  56. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/versioned.rs +0 -0
  57. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/impact.rs +0 -0
  58. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/mod.rs +0 -0
  59. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/types.rs +0 -0
  60. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/input.rs +0 -0
  61. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/lib.rs +0 -0
  62. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/cache.rs +0 -0
  63. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/columns.rs +0 -0
  64. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/discovery.rs +0 -0
  65. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/manifest.rs +0 -0
  66. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/manifest_cache.rs +0 -0
  67. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/mod.rs +0 -0
  68. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/project.rs +0 -0
  69. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/yaml_schema.rs +0 -0
  70. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/ascii.rs +0 -0
  71. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/column_graph.rs +0 -0
  72. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/dot.rs +0 -0
  73. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/html.rs +0 -0
  74. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/impact.rs +0 -0
  75. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/json.rs +0 -0
  76. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/layout.rs +0 -0
  77. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/list.rs +0 -0
  78. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/mermaid.rs +0 -0
  79. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/mod.rs +0 -0
  80. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/plain.rs +0 -0
  81. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_all_transformation_types.snap +0 -0
  82. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_escapes_special_chars.snap +0 -0
  83. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_id_collision_avoided.snap +0 -0
  84. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_impact_indirect.snap +0 -0
  85. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_impact_single.snap +0 -0
  86. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_single_model.snap +0 -0
  87. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_via_path.snap +0 -0
  88. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__impact_mermaid.snap +0 -0
  89. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__impact_mermaid_indirect_edge_label.snap +0 -0
  90. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__impact_plain.snap +0 -0
  91. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__impact_plain_multi_hop.snap +0 -0
  92. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__impact_plain_non_direct_intermediate.snap +0 -0
  93. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__mermaid_dotted_table_name.snap +0 -0
  94. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__mermaid_id_collision_avoided.snap +0 -0
  95. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__mermaid_label_escaping.snap +0 -0
  96. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__mermaid_single_model.snap +0 -0
  97. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__plain_model_path_non_direct_annotation.snap +0 -0
  98. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__plain_no_sources.snap +0 -0
  99. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__plain_single_model.snap +0 -0
  100. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__group_by_node_type.snap +0 -0
  101. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_all_edge_types.snap +0 -0
  102. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_direction_tb.snap +0 -0
  103. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_direction_tb_grouped.snap +0 -0
  104. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_group_by_directory.snap +0 -0
  105. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_lineage.snap +0 -0
  106. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_transitive_edges.snap +0 -0
  107. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__html__tests__snapshot_html_json.snap +0 -0
  108. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__impact__tests__snapshot_impact_json.snap +0 -0
  109. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__impact__tests__snapshot_impact_json_with_sql.snap +0 -0
  110. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__impact__tests__snapshot_impact_text.snap +0 -0
  111. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__impact__tests__snapshot_impact_text_with_sql.snap +0 -0
  112. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__json__tests__snapshot_json_with_sql.snap +0 -0
  113. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__json__tests__snapshot_lineage.snap +0 -0
  114. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__json__tests__snapshot_node_metadata.snap +0 -0
  115. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__list__tests__snapshot_list_json.snap +0 -0
  116. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__list__tests__snapshot_list_plain.snap +0 -0
  117. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__group_by_node_type.snap +0 -0
  118. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__mixed_direct_and_transitive_edges.snap +0 -0
  119. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__show_columns_escapes_quotes.snap +0 -0
  120. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__show_columns_lineage.snap +0 -0
  121. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__show_columns_single_model.snap +0 -0
  122. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__show_columns_with_collapse.snap +0 -0
  123. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__show_columns_with_grouping.snap +0 -0
  124. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__snapshot_direction_tb.snap +0 -0
  125. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__snapshot_direction_tb_grouped.snap +0 -0
  126. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__snapshot_group_by_directory.snap +0 -0
  127. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__snapshot_lineage.snap +0 -0
  128. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__transitive_edge_rendering.snap +0 -0
  129. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__plain__tests__snapshot_plain.snap +0 -0
  130. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__summary__tests__snapshot_summary_json.snap +0 -0
  131. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/snapshots/dlin_core__render__summary__tests__snapshot_summary_text.snap +0 -0
  132. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/summary.rs +0 -0
  133. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/svg.rs +0 -0
  134. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/crates/dlin-core/tests/column_lineage_test.rs +0 -0
  135. {dlin_cli-0.2.2 → dlin_cli-0.2.4b1}/pyproject.toml +0 -0
@@ -87,9 +87,9 @@ dependencies = [
87
87
 
88
88
  [[package]]
89
89
  name = "anyhow"
90
- version = "1.0.102"
90
+ version = "1.0.103"
91
91
  source = "registry+https://github.com/rust-lang/crates.io-index"
92
- checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
92
+ checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
93
93
 
94
94
  [[package]]
95
95
  name = "arraydeque"
@@ -230,7 +230,7 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
230
230
 
231
231
  [[package]]
232
232
  name = "dlin"
233
- version = "0.2.2"
233
+ version = "0.2.4-beta.1"
234
234
  dependencies = [
235
235
  "anyhow",
236
236
  "clap",
@@ -247,7 +247,7 @@ dependencies = [
247
247
 
248
248
  [[package]]
249
249
  name = "dlin-core"
250
- version = "0.2.2"
250
+ version = "0.2.4-beta.1"
251
251
  dependencies = [
252
252
  "anyhow",
253
253
  "clap",
@@ -398,9 +398,9 @@ dependencies = [
398
398
 
399
399
  [[package]]
400
400
  name = "granit-parser"
401
- version = "0.0.3"
401
+ version = "0.0.7"
402
402
  source = "registry+https://github.com/rust-lang/crates.io-index"
403
- checksum = "f50ba32164f9e098d5da618776a32afbb32270adcbe3d3d006107dae11e37c91"
403
+ checksum = "d03f81ad4732830d85cfd417a9f62cde6dadda4354d37d078a6084a19560aa2d"
404
404
  dependencies = [
405
405
  "arraydeque",
406
406
  "smallvec",
@@ -439,9 +439,9 @@ dependencies = [
439
439
 
440
440
  [[package]]
441
441
  name = "insta"
442
- version = "1.47.2"
442
+ version = "1.48.0"
443
443
  source = "registry+https://github.com/rust-lang/crates.io-index"
444
- checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e"
444
+ checksum = "86f0f8fee8c926415c58d6ae43a08523a26faccb2323f5e6b644fe7dd4ef6b82"
445
445
  dependencies = [
446
446
  "console",
447
447
  "once_cell",
@@ -512,9 +512,9 @@ checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b"
512
512
 
513
513
  [[package]]
514
514
  name = "minijinja"
515
- version = "2.20.0"
515
+ version = "2.21.0"
516
516
  source = "registry+https://github.com/rust-lang/crates.io-index"
517
- checksum = "2929e494b2280e1e18959bb2e121da03347ae896896fdfaceaab43c88a02803f"
517
+ checksum = "cb3d648e68cea56d9858d535ee28f9538404e2dd8cb08ed0bd05dca379477f39"
518
518
  dependencies = [
519
519
  "memo-map",
520
520
  "serde",
@@ -597,16 +597,28 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
597
597
 
598
598
  [[package]]
599
599
  name = "polyglot-sql"
600
- version = "0.4.4"
600
+ version = "0.6.2"
601
601
  source = "registry+https://github.com/rust-lang/crates.io-index"
602
- checksum = "b0d5ea9283f03d3b6c32a230e60e7c6bc854e5bec83c399c75cad02bf46e9622"
602
+ checksum = "009cbd62f8c82de46697221ee66b0cf3a6dacbda16f8c5a2ce25853940073bd5"
603
603
  dependencies = [
604
+ "polyglot-sql-ast-derive",
604
605
  "serde",
605
606
  "serde_json",
606
607
  "thiserror 1.0.69",
607
608
  "unicode-segmentation",
608
609
  ]
609
610
 
611
+ [[package]]
612
+ name = "polyglot-sql-ast-derive"
613
+ version = "0.6.2"
614
+ source = "registry+https://github.com/rust-lang/crates.io-index"
615
+ checksum = "e3250a6ffcd1d39670eb532e2b1877ad430799c49cb91b243d63c9425b868161"
616
+ dependencies = [
617
+ "proc-macro2",
618
+ "quote",
619
+ "syn",
620
+ ]
621
+
610
622
  [[package]]
611
623
  name = "proc-macro2"
612
624
  version = "1.0.106"
@@ -717,27 +729,12 @@ dependencies = [
717
729
  "winapi-util",
718
730
  ]
719
731
 
720
- [[package]]
721
- name = "scc"
722
- version = "2.4.0"
723
- source = "registry+https://github.com/rust-lang/crates.io-index"
724
- checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc"
725
- dependencies = [
726
- "sdd",
727
- ]
728
-
729
732
  [[package]]
730
733
  name = "scopeguard"
731
734
  version = "1.2.0"
732
735
  source = "registry+https://github.com/rust-lang/crates.io-index"
733
736
  checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
734
737
 
735
- [[package]]
736
- name = "sdd"
737
- version = "3.0.10"
738
- source = "registry+https://github.com/rust-lang/crates.io-index"
739
- checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
740
-
741
738
  [[package]]
742
739
  name = "serde"
743
740
  version = "1.0.228"
@@ -750,9 +747,9 @@ dependencies = [
750
747
 
751
748
  [[package]]
752
749
  name = "serde-saphyr"
753
- version = "0.0.27"
750
+ version = "0.0.29"
754
751
  source = "registry+https://github.com/rust-lang/crates.io-index"
755
- checksum = "5897b4c3faadadd35fdb6689f015641f3bc481d5adaaac56231ea15aeb243db3"
752
+ checksum = "7bd22781911de0ca6debda95f073c8f18bec65d1a94f1fa9573f3102e514cea4"
756
753
  dependencies = [
757
754
  "ahash",
758
755
  "annotate-snippets",
@@ -762,7 +759,7 @@ dependencies = [
762
759
  "granit-parser",
763
760
  "nohash-hasher",
764
761
  "num-traits",
765
- "serde",
762
+ "serde_core",
766
763
  "smallvec",
767
764
  "zmij",
768
765
  ]
@@ -802,24 +799,23 @@ dependencies = [
802
799
 
803
800
  [[package]]
804
801
  name = "serial_test"
805
- version = "3.4.0"
802
+ version = "3.5.0"
806
803
  source = "registry+https://github.com/rust-lang/crates.io-index"
807
- checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f"
804
+ checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d"
808
805
  dependencies = [
809
806
  "futures-executor",
810
807
  "futures-util",
811
808
  "log",
812
809
  "once_cell",
813
810
  "parking_lot",
814
- "scc",
815
811
  "serial_test_derive",
816
812
  ]
817
813
 
818
814
  [[package]]
819
815
  name = "serial_test_derive"
820
- version = "3.4.0"
816
+ version = "3.5.0"
821
817
  source = "registry+https://github.com/rust-lang/crates.io-index"
822
- checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9"
818
+ checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c"
823
819
  dependencies = [
824
820
  "proc-macro2",
825
821
  "quote",
@@ -3,7 +3,7 @@ members = ["crates/*"]
3
3
  resolver = "3"
4
4
 
5
5
  [workspace.package]
6
- version = "0.2.2"
6
+ version = "0.2.4-beta.1"
7
7
  edition = "2024"
8
8
  license = "MIT"
9
9
  repository = "https://github.com/eitsupi/dlin"
@@ -13,7 +13,7 @@ homepage = "https://github.com/eitsupi/dlin"
13
13
  petgraph = { version = "0.8", features = ["serde-1"] }
14
14
  serde = { version = "1", features = ["derive"] }
15
15
  serde_json = "1"
16
- serde-saphyr = "0.0.27"
16
+ serde-saphyr = "0.0.29"
17
17
  regex = "1"
18
18
  walkdir = "2"
19
19
  colored = "3"
@@ -23,7 +23,7 @@ indexmap = "2"
23
23
  minijinja = "2"
24
24
  rayon = "1"
25
25
  globset = "0.4"
26
- polyglot-sql = { version = "0.4.4", default-features = false, features = ["all-dialects", "semantic"] }
26
+ polyglot-sql = { version = "0.6.2", default-features = false, features = ["all-dialects", "semantic"] }
27
27
  path-slash = "0.2.1"
28
28
  clap = { version = "4", features = ["derive", "env"] }
29
29
  libc = "0.2"
@@ -34,7 +34,7 @@ insta = "1"
34
34
  serial_test = "3.4.0"
35
35
 
36
36
  # internal
37
- dlin-core = { version = "0.2.2", path = "crates/dlin-core" }
37
+ dlin-core = { version = "0.2.4-beta.1", path = "crates/dlin-core" }
38
38
 
39
39
  [workspace.lints.rust]
40
40
  unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dlin-cli
3
- Version: 0.2.2
3
+ Version: 0.2.4b1
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -0,0 +1,450 @@
1
+ use polyglot_sql::lineage::LineageNode;
2
+ use polyglot_sql::scope::SourceKind;
3
+ use polyglot_sql::{DialectType, Expression};
4
+
5
+ use crate::parser::manifest::Manifest;
6
+
7
+ use super::schema::build_schema_from_manifest;
8
+ use super::{ColumnSource, TransformationType};
9
+
10
+ pub(super) struct LineageContext {
11
+ pub(super) expanded_expr: Expression,
12
+ schema: Option<polyglot_sql::MappingSchema>,
13
+ dialect: DialectType,
14
+ }
15
+
16
+ pub(super) fn prepare_lineage_context(
17
+ compiled_code: &str,
18
+ manifest: &Manifest,
19
+ node: &crate::parser::manifest::ManifestNode,
20
+ dialect: DialectType,
21
+ ) -> Result<LineageContext, String> {
22
+ prepare_lineage_context_with_expr(compiled_code, manifest, node, dialect, None)
23
+ }
24
+
25
+ pub(super) fn prepare_lineage_context_with_expr(
26
+ compiled_code: &str,
27
+ manifest: &Manifest,
28
+ node: &crate::parser::manifest::ManifestNode,
29
+ dialect: DialectType,
30
+ parsed_expr: Option<&Expression>,
31
+ ) -> Result<LineageContext, String> {
32
+ let expr = match parsed_expr {
33
+ Some(e) => e.clone(),
34
+ None => polyglot_sql::parse_one(compiled_code, dialect).map_err(|e| format!("{}", e))?,
35
+ };
36
+
37
+ let schema = build_schema_from_manifest(manifest, node, dialect);
38
+
39
+ let mut expanded_expr = expr;
40
+ polyglot_sql::lineage::expand_cte_stars(
41
+ &mut expanded_expr,
42
+ schema.as_ref().map(|s| s as &dyn polyglot_sql::Schema),
43
+ );
44
+
45
+ Ok(LineageContext {
46
+ expanded_expr,
47
+ schema,
48
+ dialect,
49
+ })
50
+ }
51
+
52
+ pub(super) fn run_column_lineage(
53
+ col_name: &str,
54
+ ctx: &LineageContext,
55
+ ) -> Result<ColumnLineageResult, String> {
56
+ let dialect = Some(ctx.dialect);
57
+ let lineage_result = if let Some(ref s) = ctx.schema {
58
+ polyglot_sql::lineage::lineage_with_schema(
59
+ col_name,
60
+ &ctx.expanded_expr,
61
+ Some(s as &dyn polyglot_sql::Schema),
62
+ dialect,
63
+ false,
64
+ )
65
+ .or_else(|_| polyglot_sql::lineage::lineage(col_name, &ctx.expanded_expr, dialect, false))
66
+ } else {
67
+ polyglot_sql::lineage::lineage(col_name, &ctx.expanded_expr, dialect, false)
68
+ };
69
+
70
+ match lineage_result {
71
+ Ok(node) => {
72
+ if lineage_depends_on_unresolved_star(&node, true, false, None, Some(ctx.dialect)) {
73
+ Err(format!("Cannot find column '{}' in query", col_name))
74
+ } else {
75
+ Ok(extract_leaf_sources(&node))
76
+ }
77
+ }
78
+ Err(e) => Err(format_lineage_error(&e)),
79
+ }
80
+ }
81
+
82
+ /// Return whether lineage for this column relied on polyglot-sql's schema-less
83
+ /// star passthrough. A star elsewhere in the query is not sufficient: explicit
84
+ /// output expressions must remain traceable independently.
85
+ fn lineage_depends_on_unresolved_star(
86
+ node: &LineageNode,
87
+ is_root: bool,
88
+ is_set_operation_branch: bool,
89
+ set_operation_ordinal: Option<usize>,
90
+ dialect: Option<DialectType>,
91
+ ) -> bool {
92
+ let column_name = node
93
+ .reference_node_name
94
+ .rsplit_once('.')
95
+ .map_or(node.name.as_str(), |(_, name)| name);
96
+
97
+ let is_set_operation = matches!(
98
+ &node.source,
99
+ Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_)
100
+ );
101
+ let set_operation_ordinal = if is_set_operation {
102
+ column_to_ordinal(&node.source, column_name, dialect)
103
+ .or_else(|| synthetic_ordinal(column_name))
104
+ } else {
105
+ set_operation_ordinal
106
+ };
107
+ if is_set_operation
108
+ && set_operation_ordinal
109
+ .is_some_and(|ordinal| set_operation_has_star_dependent_branch(&node.source, ordinal))
110
+ {
111
+ return true;
112
+ }
113
+ let crosses_query_boundary =
114
+ matches!(node.source_kind, SourceKind::Cte | SourceKind::DerivedTable);
115
+ let source_has_star = has_unresolved_stars(&node.source);
116
+ let source_has_explicit_column = select_has_explicit_output(&node.source, column_name, dialect);
117
+ let branch_projection_checked = is_set_operation_branch
118
+ && !crosses_query_boundary
119
+ && matches!(&node.source, Expression::Select(_))
120
+ && set_operation_ordinal.is_some();
121
+ let branch_projection_is_star = branch_projection_checked
122
+ && set_operation_ordinal
123
+ .is_some_and(|ordinal| projection_ordinal_depends_on_star(&node.source, ordinal));
124
+
125
+ if is_root {
126
+ if matches!(&node.expression, Expression::Column(c) if c.table.is_some())
127
+ && source_has_star
128
+ && !source_has_explicit_column
129
+ {
130
+ return true;
131
+ }
132
+ } else if branch_projection_is_star
133
+ || (!branch_projection_checked
134
+ && !is_set_operation
135
+ && (is_set_operation_branch
136
+ || matches!(
137
+ &node.source,
138
+ Expression::Select(_)
139
+ | Expression::Union(_)
140
+ | Expression::Intersect(_)
141
+ | Expression::Except(_)
142
+ | Expression::Subquery(_)
143
+ | Expression::Cte(_)
144
+ | Expression::Paren(_)
145
+ ))
146
+ && source_has_star
147
+ && !source_has_explicit_column)
148
+ {
149
+ return true;
150
+ }
151
+
152
+ let child_set_operation_ordinal = if is_set_operation || !crosses_query_boundary {
153
+ set_operation_ordinal
154
+ } else {
155
+ None
156
+ };
157
+ node.downstream.iter().any(|child| {
158
+ lineage_depends_on_unresolved_star(
159
+ child,
160
+ false,
161
+ is_set_operation_branch || is_set_operation,
162
+ child_set_operation_ordinal,
163
+ dialect,
164
+ )
165
+ })
166
+ }
167
+
168
+ /// Find the positional index of a column name in a set operation's first SELECT branch.
169
+ fn column_to_ordinal(
170
+ set_operation_expr: &Expression,
171
+ column_name: &str,
172
+ dialect: Option<DialectType>,
173
+ ) -> Option<usize> {
174
+ let normalized_column_name = polyglot_sql::normalize_name(column_name, dialect, false, true);
175
+ let mut expr = set_operation_expr;
176
+
177
+ loop {
178
+ match expr {
179
+ Expression::Union(union) => expr = &union.left,
180
+ Expression::Intersect(intersect) => expr = &intersect.left,
181
+ Expression::Except(except) => expr = &except.left,
182
+ Expression::Annotated(annotated) => expr = &annotated.this,
183
+ Expression::Subquery(subquery) => expr = &subquery.this,
184
+ Expression::Cte(cte) => expr = &cte.this,
185
+ Expression::Paren(paren) => expr = &paren.this,
186
+ Expression::Select(select) => {
187
+ return select.expressions.iter().position(|expr| {
188
+ explicit_output_name(expr).is_some_and(|name| {
189
+ polyglot_sql::normalize_name(name, dialect, false, true)
190
+ == normalized_column_name
191
+ })
192
+ });
193
+ }
194
+ _ => return None,
195
+ }
196
+ }
197
+ }
198
+
199
+ /// Polyglot names an index-resolved set-operation node `_N`.
200
+ fn synthetic_ordinal(column_name: &str) -> Option<usize> {
201
+ column_name.strip_prefix('_')?.parse().ok()
202
+ }
203
+
204
+ fn projection_ordinal_depends_on_star(expr: &Expression, ordinal: usize) -> bool {
205
+ match expr {
206
+ Expression::Annotated(annotated) => {
207
+ projection_ordinal_depends_on_star(&annotated.this, ordinal)
208
+ }
209
+ Expression::Select(select) => select
210
+ .expressions
211
+ .iter()
212
+ .position(expression_is_star)
213
+ .is_some_and(|first_star_ordinal| ordinal >= first_star_ordinal),
214
+ _ => false,
215
+ }
216
+ }
217
+
218
+ fn set_operation_has_star_dependent_branch(expr: &Expression, ordinal: usize) -> bool {
219
+ match expr {
220
+ Expression::Union(union) => {
221
+ set_operation_has_star_dependent_branch(&union.left, ordinal)
222
+ || set_operation_has_star_dependent_branch(&union.right, ordinal)
223
+ }
224
+ Expression::Intersect(intersect) => {
225
+ set_operation_has_star_dependent_branch(&intersect.left, ordinal)
226
+ || set_operation_has_star_dependent_branch(&intersect.right, ordinal)
227
+ }
228
+ Expression::Except(except) => {
229
+ set_operation_has_star_dependent_branch(&except.left, ordinal)
230
+ || set_operation_has_star_dependent_branch(&except.right, ordinal)
231
+ }
232
+ Expression::Annotated(annotated) => {
233
+ set_operation_has_star_dependent_branch(&annotated.this, ordinal)
234
+ }
235
+ Expression::Subquery(subquery) => {
236
+ set_operation_has_star_dependent_branch(&subquery.this, ordinal)
237
+ }
238
+ Expression::Cte(cte) => set_operation_has_star_dependent_branch(&cte.this, ordinal),
239
+ Expression::Paren(paren) => set_operation_has_star_dependent_branch(&paren.this, ordinal),
240
+ Expression::Select(_) => projection_ordinal_depends_on_star(expr, ordinal),
241
+ _ => false,
242
+ }
243
+ }
244
+
245
+ fn select_has_explicit_output(
246
+ expr: &Expression,
247
+ column_name: &str,
248
+ dialect: Option<DialectType>,
249
+ ) -> bool {
250
+ let normalized_column_name = polyglot_sql::normalize_name(column_name, dialect, false, true);
251
+
252
+ match expr {
253
+ Expression::Annotated(annotated) => {
254
+ select_has_explicit_output(&annotated.this, column_name, dialect)
255
+ }
256
+ Expression::Select(select) => select.expressions.iter().any(|expr| {
257
+ let output_name = explicit_output_name(expr);
258
+ output_name.is_some_and(|name| {
259
+ name != "*"
260
+ && polyglot_sql::normalize_name(name, dialect, false, true)
261
+ == normalized_column_name
262
+ })
263
+ }),
264
+ Expression::Union(union) => select_has_explicit_output(&union.left, column_name, dialect),
265
+ Expression::Intersect(intersect) => {
266
+ select_has_explicit_output(&intersect.left, column_name, dialect)
267
+ }
268
+ Expression::Except(except) => {
269
+ select_has_explicit_output(&except.left, column_name, dialect)
270
+ }
271
+ Expression::Subquery(subquery) => {
272
+ select_has_explicit_output(&subquery.this, column_name, dialect)
273
+ }
274
+ Expression::Cte(cte) => select_has_explicit_output(&cte.this, column_name, dialect),
275
+ Expression::Paren(paren) => select_has_explicit_output(&paren.this, column_name, dialect),
276
+ _ => false,
277
+ }
278
+ }
279
+
280
+ fn explicit_output_name(expr: &Expression) -> Option<&str> {
281
+ match expr {
282
+ Expression::Annotated(annotated) => explicit_output_name(&annotated.this),
283
+ Expression::Alias(alias) => Some(alias.alias.name.as_str()),
284
+ Expression::Column(column) => Some(column.name.name.as_str()),
285
+ Expression::Identifier(identifier) => Some(identifier.name.as_str()),
286
+ _ => None,
287
+ }
288
+ }
289
+
290
+ pub(super) fn has_unresolved_stars(expr: &Expression) -> bool {
291
+ match expr {
292
+ Expression::Select(select) => {
293
+ let outer_has_star = select.expressions.iter().any(expression_is_star);
294
+ if outer_has_star {
295
+ return true;
296
+ }
297
+ if let Some(with) = &select.with
298
+ && with.ctes.iter().any(|cte| has_unresolved_stars(&cte.this))
299
+ {
300
+ return true;
301
+ }
302
+ if let Some(from) = &select.from
303
+ && from.expressions.iter().any(has_unresolved_stars)
304
+ {
305
+ return true;
306
+ }
307
+ if select.joins.iter().any(|j| has_unresolved_stars(&j.this)) {
308
+ return true;
309
+ }
310
+ false
311
+ }
312
+ Expression::Subquery(subq) => has_unresolved_stars(&subq.this),
313
+ Expression::Cte(cte) => has_unresolved_stars(&cte.this),
314
+ Expression::Annotated(annotated) => has_unresolved_stars(&annotated.this),
315
+ Expression::Paren(paren) => has_unresolved_stars(&paren.this),
316
+ Expression::Union(union) => {
317
+ has_unresolved_stars(&union.left) || has_unresolved_stars(&union.right)
318
+ }
319
+ Expression::Intersect(intersect) => {
320
+ has_unresolved_stars(&intersect.left) || has_unresolved_stars(&intersect.right)
321
+ }
322
+ Expression::Except(except) => {
323
+ has_unresolved_stars(&except.left) || has_unresolved_stars(&except.right)
324
+ }
325
+ _ => false,
326
+ }
327
+ }
328
+
329
+ fn expression_is_star(expr: &Expression) -> bool {
330
+ match expr {
331
+ Expression::Star(_) => true,
332
+ Expression::Column(column) => column.name.name == "*",
333
+ Expression::Annotated(annotated) => expression_is_star(&annotated.this),
334
+ _ => false,
335
+ }
336
+ }
337
+
338
+ pub(super) fn format_lineage_error(e: &polyglot_sql::Error) -> String {
339
+ let msg = e.to_string();
340
+ if let Some(rest) = msg
341
+ .strip_prefix("Parse error at line 0, column 0: ")
342
+ .or_else(|| msg.strip_prefix("Syntax error at line 0, column 0: "))
343
+ {
344
+ format!("lineage failed: {}", rest)
345
+ } else if msg.starts_with("Internal error: ") {
346
+ format!(
347
+ "lineage failed: {}",
348
+ msg.strip_prefix("Internal error: ").unwrap()
349
+ )
350
+ } else {
351
+ msg
352
+ }
353
+ }
354
+
355
+ fn classify_transformation(node: &LineageNode) -> TransformationType {
356
+ let t = classify_expression(&node.expression);
357
+ if t != TransformationType::Direct || node.downstream.is_empty() {
358
+ return t;
359
+ }
360
+ // Direct pass-through: traverse downstream to find the actual transformation type.
361
+ // This handles the case where a CTE computes an expression and a later SELECT
362
+ // references the result by name (which looks like Column → Direct at the surface).
363
+ for child in &node.downstream {
364
+ let child_t = classify_transformation(child);
365
+ if child_t != TransformationType::Direct {
366
+ return child_t;
367
+ }
368
+ }
369
+ TransformationType::Direct
370
+ }
371
+
372
+ fn classify_expression(expr: &Expression) -> TransformationType {
373
+ match expr {
374
+ Expression::Column(_) | Expression::Identifier(_) => TransformationType::Direct,
375
+ Expression::Alias(alias) => classify_expression(&alias.this),
376
+ Expression::Count(_)
377
+ | Expression::Sum(_)
378
+ | Expression::Avg(_)
379
+ | Expression::Min(_)
380
+ | Expression::Max(_) => TransformationType::Aggregation,
381
+ Expression::Cast(_) => TransformationType::Cast,
382
+ Expression::Case(_) => TransformationType::Conditional,
383
+ Expression::Add(_) | Expression::Sub(_) | Expression::Mul(_) | Expression::Div(_) => {
384
+ TransformationType::Expression
385
+ }
386
+ Expression::Anonymous(_) | Expression::Coalesce(_) | Expression::NullIf(_) => {
387
+ TransformationType::Expression
388
+ }
389
+ // Generic function calls (covers CONCAT, NULLIF, SPLIT, etc. parsed as Function)
390
+ Expression::Function(_) => TransformationType::Expression,
391
+ // Specialized scalar function variants with their own AST types
392
+ Expression::Upper(_)
393
+ | Expression::Lower(_)
394
+ | Expression::Length(_)
395
+ | Expression::Concat(_) => TransformationType::Expression,
396
+ _ => TransformationType::Unknown,
397
+ }
398
+ }
399
+
400
+ pub(super) struct ColumnLineageResult {
401
+ pub(super) sources: Vec<ColumnSource>,
402
+ pub(super) transformation: TransformationType,
403
+ }
404
+
405
+ fn extract_leaf_sources(node: &LineageNode) -> ColumnLineageResult {
406
+ let transformation = classify_transformation(node);
407
+ let mut sources = Vec::new();
408
+ collect_leaves(node, &mut sources);
409
+ sources.sort_by(|a, b| (&a.table, &a.column).cmp(&(&b.table, &b.column)));
410
+ sources.dedup();
411
+ ColumnLineageResult {
412
+ sources,
413
+ transformation,
414
+ }
415
+ }
416
+
417
+ fn collect_leaves(node: &LineageNode, sources: &mut Vec<ColumnSource>) {
418
+ if node.downstream.is_empty() {
419
+ if node.source_kind == SourceKind::Virtual {
420
+ return;
421
+ }
422
+ let name = &node.name;
423
+ if let Some((alias, column)) = name.rsplit_once('.') {
424
+ // polyglot-sql puts the SQL alias in node.name (e.g. "c.customer_id") and the
425
+ // actual table name in source_name (e.g. "stg_customers"). Use source_name
426
+ // when it genuinely differs from the alias, including fully-qualified forms
427
+ // (e.g. "shop.main.stg_orders" when the SQL uses the short name "stg_orders").
428
+ let table = if !node.source_name.is_empty() && node.source_name != alias {
429
+ node.source_name.as_str()
430
+ } else {
431
+ alias
432
+ };
433
+ sources.push(ColumnSource {
434
+ table: table.to_string(),
435
+ column: column.to_string(),
436
+ model_path: vec![],
437
+ });
438
+ } else {
439
+ sources.push(ColumnSource {
440
+ table: String::new(),
441
+ column: name.to_string(),
442
+ model_path: vec![],
443
+ });
444
+ }
445
+ } else {
446
+ for child in &node.downstream {
447
+ collect_leaves(child, sources);
448
+ }
449
+ }
450
+ }