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 public TableColumn(Table table, TConstant columnObject, int columnIndex) { 194 if (table == null || columnObject == null) 195 throw new IllegalArgumentException("TableColumn arguments can't be null."); 196 197 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 198 199 TSourceToken startToken = columnObject.getStartToken(); 200 TSourceToken endToken = columnObject.getEndToken(); 201 this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, 202 startToken.columnNo, ModelBindingManager.getGlobalHash()); 203 this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo, 204 endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash()); 205 206 this.name = columnObject.toString(); 207 208 this.table = table; 209 table.addColumn(this); 210 } 211 212 public TableColumn(Table table, TConstant columnObject) { 213 if (table == null || columnObject == null) 214 throw new IllegalArgumentException("TableColumn arguments can't be null."); 215 216 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 217 218 TSourceToken startToken = columnObject.getStartToken(); 219 TSourceToken endToken = columnObject.getEndToken(); 220 this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, 221 startToken.columnNo, ModelBindingManager.getGlobalHash()); 222 this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo, 223 endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash()); 224 225 this.name = columnObject.toString(); 226 this.table = table; 227 table.addColumn(this); 228 } 229 230 public TableColumn(Table table, TFunctionCall columnObject) { 231 if (table == null || columnObject == null) 232 throw new IllegalArgumentException("TableColumn arguments can't be null."); 233 234 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 235 236 TSourceToken startToken = columnObject.getStartToken(); 237 TSourceToken endToken = columnObject.getEndToken(); 238 this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, 239 startToken.columnNo, ModelBindingManager.getGlobalHash()); 240 this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo, 241 endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash()); 242 243 this.name = columnObject.toString(); 244 this.table = table; 245 table.addColumn(this); 246 } 247 248 249 public TableColumn(Table table, TExpression columnObject, int columnIndex) { 250 if (table == null || columnObject == null) 251 throw new IllegalArgumentException("TableColumn arguments can't be null."); 252 253 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 254 255 TSourceToken startToken = columnObject.getStartToken(); 256 TSourceToken endToken = columnObject.getEndToken(); 257 this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, 258 startToken.columnNo, ModelBindingManager.getGlobalHash()); 259 this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo, 260 endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash()); 261 262 this.name = getColumnName(columnObject); 263 264 this.table = table; 265 table.addColumn(this); 266 } 267 268 protected String getColumnName(TExpression expr) { 269 if (expr.getExpressionType() == EExpressionType.simple_constant_t) { 270 return expr.toString(); 271 } else if (expr.getExpressionType() == EExpressionType.sqlserver_proprietary_column_alias_t) { 272 return expr.getLeftOperand().toString(); 273 } else if (expr.getExpressionType() == EExpressionType.function_t) { 274 return expr.getFunctionCall().toString(); 275 } else if (expr.getExpressionType() == EExpressionType.typecast_t) { 276 return expr.getLeftOperand().toString(); 277 } else if (expr.getExpressionType() == EExpressionType.parenthesis_t) { 278 TExpression inner = expr.getLeftOperand(); 279 while (inner != null && inner.getExpressionType() == EExpressionType.parenthesis_t) { 280 inner = inner.getLeftOperand(); 281 } 282 return getColumnName(inner); 283 } else if (expr.getExpressionType() == EExpressionType.simple_object_name_t) { 284 TObjectName columnName = expr.getObjectOperand(); 285 if (columnName.getPropertyToken() != null) { 286 return columnName.getPropertyToken().getAstext(); 287 } else { 288 return columnName.getColumnNameOnly(); 289 } 290 } else if (expr.getExpressionType() == EExpressionType.subquery_t) { 291 TResultColumnList resultset = expr.getSubQuery().getResultColumnList(); 292 if (resultset != null && resultset.size() == 1) { 293 TResultColumn resultColumn = resultset.getResultColumn(0); 294 if (!SQLUtil.isEmpty(resultColumn.getColumnAlias())) { 295 return resultColumn.getColumnAlias(); 296 } else if (resultColumn.getExpr() != null) { 297 return getColumnName(resultColumn.getExpr()); 298 } else if (resultColumn.getColumnNameOnly() != null 299 && !"".equals(resultColumn.getColumnNameOnly())) { 300 if (resultColumn.getExpr().getObjectOperand() != null 301 && resultColumn.getExpr().getObjectOperand().getPropertyToken() != null) { 302 this.name = resultColumn.getExpr().getObjectOperand().getPropertyToken().getAstext(); 303 } else { 304 this.name = resultColumn.getColumnNameOnly(); 305 } 306 } else { 307 this.name = resultColumn.toString(); 308 } 309 } 310 } 311 return quote(expr.toString()); 312 } 313 314 315 private String quote(String column) { 316 EDbVendor vendor = ModelBindingManager.getGlobalVendor(); 317 String delimitedChar = TSQLEnv.delimitedChar(vendor); 318 if (!DlineageUtil.isQuote(column)){ 319 if(vendor == EDbVendor.dbvmssql || vendor == EDbVendor.dbvazuresql) { 320 return "[" + column + "]"; 321 } 322 else { 323 return delimitedChar + column + delimitedChar; 324 } 325 } 326 else return column; 327 } 328 329 public Table getTable() { 330 return table; 331 } 332 333 public long getId() { 334 return id; 335 } 336 337 public String getName() { 338 return name; 339 } 340 341 public Pair3<Long, Long, String> getStartPosition() { 342 return startPosition; 343 } 344 345 public Pair3<Long, Long, String> getEndPosition() { 346 return endPosition; 347 } 348 349 public TObjectName getColumnObject() { 350 return columnObject; 351 } 352 353 public void bindStarLinkColumns(Map<String, Set<TObjectName>> starLinkColumns) { 354 if (starLinkColumns != null && !starLinkColumns.isEmpty()) { 355 this.starLinkColumns.putAll(starLinkColumns); 356 } 357 } 358 359 public Map<String, Set<TObjectName>> getStarLinkColumns() { 360 return starLinkColumns; 361 } 362 363 public boolean hasStarLinkColumn(){ 364 return (starLinkColumns!=null && !starLinkColumns.isEmpty()); 365 } 366 367 public boolean bindStarLinkColumn(TObjectName objectName) { 368 if (objectName == null) { 369 return false; 370 } 371 372 String columnName = DlineageUtil.getColumnName(objectName); 373 374 if ("*".equals(columnName)) { 375 return false; 376 } 377 378 boolean flag = false; 379 if (!starLinkColumns.containsKey(columnName)) { 380 starLinkColumns.put(columnName, new LinkedHashSet<TObjectName>()); 381 flag = true; 382 } 383 384 starLinkColumns.get(columnName).add(objectName); 385 386 387 if(this.getStarSourceColumns()!=null) { 388 for(Column column: this.getStarSourceColumns()) { 389 boolean bind = true; 390 if(column instanceof ResultColumn) { 391 ResultSet parent = ((ResultColumn) column).getResultSet(); 392 for(ResultColumn item: parent.getColumns()) { 393 if(DlineageUtil.compareColumnIdentifier(DlineageUtil.getColumnName(item.getName()), DlineageUtil.getColumnName(objectName))) { 394 bind = false; 395 break; 396 } 397 } 398 } 399 if(column instanceof TableColumn) { 400 Table parent = ((TableColumn) column).getTable(); 401 for(TableColumn item: parent.getColumns()) { 402 if(DlineageUtil.compareColumnIdentifier(DlineageUtil.getColumnName(item.getName()), DlineageUtil.getColumnName(objectName))) { 403 bind = false; 404 break; 405 } 406 } 407 } 408 if (bind) { 409 column.bindStarLinkColumn(objectName); 410 } 411 } 412 } 413 414 return flag; 415 } 416 417 public List<TObjectName> getStarLinkColumnList() { 418 List<TObjectName> columns = new ArrayList<TObjectName>(); 419 if (starLinkColumns != null) { 420 for (Set<TObjectName> columnSet : starLinkColumns.values()) { 421 columns.addAll(columnSet); 422 } 423 } 424 return columns; 425 } 426 427 public List<String> getStarLinkColumnNames() { 428 List<String> columns = new ArrayList<>(); 429 if (starLinkColumns != null) { 430 columns.addAll(starLinkColumns.keySet()); 431 } 432 return columns; 433 } 434 435 public boolean isShowStar() { 436 return showStar; 437 } 438 439 public void setShowStar(boolean showStar) { 440 this.showStar = showStar; 441 } 442 443 public Table getView() { 444 return getTable(); 445 } 446 447 public Integer getColumnIndex() { 448 return columnIndex; 449 } 450 451 public boolean isVariant() { 452 return isVariant; 453 } 454 455 public void setVariant(boolean isVariant) { 456 this.isVariant = isVariant; 457 } 458 459 public String getDisplayName() { 460 return displayName; 461 } 462 463 public void setDisplayName(String displayName) { 464 this.displayName = displayName; 465 } 466 467 public void setExpandStar(boolean expandStar) { 468 this.expandStar = expandStar; 469 } 470 471 public boolean isExpandStar() { 472 return expandStar; 473 } 474 475 public boolean isPseduo() { 476 return isPseduo; 477 } 478 479 public void setPseduo(boolean isPseduo) { 480 this.isPseduo = isPseduo; 481 } 482 483 public void notBindStarLinkColumn(boolean notBindStarColumn) { 484 this.notBindStarColumn = notBindStarColumn; 485 } 486 487 public boolean isNotBindStarLinkColumn() { 488 return notBindStarColumn; 489 } 490 491 public Boolean getPrimaryKey() { 492 return primaryKey; 493 } 494 495 public void setPrimaryKey(Boolean primaryKey) { 496 this.primaryKey = primaryKey; 497 } 498 499 public Boolean getUnqiueKey() { 500 return unqiueKey; 501 } 502 503 public void setUnqiueKey(Boolean unqiueKey) { 504 this.unqiueKey = unqiueKey; 505 } 506 507 public Boolean getIndexKey() { 508 return indexKey; 509 } 510 511 public void setIndexKey(Boolean indexKey) { 512 this.indexKey = indexKey; 513 } 514 515 public Boolean getForeignKey() { 516 return foreignKey; 517 } 518 519 public void setForeignKey(Boolean foreignKey) { 520 this.foreignKey = foreignKey; 521 } 522 523 public String getDataType() { 524 return dataType; 525 } 526 527 public void setDataType(String dataType) { 528 this.dataType = dataType; 529 } 530 531 @Override 532 public String toString() { 533 return this.name; 534 } 535 536 public void setColumnIndex(int columnIndex) { 537 this.columnIndex = columnIndex; 538 } 539 540 public boolean isStruct() { 541 return isStruct; 542 } 543 544 public void setStruct(boolean isStruct) { 545 this.isStruct = isStruct; 546 } 547 548 public List<Transform> getTransforms() { 549 return transforms; 550 } 551 552 public void setTransform(Transform transform) { 553 if(transform==null) { 554 return; 555 } 556 if(transforms == null) { 557 transforms = new ArrayList<Transform>(); 558 } 559 if(!transforms.contains(transform)) { 560 transforms.add(transform); 561 } 562 } 563 564 public List<Object> getCandidateParents() { 565 return candidateParents; 566 } 567 568 public void setCandidateParents(List<Object> candidateParents) { 569 if (this.candidateParents == null) { 570 this.candidateParents = candidateParents; 571 } 572 else { 573 this.candidateParents.addAll(candidateParents); 574 } 575 } 576 577 public TableColumn getSourceColumn() { 578 return sourceColumn; 579 } 580 581 public void setSourceColumn(TableColumn sourceColumn) { 582 this.sourceColumn = sourceColumn; 583 } 584}