001package gudusoft.gsqlparser.ir.builder.common; 002 003import gudusoft.gsqlparser.ir.bound.*; 004import gudusoft.gsqlparser.ir.common.Evidence; 005 006import java.util.*; 007 008/** 009 * Shared base class for dialect-specific routine reference resolvers. 010 * <p> 011 * Owns the common resolve() loop: builds uppercase and name-based indexes, 012 * iterates all unresolved refs, delegates per-ref resolution to subclass, 013 * and replaces the ref list on the program. 014 * <p> 015 * Dialect-specific resolution tiers (qualified match, schema inference, 016 * default schema, etc.) belong in the subclass {@link #tryResolve} method. 017 */ 018public abstract class AbstractRoutineRefResolver { 019 020 /** 021 * Resolves all unresolved routine refs in the program. 022 * Replaces the program's ref list with updated copies where matches are found. 023 */ 024 public final void resolve(BoundProgram program) { 025 if (program == null) return; 026 027 Map<String, BoundRoutineSymbol> upperIndex = buildUpperCaseIndex(program); 028 if (upperIndex.isEmpty()) return; 029 030 Map<String, List<BoundRoutineSymbol>> nameIndex = buildNameIndex(program); 031 032 List<BoundRoutineRef> original = program.getAllRoutineRefs(); 033 List<BoundRoutineRef> resolved = new ArrayList<BoundRoutineRef>(original.size()); 034 035 for (BoundRoutineRef ref : original) { 036 if (ref.getBindingStatus() == EBindingStatus.EXACT) { 037 resolved.add(ref); 038 continue; 039 } 040 if (Boolean.TRUE.equals(ref.getProperty("externalDependency"))) { 041 resolved.add(ref); 042 continue; 043 } 044 045 BoundRoutineRef resolvedRef = tryResolve(ref, program, upperIndex, nameIndex); 046 if (resolvedRef != null) { 047 resolved.add(resolvedRef); 048 } else { 049 classifyExternalIfKnown(ref); 050 resolved.add(ref); 051 } 052 } 053 054 program.replaceRoutineRefs(resolved); 055 } 056 057 /** 058 * Attempts to resolve a single routine ref against the known routines. 059 * 060 * @param ref the unresolved ref 061 * @param program the program containing all routines 062 * @param upperIndex case-insensitive routineId → symbol index 063 * @param nameIndex case-insensitive name → list of symbols index 064 * @return resolved ref, or null if no match found 065 */ 066 protected abstract BoundRoutineRef tryResolve( 067 BoundRoutineRef ref, 068 BoundProgram program, 069 Map<String, BoundRoutineSymbol> upperIndex, 070 Map<String, List<BoundRoutineSymbol>> nameIndex); 071 072 /** 073 * Called for unresolved refs to check if they are known external dependencies. 074 * Subclasses should set properties like "externalDependency", "externalType", etc. 075 */ 076 protected abstract void classifyExternalIfKnown(BoundRoutineRef ref); 077 078 // ====== Index Building (shared) ====== 079 080 /** 081 * Builds a case-insensitive routineId → symbol index. 082 */ 083 protected final Map<String, BoundRoutineSymbol> buildUpperCaseIndex(BoundProgram program) { 084 Map<String, BoundRoutineSymbol> ciIndex = 085 new LinkedHashMap<String, BoundRoutineSymbol>(); 086 for (Map.Entry<String, BoundRoutineSymbol> entry : program.getRoutineIndex().entrySet()) { 087 ciIndex.put(entry.getKey().toUpperCase(), entry.getValue()); 088 } 089 return ciIndex; 090 } 091 092 /** 093 * Builds a case-insensitive name → list of symbols index. 094 */ 095 protected final Map<String, List<BoundRoutineSymbol>> buildNameIndex(BoundProgram program) { 096 Map<String, List<BoundRoutineSymbol>> nameIndex = 097 new LinkedHashMap<String, List<BoundRoutineSymbol>>(); 098 for (BoundRoutineSymbol sym : program.getRoutineIndex().values()) { 099 String nameKey = sym.getRoutineName().toUpperCase(); 100 List<BoundRoutineSymbol> list = nameIndex.get(nameKey); 101 if (list == null) { 102 list = new ArrayList<BoundRoutineSymbol>(); 103 nameIndex.put(nameKey, list); 104 } 105 list.add(sym); 106 } 107 return nameIndex; 108 } 109 110 // ====== Common Resolution Helpers ====== 111 112 /** 113 * Tries all KIND code variants for a given base name and argCount. 114 * Returns the first match, or null. 115 */ 116 protected static BoundRoutineSymbol tryKindVariants( 117 Map<String, BoundRoutineSymbol> ciIndex, 118 String baseName, int argCount, String[] kindCodes) { 119 // First pass: exact argCount 120 for (String kind : kindCodes) { 121 String candidateId = baseName + "/" + kind + "(" + argCount + ")"; 122 BoundRoutineSymbol match = ciIndex.get(candidateId.toUpperCase()); 123 if (match != null) { 124 return match; 125 } 126 } 127 // Second pass: default parameter tolerance (paramCount > argCount) 128 String prefix = baseName.toUpperCase() + "/"; 129 BoundRoutineSymbol bestMatch = null; 130 int bestParamCount = Integer.MAX_VALUE; 131 for (Map.Entry<String, BoundRoutineSymbol> entry : ciIndex.entrySet()) { 132 String key = entry.getKey(); 133 if (!key.startsWith(prefix)) continue; 134 int openParen = key.lastIndexOf('('); 135 int closeParen = key.lastIndexOf(')'); 136 if (openParen < 0 || closeParen <= openParen) continue; 137 try { 138 int paramCount = Integer.parseInt(key.substring(openParen + 1, closeParen)); 139 if (paramCount > argCount && paramCount < bestParamCount) { 140 bestMatch = entry.getValue(); 141 bestParamCount = paramCount; 142 } 143 } catch (NumberFormatException e) { 144 // skip 145 } 146 } 147 return bestMatch; 148 } 149 150 /** 151 * Finds candidates matching a name and exact arg count from the name index. 152 */ 153 protected static List<BoundRoutineSymbol> findByNameAndArgCount( 154 Map<String, List<BoundRoutineSymbol>> nameIndex, 155 String upperName, int argCount) { 156 List<BoundRoutineSymbol> candidates = nameIndex.get(upperName); 157 if (candidates == null || candidates.isEmpty()) { 158 return Collections.emptyList(); 159 } 160 List<BoundRoutineSymbol> matches = new ArrayList<BoundRoutineSymbol>(); 161 for (BoundRoutineSymbol sym : candidates) { 162 if (sym.getParameters().size() == argCount) { 163 matches.add(sym); 164 } 165 } 166 return matches; 167 } 168 169 /** 170 * Finds the best default-parameter match (smallest paramCount > argCount). 171 */ 172 protected static BoundRoutineSymbol findBestDefaultParamMatch( 173 Map<String, List<BoundRoutineSymbol>> nameIndex, 174 String upperName, int argCount) { 175 List<BoundRoutineSymbol> candidates = nameIndex.get(upperName); 176 if (candidates == null) return null; 177 BoundRoutineSymbol best = null; 178 int bestCount = Integer.MAX_VALUE; 179 for (BoundRoutineSymbol sym : candidates) { 180 int pc = sym.getParameters().size(); 181 if (pc > argCount && pc < bestCount) { 182 best = sym; 183 bestCount = pc; 184 } 185 } 186 return best; 187 } 188}