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}