001package gudusoft.gsqlparser.ir.semantic.diff; 002 003import java.util.Comparator; 004import java.util.Objects; 005 006/** 007 * One classified divergence between IR and dlineage projections. 008 * 009 * <p>{@link #outputName} encodes which output the divergence belongs to: 010 * 011 * <ul> 012 * <li>The lower-cased outer-output name for SELECT and aggregate divergences.</li> 013 * <li>{@code null} for FILTER/JOIN row-influence divergences (matches the 014 * null-anchor convention on {@link CanonicalLineageEdge}).</li> 015 * <li>The literal string {@code "<query>"} for the two query-wide 016 * unsupported classes.</li> 017 * </ul> 018 */ 019public final class Divergence { 020 021 /** Reserved string used by {@link DivergenceClass#UNSUPPORTED_BY_IR}/{@code _DLINEAGE}. */ 022 public static final String QUERY_WIDE = "<query>"; 023 024 /** Detail value used by the output-presence pass. */ 025 public static final String DETAIL_OUTPUT_PRESENT = "output-present"; 026 027 /** Stable ordering for deterministic JSON output. */ 028 public static final Comparator<Divergence> ORDER = 029 Comparator 030 .comparing((Divergence d) -> d.kind.name()) 031 // null sorts before non-null 032 .thenComparing(d -> d.outputName == null ? 0 : 1) 033 .thenComparing(d -> d.outputName == null ? "" : d.outputName) 034 .thenComparing(d -> d.detail); 035 036 private final DivergenceClass kind; 037 private final String outputName; 038 private final String detail; 039 040 public Divergence(DivergenceClass kind, String outputName, String detail) { 041 if (kind == null) { 042 throw new IllegalArgumentException("kind must not be null"); 043 } 044 if (detail == null || detail.isEmpty()) { 045 throw new IllegalArgumentException("detail must be non-empty"); 046 } 047 this.kind = kind; 048 this.outputName = outputName; 049 this.detail = detail; 050 } 051 052 public DivergenceClass getKind() { 053 return kind; 054 } 055 056 /** May be null for FILTER/JOIN row-influence divergences. */ 057 public String getOutputName() { 058 return outputName; 059 } 060 061 public String getDetail() { 062 return detail; 063 } 064 065 @Override 066 public boolean equals(Object o) { 067 if (this == o) return true; 068 if (!(o instanceof Divergence)) return false; 069 Divergence other = (Divergence) o; 070 return kind == other.kind 071 && Objects.equals(outputName, other.outputName) 072 && detail.equals(other.detail); 073 } 074 075 @Override 076 public int hashCode() { 077 return Objects.hash(kind, outputName, detail); 078 } 079 080 @Override 081 public String toString() { 082 return kind + " " + (outputName == null ? "<row-set>" : outputName) + " — " + detail; 083 } 084}