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