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}