001package gudusoft.gsqlparser.dlineage.routine; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.TSourceToken; 005import gudusoft.gsqlparser.catalog.runtime.CatalogHierarchy; 006import gudusoft.gsqlparser.catalog.runtime.CatalogIdentifierPolicy; 007import gudusoft.gsqlparser.catalog.runtime.CatalogLookupName; 008import gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind; 009import gudusoft.gsqlparser.catalog.runtime.CatalogParentConstraint; 010import gudusoft.gsqlparser.nodes.TObjectName; 011import gudusoft.gsqlparser.sqlenv.CanonKey; 012import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType; 013import gudusoft.gsqlparser.sqlenv.IdentifierComponentRole; 014import gudusoft.gsqlparser.sqlenv.IdentifierInputForm; 015import gudusoft.gsqlparser.sqlenv.IdentifierRolePolicy; 016import gudusoft.gsqlparser.sqlenv.IdentifierService; 017import gudusoft.gsqlparser.sqlenv.TSQLEnv; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.List; 022 023/** 024 * A routine name as written, at a declaration or a call: one interpretation of it as a 025 * {@link CatalogLookupName}, with the comparison keys of its named levels. 026 * 027 * <p>A routine is declared and called by a partial name ({@code f}, {@code s.f}, {@code pkg.f}, 028 * {@code db..p}), which is a lookup, not an identity: the levels it leaves out are OMITTED and only 029 * the caller's context (session defaults, search path) could supply them. D3b compares written names; 030 * {@link RoutineRegistry} never reports a match that relies on an OMITTED level as resolved.</p> 031 * 032 * <p>The levels come from the roles the parser gave the name's tokens (server, database, schema, 033 * package, object). Each named level's key is minted by the analysis {@link IdentifierService} from 034 * its token as written (SQL_LEXICAL), or as stored for a name taken from a model, under the comparison 035 * type of its role, or of its kind for an object level 036 * ({@link IdentifierRolePolicy#comparisonTypeOf}, {@link CatalogIdentifierPolicy#comparisonTypeOfKind}). 037 * The terminal's key depends on the kind it is compared as, so it is minted per kind 038 * ({@link #terminalKey}).</p> 039 */ 040public final class RoutineName { 041 042 /** One level of the name: its role, whether it was named, and its key when named. */ 043 public static final class Level { 044 final IdentifierComponentRole role; 045 final CatalogLookupName.SlotState state; 046 final String text; 047 final CanonKey key; 048 049 Level(IdentifierComponentRole role, CatalogLookupName.SlotState state, String text, CanonKey key) { 050 this.role = role; 051 this.state = state; 052 this.text = text; 053 this.key = key; 054 } 055 056 public IdentifierComponentRole getRole() { 057 return role; 058 } 059 060 public CatalogLookupName.SlotState getState() { 061 return state; 062 } 063 064 public String getText() { 065 return text; 066 } 067 068 /** Null unless the level was named. */ 069 public CanonKey getKey() { 070 return key; 071 } 072 } 073 074 private final CatalogLookupName lookupName; 075 /** The container levels, outermost first; a package is one, with role OBJECT. */ 076 private final List<Level> containers; 077 private final String terminalText; 078 private final IdentifierInputForm terminalForm; 079 private final CatalogObjectKind kind; 080 private final IdentifierService service; 081 private final boolean packageMember; 082 083 private RoutineName(CatalogLookupName lookupName, List<Level> containers, String terminalText, 084 IdentifierInputForm terminalForm, CatalogObjectKind kind, IdentifierService service, 085 boolean packageMember) { 086 this.lookupName = lookupName; 087 this.containers = Collections.unmodifiableList(containers); 088 this.terminalText = terminalText; 089 this.terminalForm = terminalForm; 090 this.kind = kind; 091 this.service = service; 092 this.packageMember = packageMember; 093 } 094 095 /** The request, as the catalog layer states it. */ 096 public CatalogLookupName getLookupName() { 097 return lookupName; 098 } 099 100 public List<Level> getContainers() { 101 return containers; 102 } 103 104 public String getTerminalText() { 105 return terminalText; 106 } 107 108 public CatalogObjectKind getKind() { 109 return kind; 110 } 111 112 /** Whether this interpretation names a member of a package. */ 113 public boolean isPackageMember() { 114 return packageMember; 115 } 116 117 /** Whether any container level is named. */ 118 public boolean isQualified() { 119 for (Level level : containers) { 120 if (level.state == CatalogLookupName.SlotState.PRESENT) { 121 return true; 122 } 123 } 124 return false; 125 } 126 127 /** The terminal's key when compared as a routine of {@code asKind}. */ 128 public CanonKey terminalKey(CatalogObjectKind asKind) { 129 return service.canonKeyOf(terminalText, CatalogIdentifierPolicy.comparisonTypeOfKind(asKind), terminalForm); 130 } 131 132 /** 133 * The name a routine is declared with: a member of the enclosing package when it is declared inside one 134 * unqualified, else the name as written (a qualifier of a standalone declaration is its schema). Null 135 * when the name has no shape the catalog layer can state. 136 */ 137 public static RoutineName declared(TObjectName name, CatalogObjectKind kind, EDbVendor vendor, 138 IdentifierService service, TObjectName enclosingPackage) { 139 TSourceToken[] t = tokensOf(name); 140 if (t == null) { 141 return null; 142 } 143 TSourceToken server = t[0], database = t[1], schema = t[2], pkg = t[3], object = t[4]; 144 if (enclosingPackage != null && server == null && database == null && schema == null && pkg == null) { 145 return buildInPackage(vendor, service, kind, enclosingPackage, object); 146 } 147 return build(vendor, service, kind, server, database, schema, pkg, object, pkg != null); 148 } 149 150 /** The interpretations of a called name: one, or two for an Oracle name {@code x.f} or a call inside a package. */ 151 public static List<RoutineName> interpretations(TObjectName name, CatalogObjectKind kind, EDbVendor vendor, 152 IdentifierService service, TObjectName enclosingPackage) { 153 List<RoutineName> out = new ArrayList<RoutineName>(2); 154 TSourceToken[] t = tokensOf(name); 155 if (t == null) { 156 return out; 157 } 158 TSourceToken server = t[0], database = t[1], schema = t[2], pkg = t[3], object = t[4]; 159 if (vendor == EDbVendor.dbvoracle && pkg == null && database == null && schema != null) { 160 // Oracle `x.f`: x is a schema or a package; which one only the catalog or the scope decides. 161 add(out, build(vendor, service, kind, server, null, schema, null, object, false)); 162 add(out, build(vendor, service, kind, server, null, null, schema, object, true)); 163 return out; 164 } 165 if (pkg == null && enclosingPackage != null && schema == null && database == null && server == null) { 166 // An unqualified name inside a package: a member of that package, or a routine of its schema. 167 add(out, buildInPackage(vendor, service, kind, enclosingPackage, object)); 168 } 169 add(out, build(vendor, service, kind, server, database, schema, pkg, object, pkg != null)); 170 return out; 171 } 172 173 /** 174 * The name's tokens by role: server, database, schema, package, routine; null when it names no routine. 175 * A routine name has no column part, so a name the parser read as {@code object.part} (DB2's CALL: 176 * {@code s.p} as object {@code s}, part {@code p}) is a qualifier and a routine: each role moves up one. 177 */ 178 private static TSourceToken[] tokensOf(TObjectName name) { 179 if (name == null) { 180 return null; 181 } 182 TSourceToken server = named(name.getServerToken()); 183 TSourceToken database = named(name.getDatabaseToken()); 184 TSourceToken schema = named(name.getSchemaToken()); 185 TSourceToken pkg = named(name.getPackageToken()); 186 TSourceToken object = named(name.getObjectToken()); 187 TSourceToken part = named(name.getPartToken()); 188 if (part != null) { 189 if (database == null) { 190 database = schema; 191 } 192 schema = object; 193 object = part; 194 } 195 if (object == null) { 196 return null; 197 } 198 return new TSourceToken[] {server, database, schema, pkg, object}; 199 } 200 201 /** A token that names a level: null for none, and for the empty level of {@code db..p}, which is omitted. */ 202 private static TSourceToken named(TSourceToken token) { 203 return token == null || token.toString().isEmpty() ? null : token; 204 } 205 206 private static void add(List<RoutineName> out, RoutineName name) { 207 if (name != null) { 208 out.add(name); 209 } 210 } 211 212 private static RoutineName buildInPackage(EDbVendor vendor, IdentifierService service, CatalogObjectKind kind, 213 TObjectName enclosingPackage, TSourceToken member) { 214 if (enclosingPackage.getObjectToken() == null) { 215 return null; 216 } 217 return build(vendor, service, kind, named(enclosingPackage.getServerToken()), 218 named(enclosingPackage.getDatabaseToken()), named(enclosingPackage.getSchemaToken()), 219 enclosingPackage.getObjectToken(), member, true); 220 } 221 222 private static RoutineName build(EDbVendor vendor, IdentifierService service, CatalogObjectKind kind, 223 TSourceToken server, TSourceToken database, TSourceToken schema, TSourceToken pkg, TSourceToken object, 224 boolean packageMember) { 225 CatalogHierarchy hierarchy = hierarchyOf(vendor); 226 CatalogLookupName.Builder builder = CatalogLookupName.builder(hierarchy); 227 List<Level> containers = new ArrayList<Level>(); 228 IdentifierInputForm form = IdentifierInputForm.SQL_LEXICAL; 229 TSourceToken catalogToken = database; 230 TSourceToken schemaToken = schema; 231 if (catalogToken != null && catalogToken == schemaToken) { 232 // One written qualifier the parser gave both roles (Oracle `s.pkg.f`). 233 catalogToken = null; 234 } 235 if (!hierarchy.hasSchema() && schemaToken != null) { 236 // A vendor without schemas (MySQL, Teradata, Hive, Impala): the parser's schema token is its 237 // one container level, the database. 238 if (catalogToken != null) { 239 return null; 240 } 241 catalogToken = schemaToken; 242 schemaToken = null; 243 } 244 if (server != null) { 245 builder.present(IdentifierComponentRole.SERVER, server.toString(), form); 246 containers.add(level(service, IdentifierComponentRole.SERVER, server)); 247 } 248 if (hierarchy.hasCatalog()) { 249 if (catalogToken != null) { 250 builder.present(IdentifierComponentRole.CATALOG, catalogToken.toString(), form); 251 containers.add(level(service, IdentifierComponentRole.CATALOG, catalogToken)); 252 } else { 253 builder.omitted(IdentifierComponentRole.CATALOG); 254 containers.add(new Level(IdentifierComponentRole.CATALOG, CatalogLookupName.SlotState.OMITTED, null, null)); 255 } 256 } else if (catalogToken != null) { 257 return null; 258 } 259 if (hierarchy.hasSchema()) { 260 if (schemaToken != null) { 261 builder.present(IdentifierComponentRole.SCHEMA, schemaToken.toString(), form); 262 containers.add(level(service, IdentifierComponentRole.SCHEMA, schemaToken)); 263 } else { 264 builder.omitted(IdentifierComponentRole.SCHEMA); 265 containers.add(new Level(IdentifierComponentRole.SCHEMA, CatalogLookupName.SlotState.OMITTED, null, null)); 266 } 267 } 268 if (pkg != null) { 269 builder.parentConstraint(CatalogParentConstraint.ROUTINE_CONTAINER); 270 builder.containerObject(pkg.toString(), form); 271 containers.add(new Level(IdentifierComponentRole.OBJECT, CatalogLookupName.SlotState.PRESENT, 272 pkg.toString(), service.canonKeyOf(pkg.toString(), 273 CatalogIdentifierPolicy.comparisonTypeOfKind(CatalogObjectKind.PACKAGE), form))); 274 } 275 builder.object(kind, object.toString(), form); 276 CatalogLookupName lookupName; 277 try { 278 lookupName = builder.build(kind); 279 } catch (IllegalArgumentException unrepresentable) { 280 return null; 281 } 282 return new RoutineName(lookupName, containers, object.toString(), form, kind, service, packageMember); 283 } 284 285 private static Level level(IdentifierService service, IdentifierComponentRole role, TSourceToken token) { 286 ESQLDataObjectType type = IdentifierRolePolicy.comparisonTypeOf(role); 287 return new Level(role, CatalogLookupName.SlotState.PRESENT, token.toString(), 288 service.canonKeyOf(token.toString(), type, IdentifierInputForm.SQL_LEXICAL)); 289 } 290 291 /** The namespace topology of a vendor: whether it has catalogs and schemas. */ 292 static CatalogHierarchy hierarchyOf(EDbVendor vendor) { 293 boolean catalog = TSQLEnv.supportCatalog(vendor); 294 boolean schema = TSQLEnv.supportSchema(vendor); 295 if (catalog && schema) { 296 return CatalogHierarchy.CATALOG_SCHEMA; 297 } 298 if (catalog) { 299 return CatalogHierarchy.CATALOG_ONLY; 300 } 301 if (schema) { 302 return CatalogHierarchy.SCHEMA_ONLY; 303 } 304 return CatalogHierarchy.ROOT_ONLY; 305 } 306 307 @Override 308 public String toString() { 309 StringBuilder b = new StringBuilder(); 310 for (Level level : containers) { 311 b.append(level.state == CatalogLookupName.SlotState.PRESENT ? level.text : "<" + level.role + ">").append('.'); 312 } 313 return b.append(terminalText).toString(); 314 } 315}