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