001package gudusoft.gsqlparser.sqlenv;
002
003import gudusoft.gsqlparser.EDbVendor;
004
005import java.util.Arrays;
006import java.util.Objects;
007
008/**
009 * Canonical identity of a single SQL identifier segment under one vendor's identifier
010 * rules (unification plan stage P0c, docs/refactor/identifier_normalization_unification_plan.md).
011 *
012 * <p>Two keys compare equal iff they were produced in the same rule domain — vendor,
013 * object group, policy fingerprint (rules + vendor flags, including the default
014 * collation) and collator identity — AND their canonical payloads match. The payload is
015 * the {@link IdentifierService#keyForMap} text for fold-based rules, or the
016 * {@link java.text.CollationKey} bytes (from the same collator the equality path uses)
017 * for collation-based rules. The comparison <em>rule</em> that selected the payload is
018 * deliberately NOT part of equality: under quoted-case-preserving vendors, unquoted
019 * {@code foo} (folded to {@code FOO}) and quoted {@code "FOO"} (preserved as {@code FOO})
020 * must converge on the same key even though different rules produced them.
021 *
022 * <p><strong>In-process only.</strong> Collation key bytes are only stable within one
023 * JVM/collator configuration; never persist or exchange CanonKeys. Custom
024 * {@link CollatorProvider} subclasses are isolated by class name — two instances of the
025 * same class with different behavior cannot be distinguished.
026 */
027public final class CanonKey {
028
029    private final EDbVendor vendor;
030    private final IdentifierProfile.ObjectGroup group;
031    private final long policyFingerprint;
032    private final String collatorId;      // null for fold-based keys
033    private final String canonText;       // fold payload; diagnostic-only for collation keys
034    private final byte[] collationKey;    // null for fold-based keys
035
036    CanonKey(EDbVendor vendor, IdentifierProfile.ObjectGroup group, long policyFingerprint,
037             String collatorId, String canonText, byte[] collationKey) {
038        this.vendor = vendor;
039        this.group = group;
040        this.policyFingerprint = policyFingerprint;
041        this.collatorId = collatorId;
042        this.canonText = canonText;
043        this.collationKey = collationKey;
044    }
045
046    public EDbVendor getVendor() {
047        return vendor;
048    }
049
050    public IdentifierProfile.ObjectGroup getGroup() {
051        return group;
052    }
053
054    /** Canonical text payload. For collation-based keys this is diagnostic only. */
055    public String getCanonText() {
056        return canonText;
057    }
058
059    public boolean isCollationBased() {
060        return collationKey != null;
061    }
062
063    @Override
064    public boolean equals(Object o) {
065        if (this == o) return true;
066        if (o == null || getClass() != o.getClass()) return false;
067        CanonKey that = (CanonKey) o;
068        if (vendor != that.vendor || group != that.group
069                || policyFingerprint != that.policyFingerprint
070                || !Objects.equals(collatorId, that.collatorId)) {
071            return false;
072        }
073        if (collationKey != null || that.collationKey != null) {
074            return Arrays.equals(collationKey, that.collationKey);
075        }
076        return Objects.equals(canonText, that.canonText);
077    }
078
079    @Override
080    public int hashCode() {
081        int result = Objects.hash(vendor, group, policyFingerprint, collatorId);
082        result = 31 * result + (collationKey != null ? Arrays.hashCode(collationKey) : Objects.hashCode(canonText));
083        return result;
084    }
085
086    @Override
087    public String toString() {
088        return "CanonKey{" + vendor + "/" + group
089                + (collationKey != null ? "/collation[" + canonText + "]" : "/" + canonText) + "}";
090    }
091}