001package gudusoft.gsqlparser.resolver2.namespace; 002 003import gudusoft.gsqlparser.nodes.TObjectName; 004import gudusoft.gsqlparser.nodes.TTable; 005import gudusoft.gsqlparser.resolver2.ColumnLevel; 006import gudusoft.gsqlparser.resolver2.matcher.INameMatcher; 007import gudusoft.gsqlparser.resolver2.matcher.DefaultNameMatcher; 008import gudusoft.gsqlparser.resolver2.model.ColumnReference; 009import gudusoft.gsqlparser.resolver2.model.ColumnSource; 010import gudusoft.gsqlparser.resolver2.model.ColumnSourceWithReferences; 011 012import java.util.*; 013 014/** 015 * Abstract base class for all namespaces. 016 * Provides common functionality for column resolution and caching. 017 * 018 * <p>Enhanced with reference traceability support: multiple syntactically different 019 * identifiers that refer to the same semantic column can be tracked and traced back.</p> 020 */ 021public abstract class AbstractNamespace implements INamespace { 022 023 /** Associated AST node */ 024 protected final Object node; 025 026 /** Whether this namespace has been validated */ 027 protected boolean validated = false; 028 029 /** Cached column sources (populated during validation) - keyed by normalized name */ 030 protected Map<String, ColumnSource> columnSources = null; 031 032 /** 033 * Enhanced column sources with reference traceability (keyed by normalized name). 034 * When enabled, this map stores ColumnSourceWithReferences that track all original 035 * references to each semantic column. 036 */ 037 protected Map<String, ColumnSourceWithReferences> columnSourcesWithRefs = null; 038 039 /** Whether reference traceability is enabled */ 040 protected boolean referenceTraceabilityEnabled = false; 041 042 /** Name matcher for column name comparisons */ 043 protected final INameMatcher nameMatcher; 044 045 /** 046 * Strategy for handling ambiguous columns. 047 * Defaults to -1, which means use global TBaseType.GUESS_COLUMN_STRATEGY. 048 * Can be overridden per-namespace instance. 049 */ 050 protected int guessColumnStrategy = -1; 051 052 protected AbstractNamespace(Object node, INameMatcher nameMatcher) { 053 this.node = node; 054 this.nameMatcher = nameMatcher != null ? nameMatcher : new DefaultNameMatcher(); 055 } 056 057 protected AbstractNamespace(Object node) { 058 this(node, new DefaultNameMatcher()); 059 } 060 061 /** 062 * Set the strategy for handling ambiguous columns. 063 * @param strategy One of TBaseType.GUESS_COLUMN_STRATEGY_* constants, or -1 to use global default 064 */ 065 public void setGuessColumnStrategy(int strategy) { 066 this.guessColumnStrategy = strategy; 067 } 068 069 /** 070 * Get the strategy for handling ambiguous columns. 071 * Returns the instance-level strategy if set, otherwise returns the global default. 072 * @return The strategy constant 073 */ 074 public int getGuessColumnStrategy() { 075 if (guessColumnStrategy >= 0) { 076 return guessColumnStrategy; 077 } 078 return gudusoft.gsqlparser.TBaseType.GUESS_COLUMN_STRATEGY; 079 } 080 081 @Override 082 public Object getNode() { 083 return node; 084 } 085 086 @Override 087 public boolean isValidated() { 088 return validated; 089 } 090 091 @Override 092 public void validate() { 093 if (!validated) { 094 doValidate(); 095 validated = true; 096 } 097 } 098 099 /** 100 * Subclasses override this to perform actual validation logic. 101 */ 102 protected abstract void doValidate(); 103 104 /** 105 * Enable reference traceability for this namespace. 106 * When enabled, all column additions will track original references. 107 */ 108 public void enableReferenceTraceability() { 109 this.referenceTraceabilityEnabled = true; 110 if (columnSourcesWithRefs == null) { 111 columnSourcesWithRefs = new LinkedHashMap<>(); 112 } 113 } 114 115 /** 116 * Check if reference traceability is enabled. 117 * 118 * @return true if traceability is enabled 119 */ 120 public boolean isReferenceTraceabilityEnabled() { 121 return referenceTraceabilityEnabled; 122 } 123 124 /** 125 * Add a column source with reference traceability support. 126 * 127 * <p>This method normalizes the column name and either:</p> 128 * <ul> 129 * <li>Creates a new entry if this is the first reference</li> 130 * <li>Adds a reference to an existing entry if the column already exists</li> 131 * </ul> 132 * 133 * @param columnName original column name (may include quotes) 134 * @param source the column source 135 * @param objectName the original AST node for traceability (may be null) 136 */ 137 protected void addColumnSource(String columnName, ColumnSource source, TObjectName objectName) { 138 if (columnSources == null) { 139 columnSources = new LinkedHashMap<>(); 140 } 141 142 String normalizedKey = nameMatcher.normalize(columnName); 143 144 // Add to basic map if not exists 145 if (!columnSources.containsKey(normalizedKey)) { 146 columnSources.put(normalizedKey, source); 147 } 148 149 // Add to enhanced map with references if traceability is enabled 150 if (referenceTraceabilityEnabled) { 151 if (columnSourcesWithRefs == null) { 152 columnSourcesWithRefs = new LinkedHashMap<>(); 153 } 154 155 ColumnSourceWithReferences enhanced = columnSourcesWithRefs.computeIfAbsent( 156 normalizedKey, 157 k -> new ColumnSourceWithReferences(normalizedKey, source) 158 ); 159 160 if (objectName != null) { 161 enhanced.addReference(new ColumnReference(objectName)); 162 } 163 } 164 } 165 166 /** 167 * Add a column source (backward compatible - no traceability). 168 * 169 * @param columnName original column name 170 * @param source the column source 171 */ 172 protected void addColumnSource(String columnName, ColumnSource source) { 173 addColumnSource(columnName, source, null); 174 } 175 176 @Override 177 public ColumnLevel hasColumn(String columnName) { 178 ensureValidated(); 179 180 if (columnSources == null) { 181 return ColumnLevel.NOT_EXISTS; 182 } 183 184 // Use normalized key for O(1) lookup 185 String normalizedKey = nameMatcher.normalize(columnName); 186 if (columnSources.containsKey(normalizedKey)) { 187 return ColumnLevel.EXISTS; 188 } 189 190 // Fallback to linear search for backward compatibility 191 for (String existingCol : columnSources.keySet()) { 192 if (nameMatcher.matches(existingCol, columnName)) { 193 return ColumnLevel.EXISTS; 194 } 195 } 196 197 return ColumnLevel.NOT_EXISTS; 198 } 199 200 @Override 201 public ColumnSource resolveColumn(String columnName) { 202 ensureValidated(); 203 204 if (columnSources == null) { 205 return null; 206 } 207 208 // Use normalized key for O(1) lookup 209 String normalizedKey = nameMatcher.normalize(columnName); 210 ColumnSource source = columnSources.get(normalizedKey); 211 if (source != null) { 212 return source; 213 } 214 215 // Fallback to linear search for backward compatibility 216 for (Map.Entry<String, ColumnSource> entry : columnSources.entrySet()) { 217 if (nameMatcher.matches(entry.getKey(), columnName)) { 218 return entry.getValue(); 219 } 220 } 221 222 return null; 223 } 224 225 @Override 226 public Map<String, ColumnSource> getAllColumnSources() { 227 ensureValidated(); 228 return columnSources != null 229 ? Collections.unmodifiableMap(columnSources) 230 : Collections.emptyMap(); 231 } 232 233 /** 234 * Get all column references for a specific column. 235 * 236 * <p>Requires reference traceability to be enabled.</p> 237 * 238 * @param columnName the column name (normalized or original) 239 * @return list of all references, empty if not found or traceability not enabled 240 */ 241 public List<ColumnReference> getColumnReferences(String columnName) { 242 if (columnSourcesWithRefs == null) { 243 return Collections.emptyList(); 244 } 245 246 String normalizedKey = nameMatcher.normalize(columnName); 247 ColumnSourceWithReferences enhanced = columnSourcesWithRefs.get(normalizedKey); 248 249 return enhanced != null 250 ? enhanced.getAllReferences() 251 : Collections.emptyList(); 252 } 253 254 /** 255 * Get all unique columns with their references. 256 * 257 * <p>Requires reference traceability to be enabled.</p> 258 * 259 * @return collection of enhanced column sources, empty if traceability not enabled 260 */ 261 public Collection<ColumnSourceWithReferences> getAllUniqueColumns() { 262 if (columnSourcesWithRefs == null) { 263 return Collections.emptyList(); 264 } 265 return Collections.unmodifiableCollection(columnSourcesWithRefs.values()); 266 } 267 268 /** 269 * Get enhanced column source with references for a specific column. 270 * 271 * @param columnName the column name 272 * @return enhanced column source, or null if not found 273 */ 274 public ColumnSourceWithReferences getColumnSourceWithReferences(String columnName) { 275 if (columnSourcesWithRefs == null) { 276 return null; 277 } 278 String normalizedKey = nameMatcher.normalize(columnName); 279 return columnSourcesWithRefs.get(normalizedKey); 280 } 281 282 @Override 283 public List<TTable> getAllFinalTables() { 284 TTable finalTable = getFinalTable(); 285 if (finalTable != null) { 286 return Collections.singletonList(finalTable); 287 } 288 return Collections.emptyList(); 289 } 290 291 /** 292 * Ensure this namespace is validated before accessing column info 293 */ 294 protected void ensureValidated() { 295 if (!validated) { 296 validate(); 297 } 298 } 299 300 /** 301 * Get the name matcher used by this namespace. 302 * 303 * @return the name matcher 304 */ 305 public INameMatcher getNameMatcher() { 306 return nameMatcher; 307 } 308 309 @Override 310 public String toString() { 311 return getDisplayName(); 312 } 313}