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>A successful analysis produces a non-null program and JSON with
014 * an empty (or warning-only) diagnostics list. A failed analysis
015 * produces a {@code null} program / JSON and at least one
016 * {@link Severity#ERROR ERROR}-severity diagnostic.
017 *
018 * <p>This type is immutable and thread-safe. Consumers should
019 * pattern-match on {@link Diagnostic#getCode()} (the
020 * {@link DiagnosticCode} enum is the stable contract) rather than
021 * parsing message text.
022 */
023public final class AnalysisResult {
024
025    private final String schemaVersion;
026    private final SemanticProgram program;
027    private final String json;
028    private final List<Diagnostic> diagnostics;
029    private final String statementText;
030
031    AnalysisResult(String schemaVersion,
032                   SemanticProgram program,
033                   String json,
034                   List<Diagnostic> diagnostics) {
035        this(schemaVersion, program, json, diagnostics, null);
036    }
037
038    /**
039     * Full constructor (slice 178, R3). {@code statementText} is the
040     * verbatim source slice of the analyzed top-level statement (null when
041     * not computed, e.g. a rejection before the statement was located).
042     */
043    AnalysisResult(String schemaVersion,
044                   SemanticProgram program,
045                   String json,
046                   List<Diagnostic> diagnostics,
047                   String statementText) {
048        this.schemaVersion = Objects.requireNonNull(schemaVersion, "schemaVersion");
049        this.program = program;
050        this.json = json;
051        this.diagnostics = Collections.unmodifiableList(
052                new ArrayList<>(Objects.requireNonNull(diagnostics, "diagnostics")));
053        this.statementText = statementText;
054    }
055
056    /**
057     * @return {@code true} iff a non-null program was built AND no
058     *         {@link Severity#ERROR ERROR}-severity diagnostics are
059     *         present.
060     */
061    public boolean isSuccessful() {
062        if (program == null) {
063            return false;
064        }
065        for (Diagnostic d : diagnostics) {
066            if (d.getSeverity() == Severity.ERROR) {
067                return false;
068            }
069        }
070        return true;
071    }
072
073    /** JSON schema version emitted by the analyzer (currently {@code "1"}). */
074    public String getSchemaVersion() {
075        return schemaVersion;
076    }
077
078    /**
079     * @return the built program, or {@code null} when analysis
080     *         failed. Use {@link #isSuccessful()} as the discriminator.
081     */
082    public SemanticProgram getProgram() {
083        return program;
084    }
085
086    /**
087     * @return the JSON encoding of {@link #getProgram()}, or
088     *         {@code null} when analysis failed.
089     */
090    public String getJson() {
091        return json;
092    }
093
094    /** Diagnostics emitted during analysis. Never {@code null}; may be empty. */
095    public List<Diagnostic> getDiagnostics() {
096        return diagnostics;
097    }
098
099    /**
100     * The verbatim source slice of the analyzed top-level statement
101     * (slice 178, R3). Non-null for results produced by
102     * {@link SqlSemanticAnalyzer#analyze} and
103     * {@link SqlSemanticAnalyzer#analyzeAll} once the statement is located;
104     * {@code null} for parse-level rejections before a statement exists.
105     * Lets multi-statement consumers reproduce the exact text of one
106     * statement without re-parsing or slicing the parse tree themselves.
107     */
108    public String getStatementText() {
109        return statementText;
110    }
111
112    /**
113     * @return the first {@link Severity#ERROR ERROR}-severity
114     *         diagnostic, or {@code null} when none were emitted.
115     */
116    public Diagnostic getFirstError() {
117        for (Diagnostic d : diagnostics) {
118            if (d.getSeverity() == Severity.ERROR) {
119                return d;
120            }
121        }
122        return null;
123    }
124}