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.
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/Cargo.lock +26 -26
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/Cargo.toml +2 -2
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/PKG-INFO +1 -1
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/error_format.rs +70 -0
- dlin_cli-0.2.6rc1/crates/dlin/tests/integration/render_failure.rs +255 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration_test.rs +2 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/mod.rs +99 -1
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/output.rs +90 -1
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/jinja/mod.rs +6 -4
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/jinja/render.rs +36 -4
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/jinja/tests.rs +95 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/sql/mod.rs +74 -20
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/sql/tests.rs +159 -5
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/LICENSE +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/README.md +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/Cargo.toml +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/README.md +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/cli/column.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/cli/command.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/cli/common.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/cli/graph.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/cli/misc.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/cli/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/cli/summary.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/commands/column.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/commands/debug.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/commands/graph.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/commands/manifest.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/commands/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/commands/shared.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/main.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/mcp/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/mcp/protocol.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/mcp/tests.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/src/mcp/tools.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/artifacts.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/cli.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/freshness.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/generic_test_metadata.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/manifest_only.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/manifest_only_rest.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/parsing.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin/tests/integration/sql_mode_test_warning.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/Cargo.toml +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/README.md +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/error.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/project_yaml.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/semantic_layer.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/tests/build_graph/basic.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/tests/build_graph/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/tests/build_graph/semantic_layer.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/tests/build_graph/yaml_snapshots.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/tests/generic_tests.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/tests/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/tests/ref_parsing.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/builder/tests/versioned.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/backend/catalog.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/backend/catalog_provider.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/backend/dialect.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/backend/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/backend/sqllineage.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/backend/types.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/cache.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/cross_model.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/impact.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/relation.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/schema.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/alias.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/backend_regression/basic.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/backend_regression/discovery.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/backend_regression/mod.rs +0 -0
- {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
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/cache.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/core_basic.rs +0 -0
- {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
- {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
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/core_star.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/core_star_chain.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/core_util.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/impact/core.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/impact/edge_cases.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/impact/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/tests/regression_fixtures.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/column_lineage/types.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/collapse.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/filter_graph.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/search.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/selector.rs +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/transitive.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/filter/tests/versioned.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/impact.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/graph/types.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/input.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/lib.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/cache.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/columns.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/discovery.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/jinja/reachability/analysis.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/jinja/reachability/catalog.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/jinja/reachability/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/jinja/reachability/tests.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/jinja/source.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/graph.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/report/decode.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/report/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/report/tests.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/tests/compatibility.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/tests/contents.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/tests/graph.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/tests/identifiers.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/tests/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest/tests/semantic_layer.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/manifest_cache.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/project.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/sql/recovery.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/parser/yaml_schema.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/ascii.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/column_graph.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/column_graph_tests.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/dot.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/html.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/impact.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/json.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/layout.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/list.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/mermaid.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/mod.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/plain.rs +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/summary.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/src/render/svg.rs +0 -0
- {dlin_cli-0.2.5 → dlin_cli-0.2.6rc1}/crates/dlin-core/tests/column_lineage_test.rs +0 -0
- {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.
|
|
142
|
+
version = "4.6.7"
|
|
143
143
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
144
|
-
checksum = "
|
|
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.
|
|
152
|
+
version = "4.6.7"
|
|
153
153
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
154
|
-
checksum = "
|
|
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.
|
|
164
|
+
version = "4.6.7"
|
|
165
165
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
166
|
-
checksum = "
|
|
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.
|
|
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.
|
|
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.
|
|
405
|
+
version = "0.4.20"
|
|
406
406
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
407
|
-
checksum = "
|
|
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.
|
|
418
|
+
version = "1.3.0"
|
|
419
419
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
420
|
-
checksum = "
|
|
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.
|
|
449
|
+
version = "2.14.2"
|
|
450
450
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
451
|
-
checksum = "
|
|
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.
|
|
699
|
+
version = "1.13.1"
|
|
700
700
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
701
|
-
checksum = "
|
|
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.
|
|
711
|
+
version = "0.4.18"
|
|
712
712
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
713
|
-
checksum = "
|
|
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.
|
|
722
|
+
version = "0.8.11"
|
|
723
723
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
724
|
-
checksum = "
|
|
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.
|
|
766
|
+
version = "1.3.0"
|
|
767
767
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
768
|
-
checksum = "
|
|
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.
|
|
952
|
+
version = "2.0.21"
|
|
953
953
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
954
|
-
checksum = "
|
|
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.
|
|
961
|
+
version = "2.0.21"
|
|
962
962
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
963
|
-
checksum = "
|
|
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.
|
|
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.
|
|
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,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
|
+
}
|
|
@@ -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
|
-
|
|
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.
|