001package gudusoft.gsqlparser.resolver2.scope;
002
003import gudusoft.gsqlparser.nodes.TCTE;
004import gudusoft.gsqlparser.nodes.TCTEList;
005import gudusoft.gsqlparser.resolver2.ScopeType;
006import gudusoft.gsqlparser.resolver2.namespace.CTENamespace;
007import gudusoft.gsqlparser.resolver2.namespace.INamespace;
008
009import java.util.ArrayList;
010import java.util.LinkedHashMap;
011import java.util.List;
012import java.util.Map;
013
014/**
015 * Scope for Common Table Expressions (WITH clause).
016 * Manages visibility of CTEs defined in a WITH clause.
017 *
018 * <p>Example:
019 * <pre>
020 * WITH
021 *   cte1 AS (SELECT id, name FROM users),
022 *   cte2 AS (SELECT * FROM cte1 WHERE id > 100)  -- can reference cte1
023 * SELECT * FROM cte2;  -- can reference both cte1 and cte2
024 * </pre>
025 *
026 * <p>Key features:
027 * - Multiple CTEs in same WITH clause
028 * - CTEs can reference earlier CTEs in the same WITH clause
029 * - Recursive CTE support (same CTE references itself)
030 * - CTEs are visible to the main query and to later CTEs
031 */
032public class CTEScope extends ListBasedScope {
033
034    /**
035     * Map of CTE name to CTENamespace.
036     * Preserves insertion order to handle forward references correctly.
037     */
038    private final Map<String, CTENamespace> cteMap;
039
040    /**
041     * The WITH clause CTE list node
042     */
043    private final TCTEList cteList;
044
045    public CTEScope(IScope parent, TCTEList cteList) {
046        super(parent, cteList, ScopeType.CTE);
047        this.cteList = cteList;
048        this.cteMap = new LinkedHashMap<>();
049    }
050
051    /**
052     * Add a CTE to this scope.
053     * CTEs are added in order, allowing later CTEs to reference earlier ones.
054     *
055     * @param cteName the CTE name (alias)
056     * @param cteNamespace the CTE namespace
057     */
058    public void addCTE(String cteName, CTENamespace cteNamespace) {
059        if (cteName == null || cteNamespace == null) {
060            return;
061        }
062
063        // Store in map for name resolution
064        cteMap.put(cteName, cteNamespace);
065
066        // Add as child to ListBasedScope (makes it visible to parent queries)
067        // CTEs are always non-nullable
068        addChild(cteNamespace, cteName, false);
069    }
070
071    /**
072     * Check if a CTE with the given name exists in this scope.
073     *
074     * @param cteName the CTE name to check
075     * @return true if CTE exists, false otherwise
076     */
077    public boolean hasCTE(String cteName) {
078        if (cteName == null) {
079            return false;
080        }
081
082        // Use case-insensitive matching (standard SQL behavior)
083        for (String existingName : cteMap.keySet()) {
084            if (existingName.equalsIgnoreCase(cteName)) {
085                return true;
086            }
087        }
088
089        return false;
090    }
091
092    /**
093     * Get a CTE namespace by name.
094     *
095     * @param cteName the CTE name
096     * @return the CTENamespace, or null if not found
097     */
098    public CTENamespace getCTE(String cteName) {
099        if (cteName == null) {
100            return null;
101        }
102
103        // Use case-insensitive matching (standard SQL behavior)
104        for (Map.Entry<String, CTENamespace> entry : cteMap.entrySet()) {
105            String existingName = entry.getKey();
106            if (existingName.equalsIgnoreCase(cteName)) {
107                return entry.getValue();
108            }
109        }
110
111        return null;
112    }
113
114    /**
115     * Get all CTEs in this scope.
116     *
117     * @return list of all CTE namespaces in definition order
118     */
119    public List<CTENamespace> getAllCTEs() {
120        return new ArrayList<>(cteMap.values());
121    }
122
123    /**
124     * Get the number of CTEs in this scope.
125     *
126     * @return CTE count
127     */
128    public int getCTECount() {
129        return cteMap.size();
130    }
131
132    /**
133     * Resolve a table name, checking CTEs first before delegating to parent.
134     * This allows CTEs to shadow real tables.
135     *
136     * @param tableName the table name to resolve
137     * @return the namespace for the CTE or table, or null if not found
138     */
139    @Override
140    public INamespace resolveTable(String tableName) {
141        // First check if it's a CTE in this scope
142        CTENamespace cte = getCTE(tableName);
143        if (cte != null) {
144            return cte;
145        }
146
147        // Delegate to parent scope (might be a real table)
148        return super.resolveTable(tableName);
149    }
150
151    /**
152     * Check if this scope contains a recursive CTE.
153     * A recursive CTE is one that references itself in its definition.
154     *
155     * @return true if any CTE in this scope is recursive
156     */
157    public boolean hasRecursiveCTE() {
158        for (CTENamespace cte : cteMap.values()) {
159            if (cte.isRecursive()) {
160                return true;
161            }
162        }
163        return false;
164    }
165
166    /**
167     * Get all recursive CTEs in this scope.
168     *
169     * @return list of recursive CTE namespaces
170     */
171    public List<CTENamespace> getRecursiveCTEs() {
172        List<CTENamespace> recursive = new ArrayList<>();
173        for (CTENamespace cte : cteMap.values()) {
174            if (cte.isRecursive()) {
175                recursive.add(cte);
176            }
177        }
178        return recursive;
179    }
180
181    public TCTEList getCTEList() {
182        return cteList;
183    }
184
185    @Override
186    public String toString() {
187        StringBuilder sb = new StringBuilder("CTEScope(");
188        sb.append("count=").append(cteMap.size());
189
190        if (!cteMap.isEmpty()) {
191            sb.append(", ctes=[");
192            boolean first = true;
193            for (String cteName : cteMap.keySet()) {
194                if (!first) {
195                    sb.append(", ");
196                }
197                sb.append(cteName);
198                first = false;
199            }
200            sb.append("]");
201        }
202
203        if (hasRecursiveCTE()) {
204            sb.append(", recursive=true");
205        }
206
207        sb.append(")");
208        return sb.toString();
209    }
210}