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}