001package gudusoft.gsqlparser.resolver2; 002 003import gudusoft.gsqlparser.nodes.TObjectName; 004import gudusoft.gsqlparser.nodes.TTable; 005import gudusoft.gsqlparser.resolver2.scope.GlobalScope; 006import gudusoft.gsqlparser.resolver2.scope.IScope; 007import gudusoft.gsqlparser.resolver2.scope.SelectScope; 008import gudusoft.gsqlparser.resolver2.scope.FromScope; 009import gudusoft.gsqlparser.resolver2.scope.CTEScope; 010import gudusoft.gsqlparser.resolver2.model.ScopeChild; 011import gudusoft.gsqlparser.resolver2.namespace.CTENamespace; 012import gudusoft.gsqlparser.resolver2.namespace.INamespace; 013import gudusoft.gsqlparser.resolver2.namespace.PivotNamespace; 014import gudusoft.gsqlparser.stmt.TSelectSqlStatement; 015 016import java.util.*; 017 018/** 019 * Result of scope building by ScopeBuilder. 020 * Contains the complete scope tree and column mappings. 021 */ 022public class ScopeBuildResult { 023 024 /** Global scope (root of scope tree) */ 025 private final GlobalScope globalScope; 026 027 /** Column reference -> Scope mapping */ 028 private final Map<TObjectName, IScope> columnToScopeMap; 029 030 /** All column references in order */ 031 private final List<TObjectName> allColumnReferences; 032 033 /** Statement -> SelectScope mapping */ 034 private final Map<TSelectSqlStatement, SelectScope> statementScopeMap; 035 036 /** USING column -> right-side TTable mapping for JOIN...USING resolution priority */ 037 private final Map<TObjectName, TTable> usingColumnToRightTable; 038 039 /** USING column -> left-side TTable mapping (for reporting both tables in USING) */ 040 private final Map<TObjectName, TTable> usingColumnToLeftTable; 041 042 /** TTable -> INamespace mapping for legacy compatibility (filling TTable.getAttributes()) */ 043 private final Map<TTable, INamespace> tableToNamespaceMap; 044 045 /** CTAS target tables (tables being created by CREATE TABLE AS SELECT) */ 046 private final Set<TTable> ctasTargetTables; 047 048 public ScopeBuildResult(GlobalScope globalScope, 049 Map<TObjectName, IScope> columnToScopeMap, 050 List<TObjectName> allColumnReferences, 051 Map<TSelectSqlStatement, SelectScope> statementScopeMap, 052 Map<TObjectName, TTable> usingColumnToRightTable) { 053 this(globalScope, columnToScopeMap, allColumnReferences, statementScopeMap, 054 usingColumnToRightTable, Collections.emptyMap(), Collections.emptyMap(), Collections.emptySet()); 055 } 056 057 public ScopeBuildResult(GlobalScope globalScope, 058 Map<TObjectName, IScope> columnToScopeMap, 059 List<TObjectName> allColumnReferences, 060 Map<TSelectSqlStatement, SelectScope> statementScopeMap, 061 Map<TObjectName, TTable> usingColumnToRightTable, 062 Map<TObjectName, TTable> usingColumnToLeftTable) { 063 this(globalScope, columnToScopeMap, allColumnReferences, statementScopeMap, 064 usingColumnToRightTable, usingColumnToLeftTable, Collections.emptyMap(), Collections.emptySet()); 065 } 066 067 public ScopeBuildResult(GlobalScope globalScope, 068 Map<TObjectName, IScope> columnToScopeMap, 069 List<TObjectName> allColumnReferences, 070 Map<TSelectSqlStatement, SelectScope> statementScopeMap, 071 Map<TObjectName, TTable> usingColumnToRightTable, 072 Map<TObjectName, TTable> usingColumnToLeftTable, 073 Map<TTable, INamespace> tableToNamespaceMap) { 074 this(globalScope, columnToScopeMap, allColumnReferences, statementScopeMap, 075 usingColumnToRightTable, usingColumnToLeftTable, tableToNamespaceMap, Collections.emptySet()); 076 } 077 078 public ScopeBuildResult(GlobalScope globalScope, 079 Map<TObjectName, IScope> columnToScopeMap, 080 List<TObjectName> allColumnReferences, 081 Map<TSelectSqlStatement, SelectScope> statementScopeMap, 082 Map<TObjectName, TTable> usingColumnToRightTable, 083 Map<TObjectName, TTable> usingColumnToLeftTable, 084 Map<TTable, INamespace> tableToNamespaceMap, 085 Set<TTable> ctasTargetTables) { 086 this.globalScope = globalScope; 087 this.columnToScopeMap = new HashMap<>(columnToScopeMap); 088 this.allColumnReferences = new ArrayList<>(allColumnReferences); 089 this.statementScopeMap = new HashMap<>(statementScopeMap); 090 this.usingColumnToRightTable = new HashMap<>(usingColumnToRightTable); 091 this.usingColumnToLeftTable = new HashMap<>(usingColumnToLeftTable); 092 this.tableToNamespaceMap = new HashMap<>(tableToNamespaceMap); 093 this.ctasTargetTables = new HashSet<>(ctasTargetTables); 094 } 095 096 public GlobalScope getGlobalScope() { 097 return globalScope; 098 } 099 100 public Map<TObjectName, IScope> getColumnToScopeMap() { 101 return Collections.unmodifiableMap(columnToScopeMap); 102 } 103 104 public List<TObjectName> getAllColumnReferences() { 105 return Collections.unmodifiableList(allColumnReferences); 106 } 107 108 /** 109 * Add column references to the internal list. 110 * Used by TSQLResolver2 to add cloned columns for star column tracing. 111 * 112 * @param columns the column references to add 113 */ 114 public void addColumnReferences(List<TObjectName> columns) { 115 allColumnReferences.addAll(columns); 116 } 117 118 public Map<TSelectSqlStatement, SelectScope> getStatementScopeMap() { 119 return Collections.unmodifiableMap(statementScopeMap); 120 } 121 122 /** 123 * Get the scope for a specific column reference 124 */ 125 public IScope getScopeForColumn(TObjectName column) { 126 return columnToScopeMap.get(column); 127 } 128 129 /** 130 * Get the SelectScope for a specific statement 131 */ 132 public SelectScope getScopeForStatement(TSelectSqlStatement stmt) { 133 return statementScopeMap.get(stmt); 134 } 135 136 /** 137 * Get the right-side table for a USING column. 138 * In JOIN...USING syntax, USING columns should preferentially resolve 139 * to the right-side (physical) table. 140 * 141 * @param column the USING column TObjectName 142 * @return the right-side TTable, or null if not a USING column 143 */ 144 public TTable getUsingColumnRightTable(TObjectName column) { 145 return usingColumnToRightTable.get(column); 146 } 147 148 /** 149 * Get all USING column -> right-side table mappings 150 */ 151 public Map<TObjectName, TTable> getUsingColumnToRightTable() { 152 return Collections.unmodifiableMap(usingColumnToRightTable); 153 } 154 155 /** 156 * Get the left-side table for a USING column. 157 * In JOIN...USING syntax, the USING column also applies to the left-side table. 158 * 159 * @param column the USING column TObjectName 160 * @return the left-side TTable, or null if not a USING column 161 */ 162 public TTable getUsingColumnLeftTable(TObjectName column) { 163 return usingColumnToLeftTable.get(column); 164 } 165 166 /** 167 * Get all USING column -> left-side table mappings 168 */ 169 public Map<TObjectName, TTable> getUsingColumnToLeftTable() { 170 return Collections.unmodifiableMap(usingColumnToLeftTable); 171 } 172 173 /** 174 * Get the namespace for a specific table. 175 * Used for legacy compatibility to fill TTable.getAttributes(). 176 * 177 * @param table the TTable to look up 178 * @return the INamespace for this table, or null if not found 179 */ 180 public INamespace getNamespaceForTable(TTable table) { 181 return tableToNamespaceMap.get(table); 182 } 183 184 /** 185 * Get all TTable -> INamespace mappings 186 */ 187 public Map<TTable, INamespace> getTableToNamespaceMap() { 188 return Collections.unmodifiableMap(tableToNamespaceMap); 189 } 190 191 /** 192 * Check if a table is a CTAS target table (table being created by CREATE TABLE AS SELECT). 193 * CTAS target tables are DDL targets, not existing physical tables. 194 * 195 * @param table the TTable to check 196 * @return true if this is a CTAS target table 197 */ 198 public boolean isCTASTargetTable(TTable table) { 199 return ctasTargetTables.contains(table); 200 } 201 202 /** 203 * Get all CTAS target tables 204 */ 205 public Set<TTable> getCTASTargetTables() { 206 return Collections.unmodifiableSet(ctasTargetTables); 207 } 208 209 /** 210 * Get all CTENamespaces from the scope tree. 211 * Searches through all scopes to find CTE namespaces. 212 * 213 * @return Map of CTE name -> CTENamespace 214 */ 215 public Map<String, CTENamespace> getCTENamespaces() { 216 Map<String, CTENamespace> result = new HashMap<>(); 217 collectCTENamespaces(globalScope, result, new HashSet<>()); 218 return result; 219 } 220 221 /** 222 * Recursively collect CTE namespaces from the scope tree 223 */ 224 private void collectCTENamespaces(IScope scope, Map<String, CTENamespace> result, Set<IScope> visited) { 225 if (scope == null || visited.contains(scope)) { 226 return; 227 } 228 visited.add(scope); 229 230 // Check if this scope has CTE namespaces as children 231 for (ScopeChild child : scope.getChildren()) { 232 INamespace ns = child.getNamespace(); 233 if (ns instanceof CTENamespace) { 234 CTENamespace cteNs = (CTENamespace) ns; 235 result.put(cteNs.getDisplayName(), cteNs); 236 } 237 } 238 239 // If this is a SelectScope, also check FromScope 240 if (scope instanceof SelectScope) { 241 SelectScope selectScope = (SelectScope) scope; 242 if (selectScope.getFromScope() != null) { 243 collectCTENamespaces(selectScope.getFromScope(), result, visited); 244 } 245 } 246 247 // Check all statementScopes 248 for (SelectScope selectScope : statementScopeMap.values()) { 249 if (!visited.contains(selectScope)) { 250 collectCTENamespaces(selectScope, result, visited); 251 } 252 } 253 } 254 255 /** 256 * Get all PivotNamespaces from the scope tree. 257 * Searches through all scopes to find PIVOT namespaces. 258 * 259 * @return List of PivotNamespace objects 260 */ 261 public List<PivotNamespace> getPivotNamespaces() { 262 List<PivotNamespace> result = new ArrayList<>(); 263 collectPivotNamespaces(globalScope, result, new HashSet<>()); 264 return result; 265 } 266 267 /** 268 * Recursively collect PIVOT namespaces from the scope tree 269 */ 270 private void collectPivotNamespaces(IScope scope, List<PivotNamespace> result, Set<IScope> visited) { 271 if (scope == null || visited.contains(scope)) { 272 return; 273 } 274 visited.add(scope); 275 276 // Check if this scope has PIVOT namespaces as children 277 for (ScopeChild child : scope.getChildren()) { 278 INamespace ns = child.getNamespace(); 279 if (ns instanceof PivotNamespace) { 280 PivotNamespace pivotNs = (PivotNamespace) ns; 281 result.add(pivotNs); 282 } 283 } 284 285 // If this is a SelectScope, also check FromScope 286 if (scope instanceof SelectScope) { 287 SelectScope selectScope = (SelectScope) scope; 288 if (selectScope.getFromScope() != null) { 289 collectPivotNamespaces(selectScope.getFromScope(), result, visited); 290 } 291 } 292 293 // Check all statementScopes 294 for (SelectScope selectScope : statementScopeMap.values()) { 295 if (!visited.contains(selectScope)) { 296 collectPivotNamespaces(selectScope, result, visited); 297 } 298 } 299 } 300 301 /** 302 * Get statistics about the build result 303 */ 304 public String getStatistics() { 305 return String.format( 306 "ScopeBuildResult{statements=%d, columns=%d, scopes=%d}", 307 statementScopeMap.size(), 308 allColumnReferences.size(), 309 countTotalScopes() 310 ); 311 } 312 313 /** 314 * Count total scopes in the tree 315 */ 316 private int countTotalScopes() { 317 Set<IScope> visited = new HashSet<>(); 318 countScopesRecursive(globalScope, visited); 319 return visited.size(); 320 } 321 322 private void countScopesRecursive(IScope scope, Set<IScope> visited) { 323 if (scope == null || visited.contains(scope)) { 324 return; 325 } 326 visited.add(scope); 327 328 // Count children from statementScopeMap 329 for (SelectScope selectScope : statementScopeMap.values()) { 330 if (!visited.contains(selectScope)) { 331 visited.add(selectScope); 332 if (selectScope.getFromScope() != null) { 333 visited.add(selectScope.getFromScope()); 334 } 335 } 336 } 337 } 338 339 /** 340 * Dump scope tree for debugging 341 */ 342 public String dumpScopeTree() { 343 StringBuilder sb = new StringBuilder(); 344 sb.append("=== Scope Tree ===\n"); 345 dumpScope(globalScope, sb, 0, new HashSet<>()); 346 return sb.toString(); 347 } 348 349 private void dumpScope(IScope scope, StringBuilder sb, int indent, Set<IScope> visited) { 350 if (scope == null || visited.contains(scope)) { 351 return; 352 } 353 visited.add(scope); 354 355 String prefix = repeatString(" ", indent); 356 sb.append(prefix).append(scope.toString()).append("\n"); 357 358 // Dump children 359 for (ScopeChild child : scope.getChildren()) { 360 sb.append(prefix).append(" └─ ") 361 .append(child.getAlias()).append(": ") 362 .append(child.getNamespace().toString()).append("\n"); 363 } 364 365 // Dump nested scopes (SelectScope has FromScope) 366 if (scope instanceof SelectScope) { 367 SelectScope selectScope = (SelectScope) scope; 368 if (selectScope.getFromScope() != null) { 369 dumpScope(selectScope.getFromScope(), sb, indent + 1, visited); 370 } 371 } 372 } 373 374 /** 375 * Dump all column references for debugging 376 */ 377 public String dumpColumnReferences() { 378 StringBuilder sb = new StringBuilder(); 379 sb.append("=== Column References ===\n"); 380 for (TObjectName col : allColumnReferences) { 381 IScope scope = columnToScopeMap.get(col); 382 sb.append(String.format(" %s -> %s\n", 383 col.toString(), 384 scope != null ? scope.getScopeType() : "null")); 385 } 386 return sb.toString(); 387 } 388 389 @Override 390 public String toString() { 391 return getStatistics(); 392 } 393 394 /** 395 * Repeat a string n times (JDK 1.8 compatible alternative to String.repeat()) 396 */ 397 private static String repeatString(String str, int count) { 398 if (count <= 0) { 399 return ""; 400 } 401 StringBuilder sb = new StringBuilder(str.length() * count); 402 for (int i = 0; i < count; i++) { 403 sb.append(str); 404 } 405 return sb.toString(); 406 } 407}