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