001package gudusoft.gsqlparser.resolver2.model; 002 003import gudusoft.gsqlparser.TSourceToken; 004import gudusoft.gsqlparser.nodes.TObjectName; 005 006/** 007 * Column reference record - preserves original AST node information for traceability. 008 * 009 * <p>When multiple syntactically different identifiers refer to the same semantic column 010 * (e.g., "Column1" and column1 in Oracle), this class allows tracing back from 011 * the normalized column name to all original occurrences in the SQL text.</p> 012 * 013 * <p>Example: For SQL {@code SELECT "Column1", column1 FROM dual}:</p> 014 * <ul> 015 * <li>Both references resolve to the same column COLUMN1</li> 016 * <li>Each creates a separate ColumnReference with its original text and position</li> 017 * <li>The ColumnSourceWithReferences holds both references</li> 018 * </ul> 019 * 020 * @since 3.1.0.9 021 */ 022public class ColumnReference { 023 024 /** Original AST node */ 025 private final TObjectName objectName; 026 027 /** Original text as it appears in SQL (e.g., "Column1" or column1) */ 028 private final String originalText; 029 030 /** Start offset in SQL text */ 031 private final int startOffset; 032 033 /** End offset in SQL text */ 034 private final int endOffset; 035 036 /** Line number (1-based) */ 037 private final int lineNumber; 038 039 /** Column number (1-based) */ 040 private final int columnNumber; 041 042 /** 043 * Create a column reference from a TObjectName AST node. 044 * 045 * @param objectName the original AST node 046 */ 047 public ColumnReference(TObjectName objectName) { 048 this.objectName = objectName; 049 this.originalText = objectName != null ? objectName.toString() : null; 050 051 // Extract position information from token 052 if (objectName != null && objectName.getStartToken() != null) { 053 TSourceToken startToken = objectName.getStartToken(); 054 this.startOffset = (int) startToken.offset; 055 this.lineNumber = (int) startToken.lineNo; 056 this.columnNumber = (int) startToken.columnNo; 057 058 TSourceToken endToken = objectName.getEndToken(); 059 this.endOffset = endToken != null ? (int) (endToken.offset + endToken.astext.length()) : startOffset; 060 } else { 061 this.startOffset = -1; 062 this.endOffset = -1; 063 this.lineNumber = -1; 064 this.columnNumber = -1; 065 } 066 } 067 068 /** 069 * Create a column reference with explicit values (for testing or special cases). 070 * 071 * @param objectName the original AST node (may be null) 072 * @param originalText the original text 073 * @param startOffset start offset 074 * @param endOffset end offset 075 * @param lineNumber line number 076 * @param columnNumber column number 077 */ 078 public ColumnReference(TObjectName objectName, String originalText, 079 int startOffset, int endOffset, 080 int lineNumber, int columnNumber) { 081 this.objectName = objectName; 082 this.originalText = originalText; 083 this.startOffset = startOffset; 084 this.endOffset = endOffset; 085 this.lineNumber = lineNumber; 086 this.columnNumber = columnNumber; 087 } 088 089 /** 090 * Get the original AST node. 091 * 092 * @return the TObjectName node, or null if not available 093 */ 094 public TObjectName getObjectName() { 095 return objectName; 096 } 097 098 /** 099 * Get the original text as it appears in SQL. 100 * 101 * @return original text (e.g., "Column1" with quotes, or column1 without) 102 */ 103 public String getOriginalText() { 104 return originalText; 105 } 106 107 /** 108 * Get the start offset in the SQL text. 109 * 110 * @return start offset (0-based), or -1 if not available 111 */ 112 public int getStartOffset() { 113 return startOffset; 114 } 115 116 /** 117 * Get the end offset in the SQL text. 118 * 119 * @return end offset (0-based, exclusive), or -1 if not available 120 */ 121 public int getEndOffset() { 122 return endOffset; 123 } 124 125 /** 126 * Get the line number. 127 * 128 * @return line number (1-based), or -1 if not available 129 */ 130 public int getLineNumber() { 131 return lineNumber; 132 } 133 134 /** 135 * Get the column number. 136 * 137 * @return column number (1-based), or -1 if not available 138 */ 139 public int getColumnNumber() { 140 return columnNumber; 141 } 142 143 /** 144 * Check if position information is available. 145 * 146 * @return true if position information is valid 147 */ 148 public boolean hasPositionInfo() { 149 return startOffset >= 0 && lineNumber >= 0; 150 } 151 152 @Override 153 public String toString() { 154 if (hasPositionInfo()) { 155 return String.format("%s @ line %d, col %d (offset %d-%d)", 156 originalText, lineNumber, columnNumber, startOffset, endOffset); 157 } 158 return originalText != null ? originalText : "<null>"; 159 } 160 161 @Override 162 public boolean equals(Object o) { 163 if (this == o) return true; 164 if (o == null || getClass() != o.getClass()) return false; 165 ColumnReference that = (ColumnReference) o; 166 // Two references are equal if they point to the same location 167 return startOffset == that.startOffset && 168 lineNumber == that.lineNumber && 169 columnNumber == that.columnNumber; 170 } 171 172 @Override 173 public int hashCode() { 174 int result = startOffset; 175 result = 31 * result + lineNumber; 176 result = 31 * result + columnNumber; 177 return result; 178 } 179}