dlin-cli 0.2.1__tar.gz → 0.2.2__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 (134) hide show
  1. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/Cargo.lock +8 -8
  2. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/Cargo.toml +4 -4
  3. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/PKG-INFO +1 -1
  4. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin/src/cli.rs +10 -6
  5. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin/src/main.rs +37 -4
  6. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin/src/mcp.rs +88 -3
  7. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin/tests/integration_test.rs +245 -1
  8. dlin_cli-0.2.2/crates/dlin-core/src/graph/builder/mod.rs +1254 -0
  9. dlin_cli-0.2.2/crates/dlin-core/src/graph/builder/tests/build_graph.rs +1077 -0
  10. dlin_cli-0.2.2/crates/dlin-core/src/graph/builder/tests/generic_tests.rs +184 -0
  11. dlin_cli-0.2.2/crates/dlin-core/src/graph/builder/tests/mod.rs +49 -0
  12. dlin_cli-0.2.2/crates/dlin-core/src/graph/builder/tests/ref_parsing.rs +97 -0
  13. dlin_cli-0.2.2/crates/dlin-core/src/graph/builder/tests/versioned.rs +589 -0
  14. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/graph/column_lineage/single_model.rs +3 -0
  15. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/graph/column_lineage/tests/core.rs +62 -0
  16. dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/mod.rs +598 -0
  17. dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/collapse.rs +721 -0
  18. dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/filter_graph.rs +190 -0
  19. dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/mod.rs +188 -0
  20. dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/output.rs +161 -0
  21. dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/search.rs +95 -0
  22. dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/selector.rs +323 -0
  23. dlin_cli-0.2.1/crates/dlin-core/src/graph/snapshots/dlin_core__graph__filter__tests__collapse_snapshot.snap → dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot.snap +2 -1
  24. dlin_cli-0.2.1/crates/dlin-core/src/graph/snapshots/dlin_core__graph__filter__tests__collapse_snapshot_bfs_pseudoendpoint.snap → dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_bfs_pseudoendpoint.snap +2 -1
  25. dlin_cli-0.2.1/crates/dlin-core/src/graph/snapshots/dlin_core__graph__filter__tests__collapse_snapshot_endpoints_fan_out.snap → dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_endpoints_fan_out.snap +2 -1
  26. dlin_cli-0.2.1/crates/dlin-core/src/graph/snapshots/dlin_core__graph__filter__tests__collapse_snapshot_endpoints_leaf_model.snap → dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_endpoints_leaf_model.snap +2 -1
  27. dlin_cli-0.2.1/crates/dlin-core/src/graph/snapshots/dlin_core__graph__filter__tests__collapse_snapshot_multiple_focus_models.snap → dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_multiple_focus_models.snap +2 -1
  28. dlin_cli-0.2.1/crates/dlin-core/src/graph/snapshots/dlin_core__graph__filter__tests__collapse_snapshot_no_source_exposure.snap → dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_no_source_exposure.snap +2 -1
  29. dlin_cli-0.2.1/crates/dlin-core/src/graph/snapshots/dlin_core__graph__filter__tests__collapse_snapshot_preserve_focus.snap → dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_preserve_focus.snap +2 -1
  30. dlin_cli-0.2.1/crates/dlin-core/src/graph/snapshots/dlin_core__graph__filter__tests__snapshot_transitive_node_type_filter.snap → dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__snapshot_transitive_node_type_filter.snap +2 -1
  31. dlin_cli-0.2.1/crates/dlin-core/src/graph/snapshots/dlin_core__graph__filter__tests__snapshot_transitive_select_filter.snap → dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__snapshot_transitive_select_filter.snap +2 -1
  32. dlin_cli-0.2.1/crates/dlin-core/src/graph/snapshots/dlin_core__graph__filter__tests__snapshot_transitive_select_with_node_type.snap → dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__snapshot_transitive_select_with_node_type.snap +2 -1
  33. dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/transitive.rs +177 -0
  34. dlin_cli-0.2.2/crates/dlin-core/src/graph/filter/tests/versioned.rs +128 -0
  35. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/graph/impact.rs +1 -0
  36. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/graph/types.rs +16 -0
  37. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/input.rs +49 -0
  38. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/parser/cache.rs +2 -0
  39. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/parser/jinja.rs +105 -8
  40. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/parser/manifest.rs +377 -4
  41. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/parser/sql.rs +195 -5
  42. dlin_cli-0.2.2/crates/dlin-core/src/parser/yaml_schema.rs +982 -0
  43. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/ascii.rs +23 -2
  44. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/column_graph.rs +1 -16
  45. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/dot.rs +54 -5
  46. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/html.rs +1 -0
  47. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/json.rs +8 -0
  48. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/list.rs +1 -0
  49. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/mermaid.rs +6 -0
  50. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/mod.rs +50 -0
  51. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__summary__tests__snapshot_summary_json.snap +4 -1
  52. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/summary.rs +28 -1
  53. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/svg.rs +77 -14
  54. dlin_cli-0.2.1/crates/dlin-core/src/graph/builder.rs +0 -1614
  55. dlin_cli-0.2.1/crates/dlin-core/src/graph/filter.rs +0 -2396
  56. dlin_cli-0.2.1/crates/dlin-core/src/parser/yaml_schema.rs +0 -350
  57. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/LICENSE +0 -0
  58. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/README.md +0 -0
  59. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin/Cargo.toml +0 -0
  60. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin/README.md +0 -0
  61. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/Cargo.toml +0 -0
  62. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/README.md +0 -0
  63. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/error.rs +0 -0
  64. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/graph/column_lineage/cache.rs +0 -0
  65. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/graph/column_lineage/cross_model.rs +0 -0
  66. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/graph/column_lineage/impact.rs +0 -0
  67. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/graph/column_lineage/mod.rs +0 -0
  68. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/graph/column_lineage/schema.rs +0 -0
  69. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/graph/column_lineage/tests/cache.rs +0 -0
  70. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/graph/column_lineage/tests/impact.rs +0 -0
  71. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/graph/column_lineage/tests/mod.rs +0 -0
  72. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/graph/column_lineage/types.rs +0 -0
  73. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/graph/mod.rs +0 -0
  74. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/lib.rs +0 -0
  75. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/parser/columns.rs +0 -0
  76. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/parser/discovery.rs +0 -0
  77. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/parser/manifest_cache.rs +0 -0
  78. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/parser/mod.rs +0 -0
  79. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/parser/project.rs +0 -0
  80. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/impact.rs +0 -0
  81. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/layout.rs +0 -0
  82. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/plain.rs +0 -0
  83. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_all_transformation_types.snap +0 -0
  84. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_escapes_special_chars.snap +0 -0
  85. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_id_collision_avoided.snap +0 -0
  86. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_impact_indirect.snap +0 -0
  87. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_impact_single.snap +0 -0
  88. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_single_model.snap +0 -0
  89. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_via_path.snap +0 -0
  90. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__impact_mermaid.snap +0 -0
  91. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__impact_mermaid_indirect_edge_label.snap +0 -0
  92. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__impact_plain.snap +0 -0
  93. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__impact_plain_multi_hop.snap +0 -0
  94. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__impact_plain_non_direct_intermediate.snap +0 -0
  95. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__mermaid_dotted_table_name.snap +0 -0
  96. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__mermaid_id_collision_avoided.snap +0 -0
  97. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__mermaid_label_escaping.snap +0 -0
  98. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__mermaid_single_model.snap +0 -0
  99. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__plain_model_path_non_direct_annotation.snap +0 -0
  100. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__plain_no_sources.snap +0 -0
  101. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__plain_single_model.snap +0 -0
  102. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__group_by_node_type.snap +0 -0
  103. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_all_edge_types.snap +0 -0
  104. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_direction_tb.snap +0 -0
  105. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_direction_tb_grouped.snap +0 -0
  106. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_group_by_directory.snap +0 -0
  107. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_lineage.snap +0 -0
  108. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_transitive_edges.snap +0 -0
  109. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__html__tests__snapshot_html_json.snap +0 -0
  110. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__impact__tests__snapshot_impact_json.snap +0 -0
  111. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__impact__tests__snapshot_impact_json_with_sql.snap +0 -0
  112. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__impact__tests__snapshot_impact_text.snap +0 -0
  113. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__impact__tests__snapshot_impact_text_with_sql.snap +0 -0
  114. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__json__tests__snapshot_json_with_sql.snap +0 -0
  115. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__json__tests__snapshot_lineage.snap +0 -0
  116. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__json__tests__snapshot_node_metadata.snap +0 -0
  117. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__list__tests__snapshot_list_json.snap +0 -0
  118. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__list__tests__snapshot_list_plain.snap +0 -0
  119. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__group_by_node_type.snap +0 -0
  120. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__mixed_direct_and_transitive_edges.snap +0 -0
  121. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__show_columns_escapes_quotes.snap +0 -0
  122. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__show_columns_lineage.snap +0 -0
  123. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__show_columns_single_model.snap +0 -0
  124. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__show_columns_with_collapse.snap +0 -0
  125. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__show_columns_with_grouping.snap +0 -0
  126. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__snapshot_direction_tb.snap +0 -0
  127. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__snapshot_direction_tb_grouped.snap +0 -0
  128. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__snapshot_group_by_directory.snap +0 -0
  129. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__snapshot_lineage.snap +0 -0
  130. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__transitive_edge_rendering.snap +0 -0
  131. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__plain__tests__snapshot_plain.snap +0 -0
  132. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/src/render/snapshots/dlin_core__render__summary__tests__snapshot_summary_text.snap +0 -0
  133. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/crates/dlin-core/tests/column_lineage_test.rs +0 -0
  134. {dlin_cli-0.2.1 → dlin_cli-0.2.2}/pyproject.toml +0 -0
