dlin-cli 0.2.5__tar.gz → 0.2.6rc1__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 (200) hide show
  1. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/Cargo.lock +26 -26
  2. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/Cargo.toml +2 -2
  3. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/PKG-INFO +1 -1
  4. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/error_format.rs +70 -0
  5. dlin_cli-0.2.6rc1/crates/dlin/tests/integration/render_failure.rs +255 -0
  6. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration_test.rs +2 -0
  7. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/mod.rs +99 -1
  8. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/output.rs +90 -1
  9. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/jinja/mod.rs +6 -4
  10. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/jinja/render.rs +36 -4
  11. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/jinja/tests.rs +95 -0
  12. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/sql/mod.rs +74 -20
  13. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/sql/tests.rs +159 -5
  14. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/LICENSE +0 -0
  15. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/README.md +0 -0
  16. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/Cargo.toml +0 -0
  17. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/README.md +0 -0
  18. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/cli/column.rs +0 -0
  19. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/cli/command.rs +0 -0
  20. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/cli/common.rs +0 -0
  21. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/cli/graph.rs +0 -0
  22. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/cli/misc.rs +0 -0
  23. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/cli/mod.rs +0 -0
  24. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/cli/summary.rs +0 -0
  25. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/commands/column.rs +0 -0
  26. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/commands/debug.rs +0 -0
  27. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/commands/graph.rs +0 -0
  28. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/commands/manifest.rs +0 -0
  29. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/commands/mod.rs +0 -0
  30. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/commands/shared.rs +0 -0
  31. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/main.rs +0 -0
  32. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/mcp/mod.rs +0 -0
  33. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/mcp/protocol.rs +0 -0
  34. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/mcp/tests.rs +0 -0
  35. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/mcp/tools.rs +0 -0
  36. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/artifacts.rs +0 -0
  37. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/cli.rs +0 -0
  38. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/freshness.rs +0 -0
  39. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/generic_test_metadata.rs +0 -0
  40. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/manifest_only.rs +0 -0
  41. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/manifest_only_rest.rs +0 -0
  42. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/parsing.rs +0 -0
  43. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/sql_mode_test_warning.rs +0 -0
  44. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/Cargo.toml +0 -0
  45. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/README.md +0 -0
  46. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/error.rs +0 -0
  47. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/mod.rs +0 -0
  48. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/project_yaml.rs +0 -0
  49. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/semantic_layer.rs +0 -0
  50. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/tests/build_graph/basic.rs +0 -0
  51. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/tests/build_graph/mod.rs +0 -0
  52. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/tests/build_graph/semantic_layer.rs +0 -0
  53. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/tests/build_graph/yaml_snapshots.rs +0 -0
  54. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/tests/generic_tests.rs +0 -0
  55. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/tests/mod.rs +0 -0
  56. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/tests/ref_parsing.rs +0 -0
  57. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/tests/versioned.rs +0 -0
  58. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/backend/catalog.rs +0 -0
  59. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/backend/catalog_provider.rs +0 -0
  60. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/backend/dialect.rs +0 -0
  61. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/backend/mod.rs +0 -0
  62. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/backend/sqllineage.rs +0 -0
  63. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/backend/types.rs +0 -0
  64. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/cache.rs +0 -0
  65. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/cross_model.rs +0 -0
  66. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/impact.rs +0 -0
  67. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/mod.rs +0 -0
  68. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/relation.rs +0 -0
  69. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/schema.rs +0 -0
  70. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/alias.rs +0 -0
  71. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/backend_regression/basic.rs +0 -0
  72. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/backend_regression/discovery.rs +0 -0
  73. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/backend_regression/mod.rs +0 -0
  74. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/backend_regression/set_operations.rs +0 -0
  75. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/cache.rs +0 -0
  76. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/core_basic.rs +0 -0
  77. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/core_cross_model/mod.rs +0 -0
  78. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/core_cross_model/scenarios.rs +0 -0
  79. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/core_star.rs +0 -0
  80. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/core_star_chain.rs +0 -0
  81. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/core_util.rs +0 -0
  82. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/impact/core.rs +0 -0
  83. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/impact/edge_cases.rs +0 -0
  84. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/impact/mod.rs +0 -0
  85. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/mod.rs +0 -0
  86. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/regression_fixtures.rs +0 -0
  87. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/types.rs +0 -0
  88. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/collapse.rs +0 -0
  89. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/filter_graph.rs +0 -0
  90. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/mod.rs +0 -0
  91. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/search.rs +0 -0
  92. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/selector.rs +0 -0
  93. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot.snap +0 -0
  94. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_bfs_pseudoendpoint.snap +0 -0
  95. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_endpoints_fan_out.snap +0 -0
  96. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_endpoints_leaf_model.snap +0 -0
  97. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_multiple_focus_models.snap +0 -0
  98. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_no_source_exposure.snap +0 -0
  99. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__collapse_snapshot_preserve_focus.snap +0 -0
  100. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__snapshot_transitive_node_type_filter.snap +0 -0
  101. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__snapshot_transitive_select_filter.snap +0 -0
  102. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/snapshots/dlin_core__graph__filter__tests__collapse__snapshot_transitive_select_with_node_type.snap +0 -0
  103. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/transitive.rs +0 -0
  104. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/versioned.rs +0 -0
  105. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/impact.rs +0 -0
  106. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/mod.rs +0 -0
  107. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/types.rs +0 -0
  108. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/input.rs +0 -0
  109. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/lib.rs +0 -0
  110. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/cache.rs +0 -0
  111. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/columns.rs +0 -0
  112. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/discovery.rs +0 -0
  113. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/jinja/reachability/analysis.rs +0 -0
  114. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/jinja/reachability/catalog.rs +0 -0
  115. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/jinja/reachability/mod.rs +0 -0
  116. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/jinja/reachability/tests.rs +0 -0
  117. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/jinja/source.rs +0 -0
  118. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/graph.rs +0 -0
  119. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/mod.rs +0 -0
  120. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/report/decode.rs +0 -0
  121. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/report/mod.rs +0 -0
  122. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/report/tests.rs +0 -0
  123. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/tests/compatibility.rs +0 -0
  124. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/tests/contents.rs +0 -0
  125. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/tests/graph.rs +0 -0
  126. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/tests/identifiers.rs +0 -0
  127. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/tests/mod.rs +0 -0
  128. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/tests/semantic_layer.rs +0 -0
  129. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest_cache.rs +0 -0
  130. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/mod.rs +0 -0
  131. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/project.rs +0 -0
  132. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/sql/recovery.rs +0 -0
  133. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/yaml_schema.rs +0 -0
  134. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/ascii.rs +0 -0
  135. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/column_graph.rs +0 -0
  136. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/column_graph_tests.rs +0 -0
  137. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/dot.rs +0 -0
  138. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/html.rs +0 -0
  139. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/impact.rs +0 -0
  140. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/json.rs +0 -0
  141. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/layout.rs +0 -0
  142. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/list.rs +0 -0
  143. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/mermaid.rs +0 -0
  144. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/mod.rs +0 -0
  145. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/plain.rs +0 -0
  146. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_all_transformation_types.snap +0 -0
  147. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_escapes_special_chars.snap +0 -0
  148. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_id_collision_avoided.snap +0 -0
  149. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_impact_indirect.snap +0 -0
  150. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_impact_single.snap +0 -0
  151. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_single_model.snap +0 -0
  152. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__dot_via_path.snap +0 -0
  153. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__impact_mermaid.snap +0 -0
  154. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__impact_mermaid_indirect_edge_label.snap +0 -0
  155. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__impact_plain.snap +0 -0
  156. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__impact_plain_multi_hop.snap +0 -0
  157. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__impact_plain_non_direct_intermediate.snap +0 -0
  158. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__mermaid_dotted_table_name.snap +0 -0
  159. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__mermaid_id_collision_avoided.snap +0 -0
  160. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__mermaid_label_escaping.snap +0 -0
  161. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__mermaid_single_model.snap +0 -0
  162. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__plain_model_path_non_direct_annotation.snap +0 -0
  163. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__plain_no_sources.snap +0 -0
  164. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__column_graph__tests__plain_single_model.snap +0 -0
  165. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__group_by_node_type.snap +0 -0
  166. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_all_edge_types.snap +0 -0
  167. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_direction_tb.snap +0 -0
  168. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_direction_tb_grouped.snap +0 -0
  169. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_group_by_directory.snap +0 -0
  170. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_lineage.snap +0 -0
  171. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__dot__tests__snapshot_transitive_edges.snap +0 -0
  172. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__html__tests__snapshot_html_json.snap +0 -0
  173. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__impact__tests__snapshot_impact_json.snap +0 -0
  174. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__impact__tests__snapshot_impact_json_with_sql.snap +0 -0
  175. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__impact__tests__snapshot_impact_text.snap +0 -0
  176. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__impact__tests__snapshot_impact_text_with_sql.snap +0 -0
  177. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__json__tests__snapshot_json_with_sql.snap +0 -0
  178. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__json__tests__snapshot_lineage.snap +0 -0
  179. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__json__tests__snapshot_node_metadata.snap +0 -0
  180. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__list__tests__snapshot_list_json.snap +0 -0
  181. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__list__tests__snapshot_list_plain.snap +0 -0
  182. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__group_by_node_type.snap +0 -0
  183. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__mixed_direct_and_transitive_edges.snap +0 -0
  184. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__show_columns_escapes_quotes.snap +0 -0
  185. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__show_columns_lineage.snap +0 -0
  186. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__show_columns_single_model.snap +0 -0
  187. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__show_columns_with_collapse.snap +0 -0
  188. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__show_columns_with_grouping.snap +0 -0
  189. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__snapshot_direction_tb.snap +0 -0
  190. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__snapshot_direction_tb_grouped.snap +0 -0
  191. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__snapshot_group_by_directory.snap +0 -0
  192. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__snapshot_lineage.snap +0 -0
  193. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__mermaid__tests__transitive_edge_rendering.snap +0 -0
  194. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__plain__tests__snapshot_plain.snap +0 -0
  195. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__summary__tests__snapshot_summary_json.snap +0 -0
  196. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/snapshots/dlin_core__render__summary__tests__snapshot_summary_text.snap +0 -0
  197. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/summary.rs +0 -0
  198. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/svg.rs +0 -0
  199. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/tests/column_lineage_test.rs +0 -0
  200. {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/pyproject.toml +0 -0
@@ -139,9 +139,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
139
139
 
140
140
  [[package]]
141
141
  name = "clap"
142
- version = "4.6.6"
142
+ version = "4.6.7"
143
143
  source = "registry+https://github.com/rust-lang/crates.io-index"
144
- checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
144
+ checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
145
145
  dependencies = [
146
146
  "clap_builder",
147
147
  "clap_derive",
@@ -149,9 +149,9 @@ dependencies = [
149
149
 
150
150
  [[package]]
151
151
  name = "clap_builder"
152
- version = "4.6.6"
152
+ version = "4.6.7"
153
153
  source = "registry+https://github.com/rust-lang/crates.io-index"
154
- checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
154
+ checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
155
155
  dependencies = [
156
156
  "anstream",
157
157
  "anstyle",
@@ -161,9 +161,9 @@ dependencies = [
161
161
 
162
162
  [[package]]
163
163
  name = "clap_derive"
164
- version = "4.6.4"
164
+ version = "4.6.7"
165
165
  source = "registry+https://github.com/rust-lang/crates.io-index"
166
- checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
166
+ checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0"
167
167
  dependencies = [
168
168
  "heck",
169
169
  "proc-macro2",
@@ -230,7 +230,7 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
230
230
 
231
231
  [[package]]
232
232
  name = "dlin"
233
- version = "0.2.5"
233
+ version = "0.2.6-rc.1"
234
234
  dependencies = [
235
235
  "anyhow",
236
236
  "clap",
@@ -248,7 +248,7 @@ dependencies = [
248
248
 
249
249
  [[package]]
250
250
  name = "dlin-core"
251
- version = "0.2.5"
251
+ version = "0.2.6-rc.1"
252
252
  dependencies = [
253
253
  "anyhow",
254
254
  "clap",
@@ -402,9 +402,9 @@ dependencies = [
402
402
 
403
403
  [[package]]
404
404
  name = "globset"
405
- version = "0.4.19"
405
+ version = "0.4.20"
406
406
  source = "registry+https://github.com/rust-lang/crates.io-index"
407
- checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd"
407
+ checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988"
408
408
  dependencies = [
409
409
  "aho-corasick",
410
410
  "bstr",
@@ -415,9 +415,9 @@ dependencies = [
415
415
 
416
416
  [[package]]
417
417
  name = "granit-parser"
418
- version = "1.1.0"
418
+ version = "1.3.0"
419
419
  source = "registry+https://github.com/rust-lang/crates.io-index"
420
- checksum = "4ccd1be9ebf2bd5520dbfcfb72b70ef492f654061299a097056f3e73410e38ec"
420
+ checksum = "e20f99e46474f56bd905c56e817ebddcf377a611f94c53ac4649e4d3fa3c0cd0"
421
421
  dependencies = [
422
422
  "arraydeque",
423
423
  "smallvec",
@@ -446,9 +446,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
446
446
 
447
447
  [[package]]
448
448
  name = "indexmap"
449
- version = "2.14.0"
449
+ version = "2.14.2"
450
450
  source = "registry+https://github.com/rust-lang/crates.io-index"
451
- checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
451
+ checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
452
452
  dependencies = [
453
453
  "equivalent",
454
454
  "hashbrown 0.17.1",
@@ -696,9 +696,9 @@ dependencies = [
696
696
 
697
697
  [[package]]
698
698
  name = "regex"
699
- version = "1.12.3"
699
+ version = "1.13.1"
700
700
  source = "registry+https://github.com/rust-lang/crates.io-index"
701
- checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
701
+ checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
702
702
  dependencies = [
703
703
  "aho-corasick",
704
704
  "memchr",
@@ -708,9 +708,9 @@ dependencies = [
708
708
 
709
709
  [[package]]
710
710
  name = "regex-automata"
711
- version = "0.4.14"
711
+ version = "0.4.18"
712
712
  source = "registry+https://github.com/rust-lang/crates.io-index"
713
- checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
713
+ checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
714
714
  dependencies = [
715
715
  "aho-corasick",
716
716
  "memchr",
@@ -719,9 +719,9 @@ dependencies = [
719
719
 
720
720
  [[package]]
721
721
  name = "regex-syntax"
722
- version = "0.8.10"
722
+ version = "0.8.11"
723
723
  source = "registry+https://github.com/rust-lang/crates.io-index"
724
- checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
724
+ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
725
725
 
726
726
  [[package]]
727
727
  name = "rustix"
@@ -763,9 +763,9 @@ dependencies = [
763
763
 
764
764
  [[package]]
765
765
  name = "serde-saphyr"
766
- version = "1.1.0"
766
+ version = "1.3.0"
767
767
  source = "registry+https://github.com/rust-lang/crates.io-index"
768
- checksum = "a1ec1f5cac0eb96063c64b28705255a7ed6e7d77f95c1d25e9f8b8c928006ce1"
768
+ checksum = "b8050abb251097357e24aff63ba2c52a6309ecb7d23a5474023df960a02694d8"
769
769
  dependencies = [
770
770
  "annotate-snippets",
771
771
  "base64",
@@ -949,18 +949,18 @@ dependencies = [
949
949
 
950
950
  [[package]]
951
951
  name = "thiserror"
952
- version = "2.0.20"
952
+ version = "2.0.21"
953
953
  source = "registry+https://github.com/rust-lang/crates.io-index"
954
- checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
954
+ checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
955
955
  dependencies = [
956
956
  "thiserror-impl",
957
957
  ]
958
958
 
959
959
  [[package]]
960
960
  name = "thiserror-impl"
961
- version = "2.0.20"
961
+ version = "2.0.21"
962
962
  source = "registry+https://github.com/rust-lang/crates.io-index"
963
- checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
963
+ checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
964
964
  dependencies = [
965
965
  "proc-macro2",
966
966
  "quote",
@@ -3,7 +3,7 @@ members = ["crates/*"]
3
3
  resolver = "3"
4
4
 
5
5
  [workspace.package]
6
- version = "0.2.5"
6
+ version = "0.2.6-rc.1"
7
7
  edition = "2024"
8
8
  license = "MIT"
9
9
  repository = "https://github.com/eitsupi/dlin"
@@ -38,7 +38,7 @@ insta = "1"
38
38
  serial_test = "4.0.1"
39
39
 
40
40
  # internal
41
- dlin-core = { version = "0.2.4-rc.3", path = "crates/dlin-core" }
41
+ dlin-core = { version = "0.2.6-rc.1", path = "crates/dlin-core" }
42
42
 
43
43
  [workspace.lints.rust]
44
44
  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.5
3
+ Version: 0.2.6rc1
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -1,5 +1,24 @@
1
1
  use super::*;
2
2
 
3
+ fn setup_cyclic_project() -> tempfile::TempDir {
4
+ use std::fs;
5
+
6
+ let tmp = tempfile::tempdir().unwrap();
7
+ fs::create_dir_all(tmp.path().join("models")).unwrap();
8
+ fs::write(tmp.path().join("dbt_project.yml"), "name: cycle_test\n").unwrap();
9
+ fs::write(
10
+ tmp.path().join("models/a.sql"),
11
+ "select * from {{ ref('b') }}\n",
12
+ )
13
+ .unwrap();
14
+ fs::write(
15
+ tmp.path().join("models/b.sql"),
16
+ "select * from {{ ref('a') }}\n",
17
+ )
18
+ .unwrap();
19
+ tmp
20
+ }
21
+
3
22
  #[test]
4
23
  fn test_error_format_json_on_error() {
5
24
  // Run impact on a nonexistent project to trigger an error
@@ -91,3 +110,54 @@ fn test_error_format_default_is_text() {
91
110
  "Expected text error, got: {stderr}"
92
111
  );
93
112
  }
113
+
114
+ #[test]
115
+ fn test_cycle_diagnostic_in_text_format_includes_unique_id_path() {
116
+ let project = setup_cyclic_project();
117
+ let output = std::process::Command::new(binary_path())
118
+ .args([
119
+ "--error-format",
120
+ "text",
121
+ "graph",
122
+ "-p",
123
+ project.path().to_str().unwrap(),
124
+ ])
125
+ .output()
126
+ .expect("Failed to run binary");
127
+
128
+ assert!(!output.status.success());
129
+ let stderr = String::from_utf8_lossy(&output.stderr);
130
+ assert!(
131
+ stderr.contains("cycle detected in lineage graph: model.a -> model.b -> model.a"),
132
+ "Expected the directed cycle path, got: {stderr}"
133
+ );
134
+ }
135
+
136
+ #[test]
137
+ fn test_cycle_diagnostic_in_json_format_includes_unique_id_path() {
138
+ let project = setup_cyclic_project();
139
+ let output = std::process::Command::new(binary_path())
140
+ .args([
141
+ "--error-format",
142
+ "json",
143
+ "graph",
144
+ "-p",
145
+ project.path().to_str().unwrap(),
146
+ ])
147
+ .output()
148
+ .expect("Failed to run binary");
149
+
150
+ assert!(!output.status.success());
151
+ let stderr = String::from_utf8_lossy(&output.stderr);
152
+ let parsed: serde_json::Value = serde_json::from_str(stderr.trim()).unwrap_or_else(|error| {
153
+ panic!("stderr is not valid JSON: {error}\nstderr: {stderr}");
154
+ });
155
+ assert_eq!(parsed["level"], "error");
156
+ assert!(
157
+ parsed["what"]
158
+ .as_str()
159
+ .unwrap()
160
+ .contains("model.a -> model.b -> model.a"),
161
+ "Expected the directed cycle path, got: {parsed}"
162
+ );
163
+ }
@@ -0,0 +1,255 @@
1
+ use super::*;
2
+ use std::process::Command;
3
+
4
+ fn issue_199_project(model_sql: &str) -> tempfile::TempDir {
5
+ let tmp = tempfile::tempdir().unwrap();
6
+ std::fs::create_dir_all(tmp.path().join("models")).unwrap();
7
+ std::fs::create_dir_all(tmp.path().join("macros")).unwrap();
8
+ std::fs::write(
9
+ tmp.path().join("dbt_project.yml"),
10
+ "name: issue_199\nmodel-paths: [models]\nmacro-paths: [macros]\n",
11
+ )
12
+ .unwrap();
13
+ // The project macro is valid but unused; scanning the whole prefix adds a
14
+ // false self-edge in both the failed-render and runtime-macro scenarios.
15
+ std::fs::write(tmp.path().join("models/u.sql"), model_sql).unwrap();
16
+ std::fs::write(
17
+ tmp.path().join("models/downstream.sql"),
18
+ "select * from {{ ref('u') }}\n",
19
+ )
20
+ .unwrap();
21
+ std::fs::write(tmp.path().join("models/base.sql"), "select 1 as id\n").unwrap();
22
+ std::fs::write(
23
+ tmp.path().join("macros/unused.sql"),
24
+ "{% macro unused() %}{{ ref('u') }}{% endmacro %}\n",
25
+ )
26
+ .unwrap();
27
+ tmp
28
+ }
29
+
30
+ fn run_graph(project: &std::path::Path, cache_args: &[&str]) -> std::process::Output {
31
+ Command::new(binary_path())
32
+ .args(["graph", "--project-dir"])
33
+ .arg(project)
34
+ .args(["--output", "json"])
35
+ .args(cache_args)
36
+ .output()
37
+ .expect("failed to run graph")
38
+ }
39
+
40
+ #[test]
41
+ fn test_issue_199_render_failure_does_not_create_self_cycle() {
42
+ let project = issue_199_project("select * from {{ ref('base') }}\n{{ missing_macro() }}\n");
43
+ assert_issue_project_has_no_false_self_edge(&project);
44
+
45
+ let impact = Command::new(binary_path())
46
+ .args(["impact", "u", "--project-dir"])
47
+ .arg(project.path())
48
+ .arg("--no-cache")
49
+ .output()
50
+ .expect("failed to run impact");
51
+ assert!(
52
+ impact.status.success(),
53
+ "impact failed: {}",
54
+ String::from_utf8_lossy(&impact.stderr)
55
+ );
56
+ assert!(
57
+ String::from_utf8_lossy(&impact.stdout).contains("downstream"),
58
+ "impact should include downstream model: {}",
59
+ String::from_utf8_lossy(&impact.stdout)
60
+ );
61
+ }
62
+
63
+ #[test]
64
+ fn test_issue_199_adapter_runtime_case_does_not_create_self_cycle() {
65
+ let project = issue_199_project(
66
+ "{% if is_incremental() %}select {{ gmid('d') }} from {{ ref('base') }}{% else %}select 1 from {{ ref('base') }}{% endif %}\n",
67
+ );
68
+ std::fs::write(
69
+ project.path().join("macros/gmid.sql"),
70
+ r#"
71
+ {% macro gmid(c) %}
72
+ {% if is_incremental() %}
73
+ {% set r = adapter.get_relation(this.database, this.schema, this.identifier) %}
74
+ {% if r %}{% set q = run_query('select 1') %}{% endif %}
75
+ {% endif %}
76
+ current_date()
77
+ {% endmacro %}
78
+ "#,
79
+ )
80
+ .unwrap();
81
+ assert_issue_project_has_no_false_self_edge(&project);
82
+
83
+ let impact = Command::new(binary_path())
84
+ .args(["impact", "u", "--project-dir"])
85
+ .arg(project.path())
86
+ .arg("--no-cache")
87
+ .output()
88
+ .expect("failed to run impact");
89
+ assert!(
90
+ impact.status.success(),
91
+ "adapter runtime impact failed: {}",
92
+ String::from_utf8_lossy(&impact.stderr)
93
+ );
94
+ assert!(
95
+ String::from_utf8_lossy(&impact.stdout).contains("downstream"),
96
+ "impact should include downstream model: {}",
97
+ String::from_utf8_lossy(&impact.stdout)
98
+ );
99
+ }
100
+
101
+ #[test]
102
+ fn test_runtime_macro_branches_recover_only_reachable_dependencies() {
103
+ let project = runtime_macro_branch_project();
104
+ let no_cache = run_graph(project.path(), &["--no-cache"]);
105
+ assert!(
106
+ no_cache.status.success(),
107
+ "uncached graph failed: {}",
108
+ String::from_utf8_lossy(&no_cache.stderr)
109
+ );
110
+ let no_cache_graph: serde_json::Value = serde_json::from_slice(&no_cache.stdout).unwrap();
111
+ assert_runtime_macro_edges(&no_cache_graph);
112
+
113
+ let cache_dir = project.path().join("dlin-cache");
114
+ let cache_dir_arg = cache_dir.to_str().unwrap();
115
+ let cold = run_graph(project.path(), &["--cache-dir", cache_dir_arg]);
116
+ assert!(
117
+ cold.status.success(),
118
+ "cold cached graph failed: {}",
119
+ String::from_utf8_lossy(&cold.stderr)
120
+ );
121
+ let warm = run_graph(project.path(), &["--cache-dir", cache_dir_arg]);
122
+ assert!(
123
+ warm.status.success(),
124
+ "warm cached graph failed: {}",
125
+ String::from_utf8_lossy(&warm.stderr)
126
+ );
127
+ let cold_graph: serde_json::Value = serde_json::from_slice(&cold.stdout).unwrap();
128
+ let warm_graph: serde_json::Value = serde_json::from_slice(&warm.stdout).unwrap();
129
+ assert_runtime_macro_edges(&cold_graph);
130
+ assert_runtime_macro_edges(&warm_graph);
131
+ assert_eq!(cold_graph, warm_graph, "cold and warm graph output differs");
132
+ assert_eq!(
133
+ no_cache_graph, cold_graph,
134
+ "cached and uncached graph output differs"
135
+ );
136
+ }
137
+
138
+ fn runtime_macro_branch_project() -> tempfile::TempDir {
139
+ let tmp = tempfile::tempdir().unwrap();
140
+ std::fs::create_dir_all(tmp.path().join("models")).unwrap();
141
+ std::fs::create_dir_all(tmp.path().join("macros")).unwrap();
142
+ std::fs::write(
143
+ tmp.path().join("dbt_project.yml"),
144
+ "name: runtime_macro_repro\nmodel-paths: [models]\nmacro-paths: [macros]\n",
145
+ )
146
+ .unwrap();
147
+ std::fs::write(
148
+ tmp.path().join("models/downstream.sql"),
149
+ "select {{ choose() }} as chosen\n",
150
+ )
151
+ .unwrap();
152
+ std::fs::write(tmp.path().join("models/yes_branch.sql"), "select 1 as id\n").unwrap();
153
+ std::fs::write(tmp.path().join("models/no_branch.sql"), "select 0 as id\n").unwrap();
154
+ std::fs::write(
155
+ tmp.path().join("models/schema.yml"),
156
+ "version: 2\nsources:\n - name: unused\n tables:\n - name: tbl\n",
157
+ )
158
+ .unwrap();
159
+ std::fs::write(
160
+ tmp.path().join("macros/choose.sql"),
161
+ r#"
162
+ {% macro choose() %}
163
+ {% if adapter.get_relation(identifier='x') %}
164
+ {{ ref('yes_branch') }}
165
+ {% else %}
166
+ {{ ref('no_branch') }}
167
+ {% endif %}
168
+ {% endmacro %}
169
+
170
+ {% macro unused() %}{{ ref('downstream') }}{{ source('unused', 'tbl') }}{% endmacro %}
171
+ "#,
172
+ )
173
+ .unwrap();
174
+ tmp
175
+ }
176
+
177
+ fn assert_runtime_macro_edges(graph: &serde_json::Value) {
178
+ let edges = graph["edges"].as_array().unwrap();
179
+ for source in ["model.yes_branch", "model.no_branch"] {
180
+ assert!(
181
+ edges.iter().any(|edge| {
182
+ edge["source"].as_str() == Some(source)
183
+ && edge["target"].as_str() == Some("model.downstream")
184
+ && edge["edge_type"].as_str() == Some("ref")
185
+ }),
186
+ "missing {source} edge: {edges:?}"
187
+ );
188
+ }
189
+ assert!(
190
+ !edges.iter().any(|edge| {
191
+ edge["source"].as_str() == Some("model.downstream")
192
+ && edge["target"].as_str() == Some("model.downstream")
193
+ }),
194
+ "unused macro added downstream self-edge: {edges:?}"
195
+ );
196
+ assert!(
197
+ !edges.iter().any(|edge| {
198
+ edge["source"].as_str() == Some("source.unused.tbl")
199
+ && edge["target"].as_str() == Some("model.downstream")
200
+ }),
201
+ "unused macro source edge leaked into graph: {edges:?}"
202
+ );
203
+ }
204
+
205
+ fn assert_issue_project_has_no_false_self_edge(project: &tempfile::TempDir) {
206
+ let no_cache = run_graph(project.path(), &["--no-cache"]);
207
+ assert!(
208
+ no_cache.status.success(),
209
+ "uncached graph failed: {}",
210
+ String::from_utf8_lossy(&no_cache.stderr)
211
+ );
212
+ let no_cache_graph: serde_json::Value = serde_json::from_slice(&no_cache.stdout).unwrap();
213
+ assert_no_u_self_edge(&no_cache_graph);
214
+
215
+ let cache_dir = project.path().join("dlin-cache");
216
+ let cache_dir_arg = cache_dir.to_str().unwrap();
217
+ let cold = run_graph(project.path(), &["--cache-dir", cache_dir_arg]);
218
+ assert!(
219
+ cold.status.success(),
220
+ "cold cached graph failed: {}",
221
+ String::from_utf8_lossy(&cold.stderr)
222
+ );
223
+ let warm = run_graph(project.path(), &["--cache-dir", cache_dir_arg]);
224
+ assert!(
225
+ warm.status.success(),
226
+ "warm cached graph failed: {}",
227
+ String::from_utf8_lossy(&warm.stderr)
228
+ );
229
+ let cold_graph: serde_json::Value = serde_json::from_slice(&cold.stdout).unwrap();
230
+ let warm_graph: serde_json::Value = serde_json::from_slice(&warm.stdout).unwrap();
231
+ assert_no_u_self_edge(&cold_graph);
232
+ assert_no_u_self_edge(&warm_graph);
233
+ assert_eq!(cold_graph, warm_graph, "cold and warm graph output differs");
234
+ assert_eq!(
235
+ no_cache_graph, cold_graph,
236
+ "cached and uncached graph output differs"
237
+ );
238
+
239
+ for graph in [&no_cache_graph, &cold_graph, &warm_graph] {
240
+ assert!(graph["edges"].as_array().unwrap().iter().any(|edge| {
241
+ edge["source"].as_str() == Some("model.base")
242
+ && edge["target"].as_str() == Some("model.u")
243
+ }));
244
+ }
245
+ }
246
+
247
+ fn assert_no_u_self_edge(graph: &serde_json::Value) {
248
+ let edges = graph["edges"].as_array().unwrap();
249
+ assert!(
250
+ !edges.iter().any(|edge| {
251
+ edge["source"].as_str() == Some("model.u") && edge["target"].as_str() == Some("model.u")
252
+ }),
253
+ "unexpected model.u self-edge: {edges:?}"
254
+ );
255
+ }
@@ -76,5 +76,7 @@ mod generic_test_metadata;
76
76
  mod manifest_only;
77
77
  #[path = "integration/parsing.rs"]
78
78
  mod parsing;
79
+ #[path = "integration/render_failure.rs"]
80
+ mod render_failure;
79
81
  #[path = "integration/sql_mode_test_warning.rs"]
80
82
  mod sql_mode_test_warning;
@@ -275,7 +275,14 @@ pub fn filter_graph(
275
275
  ) -> Result<LineageGraph> {
276
276
  // Check for cycles
277
277
  if petgraph::algo::is_cyclic_directed(graph) {
278
- return Err(DbtLineageError::CycleDetected.into());
278
+ let cycle = find_directed_cycle(graph)
279
+ .expect("petgraph reported a cycle but cycle path extraction found none");
280
+ return Err(
281
+ anyhow::Error::new(DbtLineageError::CycleDetected).context(format!(
282
+ "cycle detected in lineage graph: {}",
283
+ cycle.join(" -> ")
284
+ )),
285
+ );
279
286
  }
280
287
 
281
288
  let mut keep_nodes: HashSet<NodeIndex> = HashSet::new();
@@ -343,6 +350,97 @@ pub fn filter_graph(
343
350
  }
344
351
  }
345
352
 
353
+ /// Find one real directed cycle, with its first node repeated at the end.
354
+ ///
355
+ /// This runs only after the linear-time acyclic check has found a cycle. The
356
+ /// iterative DFS avoids recursion depth limits on large lineage graphs, and
357
+ /// sorted roots/neighbors make the reported path deterministic.
358
+ fn find_directed_cycle(graph: &LineageGraph) -> Option<Vec<String>> {
359
+ struct Frame {
360
+ node: NodeIndex,
361
+ neighbors: Vec<NodeIndex>,
362
+ next_neighbor: usize,
363
+ }
364
+
365
+ let mut roots: Vec<_> = graph.node_indices().collect();
366
+ roots.sort_by(|left, right| {
367
+ graph[*left]
368
+ .unique_id
369
+ .cmp(&graph[*right].unique_id)
370
+ .then_with(|| left.index().cmp(&right.index()))
371
+ });
372
+
373
+ let mut color = HashMap::<NodeIndex, u8>::new();
374
+ let mut parent = HashMap::<NodeIndex, NodeIndex>::new();
375
+
376
+ for root in roots {
377
+ if color.get(&root).copied().unwrap_or(0) != 0 {
378
+ continue;
379
+ }
380
+ color.insert(root, 1);
381
+ let mut stack = vec![Frame {
382
+ node: root,
383
+ neighbors: sorted_successors(graph, root),
384
+ next_neighbor: 0,
385
+ }];
386
+
387
+ while let Some(frame) = stack.last_mut() {
388
+ if frame.next_neighbor == frame.neighbors.len() {
389
+ color.insert(frame.node, 2);
390
+ stack.pop();
391
+ continue;
392
+ }
393
+
394
+ let node = frame.node;
395
+ let next = frame.neighbors[frame.next_neighbor];
396
+ frame.next_neighbor += 1;
397
+ match color.get(&next).copied().unwrap_or(0) {
398
+ 0 => {
399
+ parent.insert(next, node);
400
+ color.insert(next, 1);
401
+ stack.push(Frame {
402
+ node: next,
403
+ neighbors: sorted_successors(graph, next),
404
+ next_neighbor: 0,
405
+ });
406
+ }
407
+ 1 => {
408
+ let mut cycle = vec![node];
409
+ let mut cursor = node;
410
+ while cursor != next {
411
+ cursor = *parent.get(&cursor)?;
412
+ cycle.push(cursor);
413
+ }
414
+ cycle.reverse();
415
+ cycle.push(next);
416
+ return Some(
417
+ cycle
418
+ .into_iter()
419
+ .map(|index| graph[index].unique_id.clone())
420
+ .collect(),
421
+ );
422
+ }
423
+ _ => {}
424
+ }
425
+ }
426
+ }
427
+
428
+ None
429
+ }
430
+
431
+ fn sorted_successors(graph: &LineageGraph, node: NodeIndex) -> Vec<NodeIndex> {
432
+ let mut successors: Vec<_> = graph
433
+ .neighbors_directed(node, Direction::Outgoing)
434
+ .collect();
435
+ successors.sort_by(|left, right| {
436
+ graph[*left]
437
+ .unique_id
438
+ .cmp(&graph[*right].unique_id)
439
+ .then_with(|| left.index().cmp(&right.index()))
440
+ });
441
+ successors
442
+ }
443
+
346
444
  /// Build a new graph containing only the specified nodes and their interconnecting edges.
347
445
  ///
348
446
  /// Edges between kept nodes that pass through filtered-out intermediaries are dropped.