11# =============================================================================
2- # UUIDs to Integers Mappings Implementation
2+ # UUIDs to Integers Mappings
33# =============================================================================
44
55import polars as pl
66from pathlib import Path
7- from data_pipeline .shared .storage_adapter import promote_new_mapping_files
7+ from data_pipeline .shared .storage_adapter import (
8+ promote_new_mapping_files ,
9+ check_gcs_path_exists ,
10+ )
811
912ID_COLUMNS_TO_MAP = {
1013 "df_orders" : ["order_id" , "customer_id" ],
1518}
1619
1720
18- def map_uuid_to_int (df : pl .DataFrame , mapping_file_path : Path , id_column : str ) -> None :
21+ def map_uuid_to_int (
22+ df : pl .DataFrame ,
23+ id_column : str ,
24+ storage_destination : str | Path ,
25+ local_output_path : Path ,
26+ ) -> None :
1927 """
20- Enforces idempotency and persistence of UUID-to-Integer mappings for a specific column.
21-
22- Contract (Executor):
23- - Discovery: Extracts unique UUIDs from the target column to minimize memory footprint.
24- - Differential Update: Scans existing registry (if present) to identify UUIDs via set exclusion.
25- - Registry Extension: Generates sequential UInt32 identifiers starting from max(existing_id) + 1.
26- - Atomic Write: Sinks updated registry to a temporary Parquet file before performing an atomic replace to ensure data integrity.
27-
28- Invariants:
29- - Guarantees 1:1 mapping between UUID strings and UInt32 integers.
30- - Ensures newly assigned IDs are strictly greater than any existing ID in the registry.
31- - Never modifies the input DataFrame in-place or returns it.
28+ Enforces idempotency and persistence of UUID-to-Integer mappings using a Delta
29+ pattern.
30+
31+ Contract:
32+ - Discovery: Scans ALL existing mapping deltas from storage (glob) to find max_id
33+ and existing UUIDs.
34+ - Differential Update: Identifies only the new UUIDs not present in history.
35+ - Registry Extension: Generates new IDs starting from max(existing) + 1.
36+ - Delta Write: Saves ONLY the new mappings to a local run-specific file for later
37+ promotion.
3238 """
3339
3440 int_col_name = f"{ id_column } _int"
35-
3641 delta_ids = df .select (id_column ).unique ().get_column (id_column )
3742
38- if mapping_file_path .exists ():
39- mapping_lf = pl .scan_parquet (mapping_file_path )
43+ # Resolve Storage Path (Local or GCS)
44+ dest_str = str (storage_destination ).replace ("\\ " , "/" )
45+ is_gcs = dest_str .startswith ("gs://" )
46+
47+ column_storage_dir = f"{ dest_str } /{ id_column } "
48+ storage_glob = f"{ column_storage_dir } /*.parquet"
49+
50+ # Check existence and Scan history
51+ exists = (
52+ check_gcs_path_exists (column_storage_dir )
53+ if is_gcs
54+ else Path (column_storage_dir ).exists ()
55+ )
4056
57+ if exists :
58+ mapping_lf = pl .scan_parquet (storage_glob )
59+
60+ # Get max ID across all deltas
4161 max_id = mapping_lf .select (pl .col (int_col_name ).max ()).collect ().item ()
4262 max_id = max_id if max_id is not None else 0
4363
@@ -47,39 +67,22 @@ def map_uuid_to_int(df: pl.DataFrame, mapping_file_path: Path, id_column: str) -
4767 .collect ()
4868 .get_column (id_column )
4969 )
50-
51- # Compare the delta ids to existing record while streaming
5270 new_uuids = delta_ids .filter (~ delta_ids .is_in (existing_ids .to_list ()))
53- df_new_uuids = pl .DataFrame ({id_column : new_uuids })
54-
5571 else :
56- mapping_lf = pl .LazyFrame (
57- schema = {id_column : pl .String , int_col_name : pl .UInt32 }
58- )
5972 max_id = 0
60- df_new_uuids = pl . DataFrame ({ id_column : delta_ids })
73+ new_uuids = delta_ids
6174
62- if df_new_uuids .height > 0 :
75+ # If new UUIDs found, write a new detla file
76+ if new_uuids .len () > 0 :
6377 start_val = max_id + 1
64-
65- # Attach new mapped Uint32 ID
66- new_mapping_df = df_new_uuids .with_columns (
67- pl .int_range (
68- start_val , df_new_uuids .height + start_val , dtype = pl .UInt32
69- ).alias (int_col_name )
78+ new_mapping_df = pl .DataFrame ({id_column : new_uuids }).with_columns (
79+ pl .int_range (start_val , new_uuids .len () + start_val , dtype = pl .UInt32 ).alias (
80+ int_col_name
81+ )
7082 )
7183
72- # Temporarily hold while syncing updates
73- temp_map_path = mapping_file_path .with_suffix (".tmp.parquet" )
74-
75- if mapping_file_path .exists ():
76- updated_registy_lf = pl .concat ([mapping_lf , new_mapping_df .lazy ()])
77- updated_registy_lf .sink_parquet (temp_map_path )
78-
79- else :
80- new_mapping_df .write_parquet (temp_map_path )
81-
82- temp_map_path .replace (mapping_file_path )
84+ local_output_path .parent .mkdir (parents = True , exist_ok = True )
85+ new_mapping_df .write_parquet (local_output_path )
8386
8487
8588def id_mapping (
@@ -88,65 +91,42 @@ def id_mapping(
8891 mapping_dict : dict ,
8992 runtime_dir : Path ,
9093 destination : Path | str ,
94+ run_id : str ,
9195) -> pl .LazyFrame :
9296 """
93- Orchestrates the two-phase transformation of UUID strings to persistent Integer keys.
97+ Orchestrates the transformation of UUID strings to persistent Integer keys using
98+ Delta Architecture.
9499
95100 Execution Workflow:
96- 1. Update (Eager): Iterates through all specified ID columns, updating their respective physical registries on disk.
97- 2. Promote: Synchronizes newly created mapping files from the runtime environment to central storage.
98- 3. Transform (Lazy): Constructs a chain of left-joins against the persistent registries to attach integer keys.
99-
100- Contract:
101- - Integrity: Ensures every unique UUID in the input DataFrame has a corresponding entry in the mapping registry before the join.
102- - Efficiency: Utilizes LazyFrame chaining and Parquet scanning to defer data loading until terminal execution.
103- - Grain: Maintains the original grain of the input DataFrame; appends {column}_int columns.
104-
105- Outputs:
106- - A pl.LazyFrame ready for streaming execution, containing original data enriched with integer surrogates.
101+ 1. Update (Delta): Creates new mapping files only for previously unseen UUIDs.
102+ 2. Promote: Uploads the tiny new delta files to the central storage directory.
103+ 3. Transform (Lazy): Joins the input against the full set of deltas (glob scan).
107104 """
108105
109106 cols_to_map = mapping_dict .get (table_name , [])
110-
111107 if not cols_to_map :
112108 return df .lazy ()
113109
114110 for id_column in cols_to_map :
115- mapping_filename = f"{ id_column } _mapping.parquet"
116- temp_path = runtime_dir / mapping_filename
111+ local_delta_path = runtime_dir / id_column / f"map_{ run_id } .parquet"
117112
118- # When destination is a GCS URI
119- if str (destination ).startswith ("gs://" ):
120- target_path = temp_path
121- else :
122- storage_path = destination / mapping_filename # type: ignore
123- target_path = storage_path if storage_path .exists () else temp_path
124-
125- if not target_path .parent .exists ():
126- target_path .parent .mkdir (parents = True , exist_ok = True )
127-
128- map_uuid_to_int (df , target_path , id_column )
113+ map_uuid_to_int (
114+ df = df ,
115+ id_column = id_column ,
116+ storage_destination = destination ,
117+ local_output_path = local_delta_path ,
118+ )
129119
130- # Promote new mapping to storage
131120 promote_new_mapping_files (runtime_dir = runtime_dir , destination = destination )
132121
122+ # Apply mapped ids to dataframe
123+ dest_str = str (destination ).replace ("\\ " , "/" )
133124 lf_mapped = df .lazy ()
134125
135126 for id_column in cols_to_map :
136- mapping_filename = f"{ id_column } _mapping.parquet"
137-
138- # Switch between local and gcp IO
139- if str (destination ).startswith ("gs://" ):
140- destination_str = str (destination )
141- if not destination_str .endswith ("/" ):
142- destination_str += "/"
143-
144- storage_path = f"{ destination_str } { mapping_filename } "
145- else :
146- storage_path = str (destination / mapping_filename ) # type: ignore
127+ storage_glob = f"{ dest_str } /{ id_column } /*.parquet"
147128
148- # Enriched existing id registry with new mapping
149- registry_lf = pl .scan_parquet (storage_path )
129+ registry_lf = pl .scan_parquet (storage_glob )
150130 lf_mapped = lf_mapped .join (registry_lf , on = id_column , how = "left" )
151131
152132 return lf_mapped
0 commit comments