snowflake-code-unit-registry 0.7.2__tar.gz → 0.7.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/Cargo.lock +32 -26
  2. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/Cargo.toml +1 -1
  3. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/PKG-INFO +1 -1
  4. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/examples/code-unit.example.json +2 -2
  5. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/schemas/code-unit.schema.json +4 -4
  6. snowflake_code_unit_registry-0.7.5/crates/scai-state-core/src/canonical_name.rs +457 -0
  7. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/error.rs +32 -2
  8. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/generated/query_reference.rs +1 -1
  9. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/lib.rs +1 -0
  10. snowflake_code_unit_registry-0.7.5/crates/scai-state-core/src/migration/mod.rs +626 -0
  11. snowflake_code_unit_registry-0.7.5/crates/scai-state-core/src/migration/versions/mod.rs +47 -0
  12. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/registry/README.md +15 -2
  13. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/registry/tests.rs +366 -250
  14. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/registry.rs +304 -123
  15. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-python/tests/test_crud.py +2 -0
  16. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-python/tests/test_serialization.py +3 -2
  17. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/pyproject.toml +1 -1
  18. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/python/snowflake_code_unit_registry/__init__.py +2 -1
  19. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/python/snowflake_code_unit_registry/types.py +17 -9
  20. snowflake_code_unit_registry-0.7.2/crates/scai-state-core/src/migration/mod.rs +0 -390
  21. snowflake_code_unit_registry-0.7.2/crates/scai-state-core/src/migration/versions/mod.rs +0 -32
  22. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/README.md +0 -0
  23. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-error-derive/Cargo.toml +0 -0
  24. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-error-derive/src/lib.rs +0 -0
  25. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-error-derive/tests/derive_integration.rs +0 -0
  26. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/Cargo.toml +0 -0
  27. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/build.rs +0 -0
  28. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/schemas/history/README.md +0 -0
  29. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/schemas/query-reference.rs.tmpl +0 -0
  30. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/schemas/query-reference.tmpl +0 -0
  31. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/checksum.rs +0 -0
  32. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/error_trace.rs +0 -0
  33. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/filter.rs +0 -0
  34. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/generated/file_path_fields.rs +0 -0
  35. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/generated/mod.rs +0 -0
  36. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/generated/updated_at_paths.rs +0 -0
  37. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/identity.rs +0 -0
  38. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/registry/graph.rs +0 -0
  39. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/registry/in_memory.rs +0 -0
  40. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/registry/loader.rs +0 -0
  41. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/registry/paths.rs +0 -0
  42. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/registry/query.rs +0 -0
  43. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/registry/test_helpers.rs +0 -0
  44. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-core/src/validation.rs +0 -0
  45. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-python/Cargo.toml +0 -0
  46. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-python/README.md +0 -0
  47. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-python/src/lib.rs +0 -0
  48. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-python/tests/conftest.py +0 -0
  49. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-python/tests/test_batch.py +0 -0
  50. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-python/tests/test_checksum.py +0 -0
  51. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-python/tests/test_errors.py +0 -0
  52. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-python/tests/test_migration.py +0 -0
  53. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-python/tests/test_query.py +0 -0
  54. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-python/tests/test_refresh.py +0 -0
  55. {snowflake_code_unit_registry-0.7.2 → snowflake_code_unit_registry-0.7.5}/crates/scai-state-python/tests/test_validation.py +0 -0
@@ -161,9 +161,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
161
161
 
162
162
  [[package]]
163
163
  name = "cc"
164
- version = "1.2.59"
164
+ version = "1.2.60"
165
165
  source = "registry+https://github.com/rust-lang/crates.io-index"
