001 002package gudusoft.gsqlparser.dlineage.dataflow.model; 003 004import java.util.ArrayList; 005import java.util.LinkedHashMap; 006import java.util.LinkedHashSet; 007import java.util.List; 008import java.util.Map; 009import java.util.Set; 010 011import gudusoft.gsqlparser.EDbVendor; 012import gudusoft.gsqlparser.EExpressionType; 013import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType; 014import gudusoft.gsqlparser.TSourceToken; 015import gudusoft.gsqlparser.dlineage.util.DlineageUtil; 016import gudusoft.gsqlparser.dlineage.util.Pair3; 017import gudusoft.gsqlparser.nodes.TConstant; 018import gudusoft.gsqlparser.nodes.TExpression; 019import gudusoft.gsqlparser.nodes.TFunctionCall; 020import gudusoft.gsqlparser.nodes.TObjectName; 021import gudusoft.gsqlparser.nodes.TResultColumn; 022import gudusoft.gsqlparser.nodes.TResultColumnList; 023import gudusoft.gsqlparser.sqlenv.TSQLEnv; 024import gudusoft.gsqlparser.util.SQLUtil; 025 026public class TableColumn extends Column{ 027 028 029 private Table table; 030 031 private long id; 032 protected String name; 033 034 protected Pair3<Long, Long, String> startPosition; 035 protected Pair3<Long, Long, String> endPosition; 036 037 private TObjectName columnObject; 038 private Map<String, Set<TObjectName>> starLinkColumns = new LinkedHashMap<String, Set<TObjectName>>(); 039 040 private boolean showStar; 041 042 private boolean expandStar = true; 043 044 private Integer columnIndex; 045 046 private boolean isVariant; 047 048 private boolean isPseduo = false; 049 050 private boolean notBindStarColumn = false; 051 052 private String displayName; 053 054 private String dataType; 055 056 private Boolean primaryKey = false; 057 058 private Boolean unqiueKey = false; 059 060 private Boolean indexKey = false; 061 062 private Boolean foreignKey = false; 063 064 private boolean isStruct = false; 065 066 private List<Transform> transforms; 067 068 private List<Object> candidateParents; 069 070 private TableColumn sourceColumn; 071 072 public TableColumn(Table view, TObjectName columnObject, int index) { 073 this(view, columnObject); 074 this.columnIndex = index; 075 } 076 077 public TableColumn(Table table, TObjectName columnObject) { 078 if (table == null || columnObject == null) 079 throw new IllegalArgumentException("TableColumn arguments can't be null."); 080 081 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 082 083 this.columnObject = columnObject; 084 085 TSourceToken startToken = columnObject.getStartToken(); 086 TSourceToken endToken = columnObject.getEndToken(); 087 this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, 088 startToken.columnNo, ModelBindingManager.getGlobalHash()); 089 this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo, 090 endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash()); 091 092 if (columnObject.getColumnNameOnly() != null 093 && !"".equals(columnObject.getColumnNameOnly())) { 094 if (columnObject.getPartToken() != null && columnObject.getPropertyToken() != null) { 095 if(supportProperty() && columnObject.getSourceTable() == table.getTableObject()) { 096 // Use resolver info to build struct name without table alias prefix 097 String structName = null; 098 gudusoft.gsqlparser.resolver2.model.ColumnSource cs = columnObject.getColumnSource(); 099 if (cs != null && cs.isStructFieldAccess() && cs.hasFieldPath()) { 100 structName = cs.getFieldPath().toFullReference(cs.getExposedName()); 101 } 102 if (structName == null) { 103 gudusoft.gsqlparser.resolver2.model.StructFieldHint hint = columnObject.getStructFieldHint(); 104 if (hint != null && hint.getFieldPath() != null) { 105 structName = hint.toFullReference(); 106 } 107 } 108 if (structName != null) { 109 this.name = structName; 110 } else { 111 // Non-struct qualified name: keep original toString() behavior 112 this.name = columnObject.toString(); 113 } 114 } 115 else { 116 this.name = DlineageUtil.decodeColumnIdentifier(columnObject.getPartToken().getAstext()) 117 + "." + DlineageUtil.decodeColumnIdentifier(columnObject.getPropertyToken().getAstext()); 118 } 119 } 120 else if(columnObject.getColumnSource()!=null && columnObject.getColumnSource().isStructFieldAccess()) { 121 if(supportProperty() && columnObject.getSourceTable() == table.getTableObject()) { 122 // Use resolver info to build struct name without table alias prefix 123 String structName = null; 124 gudusoft.gsqlparser.resolver2.model.ColumnSource cs = columnObject.getColumnSource(); 125 if (cs != null && cs.hasFieldPath()) { 126 structName = cs.getFieldPath().toFullReference(cs.getExposedName()); 127 } 128 if (structName != null) { 129 this.name = structName; 130 } else { 131 this.name = columnObject.toString(); 132 } 133 } else { 134 this.name = DlineageUtil.decodeColumnIdentifier(columnObject.getColumnNameOnly()); 135 } 136 } 137 else if(columnObject.getStructFieldHint() != null && supportProperty() 138 && !isQualifiedColumnReference(columnObject)) { 139 this.name = columnObject.getStructFieldHint().toFullReference(); 140 } 141 else { 142 this.name = DlineageUtil.decodeColumnIdentifier(columnObject.getColumnNameOnly()); 143 } 144 } 145 else if (columnObject.toString().indexOf(".") != -1 && "".equals(columnObject.getColumnNameOnly()) && 146 supportProperty()) { 147 this.name = columnObject.toString(); 148 } 149 else if (columnObject.toString().indexOf(".") != -1) { 150 List<String> splits = SQLUtil.parseNames(columnObject.toString()); 151 this.name = DlineageUtil.decodeColumnIdentifier(splits.get(splits.size() - 1)); 152 } 153 else { 154 // A SELECT alias reaches here with a null part token 155 // (dbObjectType=column_alias, so getColumnNameOnly() is empty), which 156 // is why a CREATE VIEW published '[Volgorde]' / 'Volgorde' verbatim 157 // as the view's column name (GitHub issue #704). The argument is a 158 // TObjectName — an identifier by construction, never a value 159 // literal — so decoding its delimiters here is sound. 160 this.name = DlineageUtil.decodeAliasIdentifier(columnObject.toString()); 161 } 162 163 this.table = table; 164 table.addColumn(this); 165 166 if("*".equals(this.name) && !table.isDetermined()){ 167 showStar = true; 168 } 169 } 170 171 protected boolean supportProperty() { 172 return ModelBindingManager.getGlobalVendor() == EDbVendor.dbvbigquery || ModelBindingManager.getGlobalVendor() == EDbVendor.dbvredshift; 173 } 174 175 /** 176 * Check if a column reference is a standard qualified table.column reference 177 * (not a struct field access). Detects when tableToken matches the source table's name, 178 * e.g., "catalog.Hospital.table_update_01.Position" where table_update_01 is the table. 179 */ 180 private boolean isQualifiedColumnReference(TObjectName columnObject) { 181 if (columnObject.getSourceTable() == null || columnObject.getTableToken() == null) { 182 return false; 183 } 184 String tableTokenStr = columnObject.getTableToken().getAstext(); 185 gudusoft.gsqlparser.nodes.TTable src = columnObject.getSourceTable(); 186 // Compare tableToken with the source table's simple name (last segment) 187 String tableName = src.getTableName() != null ? src.getTableName().getTableString() : ""; 188 List<String> parts = SQLUtil.parseNames(tableName); 189 String simpleTableName = parts.isEmpty() ? tableName : parts.get(parts.size() - 1); 190 if (sameTableName(tableTokenStr, simpleTableName)) { 191 return true; 192 } 193 // Also check alias 194 String aliasName = src.getAliasName(); 195 if (aliasName != null && sameTableName(tableTokenStr, aliasName)) { 196 return true; 197 } 198 return false; 199 } 200 201 /** 202 * Table-name/alias equality routed through the unified façade; falls back to 203 * case-insensitive comparison when no analysis vendor is bound. 204 */ 205 private static boolean sameTableName(String a, String b) { 206 if (a == null || b == null) { 207 return false; 208 } 209 EDbVendor vendor = ModelBindingManager.getGlobalVendor(); 210 return vendor == null ? a.equalsIgnoreCase(b) 211 : SQLUtil.sameName(vendor, ESQLDataObjectType.dotTable, a, b); 212 } 213 214 /** 215 * Constructor for MDX and other non-SQL columns where only a name is available. 216 */ 217 public TableColumn(Table table, String columnName) { 218 if (table == null || columnName == null) 219 throw new IllegalArgumentException("TableColumn arguments can't be null."); 220 221 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 222 223 this.startPosition = new Pair3<Long, Long, String>(-1L, -1L, ModelBindingManager.getGlobalHash()); 224 this.endPosition = new Pair3<Long, Long, String>(-1L, -1L, ModelBindingManager.getGlobalHash()); 225 226 this.name = columnName; 227 this.table = table; 228 table.addColumn(this); 229 } 230 231 public TableColumn(Table table, TConstant columnObject, int columnIndex) { 232 if (table == null || columnObject == null) 233 throw new IllegalArgumentException("TableColumn arguments can't be null."); 234 235 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 236 237 TSourceToken startToken = columnObject.getStartToken(); 238 TSourceToken endToken = columnObject.getEndToken(); 239 this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, 240 startToken.columnNo, ModelBindingManager.getGlobalHash()); 241 this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo, 242 endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash()); 243 244 this.name = columnObject.toString(); 245 246 this.table = table; 247 table.addColumn(this); 248 } 249 250 public TableColumn(Table table, TConstant columnObject) { 251 if (table == null || columnObject == null) 252 throw new IllegalArgumentException("TableColumn arguments can't be null."); 253 254 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 255 256 TSourceToken startToken = columnObject.getStartToken(); 257 TSourceToken endToken = columnObject.getEndToken(); 258 this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, 259 startToken.columnNo, ModelBindingManager.getGlobalHash()); 260 this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo, 261 endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash()); 262 263 this.name = columnObject.toString(); 264 this.table = table; 265 table.addColumn(this); 266 } 267 268 public TableColumn(Table table, TFunctionCall columnObject) { 269 if (table == null || columnObject == null) 270 throw new IllegalArgumentException("TableColumn arguments can't be null."); 271 272 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 273 274 TSourceToken startToken = columnObject.getStartToken(); 275 TSourceToken endToken = columnObject.getEndToken(); 276 this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, 277 startToken.columnNo, ModelBindingManager.getGlobalHash()); 278 this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo, 279 endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash()); 280 281 this.name = columnObject.toString(); 282 this.table = table; 283 table.addColumn(this); 284 } 285 286 287 public TableColumn(Table table, TExpression columnObject, int columnIndex) { 288 if (table == null || columnObject == null) 289 throw new IllegalArgumentException("TableColumn arguments can't be null."); 290 291 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 292 293 TSourceToken startToken = columnObject.getStartToken(); 294 TSourceToken endToken = columnObject.getEndToken(); 295 this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, 296 startToken.columnNo, ModelBindingManager.getGlobalHash()); 297 this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo, 298 endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash()); 299 300 this.name = getColumnName(columnObject); 301 302 this.table = table; 303 table.addColumn(this); 304 } 305 306 protected String getColumnName(TExpression expr) { 307 if (expr.getExpressionType() == EExpressionType.simple_constant_t) { 308 return expr.toString(); 309 } else if (expr.getExpressionType() == EExpressionType.sqlserver_proprietary_column_alias_t) { 310 // "alias = expression": no TAliasClause exists, but the left operand 311 // is an alias by grammar, so it decodes like one (issue #704). 312 return DlineageUtil.decodeAliasIdentifier(expr.getLeftOperand().toString()); 313 } else if (expr.getExpressionType() == EExpressionType.function_t) { 314 return expr.getFunctionCall().toString(); 315 } else if (expr.getExpressionType() == EExpressionType.typecast_t) { 316 return expr.getLeftOperand().toString(); 317 } else if (expr.getExpressionType() == EExpressionType.parenthesis_t) { 318 TExpression inner = expr.getLeftOperand(); 319 while (inner != null && inner.getExpressionType() == EExpressionType.parenthesis_t) { 320 inner = inner.getLeftOperand(); 321 } 322 return getColumnName(inner); 323 } else if (expr.getExpressionType() == EExpressionType.simple_object_name_t) { 324 TObjectName columnName = expr.getObjectOperand(); 325 if (columnName.getPropertyToken() != null) { 326 return DlineageUtil.decodeColumnIdentifier(columnName.getPropertyToken().getAstext()); 327 } else { 328 return DlineageUtil.decodeColumnIdentifier(columnName.getColumnNameOnly()); 329 } 330 } else if (expr.getExpressionType() == EExpressionType.subquery_t) { 331 TResultColumnList resultset = expr.getSubQuery().getResultColumnList(); 332 if (resultset != null && resultset.size() == 1) { 333 TResultColumn resultColumn = resultset.getResultColumn(0); 334 if (!SQLUtil.isEmpty(resultColumn.getColumnAlias())) { 335 return resultColumn.getColumnAlias(); 336 } else if (resultColumn.getExpr() != null) { 337 return getColumnName(resultColumn.getExpr()); 338 } else if (resultColumn.getColumnNameOnly() != null 339 && !"".equals(resultColumn.getColumnNameOnly())) { 340 if (resultColumn.getExpr().getObjectOperand() != null 341 && resultColumn.getExpr().getObjectOperand().getPropertyToken() != null) { 342 this.name = DlineageUtil.decodeColumnIdentifier(resultColumn.getExpr().getObjectOperand().getPropertyToken().getAstext()); 343 } else { 344 this.name = DlineageUtil.decodeColumnIdentifier(resultColumn.getColumnNameOnly()); 345 } 346 } else { 347 this.name = resultColumn.toString(); 348 } 349 } 350 } 351 return quote(expr.toString()); 352 } 353 354 355 private String quote(String column) { 356 EDbVendor vendor = ModelBindingManager.getGlobalVendor(); 357 if (!DlineageUtil.isQuote(column)){ 358 return SQLUtil.quoteDottedName(vendor, ESQLDataObjectType.dotColumn, column); 359 } 360 else return column; 361 } 362 363 public Table getTable() { 364 return table; 365 } 366 367 public long getId() { 368 return id; 369 } 370 371 public String getName() { 372 return name; 373 } 374 375 public Pair3<Long, Long, String> getStartPosition() { 376 return startPosition; 377 } 378 379 public Pair3<Long, Long, String> getEndPosition() { 380 return endPosition; 381 } 382 383 public void setStartPosition(Pair3<Long, Long, String> startPosition) { 384 this.startPosition = startPosition; 385 } 386 387 public void setEndPosition(Pair3<Long, Long, String> endPosition) { 388 this.endPosition = endPosition; 389 } 390 391 public TObjectName getColumnObject() { 392 return columnObject; 393 } 394 395 public void bindStarLinkColumns(Map<String, Set<TObjectName>> starLinkColumns) { 396 if (starLinkColumns != null && !starLinkColumns.isEmpty()) { 397 this.starLinkColumns.putAll(starLinkColumns); 398 } 399 } 400 401 public Map<String, Set<TObjectName>> getStarLinkColumns() { 402 return starLinkColumns; 403 } 404 405 public boolean hasStarLinkColumn(){ 406 return (starLinkColumns!=null && !starLinkColumns.isEmpty()); 407 } 408 409 public boolean bindStarLinkColumn(TObjectName objectName) { 410 if (objectName == null) { 411 return false; 412 } 413 414 String columnName = DlineageUtil.getColumnName(objectName); 415 416 if ("*".equals(columnName)) { 417 return false; 418 } 419 420 boolean flag = false; 421 if (!starLinkColumns.containsKey(columnName)) { 422 starLinkColumns.put(columnName, new LinkedHashSet<TObjectName>()); 423 flag = true; 424 } 425 426 starLinkColumns.get(columnName).add(objectName); 427 428 429 if(this.getStarSourceColumns()!=null) { 430 for(Column column: this.getStarSourceColumns()) { 431 boolean bind = true; 432 if(column instanceof ResultColumn) { 433 ResultSet parent = ((ResultColumn) column).getResultSet(); 434 for(ResultColumn item: parent.getColumns()) { 435 if(DlineageUtil.compareColumnIdentifier(DlineageUtil.getColumnName(item.getName()), DlineageUtil.getColumnName(objectName))) { 436 bind = false; 437 break; 438 } 439 } 440 } 441 if(column instanceof TableColumn) { 442 Table parent = ((TableColumn) column).getTable(); 443 for(TableColumn item: parent.getColumns()) { 444 if(DlineageUtil.compareColumnIdentifier(DlineageUtil.getColumnName(item.getName()), DlineageUtil.getColumnName(objectName))) { 445 bind = false; 446 break; 447 } 448 } 449 } 450 if (bind) { 451 column.bindStarLinkColumn(objectName); 452 } 453 } 454 } 455 456 return flag; 457 } 458 459 public List<TObjectName> getStarLinkColumnList() { 460 List<TObjectName> columns = new ArrayList<TObjectName>(); 461 if (starLinkColumns != null) { 462 for (Set<TObjectName> columnSet : starLinkColumns.values()) { 463 columns.addAll(columnSet); 464 } 465 } 466 return columns; 467 } 468 469 public List<String> getStarLinkColumnNames() { 470 List<String> columns = new ArrayList<>(); 471 if (starLinkColumns != null) { 472 columns.addAll(starLinkColumns.keySet()); 473 } 474 return columns; 475 } 476 477 public boolean isShowStar() { 478 return showStar; 479 } 480 481 public void setShowStar(boolean showStar) { 482 this.showStar = showStar; 483 } 484 485 public Table getView() { 486 return getTable(); 487 } 488 489 public Integer getColumnIndex() { 490 return columnIndex; 491 } 492 493 public boolean isVariant() { 494 return isVariant; 495 } 496 497 public void setVariant(boolean isVariant) { 498 this.isVariant = isVariant; 499 } 500 501 public String getDisplayName() { 502 return displayName; 503 } 504 505 public void setDisplayName(String displayName) { 506 this.displayName = displayName; 507 } 508 509 public void setExpandStar(boolean expandStar) { 510 this.expandStar = expandStar; 511 } 512 513 public boolean isExpandStar() { 514 return expandStar; 515 } 516 517 public boolean isPseduo() { 518 return isPseduo; 519 } 520 521 public void setPseduo(boolean isPseduo) { 522 this.isPseduo = isPseduo; 523 } 524 525 public void notBindStarLinkColumn(boolean notBindStarColumn) { 526 this.notBindStarColumn = notBindStarColumn; 527 } 528 529 public boolean isNotBindStarLinkColumn() { 530 return notBindStarColumn; 531 } 532 533 public Boolean getPrimaryKey() { 534 return primaryKey; 535 } 536 537 public void setPrimaryKey(Boolean primaryKey) { 538 this.primaryKey = primaryKey; 539 } 540 541 public Boolean getUnqiueKey() { 542 return unqiueKey; 543 } 544 545 public void setUnqiueKey(Boolean unqiueKey) { 546 this.unqiueKey = unqiueKey; 547 } 548 549 public Boolean getIndexKey() { 550 return indexKey; 551 } 552 553 public void setIndexKey(Boolean indexKey) { 554 this.indexKey = indexKey; 555 } 556 557 public Boolean getForeignKey() { 558 return foreignKey; 559 } 560 561 public void setForeignKey(Boolean foreignKey) { 562 this.foreignKey = foreignKey; 563 } 564 565 public String getDataType() { 566 return dataType; 567 } 568 569 public void setDataType(String dataType) { 570 this.dataType = dataType; 571 } 572 573 @Override 574 public String toString() { 575 return this.name; 576 } 577 578 public void setColumnIndex(int columnIndex) { 579 this.columnIndex = columnIndex; 580 } 581 582 public boolean isStruct() { 583 return isStruct; 584 } 585 586 public void setStruct(boolean isStruct) { 587 this.isStruct = isStruct; 588 } 589 590 public List<Transform> getTransforms() { 591 return transforms; 592 } 593 594 public void setTransform(Transform transform) { 595 if(transform==null) { 596 return; 597 } 598 if(transforms == null) { 599 transforms = new ArrayList<Transform>(); 600 } 601 if(!transforms.contains(transform)) { 602 transforms.add(transform); 603 } 604 } 605 606 public List<Object> getCandidateParents() { 607 return candidateParents; 608 } 609 610 public void setCandidateParents(List<Object> candidateParents) { 611 if (this.candidateParents == null) { 612 this.candidateParents = candidateParents; 613 } 614 else { 615 this.candidateParents.addAll(candidateParents); 616 } 617 } 618 619 public TableColumn getSourceColumn() { 620 return sourceColumn; 621 } 622 623 public void setSourceColumn(TableColumn sourceColumn) { 624 this.sourceColumn = sourceColumn; 625 } 626}