001package gudusoft.gsqlparser.ir.semantic; 002 003import gudusoft.gsqlparser.common.structured.StructuredColumnPath; 004import gudusoft.gsqlparser.ir.semantic.joinanalysis.ColumnResolution; 005 006import java.util.Objects; 007 008/** 009 * In-statement reference to a column. Holds the FROM-clause alias and the 010 * column name as they appear in the SQL — <i>not</i> the final physical 011 * table. Physical/provenance information lives on {@link RelationSource}'s 012 * {@link gudusoft.gsqlparser.ir.semantic.binding.RelationBinding} so that 013 * later CTE/subquery slices can describe provenance separately without 014 * mutating this shape. 015 * 016 * <p>An optional {@link StructuredColumnPath} carries a typed path into a 017 * structured column (e.g. {@code nodes[*].key}) produced by a 018 * structured-dataflow adapter such as Spark {@code from_json}+{@code explode}. 019 * Existing constructors leave it null for full backward compatibility. 020 * 021 * <p>Join-analysis slice 164 (S3) adds an optional {@link SourceSpan} 022 * pointing at the source-text reference this column was read from; slice 023 * 165 (S4) adds an optional {@link ColumnResolution} surfacing the 024 * resolved base relation (GAP 5). Both are <strong>excluded</strong> from 025 * {@link #equals}/{@link #hashCode} so that set/map dedupe and golden 026 * ordering are unaffected — two refs that differ only in span or 027 * resolution remain equal. Existing constructors default them to 028 * {@code null}. 029 * 030 * <p><b>API status:</b> read-only consumption of analyzer-produced column 031 * references is part of Join Analysis Consumption Profile v1. Constructors 032 * are producer-oriented and are outside that profile. 033 */ 034public final class ColumnRef { 035 036 private final String relationAlias; 037 private final String columnName; 038 private final StructuredColumnPath structuredPath; 039 private final SourceSpan sourceSpan; 040 private final ColumnResolution resolution; 041 042 public ColumnRef(String relationAlias, String columnName) { 043 this(relationAlias, columnName, null, null, null); 044 } 045 046 public ColumnRef(String relationAlias, String columnName, StructuredColumnPath structuredPath) { 047 this(relationAlias, columnName, structuredPath, null, null); 048 } 049 050 /** Slice 164 — adds the optional source span. */ 051 public ColumnRef(String relationAlias, String columnName, 052 StructuredColumnPath structuredPath, SourceSpan sourceSpan) { 053 this(relationAlias, columnName, structuredPath, sourceSpan, null); 054 } 055 056 /** 057 * Full constructor (join-analysis slice 165). {@code sourceSpan} and 058 * {@code resolution} are optional (null when the parser cannot anchor 059 * the reference / when no resolution was attempted) and both are 060 * excluded from identity. 061 */ 062 public ColumnRef(String relationAlias, String columnName, 063 StructuredColumnPath structuredPath, SourceSpan sourceSpan, 064 ColumnResolution resolution) { 065 if (relationAlias == null || relationAlias.isEmpty()) { 066 throw new IllegalArgumentException("relationAlias must be non-empty"); 067 } 068 if (columnName == null || columnName.isEmpty()) { 069 throw new IllegalArgumentException("columnName must be non-empty"); 070 } 071 this.relationAlias = relationAlias; 072 this.columnName = columnName; 073 this.structuredPath = structuredPath; 074 this.sourceSpan = sourceSpan; 075 this.resolution = resolution; 076 } 077 078 public String getRelationAlias() { 079 return relationAlias; 080 } 081 082 public String getColumnName() { 083 return columnName; 084 } 085 086 public StructuredColumnPath getStructuredPath() { 087 return structuredPath; 088 } 089 090 /** 091 * Optional source-text span of this column reference (join-analysis 092 * slice 164). Null when the parser cannot anchor it. Excluded from 093 * {@link #equals}/{@link #hashCode}. 094 */ 095 public SourceSpan getSourceSpan() { 096 return sourceSpan; 097 } 098 099 /** 100 * Optional resolution of this column to its base relation / final 101 * table (join-analysis slice 165, GAP 5). Null when no resolution was 102 * attempted (e.g. synthetic merged-key refs); otherwise carries an 103 * explicit {@link ColumnResolution} (RESOLVED with a final-table name, 104 * or UNRESOLVED). Excluded from {@link #equals}/{@link #hashCode}. 105 */ 106 public ColumnResolution getResolution() { 107 return resolution; 108 } 109 110 @Override 111 public boolean equals(Object o) { 112 if (this == o) return true; 113 if (!(o instanceof ColumnRef)) return false; 114 ColumnRef other = (ColumnRef) o; 115 // sourceSpan and resolution intentionally excluded — see class javadoc. 116 return relationAlias.equals(other.relationAlias) 117 && columnName.equals(other.columnName) 118 && Objects.equals(structuredPath, other.structuredPath); 119 } 120 121 @Override 122 public int hashCode() { 123 // sourceSpan and resolution intentionally excluded — see class javadoc. 124 return Objects.hash(relationAlias, columnName, structuredPath); 125 } 126 127 @Override 128 public String toString() { 129 if (structuredPath != null) { 130 return relationAlias + "." + structuredPath.toDisplayString(); 131 } 132 return relationAlias + "." + columnName; 133 } 134}