snowflake-code-unit-registry 0.8.74__tar.gz → 0.8.76__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.
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/Cargo.lock +39 -13
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/Cargo.toml +4 -1
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/PKG-INFO +1 -1
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/Cargo.toml +1 -0
- snowflake_code_unit_registry-0.8.76/crates/scai-state-core/src/database_bindings.rs +473 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/lib.rs +5 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/registry/tests.rs +248 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/registry.rs +37 -1
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/src/lib.rs +64 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/pyproject.toml +1 -1
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/python/snowflake_code_unit_registry/__init__.py +100 -1
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/README.md +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-error-derive/Cargo.toml +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-error-derive/src/lib.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-error-derive/tests/derive_integration.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-log/Cargo.toml +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-log/src/lib.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-log/src/tests.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/build/file_path_fields.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/build/main.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/build/output.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/build/query_reference.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/build/schema.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/build/schema_constraints.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/build/typify.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/build/updated_at_paths.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/examples/code-unit.example.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/examples/custom-code-unit.example.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/examples/etl-code-unit.example.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/examples/script-code-unit.example.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/schemas/code-unit.schema.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/schemas/history/README.md +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/schemas/query-reference.rs.tmpl +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/schemas/query-reference.tmpl +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/bindings.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/canonical_name.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/checksum.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/dependency_view.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/error.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/error_trace.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/filter.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/generated/file_path_fields.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/generated/mod.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/generated/query_reference.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/generated/schema_constraints.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/generated/updated_at_paths.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/identity.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/migration/mod.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/migration/versions/mod.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/registry/README.md +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/registry/graph.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/registry/in_memory.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/registry/loader.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/registry/paths.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/registry/query.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/registry/test_helpers.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/registry/test_hooks.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/validation/mod.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/src/validation/script_io.rs +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/custom_with_signature.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/db_object_with_custom_kind.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/etl_part_cloud_status_data_migration.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/etl_part_cloud_status_data_validation.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/etl_part_model_name_on_non_dbt.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/etl_root_cloud_status.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/etl_root_dependencies.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/etl_root_issues.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/etl_root_required_by.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/etl_root_source_format.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/etl_signature.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/parameterized_reference_clean.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/parameterized_reference_with_cloud_status.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/parameterized_reference_with_code_status.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/parameterized_reference_with_files.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/parameterized_reference_with_issues.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/parameterized_reference_with_parts.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/parameterized_reference_with_planning.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/parameterized_reference_with_script_bindings.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/parameterized_reference_with_script_metadata.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/parameterized_reference_with_signature.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/parameterized_reference_with_source_format.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/parameterized_reference_with_target_format.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/sql_with_parts.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-core/tests/fixtures/kind-rules/sql_with_stabilization.json +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/Cargo.toml +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/README.md +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/tests/conftest.py +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/tests/test_batch.py +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/tests/test_checksum.py +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/tests/test_cloud_state.py +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/tests/test_crud.py +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/tests/test_errors.py +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/tests/test_logging.py +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/tests/test_migration.py +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/tests/test_query.py +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/tests/test_refresh.py +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/tests/test_script_bindings.py +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/tests/test_script_metadata.py +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/tests/test_serialization.py +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/crates/scai-state-python/tests/test_validation.py +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/python/snowflake_code_unit_registry/cloud_state.py +0 -0
- {snowflake_code_unit_registry-0.8.74 → snowflake_code_unit_registry-0.8.76}/python/snowflake_code_unit_registry/types.py +0 -0
|
@@ -376,9 +376,9 @@ dependencies = [
|
|
|
376
376
|
|
|
377
377
|
[[package]]
|
|
378
378
|
name = "combine"
|
|
379
|
-
version = "4.6.
|
|
379
|
+
version = "4.6.8"
|
|
380
380
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
381
|
-
checksum = "
|
|
381
|
+
checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e"
|
|
382
382
|
dependencies = [
|
|
383
383
|
"bytes",
|
|
384
384
|
"memchr",
|
|
@@ -2315,9 +2315,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
|
|
2315
2315
|
|
|
2316
2316
|
[[package]]
|
|
2317
2317
|
name = "rand"
|
|
2318
|
-
version = "0.8.
|
|
2318
|
+
version = "0.8.8"
|
|
2319
2319
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2320
|
-
checksum = "
|
|
2320
|
+
checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
|
|
2321
2321
|
dependencies = [
|
|
2322
2322
|
"libc",
|
|
2323
2323
|
"rand_chacha",
|
|
@@ -2644,6 +2644,12 @@ version = "1.0.23"
|
|
|
2644
2644
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2645
2645
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
|
2646
2646
|
|
|
2647
|
+
[[package]]
|
|
2648
|
+
name = "ryu"
|
|
2649
|
+
version = "1.0.23"
|
|
2650
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2651
|
+
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
|
2652
|
+
|
|
2647
2653
|
[[package]]
|
|
2648
2654
|
name = "same-file"
|
|
2649
2655
|
version = "1.0.6"
|
|
@@ -2655,7 +2661,7 @@ dependencies = [
|
|
|
2655
2661
|
|
|
2656
2662
|
[[package]]
|
|
2657
2663
|
name = "scai-error-derive"
|
|
2658
|
-
version = "0.8.
|
|
2664
|
+
version = "0.8.76"
|
|
2659
2665
|
dependencies = [
|
|
2660
2666
|
"err_code",
|
|
2661
2667
|
"heck",
|
|
@@ -2669,7 +2675,7 @@ dependencies = [
|
|
|
2669
2675
|
|
|
2670
2676
|
[[package]]
|
|
2671
2677
|
name = "scai-log"
|
|
2672
|
-
version = "0.8.
|
|
2678
|
+
version = "0.8.76"
|
|
2673
2679
|
dependencies = [
|
|
2674
2680
|
"chrono",
|
|
2675
2681
|
"serde",
|
|
@@ -2678,7 +2684,7 @@ dependencies = [
|
|
|
2678
2684
|
|
|
2679
2685
|
[[package]]
|
|
2680
2686
|
name = "scai-state-core"
|
|
2681
|
-
version = "0.8.
|
|
2687
|
+
version = "0.8.76"
|
|
2682
2688
|
dependencies = [
|
|
2683
2689
|
"chrono",
|
|
2684
2690
|
"err_code",
|
|
@@ -2691,6 +2697,7 @@ dependencies = [
|
|
|
2691
2697
|
"schemars",
|
|
2692
2698
|
"serde",
|
|
2693
2699
|
"serde_json",
|
|
2700
|
+
"serde_norway",
|
|
2694
2701
|
"snafu",
|
|
2695
2702
|
"sqlparser",
|
|
2696
2703
|
"tempfile",
|
|
@@ -2703,7 +2710,7 @@ dependencies = [
|
|
|
2703
2710
|
|
|
2704
2711
|
[[package]]
|
|
2705
2712
|
name = "scai-state-csharp"
|
|
2706
|
-
version = "0.8.
|
|
2713
|
+
version = "0.8.76"
|
|
2707
2714
|
dependencies = [
|
|
2708
2715
|
"interoptopus",
|
|
2709
2716
|
"interoptopus_backend_csharp",
|
|
@@ -2715,7 +2722,7 @@ dependencies = [
|
|
|
2715
2722
|
|
|
2716
2723
|
[[package]]
|
|
2717
2724
|
name = "scai-state-node"
|
|
2718
|
-
version = "0.8.
|
|
2725
|
+
version = "0.8.76"
|
|
2719
2726
|
dependencies = [
|
|
2720
2727
|
"napi",
|
|
2721
2728
|
"napi-build",
|
|
@@ -2728,7 +2735,7 @@ dependencies = [
|
|
|
2728
2735
|
|
|
2729
2736
|
[[package]]
|
|
2730
2737
|
name = "scai-state-perf"
|
|
2731
|
-
version = "0.8.
|
|
2738
|
+
version = "0.8.76"
|
|
2732
2739
|
dependencies = [
|
|
2733
2740
|
"chrono",
|
|
2734
2741
|
"criterion",
|
|
@@ -2744,7 +2751,7 @@ dependencies = [
|
|
|
2744
2751
|
|
|
2745
2752
|
[[package]]
|
|
2746
2753
|
name = "scai-state-python"
|
|
2747
|
-
version = "0.8.
|
|
2754
|
+
version = "0.8.76"
|
|
2748
2755
|
dependencies = [
|
|
2749
2756
|
"pyo3",
|
|
2750
2757
|
"pythonize",
|
|
@@ -2880,6 +2887,19 @@ dependencies = [
|
|
|
2880
2887
|
"zmij",
|
|
2881
2888
|
]
|
|
2882
2889
|
|
|
2890
|
+
[[package]]
|
|
2891
|
+
name = "serde_norway"
|
|
2892
|
+
version = "0.9.42"
|
|
2893
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2894
|
+
checksum = "e408f29489b5fd500fab51ff1484fc859bb655f32c671f307dcd733b72e8168c"
|
|
2895
|
+
dependencies = [
|
|
2896
|
+
"indexmap",
|
|
2897
|
+
"itoa",
|
|
2898
|
+
"ryu",
|
|
2899
|
+
"serde",
|
|
2900
|
+
"unsafe-libyaml-norway",
|
|
2901
|
+
]
|
|
2902
|
+
|
|
2883
2903
|
[[package]]
|
|
2884
2904
|
name = "serde_tokenstream"
|
|
2885
2905
|
version = "0.2.3"
|
|
@@ -3425,6 +3445,12 @@ version = "1.13.3"
|
|
|
3425
3445
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3426
3446
|
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
|
3427
3447
|
|
|
3448
|
+
[[package]]
|
|
3449
|
+
name = "unsafe-libyaml-norway"
|
|
3450
|
+
version = "0.2.15"
|
|
3451
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3452
|
+
checksum = "b39abd59bf32521c7f2301b52d05a6a2c975b6003521cbd0c6dc1582f0a22104"
|
|
3453
|
+
|
|
3428
3454
|
[[package]]
|
|
3429
3455
|
name = "untrusted"
|
|
3430
3456
|
version = "0.9.0"
|
|
@@ -3451,9 +3477,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
|
|
3451
3477
|
|
|
3452
3478
|
[[package]]
|
|
3453
3479
|
name = "uuid"
|
|
3454
|
-
version = "1.
|
|
3480
|
+
version = "1.26.0"
|
|
3455
3481
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3456
|
-
checksum = "
|
|
3482
|
+
checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812"
|
|
3457
3483
|
dependencies = [
|
|
3458
3484
|
"getrandom 0.4.3",
|
|
3459
3485
|
"js-sys",
|
|
@@ -3,7 +3,7 @@ resolver = "2"
|
|
|
3
3
|
members = ["crates/scai-error-derive", "crates/scai-log", "crates/scai-state-core", "crates/scai-state-python"]
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
|
-
version = "0.8.
|
|
6
|
+
version = "0.8.76"
|
|
7
7
|
edition = "2021"
|
|
8
8
|
license = "SEE LICENSE IN LICENSE"
|
|
9
9
|
repository = "https://github.com/snowflake-eng/scai-state"
|
|
@@ -11,6 +11,9 @@ repository = "https://github.com/snowflake-eng/scai-state"
|
|
|
11
11
|
[workspace.dependencies]
|
|
12
12
|
serde = { version = "1.0", features = ["derive"] }
|
|
13
13
|
serde_json = "1.0"
|
|
14
|
+
# Maintained fork of the deprecated/unmaintained serde_yaml. Reads the
|
|
15
|
+
# `database-bindings.yml` SnowConvert generates; see `database_bindings`.
|
|
16
|
+
serde_norway = "0.9"
|
|
14
17
|
snafu = "0.9"
|
|
15
18
|
err_code = "0.1"
|
|
16
19
|
uuid = { version = "1.0", features = ["v4"] }
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
//! Reads the `database-bindings.yml` SnowConvert generates and turns it into the
|
|
2
|
+
//! literal token map [`FindOptions::bindings`] consumes, so a read can return
|
|
3
|
+
//! physical database names instead of the tokens a bindable conversion wrote.
|
|
4
|
+
//!
|
|
5
|
+
//! Lives outside `registry` for the same reason [`crate::bindings`] does: it
|
|
6
|
+
//! operates on caller-supplied values, not on registry state.
|
|
7
|
+
//!
|
|
8
|
+
//! # Why this exists
|
|
9
|
+
//!
|
|
10
|
+
//! A bindable conversion records `${NAME}` in `source.database` and `<%NAME%>` in
|
|
11
|
+
//! `target.database` so one artifact can be deployed and tested in whichever
|
|
12
|
+
//! database the operator chose. Every consumer therefore has to resolve those
|
|
13
|
+
//! tokens before treating the value as a real identifier — and each one that
|
|
14
|
+
//! forgets produces a bug far from its cause. Supplying the map at the read
|
|
15
|
+
//! removes the whole class rather than patching instances of it.
|
|
16
|
+
//!
|
|
17
|
+
//! # Grammar ownership
|
|
18
|
+
//!
|
|
19
|
+
//! [`crate::bindings::apply_bindings`] is deliberately grammar-free: it matches
|
|
20
|
+
//! map keys verbatim and knows nothing about wrappers. This module is the *only*
|
|
21
|
+
//! place that knows a wrapper spelling, and it is careful about which:
|
|
22
|
+
//!
|
|
23
|
+
//! * The Snowflake side is Snow CLI templating, `<%NAME%>`, which is invariant —
|
|
24
|
+
//! so [`DatabaseBindings::snow_token_map`] owns it.
|
|
25
|
+
//! * The source side is **not** ownable here. The wrapper is a property of the
|
|
26
|
+
//! source script language and may be customer-specific, and more than one form
|
|
27
|
+
//! can be in play for a single name. [`DatabaseBindings::source_token_map`]
|
|
28
|
+
//! therefore takes the form(s) from the caller and defaults to `${NAME}`.
|
|
29
|
+
//!
|
|
30
|
+
//! # Case
|
|
31
|
+
//!
|
|
32
|
+
//! [`crate::bindings::apply_bindings`] matches byte-exact and leaves an unmatched
|
|
33
|
+
//! token alone, so a bindings file whose key case differs from the case the
|
|
34
|
+
//! conversion wrote would resolve nothing — silently. The token map therefore
|
|
35
|
+
//! emits as-written, all-lower and all-upper spellings of each name, and input
|
|
36
|
+
//! whose intended spelling is ambiguous is rejected outright. See [`wrap`].
|
|
37
|
+
//!
|
|
38
|
+
//! # Hazard: do not bind a read that mints a frozen identity
|
|
39
|
+
//!
|
|
40
|
+
//! `apply_bindings` substitutes `target.name` and `target.canonical_name` as well
|
|
41
|
+
//! as `target.database`. Consumers that derive a stable identity from those — a
|
|
42
|
+
//! baseline lookup key, a stage path, an on-disk layout — must read **without**
|
|
43
|
+
//! bindings, or the identity silently changes and previously stored artifacts stop
|
|
44
|
+
//! matching with no error raised. Read unbound for identity, bound for anything
|
|
45
|
+
//! that needs a physical database.
|
|
46
|
+
|
|
47
|
+
use std::collections::HashMap;
|
|
48
|
+
use std::path::Path;
|
|
49
|
+
|
|
50
|
+
use crate::error::{Error, Result};
|
|
51
|
+
|
|
52
|
+
/// Renders a binding name as the literal token a conversion wrote for it.
|
|
53
|
+
///
|
|
54
|
+
/// Used by [`DatabaseBindings::source_token_map`] to stay independent of any one
|
|
55
|
+
/// source-side wrapper spelling.
|
|
56
|
+
pub type TokenWrapper = fn(&str) -> String;
|
|
57
|
+
|
|
58
|
+
/// The default source-side wrapper: `${NAME}`, the BTEQ / shell-style form.
|
|
59
|
+
pub fn dollar_brace_token(name: &str) -> String {
|
|
60
|
+
format!("${{{name}}}")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// The invariant Snowflake-side wrapper: `<%NAME%>`, Snow CLI templating.
|
|
64
|
+
pub fn snow_cli_token(name: &str) -> String {
|
|
65
|
+
format!("<%{name}%>")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// The two sides of a `database-bindings.yml`.
|
|
69
|
+
///
|
|
70
|
+
/// ```yaml
|
|
71
|
+
/// source:
|
|
72
|
+
/// dutchie_test: dutchie_test # identity when the source is named that
|
|
73
|
+
/// snow:
|
|
74
|
+
/// dutchie_test: DUTCHIE_EXTRACT_MIG # the retargeted physical database
|
|
75
|
+
/// ```
|
|
76
|
+
///
|
|
77
|
+
/// Both sections are optional: a project may be bindable on one side only, and an
|
|
78
|
+
/// absent section yields an empty map rather than an error.
|
|
79
|
+
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
80
|
+
#[serde(deny_unknown_fields)]
|
|
81
|
+
pub struct DatabaseBindings {
|
|
82
|
+
/// Canonical name → physical **source** database.
|
|
83
|
+
#[serde(default)]
|
|
84
|
+
pub source: HashMap<String, String>,
|
|
85
|
+
/// Canonical name → physical **Snowflake** database.
|
|
86
|
+
#[serde(default)]
|
|
87
|
+
pub snow: HashMap<String, String>,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Parse `database-bindings.yml` contents.
|
|
91
|
+
///
|
|
92
|
+
/// Unknown top-level sections are rejected: a silently-empty map would make
|
|
93
|
+
/// substitution a no-op and hand tokens back to a caller that believes they were
|
|
94
|
+
/// resolved, which is the exact failure this module exists to prevent.
|
|
95
|
+
///
|
|
96
|
+
/// Two keys in one section that differ only by case are rejected for the same
|
|
97
|
+
/// reason — see [`reject_case_collisions`].
|
|
98
|
+
///
|
|
99
|
+
/// Returns `Err(Error::ValidationError)` when the document is not valid YAML or
|
|
100
|
+
/// does not match the two-section shape.
|
|
101
|
+
pub fn parse_database_bindings(yaml: &str) -> Result<DatabaseBindings> {
|
|
102
|
+
let parsed: DatabaseBindings = serde_norway::from_str(yaml)
|
|
103
|
+
.map_err(|e| Error::validation(format!("database-bindings.yml is not valid: {e}")))?;
|
|
104
|
+
reject_case_collisions("source", &parsed.source)?;
|
|
105
|
+
reject_case_collisions("snow", &parsed.snow)?;
|
|
106
|
+
Ok(parsed)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Reject two keys in one section that differ only by case.
|
|
110
|
+
///
|
|
111
|
+
/// [`wrap`] emits a case variant per name, so `{db: A, DB: B}` would have both
|
|
112
|
+
/// names claiming the same variant keys and the winner would depend on hash
|
|
113
|
+
/// iteration order — a nondeterministic substitution. Rejecting is the only
|
|
114
|
+
/// honest answer: nothing here can know which spelling the conversion wrote.
|
|
115
|
+
fn reject_case_collisions(section: &str, names: &HashMap<String, String>) -> Result<()> {
|
|
116
|
+
let mut folded: HashMap<String, &str> = HashMap::with_capacity(names.len());
|
|
117
|
+
for name in names.keys() {
|
|
118
|
+
if let Some(other) = folded.insert(name.to_lowercase(), name.as_str()) {
|
|
119
|
+
let (a, b) = if name.as_str() < other {
|
|
120
|
+
(name.as_str(), other)
|
|
121
|
+
} else {
|
|
122
|
+
(other, name.as_str())
|
|
123
|
+
};
|
|
124
|
+
return Err(Error::validation(format!(
|
|
125
|
+
"database-bindings.yml is not valid: `{section}` has two keys differing only by \
|
|
126
|
+
case (`{a}` and `{b}`); the token map emits case variants, so which one applied \
|
|
127
|
+
would depend on hash order"
|
|
128
|
+
)));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
Ok(())
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Read and parse a `database-bindings.yml` from disk.
|
|
135
|
+
///
|
|
136
|
+
/// Convenience over [`parse_database_bindings`] for callers holding a path. IO
|
|
137
|
+
/// failures surface as `Error::IoError`, parse failures as in
|
|
138
|
+
/// [`parse_database_bindings`].
|
|
139
|
+
pub fn read_database_bindings(path: &Path) -> Result<DatabaseBindings> {
|
|
140
|
+
let text = std::fs::read_to_string(path)?;
|
|
141
|
+
parse_database_bindings(&text)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
impl DatabaseBindings {
|
|
145
|
+
/// `<%NAME%> → physical database`, for reads that should return Snowflake
|
|
146
|
+
/// names instead of target-side tokens.
|
|
147
|
+
///
|
|
148
|
+
/// Feed straight into [`FindOptions::bindings`]. Empty when the `snow:`
|
|
149
|
+
/// section is absent, which makes the read a no-op rather than an error.
|
|
150
|
+
///
|
|
151
|
+
/// See the module-level hazard note before binding a read whose result mints
|
|
152
|
+
/// a frozen identity.
|
|
153
|
+
pub fn snow_token_map(&self) -> HashMap<String, String> {
|
|
154
|
+
wrap(&self.snow, &[snow_cli_token])
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// `wrapper(NAME) → physical database` for every wrapper form supplied.
|
|
158
|
+
///
|
|
159
|
+
/// Pass the form(s) the conversion actually wrote. Every form becomes its own
|
|
160
|
+
/// key, so a name carrying more than one spelling resolves whichever appears;
|
|
161
|
+
/// a form that was never written simply matches nothing.
|
|
162
|
+
///
|
|
163
|
+
/// Use [`Self::source_token_map_default`] for the common `${NAME}` case.
|
|
164
|
+
pub fn source_token_map(&self, wrappers: &[TokenWrapper]) -> HashMap<String, String> {
|
|
165
|
+
wrap(&self.source, wrappers)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// [`Self::source_token_map`] with the default `${NAME}` wrapper.
|
|
169
|
+
pub fn source_token_map_default(&self) -> HashMap<String, String> {
|
|
170
|
+
wrap(&self.source, &[dollar_brace_token])
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// Both sides in one map — `${NAME} → source database` and
|
|
174
|
+
/// `<%NAME%> → Snowflake database` — so one read returns fully bound units.
|
|
175
|
+
///
|
|
176
|
+
/// Merging is safe because the two wrappers are *disjoint*: a `${…}` key can
|
|
177
|
+
/// never match a `<%…%>` token or vice versa, so a slot only ever matches the
|
|
178
|
+
/// side whose grammar it was written in. This is what lets
|
|
179
|
+
/// [`FindOptions::bindings_path`] take a path and nothing else.
|
|
180
|
+
///
|
|
181
|
+
/// Uses the **default** `${NAME}` source grammar. A project whose conversion
|
|
182
|
+
/// wrote a customer-specific source wrapper gets no source-side resolution
|
|
183
|
+
/// from this — and, `apply_bindings` leaving unmatched tokens alone, would get
|
|
184
|
+
/// it *silently*. Those callers must build the map explicitly with
|
|
185
|
+
/// [`Self::source_token_map`] / [`Self::source_token_map_from_formats`] and
|
|
186
|
+
/// pass `bindings`. The snow side is unaffected: `<%NAME%>` is invariant.
|
|
187
|
+
pub fn token_map(&self) -> HashMap<String, String> {
|
|
188
|
+
let mut out = self.source_token_map_default();
|
|
189
|
+
out.extend(self.snow_token_map());
|
|
190
|
+
out
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// [`Self::source_token_map`] for callers that can only pass *data*, not a
|
|
194
|
+
/// function pointer — the language bindings.
|
|
195
|
+
///
|
|
196
|
+
/// `forms` are format strings containing `{name}` (e.g. `"${{{name}}}"` or a
|
|
197
|
+
/// customer's `"@{name}@"`). A callable cannot cross the FFI boundary and
|
|
198
|
+
/// [`TokenWrapper`] cannot be built from one, so the bindings pass strings.
|
|
199
|
+
///
|
|
200
|
+
/// This exists so the case handling in [`wrap`] applies to the bindings too.
|
|
201
|
+
/// Each of them previously expanded these forms inline, which meant the
|
|
202
|
+
/// custom-wrapper path silently skipped the case spellings — the same defect
|
|
203
|
+
/// this module was just fixed for, reintroduced three times over. One
|
|
204
|
+
/// definition, called from all four surfaces.
|
|
205
|
+
///
|
|
206
|
+
/// Returns `Err` naming the offending form when one lacks `{name}`; each
|
|
207
|
+
/// binding maps that to its own error type.
|
|
208
|
+
pub fn source_token_map_from_formats(
|
|
209
|
+
&self,
|
|
210
|
+
forms: &[String],
|
|
211
|
+
) -> std::result::Result<HashMap<String, String>, String> {
|
|
212
|
+
// Empty means "not specified", i.e. the default `${NAME}` grammar -- decided
|
|
213
|
+
// here so all four surfaces agree. They cannot agree by convention: the C# FFI
|
|
214
|
+
// receives the forms as JSON and cannot distinguish an absent list from an
|
|
215
|
+
// empty one, so it always treated empty as the default, while Python and Node
|
|
216
|
+
// passed `[]` straight through and produced an *empty* source map. That
|
|
217
|
+
// divergence resolved nothing on the source side and, `apply_bindings` leaving
|
|
218
|
+
// unmatched tokens alone, said nothing about it.
|
|
219
|
+
if forms.is_empty() {
|
|
220
|
+
return Ok(self.source_token_map_default());
|
|
221
|
+
}
|
|
222
|
+
if let Some(bad) = forms.iter().find(|f| !f.contains("{name}")) {
|
|
223
|
+
return Err(format!("source wrapper {bad:?} must contain '{{name}}'"));
|
|
224
|
+
}
|
|
225
|
+
let mut out = HashMap::with_capacity(self.source.len() * forms.len() * 3);
|
|
226
|
+
for (name, value) in &self.source {
|
|
227
|
+
for spelling in case_spellings(name) {
|
|
228
|
+
for form in forms {
|
|
229
|
+
out.insert(form.replace("{name}", &spelling), value.clone());
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
Ok(out)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/// The spellings a conversion may have written for `name`: as-written, all-lower,
|
|
238
|
+
/// all-upper. Duplicates are harmless — every consumer inserts into a map.
|
|
239
|
+
///
|
|
240
|
+
/// See [`wrap`] for why more than the as-written spelling is emitted.
|
|
241
|
+
fn case_spellings(name: &str) -> [String; 3] {
|
|
242
|
+
[name.to_string(), name.to_lowercase(), name.to_uppercase()]
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/// Expand a `name → value` map into `wrapper(spelling) → value` for each wrapper
|
|
246
|
+
/// and each case spelling of the name.
|
|
247
|
+
///
|
|
248
|
+
/// No bare-name key is ever emitted: a bare name would also match *inside* the
|
|
249
|
+
/// other side's wrapped token and corrupt it.
|
|
250
|
+
///
|
|
251
|
+
/// # Why case variants
|
|
252
|
+
///
|
|
253
|
+
/// [`crate::bindings::apply_bindings`] matches keys **byte-exact**
|
|
254
|
+
/// (`substitute_literal` uses `starts_with`), and it leaves an unmatched token
|
|
255
|
+
/// untouched rather than erroring. So a `database-bindings.yml` whose key case
|
|
256
|
+
/// differs from the case the conversion wrote into the registry would resolve
|
|
257
|
+
/// nothing, silently — the token would flow onward and surface far away as a
|
|
258
|
+
/// `001003` syntax error on the `<` of `<%NAME%>`.
|
|
259
|
+
///
|
|
260
|
+
/// The two cases genuinely differ in practice: the engine emits the canonical
|
|
261
|
+
/// name lower-case while the config normaliser upper-cases it, which is why the
|
|
262
|
+
/// desktop's own `substitute_tokens` casefolds on lookup. Rather than make the
|
|
263
|
+
/// core case-insensitive — it is deliberately literal and grammar-free, and
|
|
264
|
+
/// folding there would affect every other substitution — the compatibility lives
|
|
265
|
+
/// here, in the one place that already knows about spelling.
|
|
266
|
+
///
|
|
267
|
+
/// Emitting extra *wrapped* forms is safe: each is specific, and a form the
|
|
268
|
+
/// engine never wrote simply matches nothing. This covers as-written, all-lower
|
|
269
|
+
/// and all-upper; an arbitrary mixed-case spelling the conversion invented
|
|
270
|
+
/// (`<%Db_Name%>` from a key `db_name`) is still not matched, which is why
|
|
271
|
+
/// [`reject_case_collisions`] refuses input where the intended spelling is
|
|
272
|
+
/// ambiguous rather than guessing.
|
|
273
|
+
fn wrap(names: &HashMap<String, String>, wrappers: &[TokenWrapper]) -> HashMap<String, String> {
|
|
274
|
+
// 3 spellings per name per wrapper, before dedup.
|
|
275
|
+
let mut out = HashMap::with_capacity(names.len() * wrappers.len() * 3);
|
|
276
|
+
for (name, value) in names {
|
|
277
|
+
// Identical spellings collapse on insert; case collisions across two
|
|
278
|
+
// different names cannot occur, having been rejected at parse time.
|
|
279
|
+
for spelling in case_spellings(name) {
|
|
280
|
+
for wrapper in wrappers {
|
|
281
|
+
out.insert(wrapper(&spelling), value.clone());
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
out
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
#[cfg(test)]
|
|
289
|
+
mod tests {
|
|
290
|
+
use super::*;
|
|
291
|
+
|
|
292
|
+
/// The shape SnowConvert actually writes.
|
|
293
|
+
const REAL: &str =
|
|
294
|
+
"source:\n dutchie_test: dutchie_test\nsnow:\n dutchie_test: DUTCHIE_EXTRACT_MIG\n";
|
|
295
|
+
|
|
296
|
+
#[test]
|
|
297
|
+
fn parses_the_real_file_shape() {
|
|
298
|
+
let b = parse_database_bindings(REAL).unwrap();
|
|
299
|
+
assert_eq!(b.source.get("dutchie_test").unwrap(), "dutchie_test");
|
|
300
|
+
assert_eq!(b.snow.get("dutchie_test").unwrap(), "DUTCHIE_EXTRACT_MIG");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
#[test]
|
|
304
|
+
fn snow_map_is_wrapped_in_snow_cli_form() {
|
|
305
|
+
let b = parse_database_bindings(REAL).unwrap();
|
|
306
|
+
let m = b.snow_token_map();
|
|
307
|
+
// as-written (== lower here) plus the upper spelling: 2 keys, not 1.
|
|
308
|
+
assert_eq!(m.len(), 2);
|
|
309
|
+
assert_eq!(m.get("<%dutchie_test%>").unwrap(), "DUTCHIE_EXTRACT_MIG");
|
|
310
|
+
assert_eq!(m.get("<%DUTCHIE_TEST%>").unwrap(), "DUTCHIE_EXTRACT_MIG");
|
|
311
|
+
// no bare key: it would match inside the source-side token too
|
|
312
|
+
assert!(!m.contains_key("dutchie_test"));
|
|
313
|
+
assert!(!m.contains_key("DUTCHIE_TEST"));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/// The regression this module shipped with: byte-exact matching in
|
|
317
|
+
/// `substitute_literal` means a YAML key cased differently from the token the
|
|
318
|
+
/// conversion wrote resolves *nothing*, and `apply_bindings` leaves an
|
|
319
|
+
/// unmatched token alone — so it fails silently and surfaces as a `001003`
|
|
320
|
+
/// syntax error on the `<`, layers from the cause.
|
|
321
|
+
#[test]
|
|
322
|
+
fn key_case_need_not_match_the_token_case() {
|
|
323
|
+
// Upper-case key, as the config normaliser writes it...
|
|
324
|
+
let b = parse_database_bindings("snow:\n DUTCHIE_TEST: DUTCHIE_EXTRACT_MIG\n").unwrap();
|
|
325
|
+
let m = b.snow_token_map();
|
|
326
|
+
// ...still resolves the lower-case token the engine emits.
|
|
327
|
+
assert_eq!(m.get("<%dutchie_test%>").unwrap(), "DUTCHIE_EXTRACT_MIG");
|
|
328
|
+
assert_eq!(m.get("<%DUTCHIE_TEST%>").unwrap(), "DUTCHIE_EXTRACT_MIG");
|
|
329
|
+
|
|
330
|
+
// ...and the mirror case: lower-case key, upper-case token.
|
|
331
|
+
let b = parse_database_bindings("snow:\n dutchie_test: DUTCHIE_EXTRACT_MIG\n").unwrap();
|
|
332
|
+
assert_eq!(
|
|
333
|
+
b.snow_token_map().get("<%DUTCHIE_TEST%>").unwrap(),
|
|
334
|
+
"DUTCHIE_EXTRACT_MIG"
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/// The language bindings go through this rather than expanding inline; before
|
|
339
|
+
/// they did, the custom-wrapper path skipped the case spellings entirely.
|
|
340
|
+
#[test]
|
|
341
|
+
fn format_string_wrappers_get_the_case_spellings_too() {
|
|
342
|
+
let b = parse_database_bindings("source:\n MyDb: real_db\n").unwrap();
|
|
343
|
+
// Plain format strings carrying a literal `{name}`, per the documented
|
|
344
|
+
// convention (`"@{name}@"`) -- these are data, not Rust `format!` syntax.
|
|
345
|
+
let m = b
|
|
346
|
+
.source_token_map_from_formats(&["@{name}@".to_string(), "[{name}]".to_string()])
|
|
347
|
+
.unwrap();
|
|
348
|
+
// 2 forms x 3 spellings
|
|
349
|
+
assert_eq!(m.len(), 6);
|
|
350
|
+
for key in ["@MyDb@", "@mydb@", "@MYDB@", "[MyDb]", "[mydb]", "[MYDB]"] {
|
|
351
|
+
assert_eq!(
|
|
352
|
+
m.get(key).map(String::as_str),
|
|
353
|
+
Some("real_db"),
|
|
354
|
+
"missing {key}"
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/// An empty form list means "unspecified", not "resolve nothing" -- the C# FFI
|
|
360
|
+
/// cannot tell absent from empty, so this is the only way the four surfaces agree.
|
|
361
|
+
#[test]
|
|
362
|
+
fn no_wrapper_forms_falls_back_to_the_default_grammar() {
|
|
363
|
+
let b = parse_database_bindings("source:\n db: real\n").unwrap();
|
|
364
|
+
let from_empty = b.source_token_map_from_formats(&[]).unwrap();
|
|
365
|
+
assert_eq!(from_empty, b.source_token_map_default());
|
|
366
|
+
assert_eq!(from_empty.get("${db}").map(String::as_str), Some("real"));
|
|
367
|
+
assert!(
|
|
368
|
+
!from_empty.is_empty(),
|
|
369
|
+
"an empty form list must not silently resolve nothing"
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
#[test]
|
|
374
|
+
fn format_string_wrapper_must_contain_the_name_placeholder() {
|
|
375
|
+
let b = parse_database_bindings("source:\n db: real\n").unwrap();
|
|
376
|
+
let err = b
|
|
377
|
+
.source_token_map_from_formats(&["no-placeholder".to_string()])
|
|
378
|
+
.unwrap_err();
|
|
379
|
+
assert!(err.contains("must contain"), "unexpected error: {err}");
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#[test]
|
|
383
|
+
fn source_side_gets_the_same_case_treatment() {
|
|
384
|
+
let b = parse_database_bindings("source:\n MyDb: real_db\n").unwrap();
|
|
385
|
+
let m = b.source_token_map_default();
|
|
386
|
+
assert_eq!(m.get("${MyDb}").unwrap(), "real_db");
|
|
387
|
+
assert_eq!(m.get("${mydb}").unwrap(), "real_db");
|
|
388
|
+
assert_eq!(m.get("${MYDB}").unwrap(), "real_db");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/// Two names differing only by case make the emitted variants ambiguous, and
|
|
392
|
+
/// nothing here can know which spelling the conversion wrote — so this is
|
|
393
|
+
/// rejected rather than resolved by hash order.
|
|
394
|
+
#[test]
|
|
395
|
+
fn keys_differing_only_by_case_are_rejected() {
|
|
396
|
+
let err =
|
|
397
|
+
parse_database_bindings("snow:\n dutchie_test: A\n DUTCHIE_TEST: B\n").unwrap_err();
|
|
398
|
+
let msg = format!("{err}");
|
|
399
|
+
assert!(msg.contains("differing only by"), "unexpected error: {msg}");
|
|
400
|
+
assert!(msg.contains("dutchie_test") && msg.contains("DUTCHIE_TEST"));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/// The collision check is per-section: the same name on both sides is normal
|
|
404
|
+
/// (that is what the real file looks like) and must stay legal.
|
|
405
|
+
#[test]
|
|
406
|
+
fn the_same_name_on_both_sides_is_not_a_collision() {
|
|
407
|
+
assert!(parse_database_bindings(REAL).is_ok());
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
#[test]
|
|
411
|
+
fn source_map_defaults_to_dollar_brace() {
|
|
412
|
+
let b = parse_database_bindings(REAL).unwrap();
|
|
413
|
+
let m = b.source_token_map_default();
|
|
414
|
+
assert_eq!(m.get("${dutchie_test}").unwrap(), "dutchie_test");
|
|
415
|
+
assert!(!m.contains_key("dutchie_test"));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/// The identity mapping in the real file is not a no-op to detect: the *key*
|
|
419
|
+
/// is still a token, so substitution still has work to do.
|
|
420
|
+
#[test]
|
|
421
|
+
fn identity_mapping_still_strips_the_token() {
|
|
422
|
+
let b = parse_database_bindings(REAL).unwrap();
|
|
423
|
+
let m = b.source_token_map_default();
|
|
424
|
+
assert_eq!(m.get("${dutchie_test}").unwrap(), "dutchie_test");
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
#[test]
|
|
428
|
+
fn multiple_source_wrappers_each_get_a_key() {
|
|
429
|
+
fn at_form(name: &str) -> String {
|
|
430
|
+
format!("@{name}@")
|
|
431
|
+
}
|
|
432
|
+
let b = parse_database_bindings(REAL).unwrap();
|
|
433
|
+
let m = b.source_token_map(&[dollar_brace_token, at_form]);
|
|
434
|
+
// 2 wrappers x 2 distinct case spellings of a lower-case name.
|
|
435
|
+
assert_eq!(m.len(), 4);
|
|
436
|
+
assert_eq!(m.get("${dutchie_test}").unwrap(), "dutchie_test");
|
|
437
|
+
assert_eq!(m.get("@dutchie_test@").unwrap(), "dutchie_test");
|
|
438
|
+
assert_eq!(m.get("${DUTCHIE_TEST}").unwrap(), "dutchie_test");
|
|
439
|
+
assert_eq!(m.get("@DUTCHIE_TEST@").unwrap(), "dutchie_test");
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
#[test]
|
|
443
|
+
fn one_sided_file_is_accepted() {
|
|
444
|
+
let b = parse_database_bindings("snow:\n db: PHYSICAL\n").unwrap();
|
|
445
|
+
assert!(b.source.is_empty());
|
|
446
|
+
assert_eq!(b.snow_token_map().get("<%db%>").unwrap(), "PHYSICAL");
|
|
447
|
+
assert_eq!(b.snow_token_map().get("<%DB%>").unwrap(), "PHYSICAL");
|
|
448
|
+
// an absent side yields an empty map, so the read is a no-op not an error
|
|
449
|
+
assert!(b.source_token_map_default().is_empty());
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
#[test]
|
|
453
|
+
fn empty_document_is_accepted_as_empty_maps() {
|
|
454
|
+
let b = parse_database_bindings("{}").unwrap();
|
|
455
|
+
assert!(b.source.is_empty() && b.snow.is_empty());
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
#[test]
|
|
459
|
+
fn unknown_section_is_rejected() {
|
|
460
|
+
// Silently ignoring it would hand back tokens the caller believes are resolved.
|
|
461
|
+
let err = parse_database_bindings("source:\n a: b\nsnowflake:\n a: b\n").unwrap_err();
|
|
462
|
+
assert!(
|
|
463
|
+
format!("{err}").contains("database-bindings.yml is not valid"),
|
|
464
|
+
"unexpected error: {err}"
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
#[test]
|
|
469
|
+
fn malformed_yaml_is_rejected() {
|
|
470
|
+
let err = parse_database_bindings("source: [unclosed").unwrap_err();
|
|
471
|
+
assert!(format!("{err}").contains("database-bindings.yml is not valid"));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
pub mod bindings;
|
|
7
7
|
pub mod canonical_name;
|
|
8
8
|
pub mod checksum;
|
|
9
|
+
pub mod database_bindings;
|
|
9
10
|
pub(crate) mod dependency_view;
|
|
10
11
|
pub mod error;
|
|
11
12
|
pub mod error_trace;
|
|
@@ -33,6 +34,10 @@ pub use checksum::ChecksumValidationStatus;
|
|
|
33
34
|
pub use checksum::FileChange;
|
|
34
35
|
pub use checksum::FileType;
|
|
35
36
|
pub use checksum::SourceCodeChanges;
|
|
37
|
+
pub use database_bindings::{
|
|
38
|
+
dollar_brace_token, parse_database_bindings, read_database_bindings, snow_cli_token,
|
|
39
|
+
DatabaseBindings, TokenWrapper,
|
|
40
|
+
};
|
|
36
41
|
pub use error::BatchFailure;
|
|
37
42
|
pub use error::BatchResult;
|
|
38
43
|
pub use error::Error;
|