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 */ 030public final class ColumnRef { 031 032 private final String relationAlias; 033 private final String columnName; 034 private final StructuredColumnPath structuredPath; 035 private final SourceSpan sourceSpan; 036 private final ColumnResolution resolution; 037 038 public ColumnRef(String relationAlias, String columnName) { 039 this(relationAlias, columnName, null, null, null); 040 } 041 042 public ColumnRef(String relationAlias, String columnName, StructuredColumnPath structuredPath) { 043 this(relationAlias, columnName, structuredPath, null, null); 044 } 045 046 /** Slice 164 — adds the optional source span. */ 047 public ColumnRef(String relationAlias, String columnName, 048 StructuredColumnPath structuredPath, SourceSpan sourceSpan) { 049 this(relationAlias, columnName, structuredPath, sourceSpan, null); 050 } 051 052 /** 053 * Full constructor (join-analysis slice 165). {@code sourceSpan} and 054 * {@code resolution} are optional (null when the parser cannot anchor 055 * the reference / when no resolution was attempted) and both are 056 * excluded from identity. 057 */ 058 public ColumnRef(String relationAlias, String columnName, 059 StructuredColumnPath structuredPath, SourceSpan sourceSpan, 060 ColumnResolution resolution) { 061 if (relationAlias == null || relationAlias.isEmpty()) { 062 throw new IllegalArgumentException("relationAlias must be non-empty"); 063 } 064 if (columnName == null || columnName.isEmpty()) { 065 throw new IllegalArgumentException("columnName must be non-empty"); 066 } 067 this.relationAlias = relationAlias; 068 this.columnName = columnName; 069 this.structuredPath = structuredPath; 070 this.sourceSpan = sourceSpan; 071 this.resolution = resolution; 072 } 073 074 public String getRelationAlias() { 075 return relationAlias; 076 } 077 078 public String getColumnName() { 079 return columnName; 080 } 081 082 public StructuredColumnPath getStructuredPath() { 083 return structuredPath; 084 } 085 086 /** 087 * Optional source-text span of this column reference (join-analysis 088 * slice 164). Null when the parser cannot anchor it. Excluded from 089 * {@link #equals}/{@link #hashCode}. 090 */ 091 public SourceSpan getSourceSpan() { 092 return sourceSpan; 093 } 094 095 /** 096 * Optional resolution of this column to its base relation / final 097 * table (join-analysis slice 165, GAP 5). Null when no resolution was 098 * attempted (e.g. synthetic merged-key refs); otherwise carries an 099 * explicit {@link ColumnResolution} (RESOLVED with a final-table name, 100 * or UNRESOLVED). Excluded from {@link #equals}/{@link #hashCode}. 101 */ 102 public ColumnResolution getResolution() { 103 return resolution; 104 } 105 106 @Override 107 public boolean equals(Object o) { 108 if (this == o) return true; 109 if (!(o instanceof ColumnRef)) return false; 110 ColumnRef other = (ColumnRef) o; 111 // sourceSpan and resolution intentionally excluded — see class javadoc. 112 return relationAlias.equals(other.relationAlias) 113 && columnName.equals(other.columnName) 114 && Objects.equals(structuredPath, other.structuredPath); 115 } 116 117 @Override 118 public int hashCode() { 119 // sourceSpan and resolution intentionally excluded — see class javadoc. 120 return Objects.hash(relationAlias, columnName, structuredPath); 121 } 122 123 @Override 124 public String toString() { 125 if (structuredPath != null) { 126 return relationAlias + "." + structuredPath.toDisplayString(); 127 } 128 return relationAlias + "." + columnName; 129 } 130}