001package gudusoft.gsqlparser.ir.semantic.diff; 002 003/** 004 * Pure-function return type for both projectors. {@link #getReason()} is 005 * non-null when the projector cannot produce a meaningful canonical model. 006 * The reporter translates a non-null reason into a single query-wide 007 * {@link DivergenceClass#UNSUPPORTED_BY_DLINEAGE} or 008 * {@link DivergenceClass#UNSUPPORTED_BY_IR} divergence. 009 * 010 * <p>Keeping this as data (not a thrown exception) means the comparison 011 * harness can produce a stable JSON for every corpus SQL even when one 012 * side is unsupported. 013 */ 014public final class ProjectorResult { 015 016 public enum UnsupportedReason { 017 /** No relationships in the dlineage XML, or builder produced no statements. */ 018 NO_RELATIONSHIPS, 019 /** Dlineage XML had zero or two-plus terminal select_list resultsets. */ 020 MULTIPLE_TERMINAL_SELECTS, 021 /** Dlineage XML failed to parse. */ 022 MALFORMED_XML, 023 /** Lineage2 contract JSON failed to parse or violates the contract shape (US-010). */ 024 MALFORMED_CONTRACT_JSON, 025 /** 026 * The SemanticProgram contains a block the builder could not analyze 027 * (a degrade placeholder — see 028 * {@code SemanticIRBuildOptions.withDegradeUnsupportedNestedBlocks}). 029 * A canonical lineage model projected from such a program would be 030 * quietly missing every edge that block contributes, which is 031 * indistinguishable from that block having no edges. Refusing is the 032 * only honest answer. 033 */ 034 UNANALYZED_BLOCK 035 } 036 037 private final CanonicalLineageModel model; 038 private final UnsupportedReason reason; 039 private final String detail; 040 041 public ProjectorResult(CanonicalLineageModel model, UnsupportedReason reason, String detail) { 042 if (model == null) { 043 throw new IllegalArgumentException("model must not be null"); 044 } 045 this.model = model; 046 this.reason = reason; 047 this.detail = detail; 048 } 049 050 public static ProjectorResult ok(CanonicalLineageModel model) { 051 return new ProjectorResult(model, null, null); 052 } 053 054 public static ProjectorResult unsupported(UnsupportedReason reason, String detail) { 055 if (reason == null) { 056 throw new IllegalArgumentException("reason must not be null for unsupported result"); 057 } 058 return new ProjectorResult(CanonicalLineageModel.empty(), reason, detail); 059 } 060 061 public CanonicalLineageModel getModel() { 062 return model; 063 } 064 065 public UnsupportedReason getReason() { 066 return reason; 067 } 068 069 public String getDetail() { 070 return detail; 071 } 072 073 public boolean isSupported() { 074 return reason == null; 075 } 076}