001package gudusoft.gsqlparser.dlineage.routine; 002 003import gudusoft.gsqlparser.catalog.runtime.CatalogLookupName; 004import gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind; 005import gudusoft.gsqlparser.sqlenv.CanonKey; 006 007import java.util.ArrayList; 008import java.util.EnumSet; 009import java.util.HashMap; 010import java.util.LinkedHashSet; 011import java.util.List; 012import java.util.Map; 013import java.util.Set; 014 015/** 016 * The routines declared in one analysis, by identity, with ordered alias indexes for lookups. 017 * 018 * <p>A declaration with the identity of an earlier one replaces it in place ({@code CREATE OR REPLACE}, a 019 * package body after its specification). Lookups compare written names level by level 020 * ({@link RoutineName}): the terminal through a per-kind index, the containers by their keys and states, 021 * then the argument alias: a name-only lookup matches declarations keyed by name only, a lookup with 022 * {@code n} arguments matches a declaration's total or input count. The result says how it matched and 023 * whether it is unique ({@link RoutineLookupResult}); it never picks the first of several.</p> 024 */ 025public final class RoutineRegistry { 026 027 private final List<RoutineDeclaration> declarations = new ArrayList<RoutineDeclaration>(); 028 private final Map<RoutineDeclaration.Identity, Integer> byIdentity = new HashMap<RoutineDeclaration.Identity, Integer>(); 029 /** Declarations by the kind they were declared as, then by their terminal key under that kind. */ 030 private final Map<CatalogObjectKind, Map<CanonKey, List<Integer>>> byTerminal = 031 new HashMap<CatalogObjectKind, Map<CanonKey, List<Integer>>>(); 032 033 /** 034 * Adds a declaration, or replaces the current occurrence of its identity; returns the declaration it 035 * replaced, or null. 036 */ 037 public RoutineDeclaration declare(RoutineDeclaration declaration) { 038 Integer existing = byIdentity.get(declaration.identity()); 039 if (existing != null) { 040 RoutineDeclaration replaced = declarations.get(existing); 041 declaration.setRepresentative(replaced.getRepresentative()); 042 declarations.set(existing, declaration); 043 return replaced; 044 } 045 int index = declarations.size(); 046 declarations.add(declaration); 047 byIdentity.put(declaration.identity(), index); 048 CatalogObjectKind kind = declaration.getName().getKind(); 049 Map<CanonKey, List<Integer>> terminals = byTerminal.get(kind); 050 if (terminals == null) { 051 terminals = new HashMap<CanonKey, List<Integer>>(); 052 byTerminal.put(kind, terminals); 053 } 054 CanonKey terminal = declaration.getName().terminalKey(kind); 055 List<Integer> entries = terminals.get(terminal); 056 if (entries == null) { 057 entries = new ArrayList<Integer>(1); 058 terminals.put(terminal, entries); 059 } 060 entries.add(index); 061 return null; 062 } 063 064 /** The current occurrence of an identity, or null. */ 065 public RoutineDeclaration find(RoutineDeclaration.Identity identity) { 066 Integer index = byIdentity.get(identity); 067 return index == null ? null : declarations.get(index); 068 } 069 070 public List<RoutineDeclaration> getDeclarations() { 071 return java.util.Collections.unmodifiableList(declarations); 072 } 073 074 /** 075 * Looks a routine up by the interpretations of its written name and, when the call site keys it with 076 * one, the argument count ({@code null} for a name-only lookup). 077 */ 078 public RoutineLookupResult lookup(List<RoutineName> interpretations, Integer argumentCount) { 079 return lookup(interpretations, argumentCount, CatalogObjectKind.ROUTINE, null); 080 } 081 082 /** 083 * A lookup for a routine of {@code wanted} kind (ROUTINE when the call site does not say), seeing the local 084 * routines declared in {@code visibleScopes} (the routines enclosing the call); triggers are not callable. 085 */ 086 public RoutineLookupResult lookup(List<RoutineName> interpretations, Integer argumentCount, CatalogObjectKind wanted, 087 java.util.Collection<Object> visibleScopes) { 088 Set<RoutineDeclaration> found = new LinkedHashSet<RoutineDeclaration>(); 089 RoutineLookupResult.Branch branch = RoutineLookupResult.Branch.NONE; 090 for (RoutineName name : interpretations) { 091 RoutineLookupResult.Branch nameBranch = RoutineLookupResult.Branch.NONE; 092 List<RoutineDeclaration> nameFound = new ArrayList<RoutineDeclaration>(); 093 for (RoutineLookupResult.Branch branchWanted : EnumSet.range(RoutineLookupResult.Branch.EXACT, 094 RoutineLookupResult.Branch.PARTIAL)) { 095 nameFound = candidates(name, argumentCount, branchWanted, wanted, visibleScopes); 096 if (!nameFound.isEmpty()) { 097 nameBranch = branchWanted; 098 break; 099 } 100 } 101 if (nameFound.isEmpty()) { 102 continue; 103 } 104 if (branch == RoutineLookupResult.Branch.NONE || nameBranch.compareTo(branch) < 0) { 105 branch = nameBranch; 106 } 107 found.addAll(nameFound); 108 } 109 if (found.isEmpty()) { 110 return RoutineLookupResult.NOT_FOUND; 111 } 112 List<RoutineDeclaration> ordered = new ArrayList<RoutineDeclaration>(found); 113 java.util.Collections.sort(ordered, (a, b) -> Integer.compare(indexOf(a), indexOf(b))); 114 RoutineLookupResult.Status status; 115 if (ordered.size() > 1) { 116 status = RoutineLookupResult.Status.AMBIGUOUS; 117 } else if (branch == RoutineLookupResult.Branch.EXACT || branch == RoutineLookupResult.Branch.NAME_FALLBACK) { 118 status = RoutineLookupResult.Status.RESOLVED; 119 } else { 120 status = RoutineLookupResult.Status.REQUIRES_CONTEXT; 121 } 122 return new RoutineLookupResult(status, branch, ordered); 123 } 124 125 private int indexOf(RoutineDeclaration declaration) { 126 Integer index = byIdentity.get(declaration.identity()); 127 return index == null ? Integer.MAX_VALUE : index; 128 } 129 130 private List<RoutineDeclaration> candidates(RoutineName name, Integer argumentCount, 131 RoutineLookupResult.Branch wanted, CatalogObjectKind wantedKind, java.util.Collection<Object> visibleScopes) { 132 List<Integer> indexes = new ArrayList<Integer>(); 133 for (Map.Entry<CatalogObjectKind, Map<CanonKey, List<Integer>>> kind : byTerminal.entrySet()) { 134 if (kind.getKey() == CatalogObjectKind.TRIGGER) { 135 continue; 136 } 137 List<Integer> entries = kind.getValue().get(name.terminalKey(kind.getKey())); 138 if (entries != null) { 139 indexes.addAll(entries); 140 } 141 } 142 java.util.Collections.sort(indexes); 143 List<RoutineDeclaration> out = new ArrayList<RoutineDeclaration>(); 144 for (Integer index : indexes) { 145 RoutineDeclaration declaration = declarations.get(index); 146 CatalogObjectKind namespace = declaration.getNamespace(); 147 if (namespace != CatalogObjectKind.ROUTINE && wantedKind != CatalogObjectKind.ROUTINE && namespace != wantedKind) { 148 // The vendor keeps functions and procedures apart (DB2): a CALL does not find a function. 149 continue; 150 } 151 if (declaration.scope() != null && (visibleScopes == null || !containsIdentical(visibleScopes, declaration.scope()))) { 152 // A routine local to another is visible only inside it. 153 continue; 154 } 155 RoutineLookupResult.Branch containers = branchOf(name, declaration.getName()); 156 boolean aliasMatches; 157 if (wanted == RoutineLookupResult.Branch.NAME_FALLBACK) { 158 // A name-only lookup reaches declarations of that very name keyed with a signature (the 159 // legacy prefix match `name(`). 160 aliasMatches = containers == RoutineLookupResult.Branch.EXACT && argumentCount == null 161 && declaration.hasSignatureAliases(); 162 } else if (containers != wanted) { 163 aliasMatches = false; 164 } else if (argumentCount != null) { 165 aliasMatches = declaration.acceptsCount(argumentCount); 166 } else if (wanted == RoutineLookupResult.Branch.EXACT) { 167 aliasMatches = !declaration.hasSignatureAliases(); 168 } else { 169 // A name-only leaf match reaches only a declaration keyed with a signature, as the legacy 170 // `leaf.startsWith(name + "(")` did. 171 aliasMatches = declaration.hasSignatureAliases(); 172 } 173 if (aliasMatches) { 174 out.add(declaration); 175 } 176 } 177 return out; 178 } 179 180 private static boolean containsIdentical(java.util.Collection<Object> scopes, Object scope) { 181 for (Object candidate : scopes) { 182 if (candidate == scope) { 183 return true; 184 } 185 } 186 return false; 187 } 188 189 /** How a lookup's containers relate to a declaration's (the terminals already equal). */ 190 static RoutineLookupResult.Branch branchOf(RoutineName lookup, RoutineName declared) { 191 List<RoutineName.Level> a = lookup.getContainers(); 192 List<RoutineName.Level> b = declared.getContainers(); 193 if (lookup.isPackageMember() != declared.isPackageMember() || a.size() != b.size()) { 194 return RoutineLookupResult.Branch.NONE; 195 } 196 boolean lookupOmits = false; 197 boolean declarationOmits = false; 198 for (int i = 0; i < a.size(); i++) { 199 RoutineName.Level x = a.get(i); 200 RoutineName.Level y = b.get(i); 201 if (x.getRole() != y.getRole()) { 202 return RoutineLookupResult.Branch.NONE; 203 } 204 boolean xNamed = x.getState() == CatalogLookupName.SlotState.PRESENT; 205 boolean yNamed = y.getState() == CatalogLookupName.SlotState.PRESENT; 206 if (xNamed && yNamed) { 207 if (!x.getKey().equals(y.getKey())) { 208 return RoutineLookupResult.Branch.NONE; 209 } 210 } else if (yNamed) { 211 lookupOmits = true; 212 } else if (xNamed) { 213 declarationOmits = true; 214 } 215 } 216 if (lookupOmits && declarationOmits) { 217 return RoutineLookupResult.Branch.PARTIAL; 218 } 219 if (lookupOmits) { 220 return RoutineLookupResult.Branch.LEAF_QUALIFIED; 221 } 222 if (declarationOmits) { 223 return RoutineLookupResult.Branch.LEAF_UNQUALIFIED; 224 } 225 return RoutineLookupResult.Branch.EXACT; 226 } 227}