dlin-cli 0.2.3__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.3 → dlin_cli-0.2.4b1}/Cargo.lock +27 -15
  2. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/Cargo.toml +4 -4
  3. {dlin_cli-0.2.3 → 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.3/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.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/tests/mod.rs +4 -1
  10. dlin_cli-0.2.3/crates/dlin-core/src/graph/column_lineage/single_model.rs +0 -217
  11. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/LICENSE +0 -0
  12. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/README.md +0 -0
  13. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin/Cargo.toml +0 -0
  14. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin/README.md +0 -0
  15. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin/src/cli.rs +0 -0
  16. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin/src/main.rs +0 -0
  17. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin/src/mcp.rs +0 -0
  18. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin/tests/integration_test.rs +0 -0
  19. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/Cargo.toml +0 -0
  20. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/README.md +0 -0
  21. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/error.rs +0 -0
  22. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/builder/mod.rs +0 -0
  23. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/builder/tests/build_graph.rs +0 -0
  24. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/builder/tests/generic_tests.rs +0 -0
  25. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/builder/tests/mod.rs +0 -0
  26. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/builder/tests/ref_parsing.rs +0 -0
  27. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/builder/tests/versioned.rs +0 -0
  28. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/cache.rs +0 -0
  29. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/cross_model.rs +0 -0
  30. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/impact.rs +0 -0
  31. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/mod.rs +0 -0
  32. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/schema.rs +0 -0
  33. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/tests/cache.rs +0 -0
  34. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/tests/impact.rs +0 -0
  35. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/column_lineage/types.rs +0 -0
  36. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/mod.rs +0 -0
  37. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/collapse.rs +0 -0
  38. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/filter_graph.rs +0 -0
  39. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/mod.rs +0 -0
  40. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/output.rs +0 -0
  41. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/search.rs +0 -0
  42. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/selector.rs +0 -0
  43. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot.snap +0 -0
  44. {dlin_cli-0.2.3 → 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
  45. {dlin_cli-0.2.3 → 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
  46. {dlin_cli-0.2.3 → 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
  47. {dlin_cli-0.2.3 → 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
  48. {dlin_cli-0.2.3 → 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
  49. {dlin_cli-0.2.3 → 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
  50. {dlin_cli-0.2.3 → 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
  51. {dlin_cli-0.2.3 → 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
  52. {dlin_cli-0.2.3 → 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
  53. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/transitive.rs +0 -0
  54. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/filter/tests/versioned.rs +0 -0
  55. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/impact.rs +0 -0
  56. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/mod.rs +0 -0
  57. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/graph/types.rs +0 -0
  58. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/input.rs +0 -0
  59. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/lib.rs +0 -0
  60. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/cache.rs +0 -0
  61. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/columns.rs +0 -0
  62. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/discovery.rs +0 -0
  63. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/jinja.rs +0 -0
  64. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/manifest.rs +0 -0
  65. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/manifest_cache.rs +0 -0
  66. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/mod.rs +0 -0
  67. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/project.rs +0 -0
  68. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/sql.rs +0 -0
  69. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/parser/yaml_schema.rs +0 -0
  70. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/ascii.rs +0 -0
  71. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/column_graph.rs +0 -0
  72. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/dot.rs +0 -0
  73. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/html.rs +0 -0
  74. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/impact.rs +0 -0
  75. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/json.rs +0 -0
  76. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/layout.rs +0 -0
  77. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/list.rs +0 -0
  78. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/mermaid.rs +0 -0
  79. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/mod.rs +0 -0
  80. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/plain.rs +0 -0
  81. {dlin_cli-0.2.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → 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.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/summary.rs +0 -0
  133. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/src/render/svg.rs +0 -0
  134. {dlin_cli-0.2.3 → dlin_cli-0.2.4b1}/crates/dlin-core/tests/column_lineage_test.rs +0 -0
  135. {dlin_cli-0.2.3 → 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.3"
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.3"
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"
@@ -735,9 +747,9 @@ dependencies = [
735
747
 
736
748
  [[package]]
737
749
  name = "serde-saphyr"
738
- version = "0.0.27"
750
+ version = "0.0.29"
739
751
  source = "registry+https://github.com/rust-lang/crates.io-index"
740
- checksum = "5897b4c3faadadd35fdb6689f015641f3bc481d5adaaac56231ea15aeb243db3"
752
+ checksum = "7bd22781911de0ca6debda95f073c8f18bec65d1a94f1fa9573f3102e514cea4"
741
753
  dependencies = [
742
754
  "ahash",
743
755
  "annotate-snippets",
@@ -747,7 +759,7 @@ dependencies = [
747
759
  "granit-parser",
748
760
  "nohash-hasher",
749
761
  "num-traits",
750
- "serde",
762
+ "serde_core",
751
763
  "smallvec",
752
764
  "zmij",
753
765
  ]
@@ -3,7 +3,7 @@ members = ["crates/*"]
3
3
  resolver = "3"
4
4
 
5
5
  [workspace.package]
6
- version = "0.2.3"
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.3", 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.3
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
+ }