001package gudusoft.gsqlparser.resolver2.binding; 002 003import gudusoft.gsqlparser.TCustomSqlStatement; 004 005import java.util.ArrayList; 006import java.util.Collections; 007import java.util.List; 008import java.util.Objects; 009import java.util.Optional; 010 011/** 012 * One binding diagnostic record produced by the resolver2 binding post-pass. 013 * 014 * <p>Immutable; build via {@link #builder()}. Mirrors the shape of 015 * {@code gudusoft.gsqlparser.catalog.diagnostic.CatalogDiagnostic} on purpose 016 * — consumers see one diagnostic shape across two contexts (catalog load vs. 017 * SQL binding), but the two types are distinct (plan §5.2, R12).</p> 018 * 019 * <p>The public surface deliberately exposes textual values 020 * ({@code objectNameText}, {@code candidates}) instead of resolver2-internal 021 * nodes (plan §15 R5/R6). Diagnostic codes are stable; message text is NOT — 022 * tests assert on stable substrings only.</p> 023 */ 024public final class BindingDiagnostic { 025 026 private final BindingDiagnosticSeverity severity; 027 private final BindingDiagnosticCode code; 028 private final String message; 029 private final String objectNameText; 030 private final BindingReferenceSite site; 031 private final TCustomSqlStatement statement; 032 private final List<String> candidates; 033 034 private BindingDiagnostic(Builder b) { 035 if (b.severity == null) { 036 throw new IllegalArgumentException("BindingDiagnostic.severity is required"); 037 } 038 if (b.code == null) { 039 throw new IllegalArgumentException("BindingDiagnostic.code is required"); 040 } 041 if (b.message == null || b.message.isEmpty()) { 042 throw new IllegalArgumentException("BindingDiagnostic.message is required"); 043 } 044 this.severity = b.severity; 045 this.code = b.code; 046 this.message = b.message; 047 this.objectNameText = b.objectNameText; 048 this.site = b.site; 049 this.statement = b.statement; 050 this.candidates = b.candidates == null 051 ? Collections.<String>emptyList() 052 : Collections.unmodifiableList(new ArrayList<String>(b.candidates)); 053 } 054 055 public static Builder builder() { 056 return new Builder(); 057 } 058 059 public BindingDiagnosticSeverity severity() { 060 return severity; 061 } 062 063 public BindingDiagnosticCode code() { 064 return code; 065 } 066 067 public String message() { 068 return message; 069 } 070 071 public Optional<String> objectNameText() { 072 return Optional.ofNullable(objectNameText); 073 } 074 075 public Optional<BindingReferenceSite> site() { 076 return Optional.ofNullable(site); 077 } 078 079 public Optional<TCustomSqlStatement> statement() { 080 return Optional.ofNullable(statement); 081 } 082 083 public List<String> candidates() { 084 return candidates; 085 } 086 087 @Override 088 public boolean equals(Object o) { 089 if (this == o) return true; 090 if (!(o instanceof BindingDiagnostic)) return false; 091 BindingDiagnostic that = (BindingDiagnostic) o; 092 return severity == that.severity 093 && code == that.code 094 && message.equals(that.message) 095 && Objects.equals(objectNameText, that.objectNameText) 096 && Objects.equals(site, that.site) 097 && statement == that.statement 098 && candidates.equals(that.candidates); 099 } 100 101 @Override 102 public int hashCode() { 103 return Objects.hash(severity, code, message, objectNameText, site, 104 System.identityHashCode(statement), candidates); 105 } 106 107 @Override 108 public String toString() { 109 StringBuilder sb = new StringBuilder("BindingDiagnostic{") 110 .append(severity).append(' ').append(code).append(": ").append(message); 111 if (objectNameText != null) sb.append(" ref=").append(objectNameText); 112 if (site != null) sb.append(" site=").append(site); 113 if (!candidates.isEmpty()) sb.append(" candidates=").append(candidates); 114 return sb.append('}').toString(); 115 } 116 117 public static final class Builder { 118 119 private BindingDiagnosticSeverity severity; 120 private BindingDiagnosticCode code; 121 private String message; 122 private String objectNameText; 123 private BindingReferenceSite site; 124 private TCustomSqlStatement statement; 125 private List<String> candidates; 126 127 private Builder() { 128 } 129 130 public Builder severity(BindingDiagnosticSeverity v) { 131 this.severity = v; 132 return this; 133 } 134 135 public Builder code(BindingDiagnosticCode v) { 136 this.code = v; 137 return this; 138 } 139 140 public Builder message(String v) { 141 this.message = v; 142 return this; 143 } 144 145 public Builder objectNameText(String v) { 146 this.objectNameText = v; 147 return this; 148 } 149 150 public Builder site(BindingReferenceSite v) { 151 this.site = v; 152 return this; 153 } 154 155 public Builder statement(TCustomSqlStatement v) { 156 this.statement = v; 157 return this; 158 } 159 160 public Builder candidates(List<String> v) { 161 this.candidates = v; 162 return this; 163 } 164 165 public BindingDiagnostic build() { 166 return new BindingDiagnostic(this); 167 } 168 } 169}