001package gudusoft.gsqlparser.catalog.runtime; 002 003import gudusoft.gsqlparser.sqlenv.IdentifierComponentRole; 004 005import java.util.Arrays; 006import java.util.Collections; 007import java.util.List; 008 009/** 010 * A <strong>declared</strong> component layout for a flat qualified name (stage S0R of 011 * docs/refactor/identifier_identity_display_separation_plan.md). 012 * 013 * <h3>Why a layout must be declared and never chosen</h3> 014 * The defect this whole stage exists to remove is inferring a name's shape from how many components 015 * it has. {@code a.b} is {@code schema.table} on one call and {@code table.column} on the next, and 016 * on BigQuery it may be base column plus STRUCT field — where the first component folds under 017 * DIFFERENT case rules in each reading. Five places in this repository guessed, and each guess was 018 * a confident wrong answer for some input. 019 * 020 * <p>So when a caller genuinely has only a joined string — a compatibility API, a metadata file 021 * whose own format is flat — it must say which layout the string has. The parser then VERIFIES that 022 * the component count matches the declaration and refuses otherwise. It never picks. 023 * 024 * <p>Prefer not to be here at all: a caller that holds the parts separately should pass them 025 * separately. {@code ModelBackedCatalogProvider} is the cautionary example — it holds catalog, 026 * schema, object and column as separate fields, joins them with dots, and hands the result to a 027 * parser that splits them again, losing the boundaries it already had. 028 */ 029public enum CatalogNameLayout { 030 031 /** {@code object} */ 032 OBJECT(IdentifierComponentRole.OBJECT), 033 034 /** {@code schema.object} */ 035 SCHEMA_OBJECT(IdentifierComponentRole.SCHEMA, IdentifierComponentRole.OBJECT), 036 037 /** {@code catalog.object} — schemaless dialects such as MySQL. */ 038 CATALOG_OBJECT(IdentifierComponentRole.CATALOG, IdentifierComponentRole.OBJECT), 039 040 /** {@code catalog.schema.object} */ 041 CATALOG_SCHEMA_OBJECT(IdentifierComponentRole.CATALOG, IdentifierComponentRole.SCHEMA, 042 IdentifierComponentRole.OBJECT), 043 044 /** {@code server.catalog.schema.object} */ 045 SERVER_CATALOG_SCHEMA_OBJECT(IdentifierComponentRole.SERVER, IdentifierComponentRole.CATALOG, 046 IdentifierComponentRole.SCHEMA, IdentifierComponentRole.OBJECT), 047 048 /** {@code object.column} — a relation and one of its columns. */ 049 OBJECT_COLUMN(IdentifierComponentRole.OBJECT, IdentifierComponentRole.BASE_COLUMN), 050 051 /** {@code schema.object.column} */ 052 SCHEMA_OBJECT_COLUMN(IdentifierComponentRole.SCHEMA, IdentifierComponentRole.OBJECT, 053 IdentifierComponentRole.BASE_COLUMN), 054 055 /** {@code catalog.schema.object.column} */ 056 CATALOG_SCHEMA_OBJECT_COLUMN(IdentifierComponentRole.CATALOG, IdentifierComponentRole.SCHEMA, 057 IdentifierComponentRole.OBJECT, IdentifierComponentRole.BASE_COLUMN), 058 059 /** {@code catalog.object.column} — a column on a schemaless topology such as MySQL's. */ 060 CATALOG_OBJECT_COLUMN(IdentifierComponentRole.CATALOG, IdentifierComponentRole.OBJECT, 061 IdentifierComponentRole.BASE_COLUMN), 062 063 /** {@code server.catalog.schema.object.column} — a column through a linked server. */ 064 SERVER_CATALOG_SCHEMA_OBJECT_COLUMN(IdentifierComponentRole.SERVER, 065 IdentifierComponentRole.CATALOG, IdentifierComponentRole.SCHEMA, 066 IdentifierComponentRole.OBJECT, IdentifierComponentRole.BASE_COLUMN), 067 068 /** 069 * {@code catalog.schema.relation.child} — an index or constraint on a relation. Kept separate 070 * from the package-member layout because the two have different parent constraints: this one's 071 * container is a relation, that one's is a package. 072 */ 073 CATALOG_SCHEMA_RELATION_CHILD(IdentifierComponentRole.CATALOG, IdentifierComponentRole.SCHEMA, 074 IdentifierComponentRole.OBJECT, IdentifierComponentRole.OBJECT), 075 076 /** {@code catalog.schema.package.routine} — a routine inside a package. */ 077 CATALOG_SCHEMA_PACKAGE_MEMBER(IdentifierComponentRole.CATALOG, IdentifierComponentRole.SCHEMA, 078 IdentifierComponentRole.OBJECT, IdentifierComponentRole.OBJECT); 079 080 private final List<IdentifierComponentRole> roles; 081 082 CatalogNameLayout(IdentifierComponentRole... roles) { 083 this.roles = Collections.unmodifiableList(Arrays.asList(roles)); 084 } 085 086 /** 087 * Whether this layout may be used to seek that kind of object. 088 * 089 * <p>A layout is not just a component count: {@code CATALOG_SCHEMA_RELATION_CHILD} describes an 090 * index or a constraint ON a relation, so using it to look for a TABLE would be describing a 091 * table as the child of another table. The restriction lives here rather than as an {@code if} 092 * that recognizes one enum constant, so adding a layout forces the question to be answered. 093 */ 094 public boolean permitsTerminalKind(CatalogObjectKind kind) { 095 if (kind == null) { 096 return false; 097 } 098 switch (this) { 099 case CATALOG_SCHEMA_RELATION_CHILD: 100 return kind == CatalogObjectKind.INDEX || kind == CatalogObjectKind.CONSTRAINT; 101 case CATALOG_SCHEMA_PACKAGE_MEMBER: 102 return kind == CatalogObjectKind.ROUTINE || kind == CatalogObjectKind.FUNCTION 103 || kind == CatalogObjectKind.PROCEDURE; 104 case OBJECT_COLUMN: 105 case SCHEMA_OBJECT_COLUMN: 106 case CATALOG_SCHEMA_OBJECT_COLUMN: 107 case CATALOG_OBJECT_COLUMN: 108 case SERVER_CATALOG_SCHEMA_OBJECT_COLUMN: 109 return kind == CatalogObjectKind.COLUMN; 110 default: 111 // An object layout may seek any object kind, but never a container or a column: 112 // those have their own layouts and their own roles. 113 return CatalogNameGrammar.isObjectKind(kind); 114 } 115 } 116 117 /** The role of each component, outermost first. */ 118 public List<IdentifierComponentRole> roles() { 119 return roles; 120 } 121 122 public int size() { 123 return roles.size(); 124 } 125 126 /** 127 * The role at one position. 128 * 129 * @throws IndexOutOfBoundsException if the position is outside this layout — which is the 130 * caller having declared a layout that does not match its string 131 */ 132 public IdentifierComponentRole roleAt(int index) { 133 return roles.get(index); 134 } 135}