001package gudusoft.gsqlparser.resolver; 002 003import gudusoft.gsqlparser.*; 004import gudusoft.gsqlparser.compiler.TContext; 005import gudusoft.gsqlparser.compiler.TScope; 006import gudusoft.gsqlparser.nodes.*; 007import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType; 008import gudusoft.gsqlparser.sqlenv.TSQLEnv; 009import gudusoft.gsqlparser.stmt.TSelectSqlStatement; 010 011import java.util.Stack; 012 013/** 014 * TStarColumnExpander 负责解析和展开SQL查询中的星号列引用(*)。 015 * 016 * 工作流程: 017 * 1. 遍历所有SELECT语句 018 * 2. 对每个SELECT语句的结果列表进行处理 019 * 3. 识别并展开星号列引用: 020 * - 无限定符星号(*):展开为FROM子句中所有表的所有列 021 * - 限定符星号(table.*):展开为指定表的所有列 022 * 4. 处理EXCEPT子句,从展开的列中移除指定列 023 * 024 * Star column 解析后的列信息存储在 TObjectName 类的 attributeNodesDerivedFromFromClause 字段中: 025 * 026 * 处理示例: 027 * 1. SELECT * FROM employee 028 * -> SELECT employee.id, employee.name, employee.salary FROM employee 029 * 030 * 2. SELECT dept.* FROM department dept 031 * -> SELECT dept.id, dept.name, dept.location FROM department dept 032 * 033 * 3. SELECT emp.* EXCEPT (salary) FROM employee emp 034 * -> SELECT emp.id, emp.name FROM employee emp 035 * 036 * 关键组件: 037 * - sqlStack: 跟踪SELECT语句的嵌套层次 038 * - scopeStack: 维护解析过程中的作用域 039 * - globalScope: 存储全局SQL环境信息 040 * 041 * 与其他解析器的关系: 042 * - 在TRelationResolver之后执行 043 * - 为TAttributeResolver准备展开后的列信息 044 * - 依赖TDatabaseObjectResolver提供的表信息 045 * 046 */ 047public class TStarColumnExpander extends TParseTreeVisitor { 048 049 private final TContext globalScope; 050 private final TStatementList sqlStatements; 051 private final Stack<TScope> scopeStack; 052 private final Stack<TCustomSqlStatement> sqlStack; 053 private final TSQLResolver resolver; 054 055 /** 056 * Creates a new star column resolver instance. 057 * 058 * @param sqls The SQL statements to analyze 059 * @param scope The global context containing SQL environment 060 */ 061 public TStarColumnExpander(TStatementList sqls, TContext scope, TSQLResolver resolver) { 062 this.globalScope = scope; 063 this.sqlStatements = sqls; 064 this.scopeStack = new Stack<>(); 065 this.sqlStack = new Stack<>(); 066 this.resolver = resolver; 067 scopeStack.push(globalScope); 068 } 069 070 /** 071 * Main resolution method that processes all SQL statements. 072 * Iterates through statements and resolves star columns in each one. 073 */ 074 public void resolve() { 075 for (int i = 0; i < sqlStatements.size(); i++) { 076 sqlStatements.get(i).acceptChildren(this); 077 } 078 } 079 080 /** 081 * Removes the last part of a dotted name (e.g., "schema.table.column" -> "schema.table") 082 * 083 * @param sub The full dotted name 084 * @return The name without its last part 085 */ 086 private String removeLastDottedName(String sub) { 087 for (int i = sub.length() - 1; i >= 0; i--) { 088 if (sub.charAt(i) == '.') { 089 return sub.substring(0, i); 090 } 091 } 092 return sub; 093 } 094 095 /** 096 * Tracks SELECT statements for proper scoping of star column resolution. 097 * 098 * @param stmt The SELECT statement being processed 099 */ 100 public void preVisit(TSelectSqlStatement stmt) { 101 sqlStack.push(stmt); 102 } 103 104 /** 105 * Processes star columns in SELECT statements after their FROM clause is processed. 106 * Handles both unqualified (*) and qualified (table.*) star columns. 107 * 108 * @param stmt The SELECT statement being processed 109 */ 110 public void postVisit(TSelectSqlStatement stmt) { 111 // Skip processing if essential clauses are missing 112 if (stmt.getFromClause() == null || stmt.getResultColumnList() == null) { 113 sqlStack.pop(); 114 return; 115 } 116 117 // Skip for combined queries as they're handled through their rightmost subquery 118 if (stmt.isCombinedQuery()) { 119 sqlStack.pop(); 120 return; 121 } 122 123 processStarColumns(stmt); 124 sqlStack.pop(); 125 } 126 127 /** 128 * Processes star columns in a SELECT statement. 129 * Expands them to their actual column references. 130 */ 131 private void processStarColumns(TSelectSqlStatement stmt) { 132 for (TResultColumn resultColumn : stmt.getResultColumnList()) { 133 TObjectName objectName = resultColumn.getExpr().getObjectOperand(); 134 if (objectName == null) continue; 135 136 if (objectName.toString().equalsIgnoreCase("*")) { 137 // Handle unqualified star 138 handleUnqualifiedStar(objectName, stmt); 139 } else if (objectName.toString().endsWith(".*")) { 140 // Handle qualified star 141 handleQualifiedStar(objectName, stmt); 142 } 143 144 // Handle EXCEPT clause if present 145 if (resultColumn.getExceptColumnList() != null) { 146 handleExceptClause(resultColumn, objectName); 147 } 148 149 // Log star column information 150 logStarColumnInfo(objectName); 151 } 152 } 153 154 /** 155 * Handles unqualified star columns (*) by adding all available columns 156 * from the FROM clause. 157 * Star column 解析后的列信息存储在 TObjectName 类的 attributeNodesDerivedFromFromClause 字段中 158 */ 159 private void handleUnqualifiedStar(TObjectName objectName, TSelectSqlStatement stmt) { 160 int originalSize = objectName.getAttributeNodesDerivedFromFromClause().size(); 161 TAttributeNode.addAllNodesToList(stmt.getAttributes(), objectName.getAttributeNodesDerivedFromFromClause()); 162 resolver.reportNewColumns(objectName.getAttributeNodesDerivedFromFromClause().size() - originalSize); 163 } 164 165 /** 166 * Handles qualified star columns (table.*) by adding columns 167 * from the specified table. 168 */ 169 private void handleQualifiedStar(TObjectName objectName, TSelectSqlStatement stmt) { 170 String tablePrefix = removeLastDottedName(objectName.toString()); 171 int originalSize = objectName.getAttributeNodesDerivedFromFromClause().size(); 172 for (TAttributeNode attributeNode : stmt.getAttributes()) { 173 if (TSQLEnv.matchSubObjectNameToWhole( 174 stmt.dbvendor, 175 ESQLDataObjectType.dotTable, 176 tablePrefix, 177 removeLastDottedName(attributeNode.getName()))) { 178 TAttributeNode.addNodeToList(attributeNode, objectName.getAttributeNodesDerivedFromFromClause()); 179 } 180 } 181 resolver.reportNewColumns(objectName.getAttributeNodesDerivedFromFromClause().size() - originalSize); 182 } 183 184 /** 185 * Handles EXCEPT clause by removing specified columns from star expansion. 186 */ 187 private void handleExceptClause(TResultColumn resultColumn, TObjectName objectName) { 188 int removedCount = 0; 189 for (TObjectName exceptColumn : resultColumn.getExceptColumnList()) { 190 for (TAttributeNode node : objectName.getAttributeNodesDerivedFromFromClause()) { 191 if (node.getName().endsWith(exceptColumn.toString())) { 192 if (objectName.getAttributeNodesDerivedFromFromClause().remove(node)) { 193 removedCount++; 194 break; 195 } 196 } 197 } 198 } 199 200 if (removedCount > 0) { 201 resolver.reportNewColumns(removedCount); 202 if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){ 203 TBaseType.log( 204 String.format("%d attributes removed from star column due to EXCEPT clause", 205 removedCount), 206 TLog.DEBUG, 207 resultColumn.getStartToken() 208 ); 209 } 210 } 211 } 212 213 /** 214 * Logs information about resolved star columns for debugging. 215 */ 216 private void logStarColumnInfo(TObjectName objectName) { 217 if (objectName.toString().endsWith("*")) { 218 int count = 0; 219 if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){ 220 TBaseType.log( 221 String.format("Star column (num = %d) derived columns from FROM clause:", 222 objectName.getAttributeNodesDerivedFromFromClause().size()), 223 TLog.DEBUG, 224 objectName 225 ); 226 } 227 228 for (TAttributeNode node : objectName.getAttributeNodesDerivedFromFromClause()) { 229 logAttributeNode(node); 230 count++; 231 if (count > TLog.OUTPUT_ATTRIBUTES_MAX) { 232 if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){ 233 TBaseType.log( 234 String.format("\t...skipped after output %d attributes", count - 1), 235 TLog.DEBUG, 236 objectName.getStartToken() 237 ); 238 } 239 break; 240 } 241 } 242 } 243 } 244 245 /** 246 * Logs information about a single attribute node. 247 */ 248 private void logAttributeNode(TAttributeNode node) { 249 if (node.getSqlColumn() != null) { 250 if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){ 251 TBaseType.log( 252 String.format("\t<%s>, SQL column: <%s>", 253 node.getName(), 254 node.getSqlColumn() != null ? node.getSqlColumn().toString() : "N/A"), 255 TLog.DEBUG 256 ); 257 } 258 } else { 259 if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE) { 260 TBaseType.log( 261 String.format("\t<%s>, Select list column: <%s>", 262 node.getName(), 263 node.getSubLevelResultColumn() != null ? node.getSubLevelResultColumn().toString() : "N/A"), 264 TLog.DEBUG 265 ); 266 } 267 } 268 } 269 270 /** 271 * 处理表节点的后置访问,根据表的类型初始化其属性(即列信息)。 272 * 273 * 工作流程: 274 * 1. 根据表类型分别处理: 275 * - join: 处理连接表的属性 276 * - unnest: 处理数组展开的属性 277 * - subquery: 处理子查询的属性 278 * - objectname: 处理CTE引用的属性 279 * - pivoted_table: 处理透视表的属性 280 * 281 * 处理细节: 282 * 1. join类型表: 283 * - 调用initAttributesForJoin初始化连接后的属性 284 * - 合并左右表的属性 285 * 286 * 2. unnest类型表: 287 * - 在当前select语句上下文中初始化数组展开的属性 288 * - 使用SQL环境信息处理属性 289 * 290 * 3. subquery类型表: 291 * - 从子查询中收集属性 292 * - 如果有别名,添加别名前缀 293 * - 为后续的attribute resolve准备数据 294 * 295 * 4. objectname类型表(CTE引用): 296 * - 从CTE定义中初始化属性 297 * - 建立CTE引用和定义之间的属性映射 298 * 299 * 5. pivoted_table类型表: 300 * - 初始化透视表的属性 301 * - 处理透视操作产生的新列 302 * 303 * 属性初始化结果: 304 * - 存储在table.attributes中 305 * - 用于后续的属性解析 306 * - 支持列名解析和验证 307 * 308 * 调试信息: 309 * - 记录每种类型表的属性初始化过程 310 * - 输出属性列表供调试使用 311 * 312 * 注意事项: 313 * 1. 子查询属性需要考虑别名 314 * 2. CTE引用需要正确映射到CTE定义 315 * 3. 透视表可能产生动态列 316 * 4. 需要处理属性名冲突 317 * 318 * @param table 要处理的表节点 319 */ 320 321 322 323}