001package gudusoft.gsqlparser.catalog.runtime;
002
003import gudusoft.gsqlparser.sqlenv.IdentifierComponentRole;
004
005import java.util.ArrayList;
006import java.util.Collections;
007import java.util.List;
008
009/**
010 * Which container levels a namespace HAS — declared, never inferred (stage S0R of
011 * docs/refactor/identifier_identity_display_separation_plan.md).
012 *
013 * <h3>Why a resolved identity needs this</h3>
014 * Whether {@code db.t} is a complete identity depends on whether the namespace has a schema level.
015 * On MySQL it does not, so {@code db.t} names a table completely. On Oracle it does, so {@code db.t}
016 * is a name with an UNRESOLVED schema — and keying it as though it were complete makes it collide
017 * with the same table name in a different schema. The component list alone cannot tell those apart:
018 * both are "a catalog and an object". So the topology is declared, and validation checks the
019 * components against the declaration rather than guessing the declaration from the components.
020 *
021 * <p>{@link IdentifierComponentRole#SERVER} is deliberately not part of a topology. Every namespace
022 * can be reached through a link, and a name without a server means the local one — its absence is
023 * not a missing level.
024 */
025public enum CatalogHierarchy {
026
027    /** Catalog and schema both exist: Oracle, SQL Server, PostgreSQL, Snowflake, most dialects. */
028    CATALOG_SCHEMA(true, true),
029
030    /** A catalog (database) but no schema level: MySQL, Teradata, Hive, Impala. */
031    CATALOG_ONLY(true, false),
032
033    /** A schema with no catalog above it — a rootless topology. */
034    SCHEMA_ONLY(false, true),
035
036    /** Objects at the root, with no container level at all. */
037    ROOT_ONLY(false, false);
038
039    private final boolean catalog;
040    private final boolean schema;
041
042    CatalogHierarchy(boolean catalog, boolean schema) {
043        this.catalog = catalog;
044        this.schema = schema;
045    }
046
047    public boolean hasCatalog() {
048        return catalog;
049    }
050
051    public boolean hasSchema() {
052        return schema;
053    }
054
055    /** The container roles this topology has, outermost first. Never includes SERVER. */
056    public List<IdentifierComponentRole> containers() {
057        List<IdentifierComponentRole> roles = new ArrayList<IdentifierComponentRole>(2);
058        if (catalog) {
059            roles.add(IdentifierComponentRole.CATALOG);
060        }
061        if (schema) {
062            roles.add(IdentifierComponentRole.SCHEMA);
063        }
064        return Collections.unmodifiableList(roles);
065    }
066
067    /**
068     * Whether this topology has the given container level.
069     *
070     * <p>Only CATALOG and SCHEMA are topology questions. {@link IdentifierComponentRole#SERVER} is
071     * refused rather than answered true: a server is an optional endpoint qualifier, not a level a
072     * topology can lack, and answering "yes, always" contradicted this class's own definition.
073     */
074    public boolean has(IdentifierComponentRole role) {
075        if (role == IdentifierComponentRole.SERVER) {
076            throw new IllegalArgumentException("SERVER is not a topology level: a name without one "
077                    + "means the local endpoint, so its absence is not a missing level");
078        }
079        if (role == IdentifierComponentRole.CATALOG) {
080            return catalog;
081        }
082        if (role == IdentifierComponentRole.SCHEMA) {
083            return schema;
084        }
085        return false;
086    }
087
088}