001package gudusoft.gsqlparser.sqlenv;
002
003import java.util.Objects;
004
005/**
006 * Best-effort identifier key for rule cells the persistent API cannot serve
007 * exactly (plan §6, docs/designs/persistent-canonical-identity-implementation-plan.md).
008 *
009 * <p>Where {@link IdentifierService#persistentKeyV1} throws — collation-based
010 * cells (SQL Server CI collations compare via collator weights that cannot be
011 * reproduced portably) and mixed-kind custom profiles —
012 * {@link IdentifierService#approximateKeyV1} still serves: quote-strip plus
013 * pinned simple case-fold collapse
014 * ({@link gudusoft.gsqlparser.sqlenv.unicode.GspCaseData#simpleFoldCollapse}).
015 *
016 * <p><b>No biconditional.</b> Approximate key equality neither implies nor is
017 * implied by {@link IdentifierService#areEqual}: a CI collation may equate
018 * strings the collapse distinguishes (accent-insensitive collations) and may
019 * distinguish strings the collapse equates. Use these keys for grouping,
020 * bucketing and diagnostics — never as proof of identity.
021 *
022 * <p><b>Distinct type by design.</b> This is deliberately NOT a
023 * {@link PersistentIdentifierKey} and lives in its own policy namespace
024 * ({@code ak1-…}): the two types can never compare equal, so an approximate
025 * key can never be stored where an exact key is expected.
026 *
027 * <p>The public constructor rehydrates a persisted key; minting goes through
028 * {@link IdentifierService#approximateKeyV1}.
029 *
030 * @since 4.1.5.15 (persistent canonical-identity slice U3)
031 */
032public final class ApproximateIdentifierKey {
033
034    private final String policyId;
035    private final IdentifierProfile.ObjectGroup group;
036    private final String payload;
037
038    /**
039     * Rehydrate a key from its persisted fields.
040     *
041     * @param policyId policy identity ({@code ak1-…}) the payload was minted under
042     * @param group object group the key belongs to
043     * @param payload collapsed payload text
044     */
045    public ApproximateIdentifierKey(String policyId, IdentifierProfile.ObjectGroup group,
046                                    String payload) {
047        this.policyId = Objects.requireNonNull(policyId, "policyId");
048        this.group = Objects.requireNonNull(group, "group");
049        this.payload = Objects.requireNonNull(payload, "payload");
050    }
051
052    /** Policy identity: {@code "ak1-" + base64url(SHA-256(canonicalDescriptor))}. */
053    public String getPolicyId() {
054        return policyId;
055    }
056
057    /** Object group — part of key identity, like the exact key's. */
058    public IdentifierProfile.ObjectGroup getGroup() {
059        return group;
060    }
061
062    /** Collapsed payload text. */
063    public String getPayload() {
064        return payload;
065    }
066
067    @Override
068    public boolean equals(Object o) {
069        if (this == o) {
070            return true;
071        }
072        if (o == null || getClass() != o.getClass()) {
073            return false;
074        }
075        ApproximateIdentifierKey that = (ApproximateIdentifierKey) o;
076        return policyId.equals(that.policyId)
077                && group == that.group
078                && payload.equals(that.payload);
079    }
080
081    @Override
082    public int hashCode() {
083        int result = policyId.hashCode();
084        result = 31 * result + group.hashCode();
085        result = 31 * result + payload.hashCode();
086        return result;
087    }
088
089    @Override
090    public String toString() {
091        return "ApproximateIdentifierKey{" + policyId + "/" + group + "/" + payload + "}";
092    }
093}