001package gudusoft.gsqlparser.util;
002
003
004import gudusoft.gsqlparser.*;
005import gudusoft.gsqlparser.nodes.*;
006import gudusoft.gsqlparser.resolver2.format.DisplayNameMode;
007import gudusoft.gsqlparser.resolver2.format.DisplayNameNormalizer;
008import gudusoft.gsqlparser.sqlenv.TSQLEnv;
009import gudusoft.gsqlparser.stmt.TStoredProcedureSqlStatement;
010import gudusoft.gsqlparser.util.joinRelationAnalyze;
011
012
013import java.util.ArrayList;
014import java.util.Collections;
015import java.util.Comparator;
016import java.util.Stack;
017
018class myMetaDB implements IMetaDatabase {
019
020    String columns[][] = {
021        {"server","db","DW","AcctInfo_PT","ACCT_ID"},
022        {"server","db","DW","ImSysInfo_BC","ACCT_ID"},
023        {"server","db","DW","AcctInfo_PT","SystemOfRec"},
024        {"server","db","DW","ImSysInfo_BC","SystemOfRec"},
025        {"server","db","DW","AcctInfo_PT","OfficerCode"},
026        {"server","db","DW","ImSysInfo_BC","OpeningDate"},
027    };
028
029    public boolean checkColumn(String server, String database,String schema, String table, String column){
030       boolean bServer,bDatabase,bSchema,bTable,bColumn,bRet = false;
031        for (int i=0; i<columns.length;i++){
032            if ((server == null)||(server.length() == 0)){
033                bServer = true;
034            }else{
035                bServer = columns[i][0].equalsIgnoreCase(server);
036            }
037            if (!bServer) continue;
038
039            if ((database == null)||(database.length() == 0)){
040                bDatabase = true;
041            }else{
042                bDatabase = columns[i][1].equalsIgnoreCase(database);
043            }
044            if (!bDatabase) continue;
045
046            if ((schema == null)||(schema.length() == 0)){
047                bSchema = true;
048            }else{
049                bSchema = columns[i][2].equalsIgnoreCase(schema);
050            }
051
052            if (!bSchema) continue;
053
054            bTable = columns[i][3].equalsIgnoreCase(table);
055            if (!bTable) continue;
056
057            bColumn = columns[i][4].equalsIgnoreCase(column);
058            if (!bColumn) continue;
059
060            bRet =true;
061            break;
062
063        }
064
065        return bRet;
066    }
067
068}
069
070class TInfoRecord {
071
072    public String getSPString(){
073        if (getSPName() == null) return "N/A";
074        else return getSPName().toString();
075    }
076
077    String getTableStr(gudusoft.gsqlparser.nodes.TTable table){
078        String tableName ="";
079        if (table.getTableType() == ETableSource.subquery) {
080            tableName = "(subquery, alias:" + table.getAliasName() + ")";
081        }else{
082            tableName = table.getTableName().toString();
083            if (table.isLinkTable()){
084                tableName = tableName+"("+table.getLinkTable().getTableName().toString()+")";
085            }else if (table.isCTEName()){
086                tableName = tableName+"(CTE)";
087            }
088        }
089
090        return tableName;
091    }
092
093    public String getFullColumnName(){
094        if (dbObjectType != EDbObjectType.column) return "";
095        String columnName = getColumn().getColumnNameOnly();
096
097        if (getTable() != null 
098                        && getTable( ).getObjectNameReferences( ).searchColumnReference( getColumn() )!=-1){
099            if (getTableStr(getTable()).length() > 0){
100                columnName = getTableStr(getTable())+"."+columnName;
101            }
102            schemaName = getTable().getPrefixSchema();
103            if (schemaName.length() > 0) {
104                columnName = schemaName+"."+columnName;
105            }
106            return columnName;
107        }
108        
109       return getColumn().toString( );
110    }
111
112    public String printMe(boolean includingTitle){
113        String spTitle="\nfilename|spname|object type\n";
114        String tableTitle="\nfilename|spname|object type|schema|table|table effect\n";
115        String columnTitle="\nfilename|spname|object type|schema|table|column|location|coordinate|datatype\n";
116        String indexTitle="\nfilename|spname|object type|schema|index|table|column|location|coordinate\n";
117
118        String schemaName = "N/A";
119        String tableName = "unknownTable";
120        String indexName = "unknownIndex";
121
122        StringBuffer  sb = new StringBuffer (1024);
123        switch (dbObjectType){
124            case procedure:
125                if (includingTitle) sb.append(spTitle);
126                sb.append(getFileName()+"|"+getSPName().toString()+"|"+dbObjectType);
127                break;
128            case table:
129                if (includingTitle) sb.append(tableTitle);
130
131                tableName = getTableStr(getTable());
132                schemaName = getTable().getPrefixSchema();
133                if (schemaName.length() == 0) schemaName ="N/A";
134
135                sb.append(getFileName()+"|"+getSPString()+"|"+dbObjectType+"|"+schemaName+"|"+tableName+"|"+getTable().getEffectType());
136                break;
137            case column:
138                if (includingTitle) sb.append(columnTitle);
139                if (getTable() != null){
140                    //it's an orphan column
141                    tableName = getTableStr(getTable());
142                    schemaName = getTable().getPrefixSchema();
143                    if (schemaName.length() == 0) schemaName ="N/A";
144                }else{
145                    tableName = "missed";
146                }
147
148                String datatypeStr = "";
149                if ((getColumn().getLinkedColumnDef() != null)){
150                    //column in create table, add datatype information as well
151                    TTypeName datatype = getColumn().getLinkedColumnDef().getDatatype();
152                    datatypeStr = datatype.getDataTypeName();
153                    if (datatype.getLength() != null){
154                        datatypeStr = datatypeStr+":"+datatype.getLength().toString();
155                    } else if (datatype.getPrecision() != null){
156                        datatypeStr = datatypeStr+":"+datatype.getPrecision().toString();
157                        if (datatype.getScale() != null){
158                            datatypeStr = datatypeStr+":"+datatype.getScale().toString();
159                        }
160                    }
161                }
162                sb.append(getFileName()+"|"+getSPString()+"|"+dbObjectType+"|"+schemaName+"|"+tableName+"|"+getColumn().toString()+"|"+getColumn().getLocation()+"|("+getColumn().coordinate()+")|"+datatypeStr);
163                break;
164            case index:
165                if (includingTitle) sb.append(indexTitle);
166                if (getTable() != null){
167                    schemaName = getTable().getPrefixSchema();
168                    if (schemaName.length() == 0) schemaName ="N/A";
169                    tableName = getTable().getTableName().toString();
170                }
171                if (getIndex() != null){
172                    indexName = getIndex().toString();
173                }
174                sb.append(getFileName()+"|"+getSPString()+"|"+dbObjectType+"|"+schemaName+"|"+indexName+"|"+tableName+"|"+getColumn().toString()+"|"+getColumn().getLocation()+"|("+getColumn().coordinate()+")");
175                break;
176        }
177
178        return sb.toString();
179    }
180
181    private TObjectName index;
182
183    public void setIndex(TObjectName index) {
184        this.index = index;
185    }
186
187    public TObjectName getIndex() {
188
189        return index;
190    }
191
192    private EDbObjectType dbObjectType;
193
194    public void setDbObjectType(EDbObjectType dbObjectType) {
195        this.dbObjectType = dbObjectType;
196    }
197
198    public EDbObjectType getDbObjectType() {
199
200        return dbObjectType;
201    }
202
203    public TInfoRecord(){
204
205    }
206
207    public TInfoRecord(EDbObjectType dbObjectType){
208        this.dbObjectType = dbObjectType;
209    }
210
211    public TInfoRecord(gudusoft.gsqlparser.nodes.TTable  table){
212      this.table = table;
213        this.dbObjectType = EDbObjectType.table;
214    }
215
216    public TInfoRecord(TInfoRecord clone, EDbObjectType dbObjectType){
217        this.fileName = clone.fileName;
218        this.SPName = clone.SPName;
219        this.table = clone.table;
220        this.column = clone.column;
221        this.dbObjectType = dbObjectType;
222    }
223
224    private String fileName = "N/A";
225    private TObjectName SPName ; //stored procedure name
226
227    public void setFileName(String fileName) {
228        this.fileName = fileName;
229    }
230
231    public void setSPName(TObjectName SPName) {
232        this.SPName = SPName;
233    }
234
235    public void setSchemaName(String schemaName) {
236        this.schemaName = schemaName;
237    }
238
239    public String getFileName() {
240
241        return fileName;
242    }
243
244    public TObjectName getSPName() {
245        return SPName;
246    }
247
248    public String getSchemaName() {
249        return schemaName;
250    }
251
252    private String schemaName;
253
254//    public String tableName;
255//    public String columnName;
256
257    private gudusoft.gsqlparser.nodes.TTable  table;
258
259    public void setTable(gudusoft.gsqlparser.nodes.TTable  table) {
260        this.table = table;
261    }
262
263    public void setColumn(TObjectName column) {
264        this.column = column;
265    }
266
267    public gudusoft.gsqlparser.nodes.TTable  getTable() {
268
269        return table;
270    }
271
272    public TObjectName getColumn() {
273        return column;
274    }
275
276    private TObjectName column;
277}
278
279/**
280 * 用于从SQL语句中提取表和列信息的工具类。
281 *
282 * <h2>表输出规则(基于 ETableSource 类型)</h2>
283 *
284 * <h3>默认行为</h3>
285 * <p>默认情况下,此工具<b>仅输出具有表名(getTableName() != null)且不在排除列表中的表</b>。
286 * 以下 {@link ETableSource} 类型的表在默认情况下<b>不会</b>出现在输出的表列表中:</p>
287 * <ul>
288 *   <li>{@link ETableSource#subquery} - 子查询(始终排除)</li>
289 *   <li>{@link ETableSource#openquery} - OPENQUERY 表(始终排除)</li>
290 *   <li>{@link ETableSource#function} - 表值函数(始终排除)</li>
291 *   <li>{@link ETableSource#unnest} - UNNEST 表(因为 getTableName() 为 null,使用别名)</li>
292 *   <li>{@link ETableSource#tableExpr} - 表表达式(因为 getTableName() 为 null,使用别名)</li>
293 *   <li>CTE(公用表表达式)引用的表 - 默认排除,可通过 {@link #showCTE} 控制</li>
294 * </ul>
295 *
296 * <p>以下 {@link ETableSource} 类型的表<b>会</b>出现在默认输出中(如果 getTableName() 不为 null):</p>
297 * <ul>
298 *   <li>{@link ETableSource#objectname} - 普通基础表(始终输出)</li>
299 *   <li>{@link ETableSource#pivoted_table} - PIVOT 表</li>
300 *   <li>{@link ETableSource#lateralView} - LATERAL VIEW 表</li>
301 * </ul>
302 *
303 * <h3>控制选项</h3>
304 *
305 * <h4>1. showCTE 选项</h4>
306 * <p>控制是否输出 CTE(公用表表达式)相关的表:</p>
307 * <ul>
308 *   <li>{@code showCTE = false}(默认):CTE 表不会出现在输出中</li>
309 *   <li>{@code showCTE = true}:CTE 表会出现在输出中</li>
310 * </ul>
311 *
312 * <h4>2. onlyPhysicalTables 选项</h4>
313 * <p>控制是否仅输出物理表(与 {@link gudusoft.gsqlparser.resolver2.TSQLResolver2ResultFormatter} 的 onlyPhysicalTables 选项兼容):</p>
314 * <ul>
315 *   <li>{@code onlyPhysicalTables = false}(默认):输出所有符合条件的表</li>
316 *   <li>{@code onlyPhysicalTables = true}:<b>仅输出 {@link ETableSource#objectname} 类型的物理表</b>,
317 *       排除以下内容:
318 *       <ul>
319 *         <li>{@link ETableSource#unnest} - UNNEST 表</li>
320 *         <li>{@link ETableSource#pivoted_table} - PIVOT 表</li>
321 *         <li>{@link ETableSource#lateralView} - LATERAL VIEW 表</li>
322 *         <li>CTE(公用表表达式)引用的表</li>
323 *       </ul>
324 *   </li>
325 * </ul>
326 *
327 * <h3>如何输出所有类型的表</h3>
328 * <p>要在输出中包含CTE表,请设置 {@link #showCTE} = true:</p>
329 * <pre>{@code
330 * TGetTableColumn getTableColumn = new TGetTableColumn(EDbVendor.dbvoracle);
331 * getTableColumn.showCTE = true;  // 启用CTE表的输出
332 * getTableColumn.runText(sql);
333 * }</pre>
334 *
335 * <p><b>注意:</b>即使设置了 showCTE = true,以下类型的表仍然不会出现在表列表中:</p>
336 * <ul>
337 *   <li>{@link ETableSource#subquery} - 子查询(不是真正的物理表)</li>
338 *   <li>{@link ETableSource#openquery} - OPENQUERY 表</li>
339 *   <li>{@link ETableSource#function} - 表值函数</li>
340 *   <li>{@link ETableSource#unnest} - UNNEST 表(getTableName() 为 null)</li>
341 *   <li>{@link ETableSource#tableExpr} - 表表达式(getTableName() 为 null)</li>
342 * </ul>
343 *
344 * <h3>ETableSource 类型与输出对照表</h3>
345 * <p><b>注意:</b>此表显示的是默认配置下的行为。showUnnest、showPivotTable、showLateralView 默认都为 true,
346 * 所以即使 onlyPhysicalTables=true,这三种表类型默认也会被输出。</p>
347 * <table border="1">
348 *   <tr><th>ETableSource 类型</th><th>默认输出</th><th>showCTE=true</th><th>onlyPhysicalTables=true (默认 show*=true)</th><th>原因</th></tr>
349 *   <tr><td>objectname</td><td>✓ 输出</td><td>✓ 输出</td><td>✓ 输出</td><td>普通基础表</td></tr>
350 *   <tr><td>subquery</td><td>✗ 不输出</td><td>✗ 不输出</td><td>✗ 不输出</td><td>显式排除</td></tr>
351 *   <tr><td>openquery</td><td>✗ 不输出</td><td>✗ 不输出</td><td>✗ 不输出</td><td>显式排除</td></tr>
352 *   <tr><td>function</td><td>✗ 不输出</td><td>✗ 不输出</td><td>✗ 不输出</td><td>显式排除</td></tr>
353 *   <tr><td>CTE 表</td><td>✗ 不输出</td><td>✓ 输出</td><td>✗ 不输出</td><td>由 showCTE 控制,onlyPhysicalTables 排除</td></tr>
354 *   <tr><td>unnest</td><td>✗ 不输出</td><td>✗ 不输出</td><td>✓ 输出</td><td>由 showUnnest 控制(默认 true)</td></tr>
355 *   <tr><td>tableExpr</td><td>✗ 不输出</td><td>✗ 不输出</td><td>✗ 不输出</td><td>getTableName() 为 null</td></tr>
356 *   <tr><td>pivoted_table</td><td>✓ 输出</td><td>✓ 输出</td><td>✓ 输出</td><td>由 showPivotTable 控制(默认 true)</td></tr>
357 *   <tr><td>lateralView</td><td>✓ 输出</td><td>✓ 输出</td><td>✓ 输出</td><td>由 showLateralView 控制(默认 true)</td></tr>
358 * </table>
359 *
360 * <h3>其他相关选项</h3>
361 * <ul>
362 *   <li>{@link #showCTE} - 设置为 true 时,CTE表会包含在输出中</li>
363 *   <li>{@link #onlyPhysicalTables} - 设置为 true 时,仅输出 objectname 类型的物理表</li>
364 *   <li>{@link #showUnnest} - 设置为 true 时(默认),即使 onlyPhysicalTables=true,UNNEST 表也会被输出</li>
365 *   <li>{@link #showPivotTable} - 设置为 true 时(默认),即使 onlyPhysicalTables=true,PIVOT 表也会被输出</li>
366 *   <li>{@link #showLateralView} - 设置为 true 时(默认),即使 onlyPhysicalTables=true,LATERAL VIEW 表也会被输出</li>
367 *   <li>{@link #showColumnsOfCTE} - 设置为 true 时,显示CTE中的列</li>
368 *   <li>{@link #showTableEffect} - 设置为 true 时,显示表的操作类型(SELECT、INSERT、UPDATE、DELETE等)</li>
369 *   <li>{@link #showDetail} - 设置为 true 时,显示详细信息</li>
370 *   <li>{@link #showSummary} - 设置为 true 时(默认),显示汇总信息</li>
371 * </ul>
372 *
373 * <h3>示例</h3>
374 * <pre>{@code
375 * // 示例1:仅获取物理表(默认行为)
376 * TGetTableColumn gtc = new TGetTableColumn(EDbVendor.dbvoracle);
377 * gtc.runText("SELECT * FROM employees e, (SELECT * FROM departments) d");
378 * // 输出仅包含: employees(子查询 departments 不会出现)
379 *
380 * // 示例2:包含CTE表
381 * TGetTableColumn gtc2 = new TGetTableColumn(EDbVendor.dbvoracle);
382 * gtc2.showCTE = true;
383 * gtc2.runText("WITH cte AS (SELECT * FROM t1) SELECT * FROM cte, t2");
384 * // 输出包含: t1, t2, cte(因为 showCTE = true)
385 *
386 * // 示例3:仅输出物理表,但保留 PIVOT 表(默认行为)
387 * TGetTableColumn gtc3 = new TGetTableColumn(EDbVendor.dbvoracle);
388 * gtc3.onlyPhysicalTables = true;
389 * gtc3.runText("SELECT * FROM t1 PIVOT (SUM(amount) FOR month IN ('Jan', 'Feb'))");
390 * // 输出包含: t1 和 PIVOT 表(因为 showPivotTable 默认为 true)
391 *
392 * // 示例4:仅输出物理表,排除 PIVOT 表
393 * TGetTableColumn gtc4 = new TGetTableColumn(EDbVendor.dbvoracle);
394 * gtc4.onlyPhysicalTables = true;
395 * gtc4.showPivotTable = false;  // 排除 PIVOT 表
396 * gtc4.runText("SELECT * FROM t1 PIVOT (SUM(amount) FOR month IN ('Jan', 'Feb'))");
397 * // 输出仅包含: t1(PIVOT 表被排除)
398 * }</pre>
399 *
400 * @see ETableSource
401 * @see ETableSource#objectname
402 */
403public class TGetTableColumn{
404
405
406    private EDbVendor dbVendor;
407    private String queryStr;
408    private TGSqlParser sqlParser;
409    private EResolverType resolverType = EResolverType.RESOLVER; //default to resolver
410
411    public void setResolverType(EResolverType resolverType) {
412        this.resolverType = resolverType;
413    }
414
415    private IMetaDatabase metaDatabase = null;
416
417    public void setMetaDatabase(IMetaDatabase metaDatabase) {
418        this.metaDatabase = metaDatabase;
419        sqlParser.setMetaDatabase(metaDatabase);
420    }
421
422    private TSQLEnv sqlEnv = null;
423
424    public void setSqlEnv(TSQLEnv sqlEnv) {
425        this.sqlEnv = sqlEnv;
426        sqlParser.setSqlEnv(sqlEnv);
427    }
428
429    private StringBuffer functionlist,schemalist,
430    triggerlist,sequencelist,databaselist;
431
432    public StringBuffer infos;
433
434    public StringBuffer outList;
435
436    private ArrayList<TInfoRecord> infoList;
437
438    private ArrayList<String> fieldlist,tablelist,indexList,cteList;
439
440    private StringBuffer tableColumnList;
441
442    private String newline  = "\n";
443
444    private String sqlFileName ="N/A";
445
446    public boolean isConsole;
447    public boolean listStarColumn;
448
449    public boolean showTableEffect;
450    public boolean showColumnLocation;
451    public boolean showDatatype;
452    public boolean showIndex;
453    public boolean showColumnsOfCTE;
454    public boolean showStarColumnOfCTE;
455        public boolean linkOrphanColumnToFirstTable;
456    public boolean showDetail = false;
457    public boolean showSummary = true;
458    public boolean showTreeStructure = false;
459    public boolean showBySQLClause = false;
460    public boolean showJoin = false;
461    public boolean showCTE = false;
462    /**
463     * 控制是否仅输出物理表(与 {@link gudusoft.gsqlparser.resolver2.TSQLResolver2ResultFormatter} 的 onlyPhysicalTables 选项兼容)。
464     * <p>
465     * 当设置为 {@code true} 时,<b>仅输出 {@link ETableSource#objectname} 类型的物理表</b>,
466     * 排除以下类型:
467     * <ul>
468     *   <li>{@link ETableSource#unnest} - UNNEST 表</li>
469     *   <li>{@link ETableSource#pivoted_table} - PIVOT 表</li>
470     *   <li>{@link ETableSource#lateralView} - LATERAL VIEW 表</li>
471     *   <li>CTE(公用表表达式)引用的表</li>
472     * </ul>
473     * </p>
474     * <p>默认值:{@code false}(输出所有符合条件的表,包括 unnest、pivoted_table、lateralView 等)</p>
475     *
476     * @see gudusoft.gsqlparser.resolver2.TSQLResolver2ResultFormatter#setOnlyPhysicalTables(boolean)
477     */
478    public boolean onlyPhysicalTables = false;
479
480    /**
481     * 当设置为 {@code true}(默认)时,即使 {@code onlyPhysicalTables=true},UNNEST 表也会被输出。
482     * 当设置为 {@code false} 时,如果 {@code onlyPhysicalTables=true},UNNEST 表会被排除。
483     *
484     * @see gudusoft.gsqlparser.resolver2.TSQLResolver2ResultFormatter#setShowUnnest(boolean)
485     */
486    public boolean showUnnest = true;
487
488    /**
489     * 当设置为 {@code true}(默认)时,即使 {@code onlyPhysicalTables=true},PIVOT 表也会被输出。
490     * 当设置为 {@code false} 时,如果 {@code onlyPhysicalTables=true},PIVOT 表会被排除。
491     *
492     * @see gudusoft.gsqlparser.resolver2.TSQLResolver2ResultFormatter#setShowPivotTable(boolean)
493     */
494    public boolean showPivotTable = true;
495
496    /**
497     * 当设置为 {@code true}(默认)时,即使 {@code onlyPhysicalTables=true},LATERAL VIEW 表也会被输出。
498     * 当设置为 {@code false} 时,如果 {@code onlyPhysicalTables=true},LATERAL VIEW 表会被排除。
499     *
500     * @see gudusoft.gsqlparser.resolver2.TSQLResolver2ResultFormatter#setShowLateralView(boolean)
501     */
502    public boolean showLateralView = true;
503
504    private Stack<TStoredProcedureSqlStatement> spList;
505
506    /** Display name normalizer for stripping delimiters (same as TSQLResolver2ResultFormatter) */
507    private final DisplayNameNormalizer displayNameNormalizer;
508
509    /** Display name mode (DISPLAY, SQL_RENDER, CANONICAL) - can be changed after instantiation */
510    private DisplayNameMode displayNameMode = DisplayNameMode.SQL_RENDER;
511
512    class myTokenListHandle implements ITokenListHandle {
513        // 把 ${tx_date_yyyymm} 合并为一个token,token code为 TBasetype.ident
514        public boolean processTokenList(TSourceTokenList sourceTokenList){
515            int startIndex = -1;
516            int endIndex = -1;
517
518            for(int i=0;i< sourceTokenList.size();i++) {
519                TSourceToken token = sourceTokenList.get(i);
520
521                // Check for '$' followed immediately by '{'
522                if (token.tokencode == 36) { // Check for '$'
523                    if (i + 1 < sourceTokenList.size() && sourceTokenList.get(i + 1).tokencode == 123) { // Check for '{' immediately after '$'
524                        startIndex = i;
525                    }
526                } else if (token.tokencode == 125 && startIndex != -1) { // Check for '}'
527                    endIndex = i;
528
529                }
530
531
532                if (startIndex != -1 && endIndex != -1) {
533                    TSourceToken firstToken = sourceTokenList.get(startIndex);
534                    firstToken.tokencode = TBaseType.ident;
535                    for (int j = startIndex + 1; j <= endIndex; j++) {
536                        TSourceToken st = sourceTokenList.get(j);
537                        st.tokenstatus = ETokenStatus.tsdeleted;
538                        firstToken.setString(firstToken.getAstext() + st.getAstext());
539                    }
540
541                    //System.out.println("Found variable token: " + firstToken.toStringDebug());
542
543                    startIndex = -1;
544                    endIndex = -1;
545                }
546            }
547            return true;
548        }
549    }
550
551    String dotChar = ".";
552    public  TGetTableColumn(EDbVendor pDBVendor){
553       dbVendor = pDBVendor;
554
555        sqlParser = new TGSqlParser(dbVendor);
556        // sqlParser.setTokenListHandle(new myTokenListHandle());
557        //sqlParser.setMetaDatabase(new myMetaDB());
558
559        if (TBaseType.isEnableResolver2()) {
560            resolverType = EResolverType.RESOLVER2;
561        } else if (TBaseType.isEnableResolver()) {
562            resolverType = EResolverType.RESOLVER;
563        }
564
565        if (dbVendor == EDbVendor.dbvdax){
566            dotChar = "";
567        }
568        tablelist = new ArrayList<String>();
569        fieldlist = new ArrayList<String>();
570        indexList = new ArrayList<String>();
571        cteList = new ArrayList<String>();
572        infoList = new ArrayList<TInfoRecord>();
573
574        spList = new Stack<TStoredProcedureSqlStatement>();
575
576         infos = new StringBuffer();
577         functionlist = new StringBuffer();
578         schemalist = new StringBuffer();
579         triggerlist = new StringBuffer();
580         sequencelist = new StringBuffer();
581         databaselist  = new StringBuffer();
582        tableColumnList  = new StringBuffer();
583        outList  = new StringBuffer();
584
585        isConsole = true;
586        listStarColumn = false;
587        showTreeStructure = false;
588        showTableEffect = false;
589        showColumnLocation = false;
590        linkOrphanColumnToFirstTable = true;
591        showDatatype = false;
592        showIndex = false;
593        showCTE = false;
594        showColumnsOfCTE = false;
595        showColumnsOfCTE = false;
596
597        // Initialize display name normalizer for consistent identifier formatting
598        displayNameNormalizer = new DisplayNameNormalizer(dbVendor);
599        displayNameNormalizer.setMode(displayNameMode);
600        displayNameNormalizer.setStripDelimiters(true);
601    }
602
603    // ========== Display Name Mode Configuration ==========
604
605    /**
606     * Get the current display name mode.
607     *
608     * @return the current DisplayNameMode
609     */
610    public DisplayNameMode getDisplayNameMode() {
611        return displayNameMode;
612    }
613
614    /**
615     * Set the display name mode for identifier formatting.
616     *
617     * <p>This controls how identifiers (table names, column names) are formatted in output:</p>
618     * <ul>
619     *   <li>{@link DisplayNameMode#DISPLAY} - Strip delimiters, preserve original case
620     *       (e.g., {@code [OrderID]} → {@code OrderID})</li>
621     *   <li>{@link DisplayNameMode#SQL_RENDER} - Preserve delimiters for valid SQL regeneration
622     *       (e.g., {@code [Order ID]} → {@code [Order ID]})</li>
623     *   <li>{@link DisplayNameMode#CANONICAL} - Apply vendor-specific case folding
624     *       (e.g., Oracle: {@code MyTable} → {@code MYTABLE})</li>
625     * </ul>
626     *
627     * <p>This method can be called after instantiation to change the mode for each SQL text
628     * being processed.</p>
629     *
630     * @param mode the DisplayNameMode to use
631     * @return this instance for method chaining
632     */
633    public TGetTableColumn setDisplayNameMode(DisplayNameMode mode) {
634        this.displayNameMode = mode != null ? mode : DisplayNameMode.DISPLAY;
635        this.displayNameNormalizer.setMode(this.displayNameMode);
636        return this;
637    }
638
639    public void runText(String pQuery){
640        run(pQuery,false);
641    }
642
643    public void runFile(String pFileName){
644        sqlFileName = pFileName;
645        run(pFileName,true);
646    }
647
648    String numberOfSpace(int pNum){
649        String ret="";
650        for(int i=0;i<pNum;i++){
651            ret = ret+" ";
652        }
653        return ret;
654    }
655
656    public StringBuffer getInfos() {
657        return infos;
658    }
659
660
661    protected void run(String pQuery, boolean isFile){
662        queryStr = pQuery;
663        if (isFile) sqlParser.sqlfilename = pQuery;
664        else sqlParser.sqltext = pQuery;
665        sqlParser.setResolverType(this.resolverType);
666
667        int iRet = sqlParser.parse();
668        if (iRet != 0){
669                if(isConsole)
670                        System.out.println(sqlParser.getErrormessage());
671                else 
672                        throw new RuntimeException(sqlParser.getErrormessage());
673            return;
674        }
675
676        outList.setLength(0);
677        tablelist.clear();
678        fieldlist.clear();
679        indexList.clear();
680        cteList.clear();
681
682        for(int i=0;i<sqlParser.sqlstatements.size();i++){
683            analyzeStmt(sqlParser.sqlstatements.get(i),0);
684        }
685
686
687       // print detailed info
688        if (showDetail){
689            boolean includingTitle = true;
690            for(int i=0;i<infoList.size();i++){
691                if (i>0){
692                    includingTitle = !(infoList.get(i).getDbObjectType() == infoList.get(i-1).getDbObjectType());
693                }
694                                outputResult( infoList.get( i ).printMe( includingTitle ) );
695            }
696        }
697
698        // print summary info
699        if (showSummary){
700            removeDuplicateAndSort(tablelist);
701            removeDuplicateAndSort(fieldlist);
702            removeDuplicateAndSort(indexList);
703            removeDuplicateAndSort(cteList);
704
705            printArray("Tables:", tablelist);
706            outputResult("");
707            printArray("Fields:",fieldlist);
708            if(showIndex && (indexList.size() > 0)){
709                printArray("Indexs:",indexList);
710            }
711
712            if(showColumnsOfCTE && (cteList.size() > 0)){
713                outputResult("");
714                printArray("Ctes:",cteList);
715            }
716        }
717
718        // print tree structure
719        if (showTreeStructure){
720                        outputResult( infos.toString( ) );
721        }
722
723        if (showBySQLClause){
724            ArrayList<ETableEffectType> tableEffectTypes = new ArrayList<ETableEffectType>();
725            ArrayList<ESqlClause> columnClauses = new ArrayList<ESqlClause>();
726
727            for(int i=0;i<infoList.size();i++){
728                if (infoList.get(i).getDbObjectType() == EDbObjectType.table){
729                    if (!tableEffectTypes.contains(infoList.get(i).getTable().getEffectType())){
730                        tableEffectTypes.add(infoList.get(i).getTable().getEffectType());
731                    }
732                }
733            }
734            outputResult("Tables:");
735            for(int j=0;j<tableEffectTypes.size();j++){
736                outputResult("\t"+tableEffectTypes.get(j).toString());
737
738                for(int i=0;i<infoList.size();i++){
739                    if (infoList.get(i).getDbObjectType() == EDbObjectType.table){
740                        gudusoft.gsqlparser.nodes.TTable  lcTable = infoList.get(i).getTable();
741                        if (lcTable.getEffectType() == tableEffectTypes.get(j) && lcTable.getSubquery()==null && lcTable.getTableName()!=null){
742                                outputResult("\t\t" + lcTable.toString()+ "("+lcTable.getTableName().coordinate()+")");
743                        }
744                    }
745                }
746            }
747
748            // column
749            for(int i=0;i<infoList.size();i++){
750                if (infoList.get(i).getDbObjectType() == EDbObjectType.column){
751                    if (!columnClauses.contains(infoList.get(i).getColumn().getLocation())){
752                        columnClauses.add(infoList.get(i).getColumn().getLocation());
753                    }
754                }
755            }
756            outputResult("");
757            outputResult("Columns:");
758            for(int j=0;j<columnClauses.size();j++){
759                outputResult("\t"+columnClauses.get(j).toString());
760
761                for(int i=0;i<infoList.size();i++){
762                    if (infoList.get(i).getDbObjectType() == EDbObjectType.column){
763                        TObjectName lcColumn = infoList.get(i).getColumn();
764                        if (lcColumn.getLocation() == columnClauses.get(j)){
765                                outputResult("\t\t" +  infoList.get(i).getFullColumnName()+ "("+lcColumn.coordinate()+")");
766                        }
767                    }
768                }
769            }
770
771
772        }
773        
774        if(showJoin){
775                joinRelationAnalyze analysis = new joinRelationAnalyze( sqlParser, showColumnLocation );
776                        outputResult( analysis.getAnalysisResult( ) );
777        }
778
779      //  System.out.println("Fields:"+newline+fieldlist.toString());
780    }
781
782
783        private void outputResult( String result)
784        {
785                if (isConsole){
786                     System.out.println(result);
787             //System.out.println(TBaseType.toHex(result,"UTF-8"));
788                 }else {
789                        //if(outList.length()>0)
790                        //      outList.append(newline);
791                     outList.append(result).append( newline);
792                 }
793        }
794
795
796    void printArray(String pTitle,ArrayList<String> pList){
797                outputResult( pTitle );
798                Object str[] = pList.toArray( );
799                for ( int i = 0; i < str.length; i++ )
800                {
801                        outputResult( str[i].toString( ) );
802                }
803    }
804
805
806    void removeDuplicateAndSort(ArrayList <String>  pList){
807        Collections.sort(pList, new SortIgnoreCase() );
808
809        for ( int i = 0 ; i < pList.size() - 1 ; i ++ ) {
810             for ( int j = pList.size() - 1 ; j > i; j -- ) {
811               if (pList.get(j).equalsIgnoreCase((pList.get(i)))) {
812                 pList.remove(j);
813               }
814              }
815            }
816    }
817
818    protected void analyzeStmt(TCustomSqlStatement stmt, int pNest){
819        gudusoft.gsqlparser.nodes.TTable  lcTable = null;
820        TObjectName lcColumn = null;
821        String tn = "",cn="";
822
823        if (stmt instanceof  TStoredProcedureSqlStatement){
824            spList.push((TStoredProcedureSqlStatement)stmt);
825            TInfoRecord spRecord = new TInfoRecord(EDbObjectType.procedure);
826            spRecord.setSPName(spList.peek().getStoredProcedureName());
827        }
828        //System.out.println( numberOfSpace(pNest)+ stmt.sqlstatementtype);
829        infos.append(numberOfSpace(pNest) + stmt.sqlstatementtype+newline);
830
831        for(int i=0;i<stmt.tables.size();i++){
832            //if  (stmt.tables.getTable(i).isBaseTable())
833            //{
834             lcTable = stmt.tables.getTable(i);
835             if (showColumnsOfCTE && lcTable.isCTEName()){
836                 for(TAttributeNode node:lcTable.getAttributes()){
837                     if ((node.getName().endsWith("*")) && (!showStarColumnOfCTE)) continue;
838                     cteList.add(displayNameNormalizer.normalizeQualifiedName(node.getName()));
839                 }
840             }
841             TInfoRecord tableRecord = new TInfoRecord(lcTable);
842            tableRecord.setFileName(this.sqlFileName);
843            if (spList.size() > 0){
844                tableRecord.setSPName(spList.peek().getStoredProcedureName());
845            }
846            infoList.add(tableRecord);
847
848                        if ( lcTable.getTableType( ) == ETableSource.subquery )
849                        {
850                                tn = "(subquery, alias:" + lcTable.getAliasName( ) + ")";
851                        }
852                        else if ( lcTable.getTableType( ) == ETableSource.tableExpr )
853                        {
854                                tn = "(table expression, alias:"
855                                                + lcTable.getAliasName( )
856                                                + ")";
857                        }
858            else if ( lcTable.getTableType( ) == ETableSource.openquery )
859            {
860                tn = "(table openquery, alias:"
861                        + lcTable.getAliasName( )
862                        + ")";
863
864                if (lcTable.getSubquery() != null){
865                    analyzeStmt(lcTable.getSubquery(),pNest++);
866                }
867            }else if (lcTable.getTableType() == ETableSource.function){
868                tn = "(table-valued function:"
869                        + normalizeTableName(lcTable.getTableName())
870                        + ")";
871            }
872            else if (lcTable.getTableType() == ETableSource.pivoted_table){
873                // Check if this is UNPIVOT or PIVOT
874                boolean isUnpivot = false;
875                // First try to get from TPivotClause
876                if (lcTable.getPivotedTable() != null && lcTable.getPivotedTable().getPivotClause() != null) {
877                    isUnpivot = (lcTable.getPivotedTable().getPivotClause().getType() == TPivotClause.unpivot);
878                } else {
879                    // Fallback: check if the table alias suggests UNPIVOT
880                    // When no explicit alias is provided, TPivotClause.doParse() uses "unpivot_alias"
881                    String tableName = lcTable.getTableName() != null ? lcTable.getTableName().toString() : "";
882                    String aliasName = lcTable.getAliasName() != null ? lcTable.getAliasName() : "";
883                    isUnpivot = tableName.startsWith("unpivot_") || aliasName.startsWith("unpivot_");
884                }
885                String prefix = isUnpivot ? "(unpivot-table:" : "(pivot-table:";
886                tn = prefix + normalizeTableName(lcTable.getTableName()) + ")";
887            }
888            else if (lcTable.getTableType() == ETableSource.unnest){
889                tn = "(unnest-table:"
890                        + lcTable.getAliasName( )
891                        + ")";
892            }
893            else if (lcTable.getTableType() == ETableSource.lateralView){
894                tn = "(lateral_view:"
895                        + normalizeTableName(lcTable.getTableName())
896                        + ")";
897            }
898                        else if ( lcTable.getTableName( ) != null )
899                        {
900                                tn = normalizeTableName(lcTable.getTableName());
901                                if ( lcTable.isLinkTable( ) )
902                                {
903                                        tn = tn
904                                                        + "("
905                                                        + normalizeTableName(lcTable.getLinkTable().getTableName())
906                                                        + ")";
907                                }
908                                else if ( lcTable.isCTEName( ) )
909                                {
910                                        tn = tn + "(CTE)";
911                                }
912                        }
913                //System.out.println(numberOfSpace(pNest+1)+tn.getName());
914                if ((showTableEffect) &&(lcTable.isBaseTable())){
915                    infos.append(numberOfSpace(pNest+1)+ tn+"("+lcTable.getEffectType()+")"+newline);
916                }else{
917                    infos.append(numberOfSpace(pNest+1)+ tn+newline);
918                }
919
920                tableColumnList.append(","+tn);
921
922                // Determine if this table should be added to the tablelist
923                // When onlyPhysicalTables=true, only ETableSource.objectname tables (non-CTE) are included
924                // unless showUnnest/showPivotTable/showLateralView are true for those types
925                boolean shouldAddTable;
926                if (shouldSkipTableForPhysicalFilter(lcTable)) {
927                    shouldAddTable = false;
928                } else {
929                    shouldAddTable = !((lcTable.getTableType() == ETableSource.subquery)
930                            || (lcTable.isCTEName()&&(!showCTE))
931                            ||(lcTable.getTableType() == ETableSource.openquery)
932                            ||(lcTable.getTableType() == ETableSource.function)) && lcTable.getTableName()!=null;
933                }
934
935                if (shouldAddTable) {
936                   String tableName;
937                   if (lcTable.isLinkTable()){
938                      // tablelist.append(lcTable.getLinkTable().toString()+newline);
939                       tableName = normalizeTableName(lcTable.getLinkTable().getTableName());
940                   }else{
941                      // tablelist.append(lcTable.toString()+newline);
942                       tableName = normalizeTableName(lcTable.getTableName());
943                   }
944                   // Add table effect type if showTableEffect is enabled and it's a base table
945                   if (showTableEffect && lcTable.isBaseTable()) {
946                       tableName = tableName + "(" + lcTable.getEffectType() + ")";
947                   }
948                   tablelist.add(tableName);
949                }
950
951
952                for (int j=0;j<stmt.tables.getTable(i).getLinkedColumns().size();j++){
953                    lcColumn = stmt.tables.getTable(i).getLinkedColumns().getObjectName(j);
954                    if (lcColumn.getValidate_column_status() == TBaseType.MARKED_NOT_A_COLUMN_IN_COLUMN_RESOLVER) continue;
955                    TInfoRecord columnRecord = new TInfoRecord(tableRecord,EDbObjectType.column);
956                    columnRecord.setColumn(lcColumn);
957                    infoList.add(columnRecord);
958                    cn = normalizeColumnName(lcColumn.getColumnNameOnly());
959                    if ((showDatatype)&&(lcColumn.getLinkedColumnDef() != null)){
960                        //column in create table, add datatype information as well
961                        TTypeName datatype = lcColumn.getLinkedColumnDef().getDatatype();
962                        cn = cn + ":"+datatype.getDataTypeName();
963                        if (datatype.getLength() != null){
964                            cn = cn+":"+datatype.getLength().toString();
965                        } else if (datatype.getPrecision() != null){
966                            cn = cn+":"+datatype.getPrecision().toString();
967                            if (datatype.getScale() != null){
968                                cn = cn+":"+datatype.getScale().toString();
969                            }
970                        } else if (datatype.getDisplayLength() != null){
971                            cn = cn+":"+datatype.getDisplayLength().toString();
972                        }
973                    }
974                    //System.out.println(numberOfSpace(pNest+2)+cn.getColumnNameOnly());
975                    if (showColumnLocation){
976                        String  posStr = "";
977//                        if ( lcColumn.getColumnToken() != null) {
978//                            TSourceToken lcStartToken = lcColumn.getColumnToken();
979//                            posStr ="("+ lcStartToken.lineNo+","+lcStartToken.columnNo+ ")";
980//                        }
981                        infos.append(numberOfSpace(pNest+3)+ lcColumn.getColumnNameOnly()+posStr+"("+lcColumn.getLocation()+")"+newline);
982                    }else{
983                        infos.append(numberOfSpace(pNest+3)+ lcColumn.getColumnNameOnly()+newline);
984                    }
985
986                    // Determine if this field should be added to the fieldlist
987                    // When onlyPhysicalTables=true, only include fields from physical tables
988                    // unless showUnnest/showPivotTable/showLateralView are true for those types
989                    boolean shouldAddField;
990                    if (shouldSkipTableForPhysicalFilter(lcTable)) {
991                        shouldAddField = false;
992                    } else {
993                        shouldAddField = !((lcTable.getTableType() == ETableSource.subquery)||(lcTable.isCTEName()&&(!showCTE)));
994                    }
995
996                    if (shouldAddField){
997                         // Skip star columns for PIVOT tables - individual columns are output via namespace
998                         boolean isStarColumn = lcColumn.getColumnNameOnly().equals("*");
999                         boolean isPivotTable = lcTable.getTableType() == ETableSource.pivoted_table;
1000                         if (isStarColumn && isPivotTable) {
1001                             // Skip star for pivot tables
1002                         } else if ((listStarColumn) || (!isStarColumn)){
1003                             String fieldName;
1004                             if (lcTable.isLinkTable()){
1005                                 fieldName = normalizeTableName(lcTable.getLinkTable().getTableName()) + dotChar + cn;
1006                             }else{
1007                                 fieldName = tn + dotChar + cn;
1008                             }
1009                             // Add column location if showColumnLocation is enabled
1010                             if (showColumnLocation) {
1011                                 fieldName = fieldName + "(" + lcColumn.getLocation() + ")";
1012                             }
1013                             fieldlist.add(fieldName);
1014                         }
1015                    }
1016                    tableColumnList.append(","+tn+dotChar+ cn);
1017                }
1018                //add by grq 2023.07.09 issue=I7ITBQ
1019
1020//                if(stmt.sqlstatementtype.equals(ESqlStatementType.sstinsert)){
1021//                    TInsertSqlStatement insertStmt = (TInsertSqlStatement) stmt;
1022//                    if(insertStmt.getColumnList() == null || insertStmt.getColumnList().size()<=0){
1023//                        TSelectSqlStatement selectStmt = insertStmt.getSubQuery();
1024//                        if(selectStmt != null){
1025//                            for (int j=0;j<selectStmt.getResultColumnList().size();j++){
1026//                                TResultColumn rsColumn = selectStmt.getResultColumnList().getResultColumn(j);
1027//                                if(rsColumn.getLocation().equals(ESqlClause.unknown)){
1028//                                    rsColumn.setLocation(ESqlClause.selectList);
1029//                                }
1030//                                TInfoRecord columnRecord = new TInfoRecord(tableRecord,EDbObjectType.column);
1031//                                TObjectName objectCloumn = rsColumn.getAliasClause()==null? null: rsColumn.getAliasClause().getAliasName();
1032//                                if(objectCloumn == null){
1033//                                    objectCloumn = rsColumn.getColumnFullname();
1034//                                }
1035//                                columnRecord.setColumn(objectCloumn);
1036//                                infoList.add(columnRecord);
1037//                                cn = rsColumn.getColumnAlias();
1038//                                if(StringUtils.isEmpty(cn)){
1039//                                    cn = rsColumn.getColumnNameOnly();
1040//                                }
1041//                                if (showColumnLocation){
1042//                                    String  posStr = "";
1043//                                    infos.append(numberOfSpace(pNest+3)+ cn + posStr+"("+rsColumn.getLocation()+")"+newline);
1044//                                }else{
1045//                                    infos.append(numberOfSpace(pNest+3)+ cn + newline);
1046//                                }
1047//
1048//                                if (!((lcTable.getTableType() == ETableSource.subquery)||(lcTable.isCTEName()&&(!showCTE)))){
1049//                                    if ((listStarColumn) || (!(rsColumn.getColumnNameOnly().equals("*")))){
1050//                                        if (lcTable.isLinkTable()){
1051//                                            fieldlist.add(lcTable.getLinkTable().getTableName() + dotChar + cn );
1052//                                        }else{
1053//                                            fieldlist.add(tn + dotChar + cn );
1054//                                        }
1055//                                    }
1056//                                }
1057//                                tableColumnList.append(","+tn+dotChar+ cn);
1058//                            }
1059//                        }
1060//                    }
1061//                }
1062                //end by grq
1063            //}
1064        }
1065
1066        if (stmt.getOrphanColumns().size() > 0){
1067           // infos.append(numberOfSpace(pNest+1)+" orphan columns:"+newline);
1068            String oc = "";
1069            for (int k=0;k<stmt.getOrphanColumns().size();k++){
1070                TObjectName orphanCol = stmt.getOrphanColumns().getObjectName(k);
1071                // Skip if already resolved
1072                if (orphanCol.getResolveStatus() == TBaseType.RESOLVED_AND_FOUND)
1073                    continue;
1074                // Skip if column already has a sourceTable (it's been resolved to a table)
1075                // This handles cases like UNNEST where the table is virtual, not physical
1076                if (orphanCol.getSourceTable() != null)
1077                    continue;
1078                TInfoRecord columnRecord = new TInfoRecord(EDbObjectType.column);
1079                columnRecord.setColumn(stmt.getOrphanColumns().getObjectName(k));
1080                columnRecord.setFileName(this.sqlFileName);
1081                infoList.add(columnRecord);
1082
1083                oc = normalizeColumnName(stmt.getOrphanColumns().getObjectName(k).getColumnNameOnly());// stmt.getOrphanColumns().getObjectName(k).toString();
1084                if (showColumnLocation){
1085                    infos.append(numberOfSpace(pNest+3)+oc+"("+stmt.getOrphanColumns().getObjectName(k).getLocation()+")"+newline);
1086                }else{
1087                    infos.append(numberOfSpace(pNest+3)+oc+newline);
1088                }
1089
1090                if ((linkOrphanColumnToFirstTable)&&(stmt.getFirstPhysicalTable() != null)){
1091                    if ((listStarColumn) ||(!(oc.equalsIgnoreCase("*")))) {
1092                        String fieldName = normalizeTableName(stmt.getFirstPhysicalTable().getTableName()) + dotChar + oc;
1093                        // Add column location if showColumnLocation is enabled
1094                        if (showColumnLocation) {
1095                            fieldName = fieldName + "(" + stmt.getOrphanColumns().getObjectName(k).getLocation() + ")";
1096                        }
1097                        fieldlist.add(fieldName);
1098                    }
1099                    columnRecord.setTable(stmt.getFirstPhysicalTable());
1100                }else {
1101                    String fieldName = "missed" + dotChar + oc + "(" + stmt.getOrphanColumns().getObjectName(k).coordinate() + ")";
1102                    // Add column location if showColumnLocation is enabled (for orphan columns)
1103                    if (showColumnLocation) {
1104                        fieldName = fieldName + "(" + stmt.getOrphanColumns().getObjectName(k).getLocation() + ")";
1105                    }
1106                    fieldlist.add(fieldName);
1107                }
1108                tableColumnList.append(",missed"+dotChar+oc+newline);
1109
1110                TObjectName column = stmt.getOrphanColumns().getObjectName(k);
1111//                if (column.isOrphanColumn()){
1112//                    System.out.println("Candidate table size of column: "+ column.toString()+" ,"+ column.getCandidateTables().size());
1113//                }
1114
1115            }
1116        }
1117
1118        for(int i=0;i<stmt.getIndexColumns().size();i++){
1119            TColumnWithSortOrder indexColumn = stmt.getIndexColumns().getElement(i);
1120            TInfoRecord indexRecord = new TInfoRecord(EDbObjectType.index);
1121            indexRecord.setColumn(indexColumn.getColumnName());
1122            indexRecord.setFileName(this.sqlFileName);
1123            infoList.add(indexRecord);
1124
1125
1126            String tableName = "unknownTable";
1127            String indexName = "unknownIndex";
1128            if (indexColumn.getOwnerTable() != null){
1129                tableName = normalizeTableName(indexColumn.getOwnerTable().getTableName());
1130                indexRecord.setTable(indexColumn.getOwnerTable());
1131            }
1132            if (indexColumn.getOwnerConstraint() != null){
1133                if (indexColumn.getOwnerConstraint().getConstraintName() != null){
1134                    indexName = normalizeColumnName(indexColumn.getOwnerConstraint().getConstraintName().toString());
1135                    indexRecord.setIndex(indexColumn.getOwnerConstraint().getConstraintName());
1136                }
1137            }
1138            indexList.add(tableName + ":" + normalizeColumnName(indexColumn.getColumnName().toString())+":"+indexName);
1139        }
1140
1141        for (int i=0;i<stmt.getStatements().size();i++){
1142            analyzeStmt(stmt.getStatements().get(i),pNest+1);
1143        }
1144
1145        if (stmt instanceof  TStoredProcedureSqlStatement){
1146            TStoredProcedureSqlStatement p = (TStoredProcedureSqlStatement)stmt;
1147            for(int i=0;i<p.getBodyStatements().size();i++){
1148                analyzeStmt(p.getBodyStatements().get(i),pNest+1);
1149            }
1150            spList.pop();
1151        }
1152
1153    }
1154
1155    /**
1156     * 判断表是否为物理表(基于 {@link ETableSource#objectname} 类型)。
1157     * <p>
1158     * 仅当表类型为 {@link ETableSource#objectname} 且不是 CTE 引用时,返回 true。
1159     * 这与 {@link gudusoft.gsqlparser.resolver2.TSQLResolver2ResultFormatter#isPhysicalTable} 的行为一致。
1160     * </p>
1161     *
1162     * @param table 要检查的表
1163     * @return 如果是物理表返回 true,否则返回 false
1164     */
1165    private boolean isPhysicalTable(gudusoft.gsqlparser.nodes.TTable table) {
1166        if (table == null) return false;
1167
1168        // Only objectname type tables can be physical tables
1169        // This filters out subqueries, joins, functions, pivoted tables, unnest, lateralView, etc.
1170        if (table.getTableType() != ETableSource.objectname) {
1171            return false;
1172        }
1173
1174        // Tables from CTE are considered virtual, not physical
1175        if (table.isCTEName()) {
1176            return false;
1177        }
1178
1179        return true;
1180    }
1181
1182    /**
1183     * 检查表是否应该被跳过,基于 onlyPhysicalTables 和 show* 选项。
1184     *
1185     * <p>当 onlyPhysicalTables=true 时,表会被跳过,除非:</p>
1186     * <ul>
1187     *   <li>它是物理表(isPhysicalTable 返回 true)且不是 CTAS 目标表</li>
1188     *   <li>它是 UNNEST 表且 showUnnest=true</li>
1189     *   <li>它是 PIVOT 表且 showPivotTable=true</li>
1190     *   <li>它是 LATERAL VIEW 表且 showLateralView=true</li>
1191     * </ul>
1192     *
1193     * @param table 要检查的表
1194     * @return 如果表应该被跳过返回 true,如果应该包含返回 false
1195     */
1196    private boolean shouldSkipTableForPhysicalFilter(gudusoft.gsqlparser.nodes.TTable table) {
1197        if (!onlyPhysicalTables) {
1198            return false; // Not filtering, don't skip
1199        }
1200
1201        // CTAS target tables are DDL targets, not existing physical tables
1202        // They have ETableEffectType.tetCreateAs effect type
1203        if (table.getEffectType() == ETableEffectType.tetCreateAs) {
1204            return true;
1205        }
1206
1207        // Physical tables are always included when onlyPhysicalTables=true
1208        if (isPhysicalTable(table)) {
1209            return false;
1210        }
1211
1212        // Check if table type should be included based on show* options
1213        ETableSource tableType = table.getTableType();
1214        if (tableType == ETableSource.unnest && showUnnest) {
1215            return false;
1216        }
1217        if (tableType == ETableSource.pivoted_table && showPivotTable) {
1218            return false;
1219        }
1220        if (tableType == ETableSource.lateralView && showLateralView) {
1221            return false;
1222        }
1223
1224        // All other non-physical tables are skipped when onlyPhysicalTables=true
1225        return true;
1226    }
1227
1228    // ========== Identifier Normalization Methods ==========
1229
1230    /**
1231     * Normalize a table name for consistent output formatting.
1232     *
1233     * <p>This uses the same normalization mechanism as
1234     * {@link gudusoft.gsqlparser.resolver2.TSQLResolver2ResultFormatter} to ensure
1235     * uniform identifier formatting across both tools.</p>
1236     *
1237     * <p>The normalization strips delimiters (quotes, brackets, backticks) while
1238     * preserving the original case. This handles:</p>
1239     * <ul>
1240     *   <li>Double quotes: {@code "table_name"} → {@code table_name}</li>
1241     *   <li>Square brackets: {@code [table_name]} → {@code table_name}</li>
1242     *   <li>Backticks: {@code `table_name`} → {@code table_name}</li>
1243     *   <li>Multi-part names: {@code [schema].[table]} → {@code schema.table}</li>
1244     * </ul>
1245     *
1246     * @param tableName The TObjectName representing the table name
1247     * @return The normalized table name string
1248     */
1249    private String normalizeTableName(TObjectName tableName) {
1250        if (tableName == null) return "";
1251        String fullName = tableName.toString();
1252        if (fullName == null || fullName.isEmpty()) return "";
1253        return displayNameNormalizer.normalizeQualifiedName(fullName);
1254    }
1255
1256    /**
1257     * Normalize a column name for consistent output formatting.
1258     *
1259     * <p>This uses the same normalization mechanism as
1260     * {@link gudusoft.gsqlparser.resolver2.TSQLResolver2ResultFormatter} to ensure
1261     * uniform identifier formatting across both tools.</p>
1262     *
1263     * @param columnName The raw column name which may include quotes
1264     * @return The normalized column name
1265     */
1266    private String normalizeColumnName(String columnName) {
1267        if (columnName == null || columnName.isEmpty()) return columnName;
1268        return displayNameNormalizer.normalizeIdentifier(columnName);
1269    }
1270}
1271
1272 class SortIgnoreCase implements Comparator<Object> {
1273        public int compare(Object o1, Object o2) {
1274            String s1 = (String) o1;
1275            String s2 = (String) o2;
1276            return s1.toLowerCase().compareTo(s2.toLowerCase());
1277        }
1278    }
1279
1280