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}