001package gudusoft.gsqlparser.ir.bound; 002 003import gudusoft.gsqlparser.ir.builder.common.DynamicSqlExtraction; 004 005import java.util.ArrayList; 006import java.util.Collections; 007import java.util.LinkedHashMap; 008import java.util.List; 009import java.util.Map; 010 011/** 012 * Aggregation root for the Bound IR phase output. 013 * <p> 014 * Contains all scopes, symbols, and references produced by the bound IR builder. 015 */ 016public class BoundProgram { 017 018 /** All scopes in the program (root scope first). */ 019 private final List<BoundScope> scopes; 020 021 /** Global routine symbol table indexed by routine ID (deterministic iteration order). */ 022 private final Map<String, BoundRoutineSymbol> routineIndex; 023 024 /** Multi-value index: uppercase routine name → list of routines with that name. */ 025 private final Map<String, List<BoundRoutineSymbol>> routinesByName; 026 027 /** All object references found during binding. */ 028 private final List<BoundObjectRef> allObjectRefs; 029 030 /** All column references found during binding. */ 031 private final List<BoundColumnRef> allColumnRefs; 032 033 /** All routine references found during binding. */ 034 private final List<BoundRoutineRef> allRoutineRefs; 035 036 /** Dynamic SQL extractions (sidecar collection). */ 037 private final List<DynamicSqlExtraction> dynamicSqlExtractions; 038 039 /** Unsupported construct counter (dialect/feature → count). */ 040 private final Map<String, Integer> unsupportedCounters; 041 042 public BoundProgram() { 043 this.scopes = new ArrayList<BoundScope>(); 044 this.routineIndex = new LinkedHashMap<String, BoundRoutineSymbol>(); 045 this.routinesByName = new LinkedHashMap<String, List<BoundRoutineSymbol>>(); 046 this.allObjectRefs = new ArrayList<BoundObjectRef>(); 047 this.allColumnRefs = new ArrayList<BoundColumnRef>(); 048 this.allRoutineRefs = new ArrayList<BoundRoutineRef>(); 049 this.dynamicSqlExtractions = new ArrayList<DynamicSqlExtraction>(); 050 this.unsupportedCounters = new LinkedHashMap<String, Integer>(); 051 } 052 053 public BoundProgram(List<BoundScope> scopes, 054 Map<String, BoundRoutineSymbol> routineIndex, 055 List<BoundObjectRef> allObjectRefs, 056 List<BoundColumnRef> allColumnRefs, 057 List<BoundRoutineRef> allRoutineRefs) { 058 this.scopes = scopes != null ? scopes : new ArrayList<BoundScope>(); 059 this.routineIndex = routineIndex != null 060 ? new LinkedHashMap<String, BoundRoutineSymbol>(routineIndex) 061 : new LinkedHashMap<String, BoundRoutineSymbol>(); 062 this.routinesByName = new LinkedHashMap<String, List<BoundRoutineSymbol>>(); 063 // Rebuild the by-name index from routineIndex 064 for (BoundRoutineSymbol sym : this.routineIndex.values()) { 065 addToNameIndex(sym); 066 } 067 this.allObjectRefs = allObjectRefs != null ? allObjectRefs : new ArrayList<BoundObjectRef>(); 068 this.allColumnRefs = allColumnRefs != null ? allColumnRefs : new ArrayList<BoundColumnRef>(); 069 this.allRoutineRefs = allRoutineRefs != null ? allRoutineRefs : new ArrayList<BoundRoutineRef>(); 070 this.dynamicSqlExtractions = new ArrayList<DynamicSqlExtraction>(); 071 this.unsupportedCounters = new LinkedHashMap<String, Integer>(); 072 } 073 074 public List<BoundScope> getScopes() { return scopes; } 075 public Map<String, BoundRoutineSymbol> getRoutineIndex() { 076 return Collections.unmodifiableMap(routineIndex); 077 } 078 public Map<String, List<BoundRoutineSymbol>> getRoutinesByName() { 079 return Collections.unmodifiableMap(routinesByName); 080 } 081 public List<BoundObjectRef> getAllObjectRefs() { return allObjectRefs; } 082 public List<BoundColumnRef> getAllColumnRefs() { return allColumnRefs; } 083 public List<BoundRoutineRef> getAllRoutineRefs() { return allRoutineRefs; } 084 085 public void addScope(BoundScope scope) { scopes.add(scope); } 086 087 public void registerRoutine(BoundRoutineSymbol routine) { 088 routineIndex.put(routine.getRoutineId(), routine); 089 addToNameIndex(routine); 090 } 091 092 /** 093 * Removes a routine by its ID. Used to merge forward declarations 094 * with their implementations. 095 */ 096 public void unregisterRoutine(String routineId) { 097 BoundRoutineSymbol removed = routineIndex.remove(routineId); 098 if (removed != null) { 099 removeFromNameIndex(removed); 100 } 101 } 102 103 private void removeFromNameIndex(BoundRoutineSymbol routine) { 104 String nameKey = routine.getRoutineName().toUpperCase(); 105 List<BoundRoutineSymbol> list = routinesByName.get(nameKey); 106 if (list != null) { 107 list.remove(routine); 108 if (list.isEmpty()) { 109 routinesByName.remove(nameKey); 110 } 111 } 112 } 113 114 private void addToNameIndex(BoundRoutineSymbol routine) { 115 String nameKey = routine.getRoutineName().toUpperCase(); 116 List<BoundRoutineSymbol> list = routinesByName.get(nameKey); 117 if (list == null) { 118 list = new ArrayList<BoundRoutineSymbol>(); 119 routinesByName.put(nameKey, list); 120 } 121 list.add(routine); 122 } 123 124 public void addObjectRef(BoundObjectRef ref) { allObjectRefs.add(ref); } 125 public void addColumnRef(BoundColumnRef ref) { allColumnRefs.add(ref); } 126 public void addRoutineRef(BoundRoutineRef ref) { allRoutineRefs.add(ref); } 127 128 // ---- Dynamic SQL Extractions ---- 129 130 public List<DynamicSqlExtraction> getDynamicSqlExtractions() { 131 return Collections.unmodifiableList(dynamicSqlExtractions); 132 } 133 134 public void addDynamicSqlExtraction(DynamicSqlExtraction extraction) { 135 dynamicSqlExtractions.add(extraction); 136 } 137 138 // ---- Unsupported Construct Counters ---- 139 140 public Map<String, Integer> getUnsupportedCounters() { 141 return Collections.unmodifiableMap(unsupportedCounters); 142 } 143 144 public void incrementUnsupported(String key) { 145 Integer count = unsupportedCounters.get(key); 146 unsupportedCounters.put(key, count != null ? count + 1 : 1); 147 } 148 149 /** 150 * Looks up a routine by its ID. 151 */ 152 public BoundRoutineSymbol lookupRoutine(String routineId) { 153 return routineIndex.get(routineId); 154 } 155 156 /** 157 * Replaces the entire routine ref list (used after post-resolution pass). 158 */ 159 public void replaceRoutineRefs(List<BoundRoutineRef> resolved) { 160 allRoutineRefs.clear(); 161 allRoutineRefs.addAll(resolved); 162 } 163 164 @Override 165 public String toString() { 166 return "BoundProgram{scopes=" + scopes.size() 167 + ", routines=" + routineIndex.size() 168 + ", objectRefs=" + allObjectRefs.size() 169 + ", columnRefs=" + allColumnRefs.size() 170 + ", routineRefs=" + allRoutineRefs.size() 171 + ", dynamicSql=" + dynamicSqlExtractions.size() + "}"; 172 } 173}