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}