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}