001package gudusoft.gsqlparser.resolver2.expansion; 002 003import gudusoft.gsqlparser.nodes.TExpression; 004import gudusoft.gsqlparser.nodes.TObjectName; 005import gudusoft.gsqlparser.nodes.TResultColumn; 006import gudusoft.gsqlparser.resolver2.model.ColumnSource; 007import gudusoft.gsqlparser.resolver2.namespace.INamespace; 008 009import java.util.ArrayList; 010import java.util.List; 011import java.util.Map; 012 013/** 014 * Expands SELECT * to explicit column lists. 015 * 016 * <p>Star expansion is crucial for: 017 * - Understanding exactly which columns are selected 018 * - Data lineage tracking 019 * - Query refactoring 020 * - Schema evolution analysis 021 * 022 * <p>Supports multiple star expansion scenarios: 023 * 1. Simple star: SELECT * FROM t1 024 * 2. Qualified star: SELECT t1.* FROM t1 JOIN t2 025 * 3. Multiple stars: SELECT t1.*, t2.* FROM t1 JOIN t2 026 * 4. Mixed: SELECT t1.*, t2.id FROM t1 JOIN t2 027 * 5. Nested: SELECT * FROM (SELECT * FROM t1) sub 028 * 029 * <p>Example: 030 * <pre> 031 * -- Input: 032 * SELECT * FROM employees 033 * 034 * -- Expanded (if metadata available): 035 * SELECT employees.id, employees.name, employees.salary 036 * FROM employees 037 * </pre> 038 */ 039public class StarColumnExpander { 040 041 /** Whether to include table qualifiers in expanded columns */ 042 private final boolean includeTableQualifier; 043 044 /** Whether to expand stars in subqueries recursively */ 045 private final boolean expandSubqueries; 046 047 public StarColumnExpander() { 048 this(true, true); 049 } 050 051 public StarColumnExpander(boolean includeTableQualifier, boolean expandSubqueries) { 052 this.includeTableQualifier = includeTableQualifier; 053 this.expandSubqueries = expandSubqueries; 054 } 055 056 /** 057 * Expand a star column to explicit column list. 058 * 059 * @param starColumn the star column (SELECT *) 060 * @param namespace the namespace to expand from 061 * @return list of expanded columns, or empty if expansion not possible 062 */ 063 public List<ExpandedColumn> expandStar(TResultColumn starColumn, INamespace namespace) { 064 List<ExpandedColumn> expandedColumns = new ArrayList<>(); 065 066 if (starColumn == null || namespace == null) { 067 return expandedColumns; 068 } 069 070 // Check if this is actually a star column 071 if (!isStarColumn(starColumn)) { 072 return expandedColumns; 073 } 074 075 // Get all columns from the namespace 076 Map<String, ColumnSource> columnSources = namespace.getAllColumnSources(); 077 078 if (columnSources == null || columnSources.isEmpty()) { 079 return expandedColumns; 080 } 081 082 // Create expanded column for each source column 083 for (Map.Entry<String, ColumnSource> entry : columnSources.entrySet()) { 084 String columnName = entry.getKey(); 085 ColumnSource source = entry.getValue(); 086 087 ExpandedColumn expanded = new ExpandedColumn( 088 columnName, 089 source, 090 starColumn, 091 includeTableQualifier 092 ); 093 094 expandedColumns.add(expanded); 095 } 096 097 return expandedColumns; 098 } 099 100 /** 101 * Expand qualified star (e.g., t1.*) to explicit column list. 102 * 103 * @param starColumn the star column (SELECT t1.*) 104 * @param namespace the namespace to expand from 105 * @param tableAlias the table qualifier (e.g., "t1") 106 * @return list of expanded columns, or empty if expansion not possible 107 */ 108 public List<ExpandedColumn> expandQualifiedStar( 109 TResultColumn starColumn, 110 INamespace namespace, 111 String tableAlias) { 112 113 List<ExpandedColumn> expandedColumns = new ArrayList<>(); 114 115 if (starColumn == null || namespace == null || tableAlias == null) { 116 return expandedColumns; 117 } 118 119 // Get all columns from the namespace 120 Map<String, ColumnSource> columnSources = namespace.getAllColumnSources(); 121 122 if (columnSources == null || columnSources.isEmpty()) { 123 return expandedColumns; 124 } 125 126 // Create expanded column for each source column 127 for (Map.Entry<String, ColumnSource> entry : columnSources.entrySet()) { 128 String columnName = entry.getKey(); 129 ColumnSource source = entry.getValue(); 130 131 ExpandedColumn expanded = new ExpandedColumn( 132 columnName, 133 source, 134 starColumn, 135 tableAlias, 136 includeTableQualifier 137 ); 138 139 expandedColumns.add(expanded); 140 } 141 142 return expandedColumns; 143 } 144 145 /** 146 * Expand all stars in a result column list. 147 * 148 * @param resultColumns the result columns 149 * @param availableNamespaces list of available namespaces to expand from 150 * @return expansion result with all expanded columns 151 */ 152 public ExpansionResult expandAllStars( 153 List<TResultColumn> resultColumns, 154 List<INamespace> availableNamespaces) { 155 156 ExpansionResult result = new ExpansionResult(); 157 158 if (resultColumns == null || availableNamespaces == null) { 159 return result; 160 } 161 162 for (TResultColumn rc : resultColumns) { 163 if (isStarColumn(rc)) { 164 // This is a star column - expand it 165 if (isQualifiedStar(rc)) { 166 // Qualified star: t1.* 167 String tableAlias = getStarQualifier(rc); 168 INamespace targetNamespace = findNamespaceByAlias( 169 availableNamespaces, 170 tableAlias 171 ); 172 173 if (targetNamespace != null) { 174 List<ExpandedColumn> expanded = expandQualifiedStar( 175 rc, 176 targetNamespace, 177 tableAlias 178 ); 179 result.addExpandedStar(rc, expanded); 180 } else { 181 result.addUnexpandableStar(rc, "Namespace not found: " + tableAlias); 182 } 183 } else { 184 // Unqualified star: * 185 // Expand from all available namespaces 186 List<ExpandedColumn> allExpanded = new ArrayList<>(); 187 for (INamespace ns : availableNamespaces) { 188 List<ExpandedColumn> expanded = expandStar(rc, ns); 189 allExpanded.addAll(expanded); 190 } 191 192 if (!allExpanded.isEmpty()) { 193 result.addExpandedStar(rc, allExpanded); 194 } else { 195 result.addUnexpandableStar(rc, "No columns available for expansion"); 196 } 197 } 198 } else { 199 // Not a star column - keep as is 200 result.addNonStarColumn(rc); 201 } 202 } 203 204 return result; 205 } 206 207 /** 208 * Check if a result column is a star column (* or t.*) 209 */ 210 private boolean isStarColumn(TResultColumn rc) { 211 if (rc == null || rc.getExpr() == null) { 212 return false; 213 } 214 215 TExpression expr = rc.getExpr(); 216 if (expr.getExpressionType() != gudusoft.gsqlparser.EExpressionType.simple_object_name_t) { 217 return false; 218 } 219 220 TObjectName objName = expr.getObjectOperand(); 221 if (objName == null) { 222 return false; 223 } 224 225 String str = objName.toString(); 226 return str != null && (str.equals("*") || str.endsWith(".*")); 227 } 228 229 /** 230 * Check if a star column is qualified (t.*) 231 */ 232 private boolean isQualifiedStar(TResultColumn rc) { 233 if (!isStarColumn(rc)) { 234 return false; 235 } 236 237 TObjectName objName = rc.getExpr().getObjectOperand(); 238 String str = objName.toString(); 239 return str != null && str.endsWith(".*") && str.length() > 2; 240 } 241 242 /** 243 * Get the qualifier from a qualified star (t.* returns "t") 244 */ 245 private String getStarQualifier(TResultColumn rc) { 246 if (!isQualifiedStar(rc)) { 247 return null; 248 } 249 250 TObjectName objName = rc.getExpr().getObjectOperand(); 251 String str = objName.toString(); 252 if (str != null && str.endsWith(".*")) { 253 return str.substring(0, str.length() - 2); 254 } 255 256 return null; 257 } 258 259 /** 260 * Find a namespace by alias/name 261 */ 262 private INamespace findNamespaceByAlias(List<INamespace> namespaces, String alias) { 263 if (namespaces == null || alias == null) { 264 return null; 265 } 266 267 for (INamespace ns : namespaces) { 268 // Check if namespace display name matches 269 if (ns.getDisplayName() != null && 270 ns.getDisplayName().equalsIgnoreCase(alias)) { 271 return ns; 272 } 273 274 // For table namespaces, check table name/alias 275 if (ns.getFinalTable() != null) { 276 String tableName = ns.getFinalTable().getName(); 277 String tableAlias = ns.getFinalTable().getAliasName(); 278 279 if ((tableName != null && tableName.equalsIgnoreCase(alias)) || 280 (tableAlias != null && tableAlias.equalsIgnoreCase(alias))) { 281 return ns; 282 } 283 } 284 } 285 286 return null; 287 } 288}