001package gudusoft.gsqlparser.ir.semantic;
002
003import gudusoft.gsqlparser.common.structured.StructuredColumnPath;
004
005/**
006 * One endpoint of a {@link LineageEdge}. Either references an output column
007 * of a {@link StatementGraph} (by index + name) or a column of a base
008 * table (by qualified name + column name). The discriminator is
009 * {@link #getKind()}.
010 *
011 * <p>An optional {@link StructuredColumnPath} carries a typed path under
012 * the root column (e.g. {@code nodes[*].key}) for endpoints produced by
013 * structured-dataflow adapters. Existing factories leave it null.
014 *
015 * <p>An optional {@link SourceSpan} (US-021a) anchors the endpoint back to
016 * the source text it was derived from. Existing factories leave it null;
017 * builders attach it via {@link #withSpan(SourceSpan)}.
018 */
019public final class LineageRef {
020
021    public enum Kind {
022        STATEMENT_OUTPUT,
023        TABLE_COLUMN
024    }
025
026    private final Kind kind;
027    private final int statementIndex;
028    private final String outputName;
029    private final String qualifiedName;
030    private final String columnName;
031    private final StructuredColumnPath structuredPath;
032    private final SourceSpan span;
033
034    private LineageRef(Kind kind, int statementIndex, String outputName,
035                       String qualifiedName, String columnName,
036                       StructuredColumnPath structuredPath, SourceSpan span) {
037        this.kind = kind;
038        this.statementIndex = statementIndex;
039        this.outputName = outputName;
040        this.qualifiedName = qualifiedName;
041        this.columnName = columnName;
042        this.structuredPath = structuredPath;
043        this.span = span;
044    }
045
046    public static LineageRef statementOutput(int statementIndex, String outputName) {
047        return statementOutput(statementIndex, outputName, null);
048    }
049
050    public static LineageRef statementOutput(int statementIndex, String outputName,
051                                             StructuredColumnPath structuredPath) {
052        if (statementIndex < 0) {
053            throw new IllegalArgumentException("statementIndex must be >= 0");
054        }
055        if (outputName == null || outputName.isEmpty()) {
056            throw new IllegalArgumentException("outputName must be non-empty");
057        }
058        return new LineageRef(Kind.STATEMENT_OUTPUT, statementIndex, outputName, null, null, structuredPath, null);
059    }
060
061    public static LineageRef tableColumn(String qualifiedName, String columnName) {
062        return tableColumn(qualifiedName, columnName, null);
063    }
064
065    public static LineageRef tableColumn(String qualifiedName, String columnName,
066                                         StructuredColumnPath structuredPath) {
067        if (qualifiedName == null || qualifiedName.isEmpty()) {
068            throw new IllegalArgumentException("qualifiedName must be non-empty");
069        }
070        if (columnName == null || columnName.isEmpty()) {
071            throw new IllegalArgumentException("columnName must be non-empty");
072        }
073        return new LineageRef(Kind.TABLE_COLUMN, -1, null, qualifiedName, columnName, structuredPath, null);
074    }
075
076    /**
077     * Copy of this ref with the optional endpoint source span attached
078     * (US-021a) — for a STATEMENT_OUTPUT endpoint, the projection
079     * expression's span; for a TABLE_COLUMN endpoint, the span of the
080     * column reference it was resolved from. Returns {@code this} when
081     * {@code span} is {@code null} (nothing to attach), so callers can
082     * pass possibly-null spans unconditionally.
083     */
084    public LineageRef withSpan(SourceSpan span) {
085        if (span == null) {
086            return this;
087        }
088        return new LineageRef(kind, statementIndex, outputName, qualifiedName,
089                columnName, structuredPath, span);
090    }
091
092    public Kind getKind() {
093        return kind;
094    }
095
096    public int getStatementIndex() {
097        return statementIndex;
098    }
099
100    public String getOutputName() {
101        return outputName;
102    }
103
104    public String getQualifiedName() {
105        return qualifiedName;
106    }
107
108    public String getColumnName() {
109        return columnName;
110    }
111
112    public StructuredColumnPath getStructuredPath() {
113        return structuredPath;
114    }
115
116    /**
117     * Optional source span of this endpoint (US-021a). {@code null} unless
118     * a builder attached one via {@link #withSpan(SourceSpan)}. Null-safe:
119     * never throws.
120     */
121    public SourceSpan getSpan() {
122        return span;
123    }
124}