from dataclasses import dataclass, field from typing import Any, List, Optional from samplers.composite_business_shuffler import CompositeBusinessShuffler from samplers.sampling_phase import SamplingPhase from .column import Column @dataclass class Index: key_name: str table_schema: str table_name: str columns: List[Column] is_unique: bool is_clustered: bool is_global: bool _composite_sampler: Any = field(init=False, default=None) _exhausted_logged: bool = field(init=False, default=False) @property def is_unique_key(self) -> bool: """Judge if index is unique (PRIMARY KEY or UNIQUE KEY)""" return (self.key_name == "PRIMARY") or self.is_unique def is_composite(self) -> bool: """Judge if index is composite (multi-column index)""" return len(self.columns) > 1 def initialize_composite_sampler(self): """initialize business layer composite sampler""" # print(f"DEBUG: initialize_composite_sampler called for {self.table_name}.{self.key_name}, columns={len(self.columns)}, composite={self.is_composite()}, unique={self.is_unique_key}") if not self.is_composite() or not self.is_unique_key: # print(f"DEBUG: Skipping composite sampler for {self.table_name}.{self.key_name} (not composite or not unique)") return # only create composite sampler for unique composite keys try: # Create business layer composite sampler self._composite_sampler = CompositeBusinessShuffler(self.columns) except Exception: self._composite_sampler = None def sample_composite_key_with_phase(self, phase: SamplingPhase) -> Optional[tuple]: """Sample composite key values with phase awareness""" if not self.is_composite(): raise ValueError("Not a composite key") if not self.is_unique_key: raise ValueError("Only unique composite keys support phase-aware sampling") # Use CompositeBusinessShuffler with phase awareness if available if self._composite_sampler is not None and hasattr( self._composite_sampler, "sample_with_phase" ): result = self._composite_sampler.sample_with_phase(phase) if result is not None: return result # already converted to business values elif phase == SamplingPhase.PREPARE: # Log exhaustion warning only once per index for PREPARE phase if not hasattr(self, "_exhausted_logged") or not self._exhausted_logged: # print(f"Warning: CompositeBusinessShuffler for {self.key_name} exhausted, falling back to individual sampling") self._exhausted_logged = True # Fallback: Use individual column sampling with their full phase-aware mechanisms # This happens when: # 1. CompositeBusinessShuffler is not available # 2. CompositeBusinessShuffler is exhausted (PREPARE phase) # 3. REPLAY phase and CompositeBusinessShuffler returns None values = [] for col in self.columns: # In fallback mode, use generate new value directly as REPLAY phase to avoid BlockShuffler reset issues fallback_phase = SamplingPhase.REPLAY if self.is_unique_key else phase value = col.sample_for_insert(phase=fallback_phase) values.append(value) return tuple(values) def sample_composite_key(self) -> Optional[tuple]: """Backward compatibility: sample composite key (defaults to PREPARE phase)""" return self.sample_composite_key_with_phase(SamplingPhase.PREPARE) def reset_composite_sampler(self): """reset composite sampler""" if self._composite_sampler: self._composite_sampler.reset()