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}