001package gudusoft.gsqlparser.catalog.runtime; 002 003import gudusoft.gsqlparser.EDbVendor; 004 005import java.util.ArrayList; 006import java.util.BitSet; 007import java.util.Collections; 008import java.util.List; 009import java.util.Objects; 010 011/** 012 * Multi-segment qualified name with raw + normalized forms, per-segment quoted flags, 013 * the kind of object it identifies, and the originating vendor. 014 * 015 * <p>This type holds <em>data only</em>. Construction must go through 016 * {@link CatalogIdentifierPolicy#parse} so that case-folding is centralized in 017 * {@code IdentifierService}; the package-private constructor below is the single allowed 018 * factory pathway.</p> 019 * 020 * <p>Plan §7.2 / §9.</p> 021 */ 022public final class CatalogQualifiedName { 023 024 private final List<String> rawSegments; 025 private final List<String> normalizedSegments; 026 private final BitSet quotedFlags; 027 private final CatalogObjectKind kind; 028 private final EDbVendor vendor; 029 030 /** 031 * Package-private constructor invoked exclusively by {@link CatalogIdentifierPolicy}. 032 * Defensive copies are taken so callers cannot mutate state after construction. 033 */ 034 CatalogQualifiedName(List<String> rawSegments, 035 List<String> normalizedSegments, 036 BitSet quotedFlags, 037 CatalogObjectKind kind, 038 EDbVendor vendor) { 039 if (rawSegments == null || rawSegments.isEmpty()) { 040 throw new IllegalArgumentException("CatalogQualifiedName: rawSegments must be non-empty"); 041 } 042 if (normalizedSegments == null || normalizedSegments.size() != rawSegments.size()) { 043 throw new IllegalArgumentException( 044 "CatalogQualifiedName: normalizedSegments must match rawSegments size"); 045 } 046 if (quotedFlags == null) { 047 throw new IllegalArgumentException("CatalogQualifiedName: quotedFlags is required"); 048 } 049 if (kind == null) { 050 throw new IllegalArgumentException("CatalogQualifiedName: kind is required"); 051 } 052 if (vendor == null) { 053 throw new IllegalArgumentException("CatalogQualifiedName: vendor is required"); 054 } 055 this.rawSegments = Collections.unmodifiableList(new ArrayList<String>(rawSegments)); 056 this.normalizedSegments = Collections.unmodifiableList(new ArrayList<String>(normalizedSegments)); 057 this.quotedFlags = (BitSet) quotedFlags.clone(); 058 this.kind = kind; 059 this.vendor = vendor; 060 } 061 062 public List<String> raw() { 063 return rawSegments; 064 } 065 066 public List<String> normalized() { 067 return normalizedSegments; 068 } 069 070 public boolean isQuoted(int segmentIndex) { 071 if (segmentIndex < 0 || segmentIndex >= rawSegments.size()) { 072 throw new IndexOutOfBoundsException( 073 "segment index " + segmentIndex + " out of range [0," + rawSegments.size() + ")"); 074 } 075 return quotedFlags.get(segmentIndex); 076 } 077 078 public BitSet quotedFlags() { 079 return (BitSet) quotedFlags.clone(); 080 } 081 082 public CatalogObjectKind kind() { 083 return kind; 084 } 085 086 public EDbVendor vendor() { 087 return vendor; 088 } 089 090 public int size() { 091 return rawSegments.size(); 092 } 093 094 /** Last segment — the local object name. */ 095 public String localName() { 096 return rawSegments.get(rawSegments.size() - 1); 097 } 098 099 /** 100 * Return a copy of this name with a different {@link CatalogObjectKind}. Used by the 101 * resolver's "widened kind matrix" path (plan §9.3): a TABLE lookup needs to be tried 102 * against VIEW and MATERIALIZED_VIEW buckets too. The raw / normalized segments and 103 * quoted flags are preserved bit-for-bit; only the kind changes (which affects 104 * {@code keyForMap} and equality). 105 */ 106 public CatalogQualifiedName withKind(CatalogObjectKind newKind) { 107 if (newKind == null) { 108 throw new IllegalArgumentException("CatalogQualifiedName.withKind: newKind is required"); 109 } 110 if (newKind == this.kind) { 111 return this; 112 } 113 return new CatalogQualifiedName(rawSegments, normalizedSegments, quotedFlags, newKind, vendor); 114 } 115 116 public String normalizedLocalName() { 117 return normalizedSegments.get(normalizedSegments.size() - 1); 118 } 119 120 @Override 121 public boolean equals(Object o) { 122 if (this == o) return true; 123 if (!(o instanceof CatalogQualifiedName)) return false; 124 CatalogQualifiedName that = (CatalogQualifiedName) o; 125 return rawSegments.equals(that.rawSegments) 126 && normalizedSegments.equals(that.normalizedSegments) 127 && quotedFlags.equals(that.quotedFlags) 128 && kind == that.kind 129 && vendor == that.vendor; 130 } 131 132 @Override 133 public int hashCode() { 134 return Objects.hash(rawSegments, normalizedSegments, quotedFlags, kind, vendor); 135 } 136 137 @Override 138 public String toString() { 139 return "CatalogQualifiedName{kind=" + kind + ", vendor=" + vendor 140 + ", raw=" + rawSegments + ", norm=" + normalizedSegments + '}'; 141 } 142}