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