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}