166
- checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283"
166
+ checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
167
167
  dependencies = [
168
168
  "find-msvc-tools",
169
169
  "jobserver",
@@ -778,6 +778,12 @@ dependencies = [
778
778
  "foldhash 0.2.0",
779
779
  ]
780
780
 
781
+ [[package]]
782
+ name = "hashbrown"
783
+ version = "0.17.0"
784
+ source = "registry+https://github.com/rust-lang/crates.io-index"
785
+ checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
786
+
781
787
  [[package]]
782
788
  name = "heck"
783
789
  version = "0.5.0"
@@ -1069,12 +1075,12 @@ dependencies = [
1069
1075
 
1070
1076
  [[package]]
1071
1077
  name = "indexmap"
1072
- version = "2.13.1"
1078
+ version = "2.14.0"
1073
1079
  source = "registry+https://github.com/rust-lang/crates.io-index"
1074
- checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff"
1080
+ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
1075
1081
  dependencies = [
1076
1082
  "equivalent",
1077
- "hashbrown 0.16.1",
1083
+ "hashbrown 0.17.0",
1078
1084
  "serde",
1079
1085
  "serde_core",
1080
1086
  ]
@@ -1204,9 +1210,9 @@ dependencies = [
1204
1210
 
1205
1211
  [[package]]
1206
1212
  name = "js-sys"
1207
- version = "0.3.94"
1213
+ version = "0.3.95"
1208
1214
  source = "registry+https://github.com/rust-lang/crates.io-index"
1209
- checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9"
1215
+ checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
1210
1216
  dependencies = [
1211
1217
  "cfg-if",
1212
1218
  "futures-util",
@@ -2068,9 +2074,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
2068
2074
 
2069
2075
  [[package]]
2070
2076
  name = "rustls-webpki"
2071
- version = "0.103.10"
2077
+ version = "0.103.11"
2072
2078
  source = "registry+https://github.com/rust-lang/crates.io-index"
2073
- checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
2079
+ checksum = "20a6af516fea4b20eccceaf166e8aa666ac996208e8a644ce3ef5aa783bc7cd4"
2074
2080
  dependencies = [
2075
2081
  "aws-lc-rs",
2076
2082
  "ring",
@@ -2095,7 +2101,7 @@ dependencies = [
2095
2101
 
2096
2102
  [[package]]
2097
2103
  name = "scai-error-derive"
2098
- version = "0.7.2"
2104
+ version = "0.7.5"
2099
2105
  dependencies = [
2100
2106
  "err_code",
2101
2107
  "heck",
@@ -2109,7 +2115,7 @@ dependencies = [
2109
2115
 
2110
2116
  [[package]]
2111
2117
  name = "scai-state-core"
2112
- version = "0.7.2"
2118
+ version = "0.7.5"
2113
2119
  dependencies = [
2114
2120
  "chrono",
2115
2121
  "err_code",
@@ -2134,7 +2140,7 @@ dependencies = [
2134
2140
 
2135
2141
  [[package]]
2136
2142
  name = "scai-state-csharp"
2137
- version = "0.7.2"
2143
+ version = "0.7.5"
2138
2144
  dependencies = [
2139
2145
  "interoptopus",
2140
2146
  "interoptopus_backend_csharp",
@@ -2145,7 +2151,7 @@ dependencies = [
2145
2151
 
2146
2152
  [[package]]
2147
2153
  name = "scai-state-node"
2148
- version = "0.7.2"
2154
+ version = "0.7.5"
2149
2155
  dependencies = [
2150
2156
  "napi",
2151
2157
  "napi-build",
@@ -2157,7 +2163,7 @@ dependencies = [
2157
2163
 
2158
2164
  [[package]]
2159
2165
  name = "scai-state-python"
2160
- version = "0.7.2"
2166
+ version = "0.7.5"
2161
2167
  dependencies = [
2162
2168
  "pyo3",
2163
2169
  "pythonize",
@@ -2868,9 +2874,9 @@ dependencies = [
2868
2874
 
2869
2875
  [[package]]
2870
2876
  name = "wasm-bindgen"
2871
- version = "0.2.117"
2877
+ version = "0.2.118"
2872
2878
  source = "registry+https://github.com/rust-lang/crates.io-index"
2873
- checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0"
2879
+ checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
2874
2880
  dependencies = [
2875
2881
  "cfg-if",
2876
2882
  "once_cell",
@@ -2881,9 +2887,9 @@ dependencies = [
2881
2887
 
2882
2888
  [[package]]
2883
2889
  name = "wasm-bindgen-futures"
2884
- version = "0.4.67"
2890
+ version = "0.4.68"
2885
2891
  source = "registry+https://github.com/rust-lang/crates.io-index"
2886
- checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e"
2892
+ checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8"
2887
2893
  dependencies = [
2888
2894
  "js-sys",
2889
2895
  "wasm-bindgen",
@@ -2891,9 +2897,9 @@ dependencies = [
2891
2897
 
2892
2898
  [[package]]
2893
2899
  name = "wasm-bindgen-macro"
2894
- version = "0.2.117"
2900
+ version = "0.2.118"
2895
2901
  source = "registry+https://github.com/rust-lang/crates.io-index"
2896
- checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be"
2902
+ checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
2897
2903
  dependencies = [
2898
2904
  "quote",
2899
2905
  "wasm-bindgen-macro-support",
@@ -2901,9 +2907,9 @@ dependencies = [
2901
2907
 
2902
2908
  [[package]]
2903
2909
  name = "wasm-bindgen-macro-support"
2904
- version = "0.2.117"
2910
+ version = "0.2.118"
2905
2911
  source = "registry+https://github.com/rust-lang/crates.io-index"
2906
- checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2"
2912
+ checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
2907
2913
  dependencies = [
2908
2914
  "bumpalo",
2909
2915
  "proc-macro2",
@@ -2914,9 +2920,9 @@ dependencies = [
2914
2920
 
2915
2921
  [[package]]
2916
2922
  name = "wasm-bindgen-shared"
2917
- version = "0.2.117"
2923
+ version = "0.2.118"
2918
2924
  source = "registry+https://github.com/rust-lang/crates.io-index"
2919
- checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b"
2925
+ checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
2920
2926
  dependencies = [
2921
2927
  "unicode-ident",
2922
2928
  ]
@@ -2957,9 +2963,9 @@ dependencies = [
2957
2963
 
2958
2964
  [[package]]
2959
2965
  name = "web-sys"
2960
- version = "0.3.94"
2966
+ version = "0.3.95"
2961
2967
  source = "registry+https://github.com/rust-lang/crates.io-index"
2962
- checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a"
2968
+ checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d"
2963
2969
  dependencies = [
2964
2970
  "js-sys",
2965
2971
  "wasm-bindgen",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/scai-error-derive", "crates/scai-state-core", "crates/scai-state-python"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.7.2"
6
+ version = "0.7.5"
7
7
  edition = "2021"
8
8
  license = "SEE LICENSE IN LICENSE"
9
9
  repository = "https://github.com/snowflake-eng/scai-state"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: snowflake-code-unit-registry
3
- Version: 0.7.2
3
+ Version: 0.7.5
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: Apache Software License
@@ -2,7 +2,6 @@
2
2
  "schemaVersion": 1,
3
3
  "isMissing": false,
4
4
  "inScope": true,
5
- "containsCommit": true,
6
5
  "kind": "databaseObject",
7
6
  "id": "a3f1b2c4-5d6e-7f89-0a1b-2c3d4e5f6a7b",
8
7
  "updatedAt": "2025-01-15T10:30:00Z",
@@ -53,7 +52,8 @@
53
52
  "convertedChecksum": "defa8dbc2f2c5915579ade9fdc6aef24"
54
53
  },
55
54
  "assessment": {
56
- "status": "completed"
55
+ "status": "completed",
56
+ "containsCommit": true
57
57
  },
58
58
  "dataQuality": {
59
59
  "status": "completed",
@@ -58,10 +58,6 @@
58
58
  "description": "true if CodeUnit is in the migration scope",
59
59
  "default": true
60
60
  },
61
- "containsCommit": {
62
- "type": "boolean",
63
- "description": "True if the stored procedure contains a commit transaction"
64
- },
65
61
  "kind": {
66
62
  "type": "string",
67
63
  "title": "Kind",
@@ -208,6 +204,10 @@
208
204
  "properties": {
209
205
  "status": {
210
206
  "$ref": "#/definitions/OperationStatus"
207
+ },
208
+ "containsCommit": {
209
+ "type": "boolean",
210
+ "description": "True if the stored procedure contains a commit transaction"
211
211
  }
212
212
  }
213
213
  }
@@ -0,0 +1,457 @@
1
+ //! Canonical name computation for code unit metadata.
2
+ //!
3
+ //! A canonical name is a dot-separated identifier built from a code unit's
4
+ //! metadata fields: `database.schema.name` for most object types, or
5
+ //! `database.schema.name(TYPE,TYPE,...)` for procedures and functions
6
+ //! (using base parameter types with length/precision stripped).
7
+ //!
8
+ //! Two public entry points:
9
+ //! - [`compute`] — low-level, accepts individual fields
10
+ //! - [`for_code_unit`] — convenience wrapper that extracts fields from a [`CodeUnit`]
11
+
12
+ use crate::generated::types::{CodeUnit, ObjectType, ParameterDef};
13
+
14
+ /// Sentinel schema name used when a database is present but no schema
15
+ /// is specified. Consumers should be aware this value may appear in
16
+ /// canonical names for incompletely-specified metadata.
17
+ pub const UNKNOWN_SCHEMA: &str = "UNKNOWN_SCHEMA";
18
+
19
+ /// Selects which side of the code unit metadata to use.
20
+ ///
21
+ /// A [`CodeUnit`] has one shared `signature` block containing both source
22
+ /// and target type information on each parameter. `Side` controls which
23
+ /// type field is read: `type_` for [`Source`](Side::Source), `target_type`
24
+ /// for [`Target`](Side::Target).
25
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
26
+ pub enum Side {
27
+ /// Use source metadata and source parameter types (`type_` field).
28
+ Source,
29
+ /// Use target metadata and target parameter types (`target_type` field).
30
+ Target,
31
+ }
32
+
33
+ /// Compute a canonical name from individual metadata fields.
34
+ ///
35
+ /// Returns `None` when `name` is `None` and `object_type` is not a
36
+ /// container type (`Database` or `Schema`).
37
+ ///
38
+ /// `params` comes from the shared `signature.parameters.arguments` array.
39
+ /// Each [`ParameterDef`] carries both source (`type_`) and target
40
+ /// (`target_type`) types; `side` selects which one to use.
41
+ ///
42
+ /// # Format
43
+ ///
44
+ /// - `database.schema.name` — when all three parts are present
45
+ /// - `schema.name` — when database is missing
46
+ /// - `name` — when both database and schema are missing
47
+ /// - Append `(TYPE,TYPE,...)` for `Procedure` and `Function` object types
48
+ /// - If database is present but schema is missing, [`UNKNOWN_SCHEMA`] is
49
+ /// substituted (except for `Database` object type which has no schema)
50
+ pub fn compute(
51
+ database: Option<&str>,
52
+ schema: Option<&str>,
53
+ name: Option<&str>,
54
+ object_type: Option<&ObjectType>,
55
+ params: Option<&[ParameterDef]>,
56
+ side: Side,
57
+ ) -> Option<String> {
58
+ let is_container = matches!(object_type, Some(ObjectType::Database | ObjectType::Schema));
59
+
60
+ if name.is_none() && !is_container {
61
+ return None;
62
+ }
63
+
64
+ let mut parts: Vec<&str> = Vec::new();
65
+
66
+ if let Some(database) = database {
67
+ parts.push(database);
68
+ if !matches!(object_type, Some(ObjectType::Database)) {
69
+ parts.push(schema.unwrap_or(UNKNOWN_SCHEMA));
70
+ }
71
+ } else if let Some(schema) = schema {
72
+ parts.push(schema);
73
+ }
74
+
75
+ if let Some(name) = name {
76
+ parts.push(name);
77
+ }
78
+
79
+ let mut canonical = parts.join(".");
80
+
81
+ let needs_signature = matches!(
82
+ object_type,
83
+ Some(ObjectType::Procedure | ObjectType::Function)
84
+ );
85
+ if needs_signature {
86
+ if let Some(params) = params {
87
+ let param_types: Vec<&str> = params
88
+ .iter()
89
+ .filter_map(|param| {
90
+ let raw_type = match side {
91
+ Side::Source => param.type_.as_deref(),
92
+ Side::Target => param.target_type.as_deref(),
93
+ };
94
+ raw_type.map(strip_type_precision)
95
+ })
96
+ .collect();
97
+ if !param_types.is_empty() {
98
+ canonical.push('(');
99
+ canonical.push_str(&param_types.join(","));
100
+ canonical.push(')');
101
+ }
102
+ }
103
+ }
104
+
105
+ Some(canonical)
106
+ }
107
+
108
+ /// Compute the canonical name for one side of a [`CodeUnit`].
109
+ ///
110
+ /// Extracts metadata and signature fields from the unit, then delegates
111
+ /// to [`compute`]. The shared `signature.parameters.arguments` array is
112
+ /// passed through; `side` determines which type field is read from each
113
+ /// parameter.
114
+ pub fn for_code_unit(unit: &CodeUnit, side: Side) -> Option<String> {
115
+ let (database, schema, name, object_type) = match side {
116
+ Side::Source => {
117
+ let s = unit.source.as_ref()?;
118
+ (
119
+ s.database.as_deref(),
120
+ s.schema.as_deref(),
121
+ s.name.as_deref(),
122
+ s.object_type.as_ref(),
123
+ )
124
+ }
125
+ Side::Target => {
126
+ let t = unit.target.as_ref()?;
127
+ (
128
+ t.database.as_deref(),
129
+ t.schema.as_deref(),
130
+ t.name.as_deref(),
131
+ t.object_type.as_ref(),
132
+ )
133
+ }
134
+ };
135
+
136
+ let params = unit
137
+ .signature
138
+ .as_ref()
139
+ .and_then(|s| s.parameters.as_ref())
140
+ .map(|p| p.arguments.as_slice());
141
+
142
+ compute(database, schema, name, object_type, params, side)
143
+ }
144
+
145
+ /// Strip length/precision qualifiers from a SQL type.
146
+ ///
147
+ /// `"NVARCHAR(50)"` → `"NVARCHAR"`, `"INT"` → `"INT"`.
148
+ fn strip_type_precision(ty: &str) -> &str {
149
+ match ty.find('(') {
150
+ Some(pos) => &ty[..pos],
151
+ None => ty,
152
+ }
153
+ }
154
+
155
+ #[cfg(test)]
156
+ mod tests {
157
+ use super::*;
158
+ use crate::generated::types::{Parameters, Signature, SourceMetadata, TargetMetadata};
159
+
160
+ #[test]
161
+ fn table_full_qualified() {
162
+ let result = compute(
163
+ Some("DB"),
164
+ Some("dbo"),
165
+ Some("MyTable"),
166
+ Some(&ObjectType::Table),
167
+ None,
168
+ Side::Source,
169
+ );
170
+ assert_eq!(result.as_deref(), Some("DB.dbo.MyTable"));
171
+ }
172
+
173
+ #[test]
174
+ fn procedure_with_params_source() {
175
+ let params = vec![
176
+ ParameterDef {
177
+ type_: Some("DATETIME".to_string()),
178
+ target_type: Some("TIMESTAMP_NTZ".to_string()),
179
+ ..Default::default()
180
+ },
181
+ ParameterDef {
182
+ type_: Some("NVARCHAR(50)".to_string()),
183
+ target_type: Some("VARCHAR(50)".to_string()),
184
+ ..Default::default()
185
+ },
186
+ ];
187
+ let result = compute(
188
+ Some("DB"),
189
+ Some("dbo"),
190
+ Some("MyProc"),
191
+ Some(&ObjectType::Procedure),
192
+ Some(&params),
193
+ Side::Source,
194
+ );
195
+ assert_eq!(result.as_deref(), Some("DB.dbo.MyProc(DATETIME,NVARCHAR)"));
196
+ }
197
+
198
+ #[test]
199
+ fn procedure_with_params_target() {
200
+ let params = vec![
201
+ ParameterDef {
202
+ type_: Some("DATETIME".to_string()),
203
+ target_type: Some("TIMESTAMP_NTZ".to_string()),
204
+ ..Default::default()
205
+ },
206
+ ParameterDef {
207
+ type_: Some("NVARCHAR(50)".to_string()),
208
+ target_type: Some("VARCHAR(50)".to_string()),
209
+ ..Default::default()
210
+ },
211
+ ];
212
+ let result = compute(
213
+ Some("DB"),
214
+ Some("DBO"),
215
+ Some("MYPROC"),
216
+ Some(&ObjectType::Procedure),
217
+ Some(&params),
218
+ Side::Target,
219
+ );
220
+ assert_eq!(
221
+ result.as_deref(),
222
+ Some("DB.DBO.MYPROC(TIMESTAMP_NTZ,VARCHAR)")
223
+ );
224
+ }
225
+
226
+ #[test]
227
+ fn function_with_params() {
228
+ let params = vec![ParameterDef {
229
+ type_: Some("INT".to_string()),
230
+ target_type: Some("NUMBER".to_string()),
231
+ ..Default::default()
232
+ }];
233
+ let result = compute(
234
+ Some("DB"),
235
+ Some("dbo"),
236
+ Some("MyFunc"),
237
+ Some(&ObjectType::Function),
238
+ Some(&params),
239
+ Side::Source,
240
+ );
241
+ assert_eq!(result.as_deref(), Some("DB.dbo.MyFunc(INT)"));
242
+ }
243
+
244
+ #[test]
245
+ fn view_ignores_params() {
246
+ let params = vec![ParameterDef {
247
+ type_: Some("INT".to_string()),
248
+ ..Default::default()
249
+ }];
250
+ let result = compute(
251
+ Some("DB"),
252
+ Some("dbo"),
253
+ Some("MyView"),
254
+ Some(&ObjectType::View),
255
+ Some(&params),
256
+ Side::Source,
257
+ );
258
+ assert_eq!(
259
+ result.as_deref(),
260
+ Some("DB.dbo.MyView"),
261
+ "views should not include parameter signature"
262
+ );
263
+ }
264
+
265
+ #[test]
266
+ fn schema_and_name_only() {
267
+ let result = compute(
268
+ None,
269
+ Some("dbo"),
270
+ Some("Orphan"),
271
+ Some(&ObjectType::Table),
272
+ None,
273
+ Side::Source,
274
+ );
275
+ assert_eq!(result.as_deref(), Some("dbo.Orphan"));
276
+ }
277
+
278
+ #[test]
279
+ fn name_only() {
280
+ let result = compute(
281
+ None,
282
+ None,
283
+ Some("Lonely"),
284
+ Some(&ObjectType::Table),
285
+ None,
286
+ Side::Source,
287
+ );
288
+ assert_eq!(result.as_deref(), Some("Lonely"));
289
+ }
290
+
291
+ #[test]
292
+ fn unknown_schema_fallback() {
293
+ let result = compute(
294
+ Some("MyDB"),
295
+ None,
296
+ Some("Widget"),
297
+ Some(&ObjectType::Table),
298
+ None,
299
+ Side::Source,
300
+ );
301
+ assert_eq!(result.as_deref(), Some("MyDB.UNKNOWN_SCHEMA.Widget"));
302
+ }
303
+
304
+ #[test]
305
+ fn none_when_name_missing() {
306
+ let result = compute(
307
+ Some("DB"),
308
+ Some("dbo"),
309
+ None,
310
+ Some(&ObjectType::Table),
311
+ None,
312
+ Side::Source,
313
+ );
314
+ assert_eq!(result, None);
315
+ }
316
+
317
+ #[test]
318
+ fn database_object_type() {
319
+ let result = compute(
320
+ Some("MyDB"),
321
+ None,
322
+ None,
323
+ Some(&ObjectType::Database),
324
+ None,
325
+ Side::Source,
326
+ );
327
+ assert_eq!(result.as_deref(), Some("MyDB"));
328
+ }
329
+
330
+ #[test]
331
+ fn schema_object_type_with_database() {
332
+ let result = compute(
333
+ Some("MyDB"),
334
+ Some("Sales"),
335
+ None,
336
+ Some(&ObjectType::Schema),
337
+ None,
338
+ Side::Source,
339
+ );
340
+ assert_eq!(result.as_deref(), Some("MyDB.Sales"));
341
+ }
342
+
343
+ #[test]
344
+ fn schema_object_type_without_database() {
345
+ let result = compute(
346
+ None,
347
+ Some("Sales"),
348
+ None,
349
+ Some(&ObjectType::Schema),
350
+ None,
351
+ Side::Source,
352
+ );
353
+ assert_eq!(result.as_deref(), Some("Sales"));
354
+ }
355
+
356
+ #[test]
357
+ fn procedure_no_params_no_parens() {
358
+ let result = compute(
359
+ Some("DB"),
360
+ Some("dbo"),
361
+ Some("NoArgs"),
362
+ Some(&ObjectType::Procedure),
363
+ None,
364
+ Side::Source,
365
+ );
366
+ assert_eq!(
367
+ result.as_deref(),
368
+ Some("DB.dbo.NoArgs"),
369
+ "procedures with no params should not have empty parens"
370
+ );
371
+ }
372
+
373
+ #[test]
374
+ fn strip_type_precision_works() {
375
+ assert_eq!(strip_type_precision("NVARCHAR(50)"), "NVARCHAR");
376
+ assert_eq!(strip_type_precision("DECIMAL(18,2)"), "DECIMAL");
377
+ assert_eq!(strip_type_precision("INT"), "INT");
378
+ assert_eq!(strip_type_precision("TIMESTAMP_NTZ"), "TIMESTAMP_NTZ");
379
+ }
380
+
381
+ #[test]
382
+ fn for_code_unit_source() {
383
+ let unit = CodeUnit {
384
+ source: Some(SourceMetadata {
385
+ object_type: Some(ObjectType::Table),
386
+ database: Some("DB".to_string()),
387
+ schema: Some("dbo".to_string()),
388
+ name: Some("Orders".to_string()),
389
+ ..Default::default()
390
+ }),
391
+ ..Default::default()
392
+ };
393
+ assert_eq!(
394
+ for_code_unit(&unit, Side::Source).as_deref(),
395
+ Some("DB.dbo.Orders")
396
+ );
397
+ }
398
+
399
+ #[test]
400
+ fn for_code_unit_target() {
401
+ let unit = CodeUnit {
402
+ target: Some(TargetMetadata {
403
+ object_type: Some(ObjectType::Table),
404
+ database: Some("DB".to_string()),
405
+ schema: Some("DBO".to_string()),
406
+ name: Some("ORDERS".to_string()),
407
+ ..Default::default()
408
+ }),
409
+ ..Default::default()
410
+ };
411
+ assert_eq!(
412
+ for_code_unit(&unit, Side::Target).as_deref(),
413
+ Some("DB.DBO.ORDERS")
414
+ );
415
+ }
416
+
417
+ #[test]
418
+ fn for_code_unit_with_signature() {
419
+ let unit = CodeUnit {
420
+ source: Some(SourceMetadata {
421
+ object_type: Some(ObjectType::Procedure),
422
+ database: Some("DB".to_string()),
423
+ schema: Some("dbo".to_string()),
424
+ name: Some("MyProc".to_string()),
425
+ ..Default::default()
426
+ }),
427
+ signature: Some(Signature {
428
+ parameters: Some(Parameters {
429
+ arguments: vec![ParameterDef {
430
+ type_: Some("DATETIME".to_string()),
431
+ target_type: Some("TIMESTAMP_NTZ".to_string()),
432
+ ..Default::default()
433
+ }],
434
+ ..Default::default()
435
+ }),
436
+ ..Default::default()
437
+ }),
438
+ ..Default::default()
439
+ };
440
+ assert_eq!(
441
+ for_code_unit(&unit, Side::Source).as_deref(),
442
+ Some("DB.dbo.MyProc(DATETIME)")
443
+ );
444
+ assert_eq!(
445
+ for_code_unit(&unit, Side::Target),
446
+ None,
447
+ "target metadata is None, should return None"
448
+ );
449
+ }
450
+
451
+ #[test]
452
+ fn for_code_unit_no_metadata() {
453
+ let unit = CodeUnit::default();
454
+ assert_eq!(for_code_unit(&unit, Side::Source), None);
455
+ assert_eq!(for_code_unit(&unit, Side::Target), None);
456
+ }
457
+ }