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.stmt.TSelectSqlStatement; 008 009import java.util.ArrayList; 010import java.util.Stack; 011 012//import java.util.HashSet; 013//import java.util.Set; 014 015 016/** 017 * TStarColumnPushDownResolver 类负责解析和处理 SQL 查询中的星号(*)列引用, 018 * 将星号列引用追溯到实际的表列。 019 * 020 * 主要工作流程: 021 * 1. 从顶层查询开始,向下遍历所有子查询 022 * 2. 对每个查询中的星号列,找到其对应的实际列 023 * 3. 将这些列信息传递给下层查询 024 * 4. 最终将星号列解析到实际的表列 025 * 026 * 关键依赖类: 027 * - TContext: 提供全局作用域信息 028 * - TScope: 管理查询作用域 029 * - TSelectSqlStatement: 表示 SELECT 语句 030 * - TTable: 表示表引用 031 * - TResultColumn: 表示结果列 032 * - TObjectName: 表示对象名称 033 * - TAttributeNode: 表示属性节点 034 */ 035public class TStarColumnPushDownResolver extends TParseTreeVisitor { 036 037 /** 038 * 类的成员变量 039 */ 040 private TContext globalScope; // 全局作用域 041 private TStatementList sqlStatements; // SQL 语句列表 042 private Stack<TScope> scopeStack = new Stack<>(); // 作用域栈 043// private Set<Integer> visitedStatements = new HashSet<>(); // Track already visited statements 044 045 /** 046 * 构造函数 047 * @param sqls SQL语句列表 048 * @param scope 全局作用域 049 */ 050 public TStarColumnPushDownResolver(TStatementList sqls, TContext scope) { 051 this.globalScope = scope; 052 sqlStatements = sqls; 053 scopeStack.push(globalScope); 054 } 055 056 /** 057 * 开始解析过程 058 * 遍历所有 SQL 语句,对每个语句执行星号列下推解析 059 */ 060 public void resolve() { 061 // System.out.println("==== Start resolving star column:"); 062 063 for (int i = 0; i < sqlStatements.size(); i++) { 064 //System.out.println("\n==== Start SQL: "+ (i+1)); 065 sqlStatements.get(i).acceptChildren(this); 066 } 067 } 068 069 /** 070 * 处理 SELECT 语句中的星号列 071 * 072 * 工作流程: 073 * 1. 检查是否已处理过该语句 074 * 2. 处理 UNION 等集合操作的情况 075 * 3. 遍历结果列表中的星号列 076 * 4. 对每个星号列: 077 * - 检查是否有精确匹配的列 078 * - 处理多个表的星号情况 079 * - 根据表类型(实体表/子查询)进行相应处理 080 * 081 * @param stmt SELECT 语句对象 082 * 083 * 示例: 084 * SELECT col_1, col_11, col_2 085 * FROM ( 086 * select * 087 * FROM (select * from table_a) ta 088 * ) a 089 * LEFT OUTER JOIN ( 090 * select id, col_2 091 * FROM table_b 092 * ) b on a.id = b.id 093 */ 094 public void preVisit(TSelectSqlStatement stmt) { 095 096 // Check if we've already processed this statement instance 097// int stmtId = System.identityHashCode(stmt); 098// if (visitedStatements.contains(stmtId)) { 099// System.out.println("Statement already visited: " + stmt.getClass().getName() + "@" + Integer.toHexString(stmtId)); 100// return; 101// } 102// visitedStatements.add(stmtId); 103 104 // 把 select list 中 star column 包含的由上层传递下来的 column 关联到本语句 from clause 的 table中 105 // 如果 table 类型为 real table,直接把column关联给该table 106 // 如果 table 类型为 sub-query, 107// for (TTable table : stmt.getRelations()) { 108// TBaseType.log(String.format("List attribute nodes for %s", table.getDisplayName()), TLog.DEBUG, table.getTableName()); 109// int i = 0; 110// for (TAttributeNode node : table.getAttributes()) { 111// TBaseType.log(String.format("\t%d: %s, sub result column %s", i, node.getName(), (node.getSubLevelResultColumn() != null) ? node.getSubLevelResultColumn().toString() : "null"), TLog.DEBUG); 112// i++; 113// } 114// } 115 116 // cte 中的 sub-query 会被遍历两次,第一次只有一个 cte 的 subQuery 会被处理, 117 // 因为还没有从 up level push down column 到 star column,其他的cte subquery 不会被处理 118 // 在 postVisit(TSelectSqlStatement stmt) 进行第二次遍历时,其他的cte subquery 才被处理 119 // 为防止已经被处理的那个 cte subquery 重复处理,加这个判断 120 if (stmt.isStarColumnPushedDown()) return; 121 122 if (stmt.getResultColumnList() == null) return; 123 124 if (stmt.isCombinedQuery()) { 125 // 如果select是union等set操作,初始状态下只要 right stmt result column 中包含的上层传下来的getAttributesReferenceToStarColumn() 126 // 需要传递给 left stmt。 这个行为每次进入 select stmt都会执行,依次往下层走,是递归的,因此在这里每次只要处理本层的即可。 127 128 TResultColumnList rightList = stmt.getRightStmt().getResultColumnList(); 129 TResultColumnList leftList = stmt.getLeftStmt().getResultColumnList(); 130 int k = 0; 131 for (TResultColumn r : rightList) { 132 k++; 133 if (r.getAttributesFromUpLevelReferenceToStarColumn().size() == 0) continue; 134 // System.out.println("Size of rightList: " + r.getAttributesFromUpLevelReferenceToStarColumn().size()); 135 if (k<leftList.size()) { 136 // must be clear, otherwise, there will be duplicate columns. Not sure why, 137 // please check this testcase: testPerformanceTeradata 138 if (leftList.getResultColumn(k - 1) != null) { 139 leftList.getResultColumn(k - 1).getAttributesFromUpLevelReferenceToStarColumn().clear(); 140 } 141 } 142 for (TObjectName o : r.getAttributesFromUpLevelReferenceToStarColumn()) { 143 if (k-1 <= leftList.size() - 1){ 144 leftList.getResultColumn(k - 1).getAttributesFromUpLevelReferenceToStarColumn().add(o); 145 }else{ 146 // select list in left select is not the same as the right select 147 if (leftList.getResultColumn(0).toString().equalsIgnoreCase("*")){ 148 // 使用第一个以 * 结尾的 result column 149 leftList.getResultColumn(0).getAttributesFromUpLevelReferenceToStarColumn().add(o); 150 } 151 } 152 } 153 // System.out.println("Size of leftList: "+ k +"," + leftList.getResultColumn(k - 1).getAttributesFromUpLevelReferenceToStarColumn().size()); 154 } 155 156 157 } 158 159 // System.out.println("stmt memory address: " + stmt.getClass().getName() + "@" + Integer.toHexString(stmtId)+" line:"+stmt.getStartToken().lineNo+", column:"+stmt.getStartToken().columnNo); 160 161 162 //if (stmt.getResultColumnList() == null) return; 163 164 for (TResultColumn resultColumn : stmt.getResultColumnList()) { 165 if (!resultColumn.toString().endsWith("*")) continue; // 依次处理每个 * 字段,在select list中可能存在多个 * 字段, 例如 t1.*, t2.* 166 167 if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){ 168 TBaseType.log(String.format("Star column push down: %s, up level columns: %d", resultColumn.toString(), resultColumn.getAttributesFromUpLevelReferenceToStarColumn().size()), TLog.DEBUG, resultColumn.getStartToken()); 169 } 170 171 TObjectName starColumn = resultColumn.getExpr().getObjectOperand(); 172 // System.out.println("All candidates attributes of star column:"+starColumn.getCandidateAttributeNodes()); 173 for (TObjectName o : resultColumn.getAttributesFromUpLevelReferenceToStarColumn()) { 174 // 依次处理从 up level 传下来的,关联到该 * 字段的每个 column 175 boolean matchedWithRegularColumn = false; 176 if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){ 177 TBaseType.log(String.format("\tColumn from up level: %s", o.toString()), TLog.DEBUG, o.getStartToken()); 178 } 179 180 // 该 * 字段包含的扩展至同层 from clause 的 source column,可能的形式为 t.*, t.c, 181 // 先和 t.c 类型的 source column 进行精准匹配,如果找到,则无需往下推 182 // 另一个情况是有DDL,* 已经被成功展开,这时也是可以被match到,不会在向上寻找。 183 for (TAttributeNode attributeNode : starColumn.getAttributeNodesDerivedFromFromClause()) { 184 if (attributeNode.getName().toUpperCase().endsWith( TBaseType.getLastPartOfQualifiedName( o.toString().toUpperCase()))) { 185 matchedWithRegularColumn = true; 186 continue; 187 } 188 } 189 if (matchedWithRegularColumn) continue; 190 191 // 没有和 t.c 类型的 source column 匹配上,接下来和 t.* 类型的 source column 进行匹配 192 193 // 计算有几个 table.*, 如果有超过一个, 则无法建立正确的关联,根据 TBaseType.GUESS_COLUMN_STRATEGY 来选择 194 int count = 0; 195 TAttributeNode pickOne = null; 196 197 for (TAttributeNode attributeNode : starColumn.getAttributeNodesDerivedFromFromClause()) { 198 if (attributeNode.getName().endsWith("*")) { 199 switch (TBaseType.GUESS_COLUMN_STRATEGY) { 200 case TBaseType.GUESS_COLUMN_STRATEGY_NEAREST: 201 case TBaseType.GUESS_COLUMN_STRATEGY_NOT_PICKUP: 202 if (pickOne == null) pickOne = attributeNode; 203 break; 204 case TBaseType.GUESS_COLUMN_STRATEGY_FARTHEST: 205 pickOne = attributeNode; 206 break; 207 } 208 count++; 209 } 210 } 211 212 // 确定 从 up level 传下来的 column 关联到 本层的哪个 table 213 if (pickOne == null) { 214 if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){ 215 TBaseType.log(String.format("Can't derive source columns from from clause for star column, source column is: %s", o.toString()), TLog.ERROR,o.getStartToken()); 216 } 217 return; 218 } 219 TTable sourceTableOfStarColumn = pickOne.getTable_ref(); 220 221 if (count > 1) { 222 if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){ 223 TBaseType.log(String.format("Star column link to %d candidates tables, guess strategy used to pickup table is: %s, current linked table is: %s, " 224 , count 225 , TBaseType.GUESS_COLUMN_STRATEGY_MSG[TBaseType.GUESS_COLUMN_STRATEGY] 226 , (TBaseType.GUESS_COLUMN_STRATEGY == TBaseType.GUESS_COLUMN_STRATEGY_NOT_PICKUP) ? "None" : sourceTableOfStarColumn.toString()) 227 , TLog.WARNING, starColumn); 228 } 229 // add this column to orphan column list 230 o.setResolveStatus(TBaseType.RESOLVED_BUT_AMBIGUOUS); 231 stmt.getAncestorStmt().getOrphanColumns().addObjectName(o); 232 if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){ 233 TBaseType.log(String.format("Add orphan column <%s> to statement in star column push down resolver in stmt", o.toString()), TLog.WARNING, stmt.getAncestorStmt()); 234 } 235 } 236 237 if ((count == 1) 238 || (TBaseType.GUESS_COLUMN_STRATEGY == TBaseType.GUESS_COLUMN_STRATEGY_NEAREST) 239 || (TBaseType.GUESS_COLUMN_STRATEGY == TBaseType.GUESS_COLUMN_STRATEGY_FARTHEST) 240 ) { 241 242 stmt.setStarColumnPushedDown(true); 243 switch (sourceTableOfStarColumn.getTableType()) { 244 case objectname: 245 if (sourceTableOfStarColumn.isCTEName()) { 246 for (TAttributeNode node : sourceTableOfStarColumn.getAttributes()) { 247 node.addAttributeRefToThisNode(o); 248 } 249 if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){ 250 TBaseType.log(String.format("Push down column %s through CTE %s", o.toString(), sourceTableOfStarColumn.toString()), TLog.DEBUG, sourceTableOfStarColumn.getTableName()); 251 } 252 } else { 253 sourceTableOfStarColumn.getLinkedColumns().addObjectName(o); 254 if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){ 255 TBaseType.log(String.format("link column %s pushed down to normal table: %s", o.toString(), sourceTableOfStarColumn.toString()), TLog.DEBUG, sourceTableOfStarColumn.getTableName()); 256 } 257 } 258 break; 259 case subquery: 260 // 如果 table 是 sub-query 类型,把从 up level 传下来的 column 关联到 sub-query 中去 261 // 当遍历到 sub-query 时,可递归继续处理,直到找到关联的 real table 262 if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){ 263 TBaseType.log(String.format("Push down column %s through subquery %s", o.toString(), sourceTableOfStarColumn.getAliasName()), TLog.DEBUG, sourceTableOfStarColumn.getTableName()); 264 } 265 for (TAttributeNode node : sourceTableOfStarColumn.getAttributes()) { 266 node.addAttributeRefToThisNode(o); // push down column 267 } 268 break; 269 } 270 } 271 } 272 273 } 274 } 275 276 /** 277 * 处理 CTE (Common Table Expression) 中的星号列 278 * 279 * 工作流程: 280 * 1. 检查是否存在 CTE 281 * 2. 从后向前遍历 CTE 列表 282 * 3. 对每个 CTE: 283 * - 处理其子查询中的星号列 284 * - 特别处理 UNION 等集合操作的情况 285 * 286 * @param stmt SELECT 语句对象 287 */ 288 public void postVisit(TSelectSqlStatement stmt) { 289 // CTE list 的每个 CTE 遍历次序按照sql的书写次序为从前到后,但是 star column 关联的column push down 需要从后往前推断, 290 // 因为后面的 cte from clause 可能引用前面 cte。 所有在这里遍历 CTE list,从后往前 push down star column 中关联的 column 291 // 以便关联到最后的真实table 292 293 // Skip if we've already processed this statement instance 294// int stmtId = System.identityHashCode(stmt); 295// if (!visitedStatements.contains(stmtId)) { 296// visitedStatements.add(stmtId); 297// } 298 299 // push down column in star column 300 if (stmt.getCteList() == null) return; 301 302 303 for(int i=stmt.getCteList().size()-1;i>=0;i--){ 304 TCTE cte = stmt.getCteList().getCTE(i); 305 if (cte.getSubquery() == null) continue; 306 //System.out.println("CTE: "+cte.getTableName().toString()); 307 if (!cte.getSubquery().isCombinedQuery()){ 308 this.preVisit(cte.getSubquery()); 309 }else{ 310 // 如果是多个select 组成的 union集合,一开始只有最后一个 select 中有从上传 push down下来的数据 311 // 因此,需要把这些数据复制到每一个select中 312 ArrayList<TSelectSqlStatement> list = cte.getSubquery().getFlattenedSelects(); 313 TSelectSqlStatement last = list.get(list.size()-1); 314 315 if (last.getResultColumnList() == null) continue; 316 317 int k = 0; 318 for (TResultColumn r : last.getResultColumnList()) { 319 k++; 320 if (r.getAttributesFromUpLevelReferenceToStarColumn().size() == 0) continue; 321 for (TObjectName o : r.getAttributesFromUpLevelReferenceToStarColumn()) { 322 for(int j=list.size()-2;j>=0;j--){ 323 if (list.get(j).getResultColumnList() == null) continue; 324 if (list.get(j).getResultColumnList().getResultColumn(k - 1) == null) continue; 325 if (list.get(j).getResultColumnList().getResultColumn(k - 1).getAttributesFromUpLevelReferenceToStarColumn() == null) continue; 326 list.get(j).getResultColumnList().getResultColumn(k - 1).getAttributesFromUpLevelReferenceToStarColumn().add(o); 327 } 328 } 329 } 330 331 for(int k1=list.size()-1;k1>=0;k1--){ 332 this.preVisit(list.get(k1)); // 准备好 result column 中的 star column数据后,开始执行push down 333 } 334 } 335 } 336 } 337}