001package gudusoft.gsqlparser.resolver2.result; 002 003import gudusoft.gsqlparser.TCustomSqlStatement; 004import gudusoft.gsqlparser.TStatementList; 005import gudusoft.gsqlparser.ETableSource; 006import gudusoft.gsqlparser.nodes.TTable; 007import gudusoft.gsqlparser.nodes.TObjectName; 008import gudusoft.gsqlparser.nodes.TCTE; 009import gudusoft.gsqlparser.nodes.TParseTreeNode; 010import gudusoft.gsqlparser.resolver2.ScopeBuildResult; 011import gudusoft.gsqlparser.resolver2.scope.IScope; 012import gudusoft.gsqlparser.resolver2.scope.SelectScope; 013import gudusoft.gsqlparser.resolver2.namespace.CTENamespace; 014import gudusoft.gsqlparser.resolver2.model.ResolutionStatistics; 015import gudusoft.gsqlparser.stmt.TSelectSqlStatement; 016 017import java.util.*; 018 019/** 020 * Default implementation of IResolutionResult. 021 * Provides statement-centric query access based on ScopeBuildResult. 022 */ 023public class ResolutionResultImpl implements IResolutionResult { 024 025 private final ScopeBuildResult scopeBuildResult; 026 private final TStatementList statements; 027 private final Map<TObjectName, IScope> columnToScopeMap; 028 private final List<TObjectName> allColumnReferences; 029 030 // Lazy-initialized caches 031 private Set<String> cachedTableNames; 032 private Set<String> cachedFieldNames; 033 private Set<String> cachedCTENames; 034 private ResolutionStatistics cachedStatistics; 035 036 public ResolutionResultImpl(ScopeBuildResult scopeBuildResult, 037 TStatementList statements) { 038 this.scopeBuildResult = scopeBuildResult; 039 this.statements = statements; 040 this.columnToScopeMap = scopeBuildResult.getColumnToScopeMap(); 041 this.allColumnReferences = scopeBuildResult.getAllColumnReferences(); 042 } 043 044 // ==================== Statement-level query methods ==================== 045 046 @Override 047 public IScope getScope(TCustomSqlStatement stmt) { 048 if (stmt instanceof TSelectSqlStatement) { 049 return scopeBuildResult.getScopeForStatement((TSelectSqlStatement) stmt); 050 } 051 return null; 052 } 053 054 @Override 055 public List<TTable> getTables(TCustomSqlStatement stmt) { 056 return getTables(stmt, TableFilterOptions.DEFAULT); 057 } 058 059 @Override 060 public List<TTable> getTables(TCustomSqlStatement stmt, TableFilterOptions options) { 061 List<TTable> result = new ArrayList<>(); 062 063 if (stmt == null || stmt.tables == null) { 064 return result; 065 } 066 067 for (int i = 0; i < stmt.tables.size(); i++) { 068 TTable table = stmt.tables.getTable(i); 069 if (table == null) continue; 070 071 if (shouldIncludeTable(table, options)) { 072 result.add(table); 073 } 074 } 075 076 return result; 077 } 078 079 private boolean shouldIncludeTable(TTable table, TableFilterOptions options) { 080 ETableSource type = table.getTableType(); 081 082 // Skip JOIN (they are containers, not actual tables) 083 if (type == ETableSource.join) { 084 return false; 085 } 086 087 // Subquery filter 088 if (type == ETableSource.subquery && !options.isIncludeSubqueries()) { 089 return false; 090 } 091 092 // CTE filter 093 if (table.isCTEName() && !options.isIncludeCTEs()) { 094 return false; 095 } 096 097 // Function table filter 098 if (type == ETableSource.function && !options.isIncludeFunctionTables()) { 099 return false; 100 } 101 102 // Physical tables only filter 103 if (options.isPhysicalTablesOnly()) { 104 if (type != ETableSource.objectname || table.isCTEName()) { 105 return false; 106 } 107 } 108 109 return true; 110 } 111 112 @Override 113 public List<TObjectName> getColumns(TCustomSqlStatement stmt) { 114 List<TObjectName> result = new ArrayList<>(); 115 116 for (TObjectName col : allColumnReferences) { 117 if (belongsToStatement(col, stmt)) { 118 result.add(col); 119 } 120 } 121 122 return result; 123 } 124 125 @Override 126 public List<TObjectName> getColumnsForTable(TCustomSqlStatement stmt, TTable table) { 127 List<TObjectName> result = new ArrayList<>(); 128 129 for (TObjectName col : allColumnReferences) { 130 if (belongsToStatement(col, stmt) && col.getSourceTable() == table) { 131 result.add(col); 132 } 133 } 134 135 return result; 136 } 137 138 @Override 139 public List<TObjectName> getOrphanColumns(TCustomSqlStatement stmt) { 140 List<TObjectName> result = new ArrayList<>(); 141 142 for (TObjectName col : allColumnReferences) { 143 if (belongsToStatement(col, stmt) && col.getSourceTable() == null) { 144 result.add(col); 145 } 146 } 147 148 return result; 149 } 150 151 @Override 152 public List<TCTE> getCTEs(TCustomSqlStatement stmt) { 153 List<TCTE> result = new ArrayList<>(); 154 155 if (stmt instanceof TSelectSqlStatement) { 156 TSelectSqlStatement select = (TSelectSqlStatement) stmt; 157 if (select.getCteList() != null) { 158 for (int i = 0; i < select.getCteList().size(); i++) { 159 TCTE cte = select.getCteList().getCTE(i); 160 if (cte != null) { 161 result.add(cte); 162 } 163 } 164 } 165 } 166 167 return result; 168 } 169 170 /** 171 * Determine if a column belongs to the specified statement. 172 */ 173 private boolean belongsToStatement(TObjectName col, TCustomSqlStatement stmt) { 174 // First try scope mapping 175 IScope scope = columnToScopeMap.get(col); 176 if (scope instanceof SelectScope) { 177 SelectScope selectScope = (SelectScope) scope; 178 return selectScope.getNode() == stmt; 179 } 180 181 // Fall back to AST parent traversal 182 return isAncestor(stmt, col); 183 } 184 185 private boolean isAncestor(TCustomSqlStatement stmt, TObjectName col) { 186 Object parent = col.getParentObjectName(); 187 while (parent != null) { 188 if (parent == stmt) { 189 return true; 190 } 191 if (parent instanceof TParseTreeNode) { 192 parent = ((TParseTreeNode) parent).getParentObjectName(); 193 } else { 194 break; 195 } 196 } 197 return false; 198 } 199 200 // ==================== Global access methods ==================== 201 202 @Override 203 public Set<String> getAllTableNames() { 204 if (cachedTableNames == null) { 205 cachedTableNames = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); 206 collectAllTables(cachedTableNames); 207 } 208 return Collections.unmodifiableSet(cachedTableNames); 209 } 210 211 private void collectAllTables(Set<String> tables) { 212 for (int i = 0; i < statements.size(); i++) { 213 collectTablesRecursive(statements.get(i), tables); 214 } 215 } 216 217 private void collectTablesRecursive(TCustomSqlStatement stmt, Set<String> tables) { 218 if (stmt == null) return; 219 220 for (TTable table : getTables(stmt)) { 221 if (ResolutionUtils.isPhysicalTable(table)) { 222 tables.add(ResolutionUtils.getFullTableName(table)); 223 } 224 } 225 226 for (int i = 0; i < stmt.getStatements().size(); i++) { 227 collectTablesRecursive(stmt.getStatements().get(i), tables); 228 } 229 } 230 231 @Override 232 public Set<String> getAllFieldNames() { 233 if (cachedFieldNames == null) { 234 cachedFieldNames = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); 235 for (TObjectName col : allColumnReferences) { 236 if (col.getSourceTable() != null) { 237 cachedFieldNames.add(ResolutionUtils.getDisplayName(col)); 238 } 239 } 240 } 241 return Collections.unmodifiableSet(cachedFieldNames); 242 } 243 244 @Override 245 public Set<String> getAllCTENames() { 246 if (cachedCTENames == null) { 247 cachedCTENames = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); 248 Map<String, CTENamespace> cteNamespaces = scopeBuildResult.getCTENamespaces(); 249 if (cteNamespaces != null) { 250 cachedCTENames.addAll(cteNamespaces.keySet()); 251 } 252 } 253 return Collections.unmodifiableSet(cachedCTENames); 254 } 255 256 @Override 257 public List<TObjectName> getAllResolvedColumns() { 258 List<TObjectName> result = new ArrayList<>(); 259 for (TObjectName col : allColumnReferences) { 260 if (col.getSourceTable() != null) { 261 result.add(col); 262 } 263 } 264 return result; 265 } 266 267 @Override 268 public List<TObjectName> getAllUnresolvedColumns() { 269 List<TObjectName> result = new ArrayList<>(); 270 for (TObjectName col : allColumnReferences) { 271 if (col.getSourceTable() == null) { 272 result.add(col); 273 } 274 } 275 return result; 276 } 277 278 @Override 279 public ResolutionStatistics getStatistics() { 280 if (cachedStatistics == null) { 281 cachedStatistics = buildStatistics(); 282 } 283 return cachedStatistics; 284 } 285 286 private ResolutionStatistics buildStatistics() { 287 int total = allColumnReferences.size(); 288 int resolved = 0; 289 int ambiguous = 0; 290 int unresolved = 0; 291 int tables = 0; 292 293 for (TObjectName col : allColumnReferences) { 294 if (col.getSourceTable() != null) { 295 resolved++; 296 } else { 297 unresolved++; 298 } 299 } 300 301 for (int i = 0; i < statements.size(); i++) { 302 tables += countTablesRecursive(statements.get(i)); 303 } 304 305 // Use model.ResolutionStatistics constructor: 306 // (total, exact, ambiguous, unresolved, tableCount) 307 return new ResolutionStatistics(total, resolved, ambiguous, unresolved, tables); 308 } 309 310 private int countTablesRecursive(TCustomSqlStatement stmt) { 311 if (stmt == null) return 0; 312 int count = getTables(stmt, TableFilterOptions.PHYSICAL_ONLY).size(); 313 for (int i = 0; i < stmt.getStatements().size(); i++) { 314 count += countTablesRecursive(stmt.getStatements().get(i)); 315 } 316 return count; 317 } 318}