@@ -230,7 +230,7 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
230
230
 
231
231
  [[package]]
232
232
  name = "dlin"
233
- version = "0.2.1"
233
+ version = "0.2.2"
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.1"
250
+ version = "0.2.2"
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.2"
401
+ version = "0.0.3"
402
402
  source = "registry+https://github.com/rust-lang/crates.io-index"
403
- checksum = "b7e736dfe3881c53a7dce0685eb18202d0d9fe6911782f9870946eb9ee89d778"
403
+ checksum = "f50ba32164f9e098d5da618776a32afbb32270adcbe3d3d006107dae11e37c91"
404
404
  dependencies = [
405
405
  "arraydeque",
406
406
  "smallvec",
@@ -597,9 +597,9 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
597
597
 
598
598
  [[package]]
599
599
  name = "polyglot-sql"
600
- version = "0.4.2"
600
+ version = "0.4.4"
601
601
  source = "registry+https://github.com/rust-lang/crates.io-index"
602
- checksum = "3e264f8907f3a2050232cd06503afb637525038ddd388a2c9e03424e336cd9c8"
602
+ checksum = "b0d5ea9283f03d3b6c32a230e60e7c6bc854e5bec83c399c75cad02bf46e9622"
603
603
  dependencies = [
604
604
  "serde",
605
605
  "serde_json",
@@ -750,9 +750,9 @@ dependencies = [
750
750
 
751
751
  [[package]]
752
752
  name = "serde-saphyr"
753
- version = "0.0.26"
753
+ version = "0.0.27"
754
754
  source = "registry+https://github.com/rust-lang/crates.io-index"
755
- checksum = "dcc7fe48e34d02a97bc8e6253b8b91e5a47fe2c47eaacb5149cefbb69922eaf0"
755
+ checksum = "5897b4c3faadadd35fdb6689f015641f3bc481d5adaaac56231ea15aeb243db3"
756
756
  dependencies = [
757
757
  "ahash",
758
758
  "annotate-snippets",
@@ -3,7 +3,7 @@ members = ["crates/*"]
3
3
  resolver = "3"
4
4
 
5
5
  [workspace.package]
6
- version = "0.2.1"
6
+ version = "0.2.2"
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.26"
16
+ serde-saphyr = "0.0.27"
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.2", default-features = false, features = ["all-dialects", "semantic"] }
26
+ polyglot-sql = { version = "0.4.4", 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.1", path = "crates/dlin-core" }
37
+ dlin-core = { version = "0.2.2", 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.1
3
+ Version: 0.2.2
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -264,7 +264,7 @@ All selectors support glob patterns (*, **, ?, []):
264
264
  value_delimiter = ',',
265
265
  long_help = "\
266
266
  Filter output by node type (comma-separated). Default: all types.
267
- Available types: model, source, seed, snapshot, test, exposure.
267
+ Available types: model, source, seed, snapshot, test, exposure, semantic_model, metric, saved_query.
268
268
 
269
269
  NOTE: In sql mode, generic tests are inferred from YAML declarations
270
270
  with dlin-specific IDs. Use --source manifest for exact dependency resolution."
@@ -953,8 +953,10 @@ pub struct ColumnGraphArgs {
953
953
  #[arg(short = 'o', long, default_value = "json")]
954
954
  pub output: ColumnOutputFormat,
955
955
 
956
- /// SQL dialect for parsing (default: generic).
957
- /// [possible values: bigquery, snowflake, postgres, redshift, databricks, spark, trino, duckdb, mysql, clickhouse, oracle, hive, sqlite, presto, athena, teradata, doris, starrocks, materialize, risingwave, singlestore, cockroachdb, tidb, tsql, druid, solr, tableau, dune, fabric, drill, dremio, exasol, datafusion]
956
+ /// SQL dialect for parsing compiled SQL.
957
+ /// Auto-detected from manifest.metadata.adapter_type when omitted; required if the manifest
958
+ /// does not declare an adapter_type.
959
+ /// [possible values: generic, bigquery, snowflake, postgres, duckdb, redshift, databricks, spark, trino, mysql, clickhouse, oracle, hive, sqlite, presto, athena, teradata, doris, starrocks, materialize, risingwave, singlestore, cockroachdb, tidb, tsql, druid, solr, tableau, dune, fabric, drill, dremio, exasol, datafusion]
958
960
  #[arg(long)]
959
961
  pub dialect: Option<DialectType>,
960
962
 
@@ -996,8 +998,10 @@ pub struct ColumnImpactArgs {
996
998
  #[arg(short = 'o', long, default_value = "json")]
997
999
  pub output: ColumnOutputFormat,
998
1000
 
999
- /// SQL dialect for parsing (default: generic).
1000
- /// [possible values: bigquery, snowflake, postgres, redshift, databricks, spark, trino, duckdb, mysql, clickhouse, oracle, hive, sqlite, presto, athena, teradata, doris, starrocks, materialize, risingwave, singlestore, cockroachdb, tidb, tsql, druid, solr, tableau, dune, fabric, drill, dremio, exasol, datafusion]
1001
+ /// SQL dialect for parsing compiled SQL.
1002
+ /// Auto-detected from manifest.metadata.adapter_type when omitted; required if the manifest
1003
+ /// does not declare an adapter_type.
1004
+ /// [possible values: generic, bigquery, snowflake, postgres, duckdb, redshift, databricks, spark, trino, mysql, clickhouse, oracle, hive, sqlite, presto, athena, teradata, doris, starrocks, materialize, risingwave, singlestore, cockroachdb, tidb, tsql, druid, solr, tableau, dune, fabric, drill, dremio, exasol, datafusion]
1001
1005
  #[arg(long)]
1002
1006
  pub dialect: Option<DialectType>,
1003
1007
 
@@ -1218,7 +1222,7 @@ Examples:
1218
1222
  value_delimiter = ',',
1219
1223
  long_help = "\
1220
1224
  Filter output by node type (comma-separated). Default: all types.
1221
- Available types: model, source, seed, snapshot, test, exposure.
1225
+ Available types: model, source, seed, snapshot, test, exposure, semantic_model, metric, saved_query.
1222
1226
 
1223
1227
  NOTE: In sql mode, generic tests are inferred from YAML declarations
1224
1228
  with dlin-specific IDs. Use --source manifest for exact dependency resolution."
@@ -810,6 +810,39 @@ fn resolve_manifest_path(manifest_arg: &Path) -> Result<PathBuf> {
810
810
  }
811
811
  }
812
812
 
813
+ /// Resolve the SQL dialect to use for column lineage.
814
+ ///
815
+ /// Precedence: CLI flag > manifest adapter_type > error.
816
+ /// Avoids silent fallback to Generic, which produces incorrect results for
817
+ /// warehouse-specific SQL (e.g. BigQuery backtick quoting, Snowflake QUALIFY).
818
+ fn resolve_dialect(
819
+ cli_dialect: Option<DialectType>,
820
+ manifest: &parser::manifest::Manifest,
821
+ ) -> Result<DialectType> {
822
+ if let Some(d) = cli_dialect {
823
+ return Ok(d);
824
+ }
825
+ match manifest.metadata.adapter_type.as_deref() {
826
+ Some(adapter) if !adapter.trim().is_empty() => {
827
+ adapter.trim().parse::<DialectType>().map_err(|_| {
828
+ anyhow::anyhow!(
829
+ "manifest adapter_type '{}' has no matching SQL dialect; \
830
+ use --dialect to specify one explicitly (e.g. --dialect postgres)",
831
+ adapter.trim()
832
+ )
833
+ })
834
+ }
835
+ Some(_) => anyhow::bail!(
836
+ "manifest adapter_type is empty; \
837
+ use --dialect to specify the SQL dialect (e.g. --dialect bigquery)"
838
+ ),
839
+ None => anyhow::bail!(
840
+ "manifest does not specify an adapter_type; \
841
+ use --dialect to specify the SQL dialect (e.g. --dialect bigquery)"
842
+ ),
843
+ }
844
+ }
845
+
813
846
  /// Run the `column-lineage` subcommand
814
847
  #[cfg(not(tarpaulin_include))]
815
848
  #[allow(clippy::too_many_arguments)]
@@ -824,8 +857,6 @@ fn run_column_lineage_command(
824
857
  no_cache: bool,
825
858
  refresh_cache: bool,
826
859
  ) -> Result<()> {
827
- let dialect = dialect.unwrap_or(DialectType::Generic);
828
-
829
860
  let project_dir = project_dir
830
861
  .canonicalize()
831
862
  .unwrap_or_else(|_| project_dir.to_path_buf());
@@ -847,6 +878,8 @@ fn run_column_lineage_command(
847
878
  let resolved_manifest_path = resolve_manifest_path_or_default(manifest_path, &project_dir)?;
848
879
  let manifest = parser::manifest::load_manifest(&resolved_manifest_path)?;
849
880
 
881
+ let dialect = resolve_dialect(dialect, &manifest)?;
882
+
850
883
  let models = if input::has_path_like_input(&raw_inputs) {
851
884
  let dag = parser::manifest::build_graph_from_parsed_manifest(&manifest)?;
852
885
  let cwd = std::env::current_dir()
@@ -1038,8 +1071,6 @@ fn run_column_impact_command(
1038
1071
  no_cache: bool,
1039
1072
  refresh_cache: bool,
1040
1073
  ) -> Result<()> {
1041
- let dialect = dialect.unwrap_or(DialectType::Generic);
1042
-
1043
1074
  if columns.is_empty() {
1044
1075
  anyhow::bail!("no columns specified (use --column)");
1045
1076
  }
@@ -1051,6 +1082,8 @@ fn run_column_impact_command(
1051
1082
  let resolved = resolve_manifest_path_or_default(manifest_path, &project_dir)?;
1052
1083
  let manifest = parser::manifest::load_manifest(&resolved)?;
1053
1084
 
1085
+ let dialect = resolve_dialect(dialect, &manifest)?;
1086
+
1054
1087
  let mut cache = if no_cache {
1055
1088
  graph::column_lineage::ColumnLineageCache::disabled()
1056
1089
  } else if refresh_cache {
@@ -231,7 +231,7 @@ fn tools() -> Vec<Value> {
231
231
  "query": { "type": "string", "description": "Case-insensitive substring filter applied to node name, description, tags, and file path. Returns all nodes when omitted." },
232
232
  "node_types": {
233
233
  "type": "array",
234
- "items": { "type": "string", "enum": ["model", "source", "seed", "snapshot", "test", "exposure"] },
234
+ "items": { "type": "string", "enum": ["model", "source", "seed", "snapshot", "test", "exposure", "semantic_model", "metric", "saved_query"] },
235
235
  "description": "Node types to include. Defaults to all types when omitted."
236
236
  }
237
237
  },
@@ -247,7 +247,7 @@ fn tools() -> Vec<Value> {
247
247
  "names": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "Node names (e.g. 'orders') or dbt unique IDs (e.g. 'model.my_project.orders', 'source.my_project.raw.orders') to look up." },
248
248
  "node_types": {
249
249
  "type": "array",
250
- "items": { "type": "string", "enum": ["model", "source", "seed", "snapshot", "test", "exposure"] },
250
+ "items": { "type": "string", "enum": ["model", "source", "seed", "snapshot", "test", "exposure", "semantic_model", "metric", "saved_query"] },
251
251
  "description": "Restrict results to these node types. Nodes whose type does not match are reported in not_found."
252
252
  }
253
253
  },
@@ -451,6 +451,9 @@ fn node_type_from_str(s: &str) -> Option<NodeType> {
451
451
  "snapshot" => Some(NodeType::Snapshot),
452
452
  "test" => Some(NodeType::Test),
453
453
  "exposure" => Some(NodeType::Exposure),
454
+ "semantic_model" => Some(NodeType::SemanticModel),
455
+ "metric" => Some(NodeType::Metric),
456
+ "saved_query" => Some(NodeType::SavedQuery),
454
457
  _ => None,
455
458
  }
456
459
  }
@@ -770,7 +773,8 @@ fn normalize_manifest_unique_id(name: &str) -> Option<String> {
770
773
  None
771
774
  }
772
775
  }
773
- "model" | "seed" | "snapshot" | "exposure" => {
776
+ "model" | "seed" | "snapshot" | "exposure" | "semantic_model" | "metric"
777
+ | "saved_query" => {
774
778
  parts.remove(1);
775
779
  Some(parts.join("."))
776
780
  }
@@ -1268,6 +1272,87 @@ mod tests {
1268
1272
  assert_eq!(normalized, Some("test.my_test_name".to_string()));
1269
1273
  }
1270
1274
 
1275
+ #[test]
1276
+ fn normalize_manifest_unique_id_supports_semantic_layer_types() {
1277
+ assert_eq!(
1278
+ normalize_manifest_unique_id("semantic_model.my_project.orders"),
1279
+ Some("semantic_model.orders".to_string())
1280
+ );
1281
+ assert_eq!(
1282
+ normalize_manifest_unique_id("metric.my_project.revenue"),
1283
+ Some("metric.revenue".to_string())
1284
+ );
1285
+ assert_eq!(
1286
+ normalize_manifest_unique_id("saved_query.my_project.revenue_metrics"),
1287
+ Some("saved_query.revenue_metrics".to_string())
1288
+ );
1289
+ }
1290
+
1291
+ #[test]
1292
+ fn find_nodes_resolves_semantic_layer_full_unique_ids() {
1293
+ let manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
1294
+ .join("..")
1295
+ .join("..")
1296
+ .join("refs")
1297
+ .join("jaffle-shop")
1298
+ .join("target")
1299
+ .join("manifest.json");
1300
+ if !manifest_path.exists() {
1301
+ eprintln!(
1302
+ "SKIP: jaffle-shop fixture not found at {manifest_path:?}; run `make fixtures` to enable this test"
1303
+ );
1304
+ return;
1305
+ }
1306
+ let state = McpState::load(McpArgs {
1307
+ project_dir: manifest_path
1308
+ .parent()
1309
+ .unwrap()
1310
+ .parent()
1311
+ .unwrap()
1312
+ .to_path_buf(),
1313
+ manifest_path: Some(manifest_path),
1314
+ dialect: DialectType::Generic,
1315
+ })
1316
+ .unwrap();
1317
+
1318
+ let result = find_nodes(
1319
+ &json!({
1320
+ "names": [
1321
+ "semantic_model.jaffle_shop.supplies",
1322
+ "metric.jaffle_shop.revenue",
1323
+ "saved_query.jaffle_shop.revenue_metrics"
1324
+ ]
1325
+ }),
1326
+ &state,
1327
+ )
1328
+ .unwrap();
1329
+
1330
+ assert_eq!(
1331
+ result["not_found"],
1332
+ json!([]),
1333
+ "all full unique IDs should resolve"
1334
+ );
1335
+ assert_eq!(result["count"], 3);
1336
+ let unique_ids: Vec<&str> = result["nodes"]
1337
+ .as_array()
1338
+ .unwrap()
1339
+ .iter()
1340
+ .map(|n| n["unique_id"].as_str().unwrap())
1341
+ .collect();
1342
+ assert!(
1343
+ unique_ids.contains(&"semantic_model.supplies"),
1344
+ "semantic_model node should resolve to correct type"
1345
+ );
1346
+ assert!(
1347
+ unique_ids.contains(&"metric.revenue"),
1348
+ "metric node should resolve to correct type"
1349
+ );
1350
+ assert!(
1351
+ unique_ids.contains(&"saved_query.revenue_metrics"),
1352
+ "saved_query node should resolve to correct type"
1353
+ );
1354
+ }
1355
+
1271
1356
  #[test]
1272
1357
  fn extract_column_not_found_name_parses_column_prefix() {
1273
1358
  assert_eq!(
@@ -2401,7 +2401,7 @@ mod manifest_only_mode {
2401
2401
  fs::create_dir_all(tmp.path().join("target")).unwrap();
2402
2402
 
2403
2403
  let manifest_json = r#"{
2404
- "metadata": {},
2404
+ "metadata": {"adapter_type": "postgres"},
2405
2405
  "nodes": {
2406
2406
  "model.test_project.stg_orders": {
2407
2407
  "unique_id": "model.test_project.stg_orders",
@@ -2471,4 +2471,248 @@ mod manifest_only_mode {
2471
2471
  stdout
2472
2472
  );
2473
2473
  }
2474
+
2475
+ /// Build a manifest JSON string with the given adapter_type value (or none).
2476
+ fn manifest_json_with_adapter(adapter_type: Option<&str>) -> String {
2477
+ let metadata = match adapter_type {
2478
+ Some(a) => format!(r#"{{"adapter_type": "{}"}}"#, a),
2479
+ None => "{}".to_string(),
2480
+ };
2481
+ format!(
2482
+ r#"{{
2483
+ "metadata": {},
2484
+ "nodes": {{
2485
+ "model.test_project.stg_orders": {{
2486
+ "unique_id": "model.test_project.stg_orders",
2487
+ "name": "stg_orders",
2488
+ "original_file_path": "models/stg_orders.sql",
2489
+ "resource_type": "model",
2490
+ "depends_on": {{"nodes": []}},
2491
+ "config": {{"materialized": "view", "tags": []}},
2492
+ "description": "",
2493
+ "compiled_code": "SELECT order_id FROM raw.orders",
2494
+ "columns": {{"order_id": {{"name": "order_id", "description": ""}}}}
2495
+ }}
2496
+ }},
2497
+ "sources": {{}},
2498
+ "exposures": {{}}
2499
+ }}"#,
2500
+ metadata
2501
+ )
2502
+ }
2503
+
2504
+ fn write_manifest(dir: &std::path::Path, json: &str) {
2505
+ fs::create_dir_all(dir.join("target")).unwrap();
2506
+ fs::write(dir.join("target/manifest.json"), json).unwrap();
2507
+ }
2508
+
2509
+ #[test]
2510
+ fn test_column_upstream_auto_detects_dialect_from_manifest_adapter_type() {
2511
+ // When --dialect is omitted but manifest has adapter_type, dialect is auto-detected.
2512
+ let tmp = tempfile::tempdir().unwrap();
2513
+ write_manifest(tmp.path(), &manifest_json_with_adapter(Some("postgres")));
2514
+
2515
+ let output = Command::new(binary_path())
2516
+ .args([
2517
+ "column",
2518
+ "upstream",
2519
+ "stg_orders",
2520
+ "--project-dir",
2521
+ tmp.path().to_str().unwrap(),
2522
+ "--no-cache",
2523
+ ])
2524
+ .output()
2525
+ .expect("Failed to run binary");
2526
+
2527
+ assert!(
2528
+ output.status.success(),
2529
+ "should succeed with auto-detected dialect from adapter_type; stderr: {}",
2530
+ String::from_utf8_lossy(&output.stderr)
2531
+ );
2532
+ }
2533
+
2534
+ #[test]
2535
+ fn test_column_upstream_errors_when_no_adapter_type_and_no_dialect_flag() {
2536
+ // When both --dialect and manifest adapter_type are absent, the command must fail
2537
+ // with an actionable error rather than silently using Generic.
2538
+ let tmp = tempfile::tempdir().unwrap();
2539
+ write_manifest(tmp.path(), &manifest_json_with_adapter(None));
2540
+
2541
+ let output = Command::new(binary_path())
2542
+ .args([
2543
+ "column",
2544
+ "upstream",
2545
+ "stg_orders",
2546
+ "--project-dir",
2547
+ tmp.path().to_str().unwrap(),
2548
+ "--no-cache",
2549
+ ])
2550
+ .output()
2551
+ .expect("Failed to run binary");
2552
+
2553
+ assert!(
2554
+ !output.status.success(),
2555
+ "should fail when adapter_type is absent and --dialect is not given"
2556
+ );
2557
+ let stderr = String::from_utf8_lossy(&output.stderr);
2558
+ assert!(
2559
+ stderr.contains("does not specify an adapter_type"),
2560
+ "error message should say 'does not specify an adapter_type': {}",
2561
+ stderr
2562
+ );
2563
+ }
2564
+
2565
+ #[test]
2566
+ fn test_column_upstream_errors_on_unknown_adapter_type() {
2567
+ // An unrecognised adapter_type in the manifest must produce a clear error
2568
+ // telling the user to pass --dialect explicitly.
2569
+ let tmp = tempfile::tempdir().unwrap();
2570
+ write_manifest(
2571
+ tmp.path(),
2572
+ &manifest_json_with_adapter(Some("unknown_warehouse")),
2573
+ );
2574
+
2575
+ let output = Command::new(binary_path())
2576
+ .args([
2577
+ "column",
2578
+ "upstream",
2579
+ "stg_orders",
2580
+ "--project-dir",
2581
+ tmp.path().to_str().unwrap(),
2582
+ "--no-cache",
2583
+ ])
2584
+ .output()
2585
+ .expect("Failed to run binary");
2586
+
2587
+ assert!(
2588
+ !output.status.success(),
2589
+ "should fail when adapter_type is not a known dialect"
2590
+ );
2591
+ let stderr = String::from_utf8_lossy(&output.stderr);
2592
+ assert!(
2593
+ stderr.contains("unknown_warehouse") && stderr.contains("--dialect"),
2594
+ "error should name the unknown adapter and suggest --dialect: {}",
2595
+ stderr
2596
+ );
2597
+ }
2598
+
2599
+ #[test]
2600
+ fn test_column_upstream_dialect_flag_overrides_manifest_adapter_type() {
2601
+ // --dialect takes precedence over whatever adapter_type the manifest declares.
2602
+ let tmp = tempfile::tempdir().unwrap();
2603
+ // adapter_type is deliberately wrong (trino); --dialect postgres should win.
2604
+ write_manifest(tmp.path(), &manifest_json_with_adapter(Some("trino")));
2605
+
2606
+ let output = Command::new(binary_path())
2607
+ .args([
2608
+ "column",
2609
+ "upstream",
2610
+ "stg_orders",
2611
+ "--project-dir",
2612
+ tmp.path().to_str().unwrap(),
2613
+ "--dialect",
2614
+ "postgres",
2615
+ "--no-cache",
2616
+ ])
2617
+ .output()
2618
+ .expect("Failed to run binary");
2619
+
2620
+ assert!(
2621
+ output.status.success(),
2622
+ "--dialect flag should override adapter_type in manifest; stderr: {}",
2623
+ String::from_utf8_lossy(&output.stderr)
2624
+ );
2625
+ }
2626
+
2627
+ #[test]
2628
+ fn test_column_downstream_errors_when_no_adapter_type_and_no_dialect_flag() {
2629
+ // Same error behaviour for the impact (downstream) subcommand.
2630
+ let tmp = tempfile::tempdir().unwrap();
2631
+ write_manifest(tmp.path(), &manifest_json_with_adapter(None));
2632
+
2633
+ let output = Command::new(binary_path())
2634
+ .args([
2635
+ "column",
2636
+ "downstream",
2637
+ "stg_orders",
2638
+ "--column",
2639
+ "order_id",
2640
+ "--project-dir",
2641
+ tmp.path().to_str().unwrap(),
2642
+ "--no-cache",
2643
+ ])
2644
+ .output()
2645
+ .expect("Failed to run binary");
2646
+
2647
+ assert!(
2648
+ !output.status.success(),
2649
+ "column downstream should also fail without adapter_type and --dialect"
2650
+ );
2651
+ let stderr = String::from_utf8_lossy(&output.stderr);
2652
+ assert!(
2653
+ stderr.contains("does not specify an adapter_type"),
2654
+ "error message should say 'does not specify an adapter_type': {}",
2655
+ stderr
2656
+ );
2657
+ }
2658
+
2659
+ #[test]
2660
+ fn test_column_upstream_errors_on_empty_adapter_type() {
2661
+ // An empty string adapter_type must not silently fall back to Generic.
2662
+ let tmp = tempfile::tempdir().unwrap();
2663
+ write_manifest(tmp.path(), &manifest_json_with_adapter(Some("")));
2664
+
2665
+ let output = Command::new(binary_path())
2666
+ .args([
2667
+ "column",
2668
+ "upstream",
2669
+ "stg_orders",
2670
+ "--project-dir",
2671
+ tmp.path().to_str().unwrap(),
2672
+ "--no-cache",
2673
+ ])
2674
+ .output()
2675
+ .expect("Failed to run binary");
2676
+
2677
+ assert!(
2678
+ !output.status.success(),
2679
+ "empty adapter_type should fail rather than silently using Generic"
2680
+ );
2681
+ let stderr = String::from_utf8_lossy(&output.stderr);
2682
+ assert!(
2683
+ stderr.contains("adapter_type is empty"),
2684
+ "error message should say 'adapter_type is empty': {}",
2685
+ stderr
2686
+ );
2687
+ }
2688
+
2689
+ #[test]
2690
+ fn test_column_upstream_errors_on_whitespace_only_adapter_type() {
2691
+ // A whitespace-only adapter_type must not silently fall back to Generic.
2692
+ let tmp = tempfile::tempdir().unwrap();
2693
+ write_manifest(tmp.path(), &manifest_json_with_adapter(Some(" ")));
2694
+
2695
+ let output = Command::new(binary_path())
2696
+ .args([
2697
+ "column",
2698
+ "upstream",
2699
+ "stg_orders",
2700
+ "--project-dir",
2701
+ tmp.path().to_str().unwrap(),
2702
+ "--no-cache",
2703
+ ])
2704
+ .output()
2705
+ .expect("Failed to run binary");
2706
+
2707
+ assert!(
2708
+ !output.status.success(),
2709
+ "whitespace-only adapter_type should fail rather than silently using Generic"
2710
+ );
2711
+ let stderr = String::from_utf8_lossy(&output.stderr);
2712
+ assert!(
2713
+ stderr.contains("adapter_type is empty"),
2714
+ "error message should say 'adapter_type is empty': {}",
2715
+ stderr
2716
+ );
2717
+ }
2474
2718
  }