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}