001package gudusoft.gsqlparser.ir.semantic; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.List; 006import java.util.Objects; 007 008/** 009 * Result of a {@link SqlSemanticAnalyzer#analyze} call. Carries the 010 * built {@link SemanticProgram}, the JSON-encoded form, and the list 011 * of {@link Diagnostic}s emitted during analysis. 012 * 013 * <p><b>API status: supported read-only result.</b> Public getters and 014 * {@link #isSuccessful()} are part of Join Analysis Consumption Profile v1. 015 * Callers must test success before dereferencing {@link #getProgram()} or 016 * {@link #getJson()} and should inspect diagnostics on successful results too. 017 * 018 * <p>A successful analysis produces a non-null program and JSON with 019 * an empty (or warning-only) diagnostics list. A failed analysis 020 * produces a {@code null} program / JSON and at least one 021 * {@link Severity#ERROR ERROR}-severity diagnostic. 022 * 023 * <p>This type is immutable and thread-safe. Consumers should 024 * pattern-match on {@link Diagnostic#getCode()} (the 025 * {@link DiagnosticCode} enum is the stable contract) rather than 026 * parsing message text. 027 */ 028public final class AnalysisResult { 029 030 private final String schemaVersion; 031 private final SemanticProgram program; 032 private final String json; 033 private final List<Diagnostic> diagnostics; 034 private final String statementText; 035 036 AnalysisResult(String schemaVersion, 037 SemanticProgram program, 038 String json, 039 List<Diagnostic> diagnostics) { 040 this(schemaVersion, program, json, diagnostics, null); 041 } 042 043 /** 044 * Full constructor (slice 178, R3). {@code statementText} is the 045 * verbatim source slice of the analyzed top-level statement (null when 046 * not computed, e.g. a rejection before the statement was located). 047 */ 048 AnalysisResult(String schemaVersion, 049 SemanticProgram program, 050 String json, 051 List<Diagnostic> diagnostics, 052 String statementText) { 053 this.schemaVersion = Objects.requireNonNull(schemaVersion, "schemaVersion"); 054 this.program = program; 055 this.json = json; 056 this.diagnostics = Collections.unmodifiableList( 057 new ArrayList<>(Objects.requireNonNull(diagnostics, "diagnostics"))); 058 this.statementText = statementText; 059 } 060 061 /** 062 * @return {@code true} iff a non-null program was built AND no 063 * {@link Severity#ERROR ERROR}-severity diagnostics are 064 * present. 065 */ 066 public boolean isSuccessful() { 067 if (program == null) { 068 return false; 069 } 070 for (Diagnostic d : diagnostics) { 071 if (d.getSeverity() == Severity.ERROR) { 072 return false; 073 } 074 } 075 return true; 076 } 077 078 /** 079 * JSON schema version selected for this result's payload. For a successful 080 * result this equals the top-level {@code schemaVersion} in 081 * {@link #getJson()}; rejected results without JSON carry the analyzer's 082 * baseline version. 083 */ 084 public String getSchemaVersion() { 085 return schemaVersion; 086 } 087 088 /** 089 * @return the built program, or {@code null} when analysis 090 * failed. Use {@link #isSuccessful()} as the discriminator. 091 */ 092 public SemanticProgram getProgram() { 093 return program; 094 } 095 096 /** 097 * @return the JSON encoding of {@link #getProgram()}, or 098 * {@code null} when analysis failed. 099 */ 100 public String getJson() { 101 return json; 102 } 103 104 /** Diagnostics emitted during analysis. Never {@code null}; may be empty. */ 105 public List<Diagnostic> getDiagnostics() { 106 return diagnostics; 107 } 108 109 /** 110 * The verbatim source slice of the analyzed top-level statement 111 * (slice 178, R3). Non-null for results produced by 112 * {@link SqlSemanticAnalyzer#analyze} and 113 * {@link SqlSemanticAnalyzer#analyzeAll} once the statement is located; 114 * {@code null} for parse-level rejections before a statement exists. 115 * Lets multi-statement consumers reproduce the exact text of one 116 * statement without re-parsing or slicing the parse tree themselves. 117 */ 118 public String getStatementText() { 119 return statementText; 120 } 121 122 /** 123 * @return the first {@link Severity#ERROR ERROR}-severity 124 * diagnostic, or {@code null} when none were emitted. 125 */ 126 public Diagnostic getFirstError() { 127 for (Diagnostic d : diagnostics) { 128 if (d.getSeverity() == Severity.ERROR) { 129 return d; 130 } 131 } 132 return null; 133 } 134}