001 002package gudusoft.gsqlparser.dlineage.dataflow.model; 003 004import java.util.*; 005import java.util.concurrent.atomic.AtomicInteger; 006 007import gudusoft.gsqlparser.EDbVendor; 008import gudusoft.gsqlparser.EExpressionType; 009import gudusoft.gsqlparser.EFunctionType; 010import gudusoft.gsqlparser.TSourceToken; 011import gudusoft.gsqlparser.dlineage.util.DlineageUtil; 012import gudusoft.gsqlparser.dlineage.util.Pair; 013import gudusoft.gsqlparser.dlineage.util.Pair3; 014import gudusoft.gsqlparser.nodes.TExpression; 015import gudusoft.gsqlparser.nodes.TFunctionCall; 016import gudusoft.gsqlparser.nodes.TObjectName; 017import gudusoft.gsqlparser.nodes.TParseTreeNode; 018import gudusoft.gsqlparser.nodes.TResultColumn; 019import gudusoft.gsqlparser.nodes.TResultColumnList; 020import gudusoft.gsqlparser.nodes.TTable; 021import gudusoft.gsqlparser.util.SQLUtil; 022 023public class ResultColumn extends Column{ 024 025 protected ResultSet resultSet; 026 027 protected long id; 028 029 protected String alias; 030 protected Pair3<Long, Long, String> aliasStartPosition; 031 protected Pair3<Long, Long, String> aliasEndPosition; 032 033 protected String fullName; 034 protected String name; 035 036 protected Pair3<Long, Long, String> startPosition; 037 protected Pair3<Long, Long, String> endPosition; 038 039 protected TParseTreeNode columnObject; 040 041 protected String refColumnName; 042 043 /** 044 * A live view of this column's star links ({@link #getStarLinkColumns()}). 045 * 046 * @deprecated read and write the star links through the column's methods; assigning this field does not 047 * change them. 048 */ 049 @Deprecated 050 protected Map<String, Set<TObjectName>> starLinkColumns = starLinks().legacyView(); 051 052 private boolean showStar = true; 053 054 protected boolean isFunction = false; 055 056 private boolean isPseduo = false; 057 058 private boolean isStruct = false; 059 060 private List<Transform> transforms; 061 062 public ResultColumn() { 063 064 } 065 066 public ResultColumn(ResultSet resultSet, TParseTreeNode columnObject) { 067 if (columnObject == null || resultSet == null) 068 throw new IllegalArgumentException("ResultColumn arguments can't be null."); 069 070 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 071 072 this.resultSet = resultSet; 073 resultSet.addColumn(this); 074 075 this.columnObject = columnObject; 076 077 TSourceToken startToken = columnObject.getStartToken(); 078 TSourceToken endToken = columnObject.getEndToken(); 079 080 if (columnObject instanceof TObjectName) { 081 setStarColumn(DlineageUtil.isStarName((TObjectName) columnObject)); 082 } else if (columnObject instanceof TResultColumn) { 083 setStarColumn(isStarItem((TResultColumn) columnObject)); 084 } 085 if (columnObject instanceof TObjectName) { 086 setIdentifierKey(DlineageUtil.columnKey((TObjectName) columnObject)); 087 TObjectName reference = (TObjectName) columnObject; 088 if (gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.forVendor(ModelBindingManager.getGlobalVendor()).buildsDeclaredTree() && reference.getFieldPathRead() != null 089 && reference.getFieldPathBaseToken() != null) { 090 // STRUCT tree: a field read through a CTE or derived table, customer.address.city, reads the column 091 // customer of its result set; the grammar's column slot (address) names no column (Mantis 4830) 092 this.name = DlineageUtil.decodeColumnIdentifier(reference.getFieldPathBaseToken().toString()); 093 } else if (((TObjectName) columnObject).getColumnNameOnly() != null 094 && !"".equals(((TObjectName) columnObject).getColumnNameOnly())) { 095 this.name = DlineageUtil.decodeColumnIdentifier(((TObjectName) columnObject).getColumnNameOnly()); 096 } else { 097 // Empty columnNameOnly means a null part token, which is what an 098 // ALIAS TObjectName looks like (dbObjectType=column_alias). This is 099 // the path MSSQL's "alias = expression" form takes, and leaving it 100 // raw published RS-1."[Alias]" beside a view column "Alias" (#704). 101 this.name = DlineageUtil.decodeAliasIdentifier(((TObjectName) columnObject).toString()); 102 } 103 } else 104 this.name = columnObject.toString(); 105 if (this.name == null) { 106 this.name = ""; 107 } 108 109 this.fullName = columnObject.toString(); 110 if (this.fullName == null) { 111 this.fullName = this.name; 112 } 113 114 if (startToken != null) { 115 this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, startToken.columnNo, 116 ModelBindingManager.getGlobalHash()); 117 } 118 if (endToken != null) { 119 this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo, 120 endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), 121 ModelBindingManager.getGlobalHash()); 122 } 123 } 124 125 public ResultColumn(ResultSet resultSet, TResultColumn resultColumnObject) { 126 if (resultColumnObject == null || resultSet == null) 127 throw new IllegalArgumentException("ResultColumn arguments can't be null."); 128 129 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 130 131 this.resultSet = resultSet; 132 resultSet.addColumn(this); 133 134 this.columnObject = resultColumnObject; 135 setStarColumn(isStarItem(resultColumnObject)); 136 if (resultColumnObject.getAliasClause() != null 137 && resultColumnObject.getAliasClause().getAliasName() != null) { 138 setIdentifierKey(DlineageUtil.columnKey(resultColumnObject.getAliasClause().getAliasName())); 139 } else if (resultColumnObject.getExpr() != null 140 && resultColumnObject.getExpr().getExpressionType() == EExpressionType.simple_object_name_t) { 141 setIdentifierKey(DlineageUtil.columnKey(resultColumnObject.getExpr().getObjectOperand())); 142 } 143 144 if (resultColumnObject.getAliasClause() != null) { 145 this.alias = resultColumnObject.getAliasClause().toString(); 146 TSourceToken startToken = resultColumnObject.getAliasClause().getStartToken(); 147 TSourceToken endToken = resultColumnObject.getAliasClause().getEndToken(); 148 this.aliasStartPosition = new Pair3<Long, Long, String>(startToken.lineNo, startToken.columnNo, 149 ModelBindingManager.getGlobalHash()); 150 this.aliasEndPosition = new Pair3<Long, Long, String>(endToken.lineNo, 151 endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash()); 152 153 // The alias is an identifier: publish the name a catalog stores, not 154 // the syntactic spelling. Replaces a vendor-blind apostrophe strip 155 // that left [x] / "x" / `x` delimited (GitHub issue #704). 156 // Collision detection must key on the name we PUBLISH, not on the 157 // spelling. `SELECT a AS [x], b AS x` decodes both to x, and keying 158 // on the raw text would emit two indistinguishable RS-1.x targets — 159 // the canonical lineage walker keys nodes by (parent, column), so a 160 // trace of x would conflate two different sources. 161 String decodedAlias = DlineageUtil.decodeAliasIdentifier(this.alias); 162 String aliasKey = decodedAlias.toLowerCase(Locale.ROOT); 163 if(resultSet.getAliasMap().containsKey(aliasKey)){ 164 int index = resultSet.getAliasMap().get(aliasKey).incrementAndGet(); 165 this.name = decodedAlias + "(" + index + ")"; 166 setRenamedForCollision(true); 167 } 168 else { 169 resultSet.getAliasMap().put(aliasKey, new AtomicInteger(0)); 170 this.name = decodedAlias; 171 } 172 173 if(resultColumnObject.getExpr().getFunctionCall()!=null) { 174 TFunctionCall functionObj = resultColumnObject.getExpr().getFunctionCall(); 175 if (functionObj.getFunctionType() == EFunctionType.array_agg_t 176 && ModelBindingManager.getGlobalVendor() == EDbVendor.dbvbigquery 177 && functionObj.getArgs() != null 178 && functionObj.getArgs().size() == 1) { 179 180 TExpression arg0 = functionObj.getArgs().getExpression(0); 181 if (arg0 != null 182 && arg0.getExpressionType() == EExpressionType.simple_object_name_t) { 183 184 TObjectName on = arg0.getObjectOperand(); 185 if (on.getSourceTable()!=null) { 186 // The aggregated row, a whole-row star of the table it reads. 187 this.setStruct(true); 188 this.name = "*"; 189 setStarColumn(true); 190 } 191 } 192 } 193 } 194 } else if (resultColumnObject.getExpr() != null) { 195 this.name = getColumnName(resultColumnObject.getExpr()); 196 } 197 198 if(this.name == null){ 199 if (resultColumnObject.getColumnNameOnly() != null 200 && !"".equals(resultColumnObject.getColumnNameOnly())) { 201 if (resultColumnObject.getExpr().getObjectOperand() != null 202 && resultColumnObject.getExpr().getObjectOperand().getPropertyToken() != null) { 203 this.name = DlineageUtil.decodeColumnIdentifier(DlineageUtil.fieldLeafText(resultColumnObject.getExpr().getObjectOperand())); 204 } else { 205 this.name = DlineageUtil.decodeColumnIdentifier(resultColumnObject.getColumnNameOnly()); 206 } 207 } else { 208 this.name = resultColumnObject.toString(); 209 } 210 } 211 212 if (resultColumnObject.getExpr().getExpressionType() == EExpressionType.function_t) { 213 this.fullName = resultColumnObject.getExpr().getFunctionCall().toString(); 214 } else if (resultColumnObject.getExpr().getExpressionType() == EExpressionType.typecast_t) { 215 this.fullName = resultColumnObject.getExpr().getLeftOperand().toString(); 216 } else { 217 this.fullName = resultColumnObject.toString(); 218 } 219 220 TSourceToken startToken = resultColumnObject.getStartToken(); 221 TSourceToken endToken = resultColumnObject.getEndToken(); 222 this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, startToken.columnNo, 223 ModelBindingManager.getGlobalHash()); 224 this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo, endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), 225 ModelBindingManager.getGlobalHash()); 226 } 227 228 public ResultColumn(ResultSet resultSet, TResultColumn resultColumnObject, String refColumnName) { 229 if (resultColumnObject == null || resultSet == null) 230 throw new IllegalArgumentException("ResultColumn arguments can't be null."); 231 232 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 233 234 this.resultSet = resultSet; 235 resultSet.addColumn(this); 236 237 this.columnObject = resultColumnObject; 238 this.refColumnName = refColumnName; 239 240 this.name = refColumnName; 241 242 TSourceToken startToken = resultColumnObject.getStartToken(); 243 TSourceToken endToken = resultColumnObject.getEndToken(); 244 this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, startToken.columnNo, 245 ModelBindingManager.getGlobalHash()); 246 this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo, endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), 247 ModelBindingManager.getGlobalHash()); 248 } 249 250 /** 251 * Whether a select-list item stands for a star, by the parse: {@code *}, {@code t.*}, or an item the model names 252 * after an inner star ({@code (*)}, a scalar {@code (SELECT * ...)}), following {@link #getColumnName(TExpression)}. 253 * An item with an alias is named by it: a column. 254 */ 255 private static boolean isStarItem(TResultColumn item) { 256 return item.getAliasClause() == null && item.getExpr() != null && isStarExpr(item.getExpr()); 257 } 258 259 private static boolean isStarExpr(TExpression expr) { 260 while (expr != null) { 261 switch (expr.getExpressionType()) { 262 case parenthesis_t: 263 expr = expr.getLeftOperand(); 264 break; 265 case simple_object_name_t: 266 return DlineageUtil.isStarName(expr.getObjectOperand()); 267 case subquery_t: 268 TResultColumnList items = expr.getSubQuery() == null ? null : expr.getSubQuery().getResultColumnList(); 269 if (items == null || items.size() != 1 || !SQLUtil.isEmpty(items.getResultColumn(0).getColumnAlias())) { 270 return false; 271 } 272 expr = items.getResultColumn(0).getExpr(); 273 break; 274 default: 275 return false; 276 } 277 } 278 return false; 279 } 280 281 protected String getColumnName(TExpression expr) { 282 if (expr.getExpressionType() == EExpressionType.simple_constant_t) { 283 return expr.toString(); 284 } else if (expr.getExpressionType() == EExpressionType.sqlserver_proprietary_column_alias_t) { 285 this.alias = expr.getLeftOperand().toString(); 286 TSourceToken startToken = expr.getLeftOperand().getStartToken(); 287 TSourceToken endToken = expr.getLeftOperand().getEndToken(); 288 this.aliasStartPosition = new Pair3<Long, Long, String>(startToken.lineNo, startToken.columnNo, 289 ModelBindingManager.getGlobalHash()); 290 this.aliasEndPosition = new Pair3<Long, Long, String>(endToken.lineNo, 291 endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash()); 292 293 return DlineageUtil.decodeAliasIdentifier(this.alias); 294 } else if (expr.getExpressionType() == EExpressionType.function_t) { 295 return expr.getFunctionCall().toString(); 296 } else if (expr.getExpressionType() == EExpressionType.typecast_t) { 297 return expr.getLeftOperand().toString(); 298 } else if (expr.getExpressionType() == EExpressionType.parenthesis_t) { 299 TExpression inner = expr.getLeftOperand(); 300 while (inner != null && inner.getExpressionType() == EExpressionType.parenthesis_t) { 301 inner = inner.getLeftOperand(); 302 } 303 return getColumnName(inner); 304 } else if (expr.getExpressionType() == EExpressionType.simple_object_name_t) { 305 TObjectName columnName = expr.getObjectOperand(); 306 if (columnName.getPropertyToken() != null) { 307 return DlineageUtil.decodeColumnIdentifier(DlineageUtil.fieldLeafText(columnName)); 308 } else { 309 if (SQLUtil.isEmpty(columnName.getColumnNameOnly())) { 310 return columnName.toString(); 311 } else { 312 return DlineageUtil.decodeColumnIdentifier(columnName.getColumnNameOnly()); 313 } 314 } 315 } else if (expr.getExpressionType() == EExpressionType.subquery_t) { 316 TResultColumnList resultset = expr.getSubQuery().getResultColumnList(); 317 if (resultset != null && resultset.size() == 1) { 318 TResultColumn resultColumn = resultset.getResultColumn(0); 319 if (!SQLUtil.isEmpty(resultColumn.getColumnAlias())) { 320 return resultColumn.getColumnAlias(); 321 } else if (resultColumn.getExpr() != null) { 322 return getColumnName(resultColumn.getExpr()); 323 } else if (resultColumn.getColumnNameOnly() != null 324 && !"".equals(resultColumn.getColumnNameOnly())) { 325 if (resultColumn.getExpr().getObjectOperand() != null 326 && resultColumn.getExpr().getObjectOperand().getPropertyToken() != null) { 327 this.name = DlineageUtil.decodeColumnIdentifier(DlineageUtil.fieldLeafText(resultColumn.getExpr().getObjectOperand())); 328 } else { 329 this.name = DlineageUtil.decodeColumnIdentifier(resultColumn.getColumnNameOnly()); 330 } 331 } else { 332 this.name = resultColumn.toString(); 333 } 334 } 335 } 336 return null; 337 } 338 339 public ResultColumn(SelectResultSet resultSet, Pair<TResultColumn, TObjectName> starColumnPair) { 340 if (starColumnPair == null || resultSet == null) 341 throw new IllegalArgumentException("ResultColumn arguments can't be null."); 342 343 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 344 345 this.resultSet = resultSet; 346 resultSet.addColumn(this); 347 348 this.columnObject = starColumnPair.first; 349 350 TSourceToken startToken = columnObject.getStartToken(); 351 TSourceToken endToken = columnObject.getEndToken(); 352 353 this.name = DlineageUtil.decodeColumnIdentifier(((TObjectName) columnObject).getColumnNameOnly()); 354 if(name == null){ 355 name = DlineageUtil.decodeAliasIdentifier(((TObjectName) columnObject).toString()); 356 } 357 this.fullName = columnObject.toString(); 358 359 this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, startToken.columnNo, 360 ModelBindingManager.getGlobalHash()); 361 this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo, endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), 362 ModelBindingManager.getGlobalHash()); 363 } 364 365 // private int getIndexOf(TResultColumnList resultColumnList, 366 // TResultColumn resultColumnObject) { 367 // for (int i = 0; i < resultColumnList.size(); i++) { 368 // if (resultColumnList.getResultColumn(i) == resultColumnObject) 369 // return i; 370 // } 371 // return -1; 372 // } 373 374 public ResultSet getResultSet() { 375 return resultSet; 376 } 377 378 public long getId() { 379 return id; 380 } 381 382 public String getAlias() { 383 return alias; 384 } 385 386 public Pair3<Long, Long, String> getAliasStartPosition() { 387 return aliasStartPosition; 388 } 389 390 public Pair3<Long, Long, String> getAliasEndPosition() { 391 return aliasEndPosition; 392 } 393 394 public String getFullName() { 395 return fullName; 396 } 397 398 public Pair3<Long, Long, String> getStartPosition() { 399 return startPosition; 400 } 401 402 public Pair3<Long, Long, String> getEndPosition() { 403 return endPosition; 404 } 405 406 public TParseTreeNode getColumnObject() { 407 return columnObject; 408 } 409 410 public String getName() { 411 return name; 412 } 413 414 /** 415 * Names a column of an UPDATE or MERGE action after the attribute it writes. {@code SET tgt.objcol.a1 = v} 416 * writes {@code objcol.a1}, but the column segment alone names it {@code objcol}. Two attributes of one column 417 * set by the same action then share a name, and the output merge of same-named columns cross-wires their 418 * values (Mantis #4747). The name comes from {@link DlineageUtil#propertyColumnPath}, which also names the 419 * written {@link TableColumn} outside the STRUCT-path vendors; for those, it is the display of the field path 420 * written, {@code customer.address.city}, the name of the field path node the action writes. 421 */ 422 void nameAfterWrittenAttribute(TObjectName target) { 423 gudusoft.gsqlparser.common.structured.StructuredColumnPath fieldPath = 424 gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.forVendor(ModelBindingManager.getGlobalVendor()).buildsDeclaredTree() ? target.getFieldPathRead() : null; 425 String path = fieldPath != null ? fieldPath.toDisplayString() : DlineageUtil.propertyColumnPath(target); 426 if (path != null) { 427 this.name = path; 428 } 429 } 430 431 @Override 432 void propagateStarLink(TObjectName objectName) { 433 if (this.getStarSourceColumns() != null) { 434 StarLinkKey nameKey = StarLinkStore.IDENTITY && !this.getStarSourceColumns().isEmpty() 435 ? StarLinkStore.keyOf(objectName) : null; 436 for (Column column : this.getStarSourceColumns()) { 437 boolean find = false; 438 if (column instanceof ResultColumn) { 439 ResultSet resultset = ((ResultColumn) column).getResultSet(); 440 for (ResultColumn resultColumn : resultset.getColumns()) { 441 Boolean same = StarLinkStore.identityAnswer(resultColumn, nameKey); 442 if (same != null ? same.booleanValue() : DlineageUtil.sameColumnDisplay(resultColumn.name, objectName.toString())) { 443 find = true; 444 break; 445 } 446 } 447 } 448 if (!find) { 449 column.bindStarLinkColumn(objectName); 450 } 451 } 452 } 453 } 454 455 456 /** 457 * The ordinal of the star link {@code objectName} would join. A name no link has gives the last ordinal (-1 458 * when there is no link). 459 */ 460 public int indexOfStarLinkColumn(TObjectName objectName) { 461 if (objectName == null) { 462 return -1; 463 } 464 465 String columnName = StarLinkStore.legacyDisplay(objectName); 466 467 if (DlineageUtil.isStarName(objectName)) { 468 return -1; 469 } 470 471 return starLinks().legacyIndexOf(objectName, columnName); 472 } 473 474 /** Removes the star link {@code objectName} would join, with every name it holds. */ 475 public void unbindStarLinkColumn(TObjectName objectName) { 476 if (objectName == null) { 477 return; 478 } 479 480 starLinks().unbind(objectName); 481 } 482 483 /** 484 * {@link #bindStarLinkColumn(TObjectName)} that places a new star link at ordinal {@code index}, without passing 485 * the name on to the star's source columns. 486 */ 487 public void bindStarLinkColumn(TObjectName objectName, int index) { 488 if (objectName == null) { 489 return; 490 } 491 492 starLinks().bindAt(objectName, index); 493 } 494 495 496 public boolean isShowStar() { 497 return showStar; 498 } 499 500 public void setShowStar(boolean showStar) { 501 this.showStar = showStar; 502 } 503 504 public boolean isFunction() { 505 return isFunction; 506 } 507 508 public void setFunction(boolean isFunction) { 509 this.isFunction = isFunction; 510 } 511 512 public boolean isPseduo() { 513 return isPseduo; 514 } 515 516 public void setPseduo(boolean isPseduo) { 517 this.isPseduo = isPseduo; 518 } 519 520 public String getRefColumnName() { 521 return refColumnName; 522 } 523 524 @Override 525 public String toString() { 526 return this.name; 527 } 528 529 public boolean isStruct() { 530 return isStruct; 531 } 532 533 public void setStruct(boolean isStruct) { 534 this.isStruct = isStruct; 535 } 536 537 public List<Transform> getTransforms() { 538 return transforms; 539 } 540 541 public void setTransform(Transform transform) { 542 if(transform==null) { 543 return; 544 } 545 if(transforms == null) { 546 transforms = new ArrayList<Transform>(); 547 } 548 if(!transforms.contains(transform)) { 549 transforms.add(transform); 550 } 551 } 552}