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}