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}