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}