001package gudusoft.gsqlparser.sqlenv;
002
003/**
004 * Maps a component's semantic role to the {@link ESQLDataObjectType} whose comparison and quoting
005 * rules apply (stage S0R of docs/refactor/identifier_identity_display_separation_plan.md).
006 *
007 * <h3>Why role and comparison type are two different things</h3>
008 * {@link IdentifierComponentRole} answers "what IS this component" — a server, a schema, the base
009 * column. {@link ESQLDataObjectType} answers "under which rules is it compared and quoted". They
010 * are not the same question and must not be the same enum: a role set needs SERVER and
011 * STRUCT_FIELD, which no comparison domain of its own exists for, while the comparison type must
012 * stay the thing {@code IdentifierProfile} and {@code IdentifierCodec} are keyed by — the profile
013 * routes it to an {@code ObjectGroup} for case rules, and the codec needs the full type because
014 * quoting differs per type on some vendors (SQL Server quotes a column differently from a table).
015 *
016 * <h3>Two owners, split by layer — not one</h3>
017 * This class owns the roles whose comparison type the ROLE alone decides. The remaining case,
018 * {@link IdentifierComponentRole#OBJECT}, depends on the object's entity kind and is owned by
019 * {@code CatalogIdentifierPolicy.comparisonTypeOfKind} in the catalog layer. The split is
020 * structural, not stylistic: {@code catalog.runtime} already depends on {@code sqlenv}, so holding
021 * the kind mapping here would close a package cycle and make the low-level identifier layer
022 * import a high-level catalog type that calls straight back into it.
023 *
024 * <h3>What this class deliberately does NOT decide</h3>
025 * The legal ORDER of roles. An earlier version carried a pairwise {@code mayFollow} predicate, and
026 * it was the wrong abstraction: a pairwise test cannot express "at most one SERVER", "a column key
027 * must terminate in a base column or a field path", or the kind-dependent nesting that lets a
028 * PACKAGE contain a PROCEDURE but forbids a SEQUENCE containing a column — and it silently merged
029 * five different languages (resolved identity, partial lookup, column path, package member, table
030 * child) into one. Sequence validation belongs to each domain's whole-shape validator, which sees
031 * the complete component vector, the entity kinds and the declared layout.
032 */
033public final class IdentifierRolePolicy {
034
035    private IdentifierRolePolicy() {
036    }
037
038    /**
039     * The comparison/quoting type for a component whose role determines it.
040     *
041     * <p>{@link IdentifierComponentRole#OBJECT} is deliberately REFUSED: the comparison type of a
042     * named catalog object depends on its entity kind, which is a catalog concept this package
043     * must not depend on. A catalog caller asks
044     * {@code CatalogIdentifierPolicy.comparisonTypeOfKind} for those. Keeping the two apart is
045     * what stops the low-level identifier layer from importing the high-level catalog layer.
046     *
047     * @throws IllegalArgumentException for a null role or for {@code OBJECT}
048     */
049    public static ESQLDataObjectType comparisonTypeOf(IdentifierComponentRole role) {
050        if (role == null) {
051            throw new IllegalArgumentException("component role is null");
052        }
053        switch (role) {
054            case SERVER:
055                return ESQLDataObjectType.dotServer;
056            case CATALOG:
057                return ESQLDataObjectType.dotCatalog;
058            case SCHEMA:
059                return ESQLDataObjectType.dotSchema;
060            case BASE_COLUMN:
061                // A struct field that IS an identifier compares under the same rules as a column:
062                // it is an attribute name in the same namespace family, and no dialect gives
063                // composite attributes their own case rules.
064            case STRUCT_FIELD:
065                return ESQLDataObjectType.dotColumn;
066            case DATABASE_LINK:
067                return ESQLDataObjectType.dotDblink;
068            case OBJECT:
069                throw new IllegalArgumentException(
070                        "an OBJECT component's comparison type depends on its CatalogObjectKind; "
071                                + "ask CatalogIdentifierPolicy.comparisonTypeOfKind instead");
072            default:
073                throw new IllegalStateException("unmapped component role: " + role);
074        }
075    }
076
077    /** Whether this role's comparison type is decided by the role alone. */
078    public static boolean isRoleDecided(IdentifierComponentRole role) {
079        return role != null && role != IdentifierComponentRole.OBJECT;
080    }
081}