001package gudusoft.gsqlparser.dlineage.routine; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.EParameterMode; 005import gudusoft.gsqlparser.catalog.runtime.CatalogLookupName; 006import gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind; 007import gudusoft.gsqlparser.nodes.TParameterDeclaration; 008import gudusoft.gsqlparser.nodes.TParameterDeclarationList; 009import gudusoft.gsqlparser.sqlenv.CanonKey; 010import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType; 011import gudusoft.gsqlparser.sqlenv.IdentifierInputForm; 012import gudusoft.gsqlparser.sqlenv.IdentifierService; 013 014import java.util.ArrayList; 015import java.util.Arrays; 016import java.util.Collections; 017import java.util.List; 018import java.util.Objects; 019 020/** 021 * One declared routine: its name as written (one interpretation), its kind, its parameters, and the scope it 022 * is declared in when it is local to another routine. 023 * 024 * <p>Its {@link #identity() identity} is the routine it declares: the name's keys, the namespace it lives in, 025 * and the parameters as far as the vendor tells routines of one name apart by them ({@link Signature}). Two 026 * declarations with one identity are one routine declared twice ({@code CREATE OR REPLACE}, a package 027 * specification and its body); where the vendor overloads, a same-named declaration with other parameters is 028 * another routine. Parameter counts are only lookup aliases: a call supplies arguments, not types, and matches 029 * a declaration when the number of arguments lies between its required inputs and its inputs, or equals its 030 * parameter count.</p> 031 */ 032public final class RoutineDeclaration { 033 034 /** How much of its parameter list a vendor identifies a routine by. */ 035 public enum Signature { 036 /** The name alone: the vendor does not overload (SQL Server, MySQL, BigQuery, HANA, ...), or a standalone 037 * Oracle routine, which CREATE OR REPLACE replaces whatever its parameters. */ 038 NAME_ONLY, 039 /** The number of parameters (DB2 procedures). */ 040 COUNT, 041 /** The types of the input parameters (PostgreSQL and its family: OUT parameters are not part of it). */ 042 INPUT_TYPES, 043 /** The types of all parameters (DB2 functions, Snowflake, Teradata functions). */ 044 TYPES, 045 /** Kind, parameter names, modes and types: an Oracle package member or local routine, matched between a 046 * specification and its body as Oracle matches it. */ 047 NAMED_TYPES 048 } 049 050 /** The signature a vendor identifies a routine of this kind by. */ 051 public static Signature signatureOf(EDbVendor vendor, CatalogObjectKind kind, boolean memberOrLocal) { 052 switch (vendor) { 053 case dbvoracle: 054 case dbvdameng: 055 return memberOrLocal ? Signature.NAMED_TYPES : Signature.NAME_ONLY; 056 case dbvpostgresql: 057 case dbvgreenplum: 058 case dbvedb: 059 case dbvgaussdb: 060 case dbvredshift: 061 return Signature.INPUT_TYPES; 062 case dbvdb2: 063 return kind == CatalogObjectKind.PROCEDURE ? Signature.COUNT : Signature.TYPES; 064 case dbvsnowflake: 065 return Signature.TYPES; 066 case dbvteradata: 067 return kind == CatalogObjectKind.FUNCTION ? Signature.TYPES : Signature.NAME_ONLY; 068 default: 069 return Signature.NAME_ONLY; 070 } 071 } 072 073 /** Whether the vendor tells routines of this kind apart by their parameters (their identity is not the name alone). */ 074 public static boolean identityOverloads(EDbVendor vendor, CatalogObjectKind kind, boolean memberOrLocal) { 075 return signatureOf(vendor, kind, memberOrLocal) != Signature.NAME_ONLY; 076 } 077 078 /** 079 * Whether declarations of this kind are keyed with their argument counts, so a call is looked up by its 080 * argument count: where the vendor overloads routines of the kind, and wherever the legacy registry keyed them 081 * so (standalone Oracle routines, MySQL, HANA). A count alias on a routine alone under its name finds nothing it 082 * should not; the identity stays the name alone. 083 */ 084 public static boolean usesCountAliases(EDbVendor vendor, CatalogObjectKind kind, boolean memberOrLocal) { 085 return identityOverloads(vendor, kind, memberOrLocal) 086 || gudusoft.gsqlparser.dlineage.util.DlineageUtil.supportFunctionOverride(vendor); 087 } 088 089 /** Whether a call site keys its lookup with the argument count: some routine of the vendor uses count aliases. */ 090 public static boolean callsCounted(EDbVendor vendor) { 091 return usesCountAliases(vendor, CatalogObjectKind.FUNCTION, true) 092 || usesCountAliases(vendor, CatalogObjectKind.PROCEDURE, true); 093 } 094 095 /** The namespace a kind lives in: one for functions and procedures (as today), except where the vendor 096 * keeps them apart (DB2); triggers apart everywhere. */ 097 static CatalogObjectKind namespaceOf(EDbVendor vendor, CatalogObjectKind kind) { 098 if (kind == CatalogObjectKind.TRIGGER) { 099 return CatalogObjectKind.TRIGGER; 100 } 101 if (vendor == EDbVendor.dbvdb2 && (kind == CatalogObjectKind.FUNCTION || kind == CatalogObjectKind.PROCEDURE)) { 102 return kind; 103 } 104 return CatalogObjectKind.ROUTINE; 105 } 106 107 private final Object origin; 108 private Object representative; 109 private final RoutineName name; 110 private final CatalogObjectKind namespace; 111 private final List<ArgumentTypeKey> types; 112 private final int totalCount; 113 private final int inputCount; 114 private final int requiredCount; 115 private final boolean unboundedInputs; 116 private final boolean signatureAliases; 117 private final Identity identity; 118 119 RoutineDeclaration(Object origin, RoutineName name, CatalogObjectKind namespace, List<ArgumentTypeKey> types, 120 int totalCount, int inputCount, int requiredCount, boolean unboundedInputs, boolean signatureAliases, 121 Identity identity) { 122 this.origin = origin; 123 this.representative = origin; 124 this.name = name; 125 this.namespace = namespace; 126 this.types = types == null ? null : Collections.unmodifiableList(types); 127 this.totalCount = totalCount; 128 this.inputCount = inputCount; 129 this.requiredCount = requiredCount; 130 this.unboundedInputs = unboundedInputs; 131 this.signatureAliases = signatureAliases; 132 this.identity = identity; 133 } 134 135 /** 136 * A declaration from its AST: {@code scope} the routine it is local to (null for a top-level or package 137 * routine). A statement with no parameter list ({@code parameters} null: Oracle {@code CREATE PROCEDURE p IS}, 138 * DB2 {@code CREATE FUNCTION f ()}) declares a routine with no parameters. 139 */ 140 public static RoutineDeclaration of(Object origin, RoutineName name, TParameterDeclarationList parameters, 141 Object scope, EDbVendor vendor, IdentifierService service) { 142 boolean memberOrLocal = name.isPackageMember() || scope != null; 143 int size = parameters == null ? 0 : parameters.size(); 144 List<ArgumentTypeKey> types = new ArrayList<ArgumentTypeKey>(size); 145 List<EParameterMode> modes = new ArrayList<EParameterMode>(size); 146 List<Object> parameterNames = new ArrayList<Object>(size); 147 List<ArgumentTypeKey> inputTypes = new ArrayList<ArgumentTypeKey>(size); 148 int total = size; 149 int inputs = 0; 150 int required = 0; 151 boolean unbounded = false; 152 if (parameters != null) { 153 for (int i = 0; i < parameters.size(); i++) { 154 TParameterDeclaration parameter = parameters.getParameterDeclarationItem(i); 155 ArgumentTypeKey type = ArgumentTypeKey.of(parameter.getDataType(), vendor, service); 156 EParameterMode mode = parameter.getParameterMode(); 157 types.add(type); 158 modes.add(mode); 159 parameterNames.add(parameterKey(parameter, service)); 160 if (mode != EParameterMode.out && mode != EParameterMode.output) { 161 // IN, INOUT, VARIADIC, or no mode written (IN). 162 inputs++; 163 inputTypes.add(type); 164 if (mode == EParameterMode.variadic) { 165 // Any number of arguments, none included. 166 unbounded = true; 167 } else if (parameter.getDefaultValue() == null) { 168 required++; 169 } 170 } 171 } 172 } 173 Signature signature = signatureOf(vendor, name.getKind(), memberOrLocal); 174 Object key; 175 switch (signature) { 176 case COUNT: 177 key = Integer.valueOf(total); 178 break; 179 case INPUT_TYPES: 180 key = inputTypes; 181 break; 182 case TYPES: 183 key = types; 184 break; 185 case NAMED_TYPES: 186 key = Arrays.asList(name.getKind(), types, modes, parameterNames); 187 break; 188 default: 189 key = null; 190 } 191 CatalogObjectKind namespace = namespaceOf(vendor, name.getKind()); 192 return new RoutineDeclaration(origin, name, namespace, types, total, inputs, required, unbounded, 193 usesCountAliases(vendor, name.getKind(), memberOrLocal), 194 new Identity(name, namespace, scope, key)); 195 } 196 197 /** {@link #of(Object, RoutineName, TParameterDeclarationList, Object, EDbVendor, IdentifierService)} for a 198 * top-level routine; {@code model} is not kept (the typed registry holds declarations, not legacy models). */ 199 public static RoutineDeclaration of(Object model, Object origin, RoutineName name, TParameterDeclarationList parameters, 200 EDbVendor vendor, IdentifierService service) { 201 return of(origin, name, parameters, null, vendor, service); 202 } 203 204 private static Object parameterKey(TParameterDeclaration parameter, IdentifierService service) { 205 if (parameter.getParameterName() == null) { 206 return ""; 207 } 208 String text = parameter.getParameterName().toString(); 209 try { 210 return service.canonKeyOf(text, ESQLDataObjectType.dotParameter, IdentifierInputForm.SQL_LEXICAL); 211 } catch (IllegalArgumentException notKeyable) { 212 return text; 213 } 214 } 215 216 /** A routine known only from a call or an EXEC of a module never defined here: a name and a count. */ 217 public static RoutineDeclaration ofCall(Object origin, RoutineName name, Integer argumentCount, EDbVendor vendor) { 218 int count = argumentCount == null ? 0 : argumentCount; 219 CatalogObjectKind namespace = namespaceOf(vendor, name.getKind()); 220 return new RoutineDeclaration(origin, name, namespace, null, count, count, count, false, argumentCount != null, 221 new Identity(name, namespace, null, argumentCount)); 222 } 223 224 /** The statement or node this occurrence was declared by. */ 225 public Object getOrigin() { 226 return origin; 227 } 228 229 /** The first occurrence of this declaration's identity: the one a later occurrence replaced keeps its place. */ 230 public Object getRepresentative() { 231 return representative; 232 } 233 234 void setRepresentative(Object representative) { 235 this.representative = representative; 236 } 237 238 public RoutineName getName() { 239 return name; 240 } 241 242 public CatalogObjectKind getNamespace() { 243 return namespace; 244 } 245 246 public List<ArgumentTypeKey> getTypes() { 247 return types; 248 } 249 250 public int getTotalCount() { 251 return totalCount; 252 } 253 254 /** Whether the declaration is keyed with its signature (a lookup by name only reaches it by the fallback). */ 255 public boolean hasSignatureAliases() { 256 return signatureAliases; 257 } 258 259 /** 260 * Whether a call with {@code count} arguments can be this routine: any count for a routine keyed by its name 261 * alone, else its parameter count, or a count from its required inputs up to its inputs (no upper bound with a 262 * VARIADIC parameter). 263 */ 264 public boolean acceptsCount(int count) { 265 return !signatureAliases || count == totalCount 266 || (count >= requiredCount && (unboundedInputs || count <= inputCount)); 267 } 268 269 public Identity identity() { 270 return identity; 271 } 272 273 /** The identity of a declared routine. */ 274 public static final class Identity { 275 private final List<Object> containers; 276 private final CanonKey terminal; 277 private final CatalogObjectKind namespace; 278 private final Object scope; 279 private final Object signature; 280 281 Identity(RoutineName name, CatalogObjectKind namespace, Object scope, Object signature) { 282 List<Object> keys = new ArrayList<Object>(); 283 for (RoutineName.Level level : name.getContainers()) { 284 keys.add(level.getState() == CatalogLookupName.SlotState.PRESENT ? level.getKey() 285 : level.getRole() + ":" + level.getState()); 286 } 287 this.containers = keys; 288 this.namespace = namespace; 289 this.terminal = name.terminalKey(name.getKind()); 290 this.scope = scope; 291 this.signature = signature; 292 } 293 294 @Override 295 public boolean equals(Object o) { 296 if (this == o) { 297 return true; 298 } 299 if (!(o instanceof Identity)) { 300 return false; 301 } 302 Identity other = (Identity) o; 303 return namespace == other.namespace && scope == other.scope && containers.equals(other.containers) 304 && terminal.equals(other.terminal) && Objects.equals(signature, other.signature); 305 } 306 307 @Override 308 public int hashCode() { 309 return Objects.hash(namespace, System.identityHashCode(scope), containers, terminal, signature); 310 } 311 } 312 313 /** The scope a declaration is local to, if any: visible only to lookups inside it. */ 314 Object scope() { 315 return identity.scope; 316 } 317 318 @Override 319 public String toString() { 320 return name + (types == null ? "" : String.valueOf(types)); 321 } 322}