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            TSelectSqlStatement farLeftStmt = stmt.getFarLeftStmt();
126            TSelectSqlStatement rightStmt = stmt.getRightStmt();
127            if (farLeftStmt != null && rightStmt != null) {
128                TResultColumnList leftList = farLeftStmt.getResultColumnList();
129                TResultColumnList rightList = rightStmt.getResultColumnList();
130                int k = 0;
131                if (leftList != null && rightList != null){
132                    for (TResultColumn r : leftList) {
133                        k++;
134                        if (r.getAttributesFromUpLevelReferenceToStarColumn().size() == 0) continue;
135                        // System.out.println("Size of rightList: " + r.getAttributesFromUpLevelReferenceToStarColumn().size());
136                        if (k<rightList.size()) {
137                            // must be clear, otherwise, there will be duplicate columns. Not sure why,
138                            // please check this testcase: testPerformanceTeradata
139                            if (rightList.getResultColumn(k - 1) != null) {
140                                rightList.getResultColumn(k - 1).getAttributesFromUpLevelReferenceToStarColumn().clear();
141                            }
142                        }
143                        for (TObjectName o : r.getAttributesFromUpLevelReferenceToStarColumn()) {
144                            if (k-1 <= rightList.size() - 1){
145                                rightList.getResultColumn(k - 1).getAttributesFromUpLevelReferenceToStarColumn().add(o);
146                            }else{
147                                // select list in left select is not the same as the right select
148                                if (rightList.getResultColumn(0).toString().equalsIgnoreCase("*")){
149                                    // 使用第一个以 * 结尾的 result column
150                                    rightList.getResultColumn(0).getAttributesFromUpLevelReferenceToStarColumn().add(o);
151                                }
152                            }
153                        }
154                    }
155                }
156            }
157        }
158
159        //if (stmt.getResultColumnList() == null) return;
160
161        for (TResultColumn resultColumn : stmt.getResultColumnList()) {
162            if (!resultColumn.toString().endsWith("*")) continue; // 依次处理每个 * 字段,在select list中可能存在多个 * 字段, 例如 t1.*, t2.*
163
164            if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
165                TBaseType.log(String.format("Star column push down: %s, up level columns: %d", resultColumn.toString(), resultColumn.getAttributesFromUpLevelReferenceToStarColumn().size()), TLog.DEBUG, resultColumn.getStartToken());
166            }
167
168            TObjectName starColumn = resultColumn.getExpr().getObjectOperand();
169            // System.out.println("All candidates attributes of star column:"+starColumn.getCandidateAttributeNodes());
170            for (TObjectName o : resultColumn.getAttributesFromUpLevelReferenceToStarColumn()) {
171                // 依次处理从 up level 传下来的,关联到该 * 字段的每个 column
172                boolean matchedWithRegularColumn = false;
173                if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
174                    TBaseType.log(String.format("\tColumn from up level: %s", o.toString()), TLog.DEBUG, o.getStartToken());
175                }
176
177                // 该 * 字段包含的扩展至同层 from clause 的 source column,可能的形式为 t.*, t.c,
178                // 先和 t.c 类型的 source column 进行精准匹配,如果找到,则无需往下推
179                // 另一个情况是有DDL,* 已经被成功展开,这时也是可以被match到,不会在向上寻找。
180                for (TAttributeNode attributeNode : starColumn.getAttributeNodesDerivedFromFromClause()) {
181                    if (attributeNode.getName().toUpperCase().endsWith( TBaseType.getLastPartOfQualifiedName( o.toString().toUpperCase()))) {
182                        matchedWithRegularColumn = true;
183                        continue;
184                    }
185                }
186                if (matchedWithRegularColumn) continue;
187
188                // 没有和 t.c 类型的 source column 匹配上,接下来和 t.* 类型的 source column 进行匹配
189
190                // 计算有几个 table.*, 如果有超过一个, 则无法建立正确的关联,根据 TBaseType.GUESS_COLUMN_STRATEGY 来选择
191                int count = 0;
192                TAttributeNode pickOne = null;
193
194                for (TAttributeNode attributeNode : starColumn.getAttributeNodesDerivedFromFromClause()) {
195                    if (attributeNode.getName().endsWith("*")) {
196                        switch (TBaseType.GUESS_COLUMN_STRATEGY) {
197                            case TBaseType.GUESS_COLUMN_STRATEGY_NEAREST:
198                            case TBaseType.GUESS_COLUMN_STRATEGY_NOT_PICKUP:
199                                if (pickOne == null) pickOne = attributeNode;
200                                break;
201                            case TBaseType.GUESS_COLUMN_STRATEGY_FARTHEST:
202                                pickOne = attributeNode;
203                                break;
204                        }
205                        count++;
206                    }
207                }
208
209                // 确定 从 up level 传下来的 column 关联到 本层的哪个 table
210                if (pickOne == null) {
211                    if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
212                        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());
213                    }
214                    return;
215                }
216                TTable sourceTableOfStarColumn = pickOne.getTable_ref();
217
218                if (count > 1) {
219                    if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
220                        TBaseType.log(String.format("Star column link to %d candidates tables, guess strategy used to pickup table is: %s, current linked table is: %s, "
221                                    , count
222                                    , TBaseType.GUESS_COLUMN_STRATEGY_MSG[TBaseType.GUESS_COLUMN_STRATEGY]
223                                    , (TBaseType.GUESS_COLUMN_STRATEGY == TBaseType.GUESS_COLUMN_STRATEGY_NOT_PICKUP) ? "None" : sourceTableOfStarColumn.toString())
224                            , TLog.WARNING, starColumn);
225                    }
226                    // add this column to orphan column list
227                    o.setResolveStatus(TBaseType.RESOLVED_BUT_AMBIGUOUS);
228                    stmt.getAncestorStmt().getOrphanColumns().addObjectName(o);
229                    if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
230                        TBaseType.log(String.format("Add orphan column <%s> to statement in star column push down resolver in stmt", o.toString()), TLog.WARNING, stmt.getAncestorStmt());
231                    }
232                }
233
234                if ((count == 1)
235                        || (TBaseType.GUESS_COLUMN_STRATEGY == TBaseType.GUESS_COLUMN_STRATEGY_NEAREST)
236                        || (TBaseType.GUESS_COLUMN_STRATEGY == TBaseType.GUESS_COLUMN_STRATEGY_FARTHEST)
237                ) {
238
239                    stmt.setStarColumnPushedDown(true);
240                    switch (sourceTableOfStarColumn.getTableType()) {
241                        case objectname:
242                            if (sourceTableOfStarColumn.isCTEName()) {
243                                for (TAttributeNode node : sourceTableOfStarColumn.getAttributes()) {
244                                    node.addAttributeRefToThisNode(o);
245                                }
246                                if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
247                                    TBaseType.log(String.format("Push down column %s through CTE %s", o.toString(), sourceTableOfStarColumn.toString()), TLog.DEBUG, sourceTableOfStarColumn.getTableName());
248                                }
249                            } else {
250                                sourceTableOfStarColumn.getLinkedColumns().addObjectName(o,true);
251                                if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
252                                    TBaseType.log(String.format("link column %s pushed down to normal table: %s", o.toString(), sourceTableOfStarColumn.toString()), TLog.DEBUG, sourceTableOfStarColumn.getTableName());
253                                }
254                            }
255                            break;
256                        case subquery:
257                            // 如果 table 是 sub-query 类型,把从 up level 传下来的 column 关联到 sub-query 中去
258                            // 当遍历到 sub-query 时,可递归继续处理,直到找到关联的 real table
259                            if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
260                                TBaseType.log(String.format("Push down column %s through subquery %s", o.toString(), sourceTableOfStarColumn.getAliasName()), TLog.DEBUG, sourceTableOfStarColumn.getTableName());
261                            }
262                            for (TAttributeNode node : sourceTableOfStarColumn.getAttributes()) {
263                                node.addAttributeRefToThisNode(o); // push down column
264                            }
265                            break;
266                    }
267                }
268            }
269
270        }
271    }
272
273    /**
274     * 处理 CTE (Common Table Expression) 中的星号列
275     * 
276     * 工作流程:
277     * 1. 检查是否存在 CTE
278     * 2. 从后向前遍历 CTE 列表
279     * 3. 对每个 CTE:
280     *    - 处理其子查询中的星号列
281     *    - 特别处理 UNION 等集合操作的情况
282     *
283     * @param stmt SELECT 语句对象
284     */
285    public void postVisit(TSelectSqlStatement stmt) {
286        // CTE list 的每个 CTE 遍历次序按照sql的书写次序为从前到后,但是 star column 关联的column push down 需要从后往前推断,
287        // 因为后面的 cte from clause 可能引用前面 cte。 所有在这里遍历 CTE list,从后往前 push down star column 中关联的 column
288        // 以便关联到最后的真实table
289
290        // Skip if we've already processed this statement instance
291//        int stmtId = System.identityHashCode(stmt);
292//        if (!visitedStatements.contains(stmtId)) {
293//            visitedStatements.add(stmtId);
294//        }
295
296        // push down column in star column
297        if (stmt.getCteList() == null) return;
298
299
300        for(int i=stmt.getCteList().size()-1;i>=0;i--){
301            TCTE cte = stmt.getCteList().getCTE(i);
302            if (cte.getSubquery() == null) continue;
303            //System.out.println("CTE: "+cte.getTableName().toString());
304            if (!cte.getSubquery().isCombinedQuery()){
305                this.preVisit(cte.getSubquery());
306            }else{
307                // 如果是多个select 组成的 union集合,一开始只有最后一个 select 中有从上传 push down下来的数据
308                // 因此,需要把这些数据复制到每一个select中
309                ArrayList<TSelectSqlStatement> list = cte.getSubquery().getFlattenedSelects();
310                if (list == null || list.isEmpty()) continue;
311                TSelectSqlStatement last = list.get(0); //list.get(list.size()-1);
312                if (last == null) continue;
313
314                if (last.getResultColumnList() == null) continue;
315
316                int k = 0;
317                for (TResultColumn r : last.getResultColumnList()) {
318                    k++;
319                    if (r.getAttributesFromUpLevelReferenceToStarColumn().size() == 0) continue;
320                    for (TObjectName o : r.getAttributesFromUpLevelReferenceToStarColumn()) {
321                        //for(int j=list.size()-2;j>=0;j--){
322                        for(int j=1;j<list.size();j++){
323                            TSelectSqlStatement selectSqlStatement = list.get(j);
324                            if (selectSqlStatement == null) continue;
325                            if (selectSqlStatement.getResultColumnList() == null) continue;
326                            if (selectSqlStatement.getResultColumnList().getResultColumn(k - 1) == null) continue;
327                            if (selectSqlStatement.getResultColumnList().getResultColumn(k - 1).getAttributesFromUpLevelReferenceToStarColumn() == null) continue;
328                            selectSqlStatement.getResultColumnList().getResultColumn(k - 1).getAttributesFromUpLevelReferenceToStarColumn().add(o);
329                        }
330                    }
331                }
332
333                for(int k1=list.size()-1;k1>=0;k1--){
334                    TSelectSqlStatement selectStmt = list.get(k1);
335                    if (selectStmt != null) {
336                        this.preVisit(selectStmt); // 准备好 result column 中的 star column数据后,开始执行push down
337                    }
338                }
339            }
340        }
341    }
342}