001package gudusoft.gsqlparser.sqlenv;
002
003import java.util.Objects;
004
005/**
006 * Persistable canonical identity of a single identifier segment (plan §6,
007 * docs/designs/persistent-canonical-identity-implementation-plan.md).
008 *
009 * <p>Unlike {@link CanonKey} (in-process only), this key is stable across
010 * JVMs, JDK versions and library releases as long as the resolved identifier
011 * policy is unchanged: the payload is produced by the pinned fold engine
012 * ({@link gudusoft.gsqlparser.sqlenv.unicode.GspCaseData}, never JVM case
013 * tables) and the {@code policyId} is a content hash of every behavior the
014 * payload's equivalence domain depends on. A policyId change MEANS the
015 * equivalence relation may have changed — stored keys with a different
016 * policyId must not be compared payload-wise, they must be re-minted.
017 *
018 * <p><b>Value semantics.</b> {@link #equals}/{@link #hashCode} range over all
019 * three of {@code (policyId, group, payload)}. The object group is part of
020 * identity because vendors can give different rule cells to different object
021 * kinds (BigQuery: tables case-sensitive, columns not) — the same spelling
022 * under the same vendor is still two different names in two groups.
023 *
024 * <p><b>Biconditional.</b> For two {@link IdentifierInputForm#SQL_LEXICAL}
025 * inputs {@code a}, {@code b} minted by the same profile-bound
026 * {@link IdentifierService} and object type:
027 * {@code persistentKeyV1(a).equals(persistentKeyV1(b))} ⟺
028 * {@code areEqual(a, b)}. {@link IdentifierInputForm#CATALOG_STORED} keys
029 * join the same equivalence domain: a stored name keys equal to the lexical
030 * spelling that produced it (in folding cells, via the cell's fold-image).
031 * In cells whose unquoted branch folds while the quoted branch compares
032 * insensitively without folding, the quoted spelling's collapse payload is a
033 * different map than the fold wherever full and simple folding disagree
034 * (final sigma) — that asymmetry is {@code areEqual}'s own branch semantics,
035 * mirrored here, so stored keys side with the fold-image.
036 *
037 * <p><b>Never interchangeable with {@link ApproximateIdentifierKey}.</b>
038 * That type is a different class in a different policy namespace
039 * ({@code ak1-…}); the two can never compare equal and approximate keys must
040 * never be stored where exact keys are expected.
041 *
042 * <p>The public constructor exists to rehydrate a key persisted elsewhere
043 * (store the three fields, reconstruct on read). Minting new keys goes
044 * through {@link IdentifierService#persistentKeyV1}.
045 *
046 * @since 4.1.5.15 (persistent canonical-identity slice U3)
047 */
048public final class PersistentIdentifierKey {
049
050    private final String policyId;
051    private final IdentifierProfile.ObjectGroup group;
052    private final String payload;
053
054    /**
055     * Rehydrate a key from its persisted fields.
056     *
057     * @param policyId policy identity ({@code pk1-…}) the payload was minted under
058     * @param group object group the key belongs to
059     * @param payload canonical payload text
060     */
061    public PersistentIdentifierKey(String policyId, IdentifierProfile.ObjectGroup group,
062                                   String payload) {
063        this.policyId = Objects.requireNonNull(policyId, "policyId");
064        this.group = Objects.requireNonNull(group, "group");
065        this.payload = Objects.requireNonNull(payload, "payload");
066    }
067
068    /** Policy identity: {@code "pk1-" + base64url(SHA-256(canonicalDescriptor))}. */
069    public String getPolicyId() {
070        return policyId;
071    }
072
073    /** Object group — group IS identity (owner decision 4). */
074    public IdentifierProfile.ObjectGroup getGroup() {
075        return group;
076    }
077
078    /** Canonical payload text. */
079    public String getPayload() {
080        return payload;
081    }
082
083    @Override
084    public boolean equals(Object o) {
085        if (this == o) {
086            return true;
087        }
088        if (o == null || getClass() != o.getClass()) {
089            return false;
090        }
091        PersistentIdentifierKey that = (PersistentIdentifierKey) o;
092        return policyId.equals(that.policyId)
093                && group == that.group
094                && payload.equals(that.payload);
095    }
096
097    @Override
098    public int hashCode() {
099        int result = policyId.hashCode();
100        result = 31 * result + group.hashCode();
101        result = 31 * result + payload.hashCode();
102        return result;
103    }
104
105    @Override
106    public String toString() {
107        return "PersistentIdentifierKey{" + policyId + "/" + group + "/" + payload + "}";
108    }
109}