001package gudusoft.gsqlparser.dlineage.dataflow.model.xml; 002 003import gudusoft.gsqlparser.dlineage.dataflow.model.ModelBindingManager; 004import gudusoft.gsqlparser.dlineage.dataflow.model.json.Coordinate; 005import gudusoft.gsqlparser.dlineage.util.DlineageUtil; 006import gudusoft.gsqlparser.dlineage.util.LegacyDlineageWireNamesV1; 007import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType; 008import gudusoft.gsqlparser.sqlenv.QualifiedCanonKey; 009import gudusoft.gsqlparser.sqlenv.TSQLEnv; 010import gudusoft.gsqlparser.util.SQLUtil; 011 012import javax.xml.bind.annotation.XmlAttribute; 013import javax.xml.bind.annotation.XmlElement; 014import javax.xml.bind.annotation.XmlTransient; 015import javax.xml.bind.annotation.XmlType; 016import java.util.ArrayList; 017import java.util.LinkedHashSet; 018import java.util.List; 019 020@XmlType( 021 propOrder = {"id", "server", "userName", "database", "schema", "name", "displayName", "alias", "uri", "type", "subType", 022 "processIds", "procedureId", "fileType", "fileFormat", "location", "dataSourceName", "namespace", "isTarget", "coordinate", "columns", "parent", "more", "fromDDL", "candidateTables", 023 "endpointKind", "endpointIntroduction", "createdInSql", "normalizedName", "templatedParts"} 024) 025public class table implements Cloneable { 026 027 private QualifiedCanonKey identifierKey; 028 029 /** Same-run identity supplied by the producer, never inferred from display text. */ 030 @XmlTransient 031 public QualifiedCanonKey getIdentifierKey() { 032 return identifierKey; 033 } 034 035 public void setIdentifierKey(QualifiedCanonKey identifierKey) { 036 this.identifierKey = identifierKey; 037 } 038 039 private boolean scopedTemporary; 040 041 /** 042 * Same-run fact, never serialized: this is one routine body's temporary table (see 043 * {@link gudusoft.gsqlparser.sqlenv.TemporaryTableSemantics}), a different table from any 044 * other of the same name, so it is never merged with one. 045 */ 046 @XmlTransient 047 public boolean isScopedTemporary() { 048 return scopedTemporary; 049 } 050 051 public void setScopedTemporary(boolean scopedTemporary) { 052 this.scopedTemporary = scopedTemporary; 053 } 054 055 private boolean arrayRelation; 056 057 /** 058 * Same-run fact, never serialized: this is the relation a table-generating construct adds to one SELECT (a 059 * Hive-family LATERAL VIEW), a different table from any other of the same name, so it is never merged with one 060 * (nested-access plan section 4.3, Mantis 4890). 061 */ 062 @XmlTransient 063 public boolean isArrayRelation() { 064 return arrayRelation; 065 } 066 067 public void setArrayRelation(boolean arrayRelation) { 068 this.arrayRelation = arrayRelation; 069 } 070 071 private QualifiedCanonKey mergeIdentity; 072 073 /** 074 * For a function table (a call): the identity of the routine it calls, from the call's name as written, taken 075 * when the analysis published it. Normalized output renames the table afterwards, and an XML document drops it. 076 */ 077 private Object routineKey; 078 079 @XmlTransient 080 public Object getRoutineKey() { 081 return routineKey; 082 } 083 084 public void setRoutineKey(Object routineKey) { 085 this.routineKey = routineKey; 086 } 087 088 /** 089 * Same-run identity of a physical table or view under the analysis's identifier rules, for every 090 * vendor, as the producing model held it. Merges split two tables that share a folded name only 091 * when these differ; it is never inferred from the published name, and it does not survive 092 * serialization. 093 */ 094 @XmlTransient 095 public QualifiedCanonKey getMergeIdentity() { 096 return mergeIdentity; 097 } 098 099 public void setMergeIdentity(QualifiedCanonKey mergeIdentity) { 100 this.mergeIdentity = mergeIdentity; 101 } 102 103 private boolean publishedNameIsSqlSpelling; 104 105 /** 106 * Same-run marker, set by producers whose published names are SQL spellings by construction (the 107 * SQLDep, Grabit and sqlflow metadata readers): the table's identity is its published name read as 108 * SQL under the analysis's identifier rules, which a merge applies with its own service. Not 109 * serialized. 110 */ 111 @XmlTransient 112 public boolean isPublishedNameSqlSpelling() { 113 return publishedNameIsSqlSpelling; 114 } 115 116 public void setPublishedNameSqlSpelling(boolean publishedNameIsSqlSpelling) { 117 this.publishedNameIsSqlSpelling = publishedNameIsSqlSpelling; 118 } 119 120 private table mintedNames; 121 122 /** 123 * Same-run: set on a merged table whose names no minting model published (a metadata reader's spelling, 124 * or a table's that carries no identity) while a table merged into it had such names; the first such 125 * table's names and identity, as a table holding only those. A later merge that joins across legacy 126 * folded names publishes these, as one merge of all its inputs would. Not serialized. 127 */ 128 @XmlTransient 129 public table getMintedNames() { 130 return mintedNames; 131 } 132 133 public void setMintedNames(table mintedNames) { 134 this.mintedNames = mintedNames; 135 } 136 137 private boolean acrossLegacyNames; 138 139 /** 140 * Same-run: whether this table merges tables whose legacy folded names differ, which only identities 141 * join. A later merge that includes it chooses names as a merge across legacy names does. Not 142 * serialized. 143 */ 144 @XmlTransient 145 public boolean isAcrossLegacyNames() { 146 return acrossLegacyNames; 147 } 148 149 public void setAcrossLegacyNames(boolean acrossLegacyNames) { 150 this.acrossLegacyNames = acrossLegacyNames; 151 } 152 153 private java.util.Map<String, String> carriedPublishedNames; 154 155 /** 156 * Same-run: on a merged table, the exact names that the tables carrying an identity merged into it published, 157 * other than the one it publishes itself, each with its legacy folded key, in merge order. A later merge 158 * matches a table that carries no identity on these as on the table's own name (an exact published name), 159 * as one merge of all the inputs would. Null when there are none. Not serialized. 160 */ 161 @XmlTransient 162 public java.util.Map<String, String> getCarriedPublishedNames() { 163 return carriedPublishedNames; 164 } 165 166 public void setCarriedPublishedNames(java.util.Map<String, String> carriedPublishedNames) { 167 this.carriedPublishedNames = carriedPublishedNames; 168 } 169 170 private String id; 171 172 private String server; 173 174 private String userName; 175 176 private String database; 177 178 private String schema; 179 180 private String name; 181 182 private String displayName; 183 184 private String alias; 185 186 private String type; 187 188 private String subType; 189 190 private String uri; 191 192 private List<String> processIds; 193 194 private String procedureId; 195 196 private String isTarget; 197 198 private StringBuffer coordinate = new StringBuffer(); 199 200 private List<column> columns; 201 202 private String parent; 203 204 private String fileType; 205 206 private String fileFormat; 207 208 private String location; 209 210 private String dataSourceName; 211 212 private String namespace; 213 214 @XmlTransient 215 private String starStmt; 216 217 private Boolean more; 218 219 @XmlTransient 220 private String isDetermined; 221 222 private String fromDDL; 223 224 /** 225 * Qualified candidate table names when this one-part reference is ambiguous 226 * across two or more schemas of the default catalog (e.g. {@code testdb.s1.orders}, 227 * {@code testdb.s2.orders}) and no object of that name exists in the default 228 * schema. Empty/absent for normally-resolved references. See 229 * docs/tmp/unqualified-name-default-schema-masks-cross-schema-ambiguity.md. 230 */ 231 private List<String> candidateTables; 232 233 /** 234 * Authoritative endpoint classification (additive). Carried as Strings so a null 235 * value is omitted from XML, keeping existing output byte-identical for endpoints 236 * that aren't classified. Populated from the internal model only when non-default. 237 * See docs/tmp/dlineage-authoritative-endpoint-classification.md. 238 */ 239 private String endpointKind; 240 241 private String endpointIntroduction; 242 243 private String createdInSql; 244 245 private String normalizedName; 246 247 /** 248 * Which identifier segments of {@link #getName()} a dynamic-SQL fold assembled at 249 * runtime, pipe-joined outermost-first ({@code "db"}, {@code "db|name"}, ...). Emitted 250 * only for {@code endpointKind} endpoints the analyzer marked inside a fold context; 251 * absent otherwise, so existing output stays byte-identical. See 252 * docs/tmp/dynamic-template-endpoint-provenance.md. 253 */ 254 private String templatedParts; 255 256 @XmlTransient 257 private LinkedHashSet<String> coordinateItems = new LinkedHashSet<String>(); 258 259 @XmlAttribute(required = false) 260 public String getAlias() { 261 return alias; 262 } 263 264 public void setAlias(String alias) { 265 this.alias = alias; 266 } 267 268 @XmlElement(name = "column", required = false) 269 public List<column> getColumns() { 270 if (this.columns == null) { 271 this.columns = new ArrayList<column>(); 272 } 273 return columns; 274 } 275 276 public void setColumns(List<column> columns) { 277 this.columns = columns; 278 } 279 280 @XmlAttribute(required = false) 281 public String getCoordinate() { 282 String result = coordinate.toString(); 283 if (SQLUtil.isEmpty(result)) 284 return null; 285 return result; 286 } 287 288 public void appendCoordinate(String coordinate) { 289 if (!coordinateItems.contains(coordinate)) { 290 coordinateItems.add(coordinate); 291 rebuildCoordinate(); 292 } 293 } 294 295 private void rebuildCoordinate() { 296 this.coordinate.setLength(0); 297 298 List<String> itemsList = new ArrayList<>(coordinateItems); 299 boolean hasMultiplePairs = itemsList.size() > 1; 300 301 String separator = ""; 302 for (String coordPair : itemsList) { 303 // 只有多对坐标时才过滤无效对 304 if (!hasMultiplePairs || !isBothCoordsInvalid(coordPair)) { 305 this.coordinate.append(separator).append(coordPair); 306 separator = ","; 307 } 308 } 309 } 310 311 /** 312 * 判断一对坐标是否两个都是无效坐标([-1,-1,x] 格式) 313 */ 314 private static boolean isBothCoordsInvalid(String coordPair) { 315 return coordPair != null 316 && coordPair.contains("[-1,-1,") 317 && countOccurrences(coordPair, "[-1,-1,") >= 2; 318 } 319 320 private static int countOccurrences(String str, String target) { 321 if (str == null || target == null || target.isEmpty()) { 322 return 0; 323 } 324 int count = 0; 325 int idx = 0; 326 while ((idx = str.indexOf(target, idx)) != -1) { 327 count++; 328 idx += target.length(); 329 } 330 return count; 331 } 332 333 public void setCoordinate(String coordinate) { 334 if (SQLUtil.isEmpty(coordinate)) { 335 return; 336 } 337 this.coordinate.setLength(0); 338 this.coordinateItems.clear(); 339 340 // 先分割出所有坐标 [line,col,fileIdx] 341 List<String> allCoords = new ArrayList<>(); 342 int start = 0; 343 while (start < coordinate.length()) { 344 int open = coordinate.indexOf('[', start); 345 if (open < 0) break; 346 int close = coordinate.indexOf(']', open); 347 if (close < 0) break; 348 allCoords.add(coordinate.substring(open, close + 1)); 349 start = close + 1; 350 } 351 352 // 计算坐标对数量(每两个坐标组成一对) 353 int pairCount = (allCoords.size() + 1) / 2; 354 355 // 如果只有一对坐标,直接保留 356 if (pairCount <= 1) { 357 this.coordinate.append(coordinate); 358 this.coordinateItems.add(coordinate); 359 return; 360 } 361 362 // 有多对坐标时,过滤掉无效的坐标对(两个坐标都是 [-1,-1,x] 格式) 363 StringBuilder sb = new StringBuilder(); 364 for (int i = 0; i < allCoords.size(); i += 2) { 365 String coord1 = allCoords.get(i); 366 String coord2 = (i + 1 < allCoords.size()) ? allCoords.get(i + 1) : ""; 367 368 // 检查这一对是否都是无效坐标 369 boolean pairIsInvalid = isInvalidCoord(coord1) && isInvalidCoord(coord2); 370 371 if (!pairIsInvalid) { 372 if (sb.length() > 0) { 373 sb.append(","); 374 } 375 sb.append(coord1); 376 this.coordinateItems.add(coord1); 377 if (!coord2.isEmpty()) { 378 sb.append(",").append(coord2); 379 this.coordinateItems.add(coord2); 380 } 381 } 382 } 383 this.coordinate.append(sb.toString()); 384 } 385 386 private static boolean isInvalidCoord(String coord) { 387 if (coord == null || coord.isEmpty()) { 388 return false; 389 } 390 String trimmed = coord.trim(); 391 if (trimmed.indexOf("-1") != -1 && trimmed.startsWith("[") && trimmed.endsWith("]")) { 392 String inner = trimmed.substring(1, trimmed.length() - 1); 393 String[] coords = inner.split(","); 394 if (coords.length >= 2 && "-1".equals(coords[0].trim()) && "-1".equals(coords[1].trim())) { 395 return true; 396 } 397 } 398 return false; 399 } 400 401 public void clearCoordinate() { 402 this.coordinate = new StringBuffer(); 403 } 404 405 @XmlAttribute(required = false) 406 public String getUserName() { 407 return userName; 408 } 409 410 public void setUserName(String userName) { 411 this.userName = userName; 412 } 413 414 @XmlAttribute(required = false) 415 public String getServer() { 416 return server; 417 } 418 419 public void setServer(String server) { 420 this.server = server; 421 } 422 423 @XmlAttribute(required = false) 424 public String getName() { 425 return name; 426 } 427 428 public void setName(String name) { 429 this.name = name; 430 } 431 432 @XmlAttribute(required = false) 433 public String getDisplayName() { 434 return displayName; 435 } 436 437 public void setDisplayName(String displayName) { 438 this.displayName = displayName; 439 } 440 441 @XmlAttribute(required = false) 442 public String getId() { 443 return id; 444 } 445 446 public void setId(String id) { 447 this.id = id; 448 } 449 450 @XmlAttribute(required = false) 451 public List<String> getProcessIds() { 452 return processIds; 453 } 454 455 public void setProcessIds(List<String> processIds) { 456 this.processIds = processIds; 457 } 458 459 @XmlAttribute(required = false) 460 public String getProcedureId() { 461 return procedureId; 462 } 463 464 public void setProcedureId(String procedureId) { 465 this.procedureId = procedureId; 466 } 467 468 @XmlElement(name = "candidate", required = false) 469 public List<String> getCandidateTables() { 470 return candidateTables; 471 } 472 473 public void setCandidateTables(List<String> candidateTables) { 474 this.candidateTables = candidateTables; 475 } 476 477 @XmlAttribute(required = false) 478 public String getType() { 479 return type; 480 } 481 482 public void setType(String type) { 483 this.type = type; 484 } 485 486 @XmlAttribute(required = false) 487 public String getUri() { 488 return uri; 489 } 490 491 public void setUri(String uri) { 492 this.uri = uri; 493 } 494 495 @XmlAttribute(required = false) 496 public String getFileType() { 497 return fileType; 498 } 499 500 public void setFileType(String fileType) { 501 this.fileType = fileType; 502 } 503 504 @XmlAttribute(required = false) 505 public String getFileFormat() { 506 return fileFormat; 507 } 508 509 public void setFileFormat(String fileFormat) { 510 this.fileFormat = fileFormat; 511 } 512 513 @XmlAttribute(required = false) 514 public String getLocation() { 515 return location; 516 } 517 518 public void setLocation(String location) { 519 this.location = location; 520 } 521 522 /** The external data source a file path is read through (SQL Server BULK INSERT ... WITH (DATA_SOURCE = ...)). */ 523 @XmlAttribute(required = false) 524 public String getDataSourceName() { 525 return dataSourceName; 526 } 527 528 public void setDataSourceName(String dataSourceName) { 529 this.dataSourceName = dataSourceName; 530 } 531 532 @XmlAttribute(required = false) 533 public String getNamespace() { 534 return namespace; 535 } 536 537 public void setNamespace(String namespace) { 538 this.namespace = namespace; 539 } 540 541 @XmlTransient 542 public String getStarStmt() { 543 return starStmt; 544 } 545 546 public void setStarStmt(String starStmt) { 547 this.starStmt = starStmt; 548 } 549 550 @XmlTransient 551 public String getIsDetermined() { 552 return isDetermined; 553 } 554 555 public void setIsDetermined(String isDetermined) { 556 this.isDetermined = isDetermined; 557 } 558 559 public boolean isFunction() { 560 return "function".equals(type) || "function".equals(subType); 561 } 562 563 public boolean isView() { 564 return "view".equals(type); 565 } 566 567 public boolean isDatabaseType() { 568 return "database".equals(type); 569 } 570 571 public boolean isSchemaType() { 572 return "schema".equals(type); 573 } 574 575 public boolean isSequence() { 576 return "sequence".equals(type); 577 } 578 579 public boolean isStage() { 580 return "stage".equals(type); 581 } 582 583 public boolean isDataSource() { 584 return "dataSource".equals(type); 585 } 586 587 public boolean isStream() { 588 return "stream".equals(type); 589 } 590 591 public boolean isVariable() { 592 return "variable".equals(type); 593 } 594 595 public boolean isCursor() { 596 return "cursor".equals(type); 597 } 598 599 public boolean isFile() { 600 return "file".equals(type) || "path".equals(type); 601 } 602 603 public boolean isTable() { 604 return "table".equals(type) || "pseudoTable".equals(type) || "constantTable".equals(type); 605 } 606 607 public boolean isPseudoTable() { 608 return "pseudoTable".equals(type); 609 } 610 611 public boolean isConstantTable() { 612 return "pseudoTable".equals(type); 613 } 614 615 public boolean isResultSet() { 616 return type != null && !isView() && !isCursor() && !isTable() && !isStage() && !isSequence() && !isDataSource() && !isDatabaseType() && !isSchemaType() && !isStream() && !isVariable() && !isFile(); 617 } 618 619 @XmlAttribute(name = "isTarget", required = false) 620 public String getIsTarget() { 621 return isTarget; 622 } 623 624 public boolean isTarget() { 625 return "true".equals(isTarget); 626 } 627 628 @XmlAttribute(required = false) 629 public String getParent() { 630 return parent; 631 } 632 633 public void setParent(String parent) { 634 this.parent = parent; 635 } 636 637 @XmlAttribute(required = false) 638 public String getDatabase() { 639 return database; 640 } 641 642 public void setDatabase(String database) { 643 if (SQLUtil.parseNames(database).size() > 1) { 644 database = "\"" + database + "\""; 645 } 646 this.database = database; 647 } 648 649 @XmlAttribute(required = false) 650 public String getSchema() { 651 return schema; 652 } 653 654 public void setSchema(String schema) { 655 if (SQLUtil.parseNames(schema).size() > 1) { 656 schema = "\"" + schema + "\""; 657 } 658 this.schema = schema; 659 } 660 661 @XmlAttribute(required = false) 662 public String getSubType() { 663 return subType; 664 } 665 666 public void setSubType(String subType) { 667 this.subType = subType; 668 } 669 670 public String getFullName() { 671 if (isDatabaseType()) { 672 return database; 673 } 674 StringBuilder fullName = new StringBuilder(); 675 if (!SQLUtil.isEmpty(database)) { 676 fullName.append(database).append("."); 677 } 678 if (!SQLUtil.isEmpty(schema)) { 679 fullName.append(schema).append("."); 680 } 681 if (fullName.length() > 0) { 682 fullName.append(getTableNameOnly()); 683 } else { 684 fullName.append(name); 685 } 686 return fullName.toString(); 687 } 688 689 public String getFullSchemaName() { 690 StringBuilder fullName = new StringBuilder(); 691 if (!SQLUtil.isEmpty(database)) { 692 if(ModelBindingManager.getGlobalVendor()!=null) { 693 fullName.append(LegacyDlineageWireNamesV1.name(database, ESQLDataObjectType.dotCatalog)).append("."); 694 } 695 else{ 696 fullName.append(database).append("."); 697 } 698 } 699 if (!SQLUtil.isEmpty(schema)) { 700 if(ModelBindingManager.getGlobalVendor()!=null) { 701 fullName.append(LegacyDlineageWireNamesV1.name(schema, ESQLDataObjectType.dotSchema)); 702 } 703 else { 704 fullName.append(schema).append("."); 705 } 706 } 707 String fullSchemaName = fullName.toString(); 708 if (fullSchemaName.endsWith(".")) { 709 fullSchemaName = fullSchemaName.substring(0, fullSchemaName.length() - 1); 710 } 711 if (fullSchemaName.length() == 0) { 712 fullSchemaName = TSQLEnv.DEFAULT_SCHEMA_NAME; 713 } 714 return fullSchemaName; 715 } 716 717 public String getTableNameOnly() { 718 if (name.indexOf("@") != -1 && SQLUtil.trimColumnStringQuote(name.substring(name.lastIndexOf("@") + 1).trim()).equals(SQLUtil.trimColumnStringQuote(database))) { 719 List<String> segments = SQLUtil.parseNames(name.substring(0, name.lastIndexOf("@")).trim()); 720 if (segments.size() > 2) { 721 return SQLUtil.mergeSegments(segments, 2); 722 } 723 return segments.get(segments.size() - 1); 724 } else { 725 List<String> segments = SQLUtil.parseNames(name); 726 if (segments.size() > 2) { 727 return SQLUtil.mergeSegments(segments, 2); 728 } 729 return segments.get(segments.size() - 1); 730 } 731 } 732 733 public void setIsTarget(String isTarget) { 734 this.isTarget = isTarget; 735 } 736 737 public int getOccurrencesNumber() { 738 return PositionUtil.getOccurrencesNumber(coordinate.toString()); 739 } 740 741 public Coordinate getStartPos(int index) { 742 return PositionUtil.getStartPos(coordinate.toString(), index); 743 } 744 745 public Coordinate getEndPos(int index) { 746 return PositionUtil.getEndPos(coordinate.toString(), index); 747 } 748 749 public Boolean getMore() { 750 return more; 751 } 752 753 public void setMore(Boolean more) { 754 this.more = more; 755 } 756 757 @XmlAttribute(required = false) 758 public String getFromDDL() { 759 return fromDDL; 760 } 761 762 public void setFromDDL(String fromDDL) { 763 this.fromDDL = fromDDL; 764 } 765 766 @XmlAttribute(required = false) 767 public String getEndpointKind() { 768 return endpointKind; 769 } 770 771 public void setEndpointKind(String endpointKind) { 772 this.endpointKind = endpointKind; 773 } 774 775 @XmlAttribute(required = false) 776 public String getEndpointIntroduction() { 777 return endpointIntroduction; 778 } 779 780 public void setEndpointIntroduction(String endpointIntroduction) { 781 this.endpointIntroduction = endpointIntroduction; 782 } 783 784 @XmlAttribute(required = false) 785 public String getCreatedInSql() { 786 return createdInSql; 787 } 788 789 public void setCreatedInSql(String createdInSql) { 790 this.createdInSql = createdInSql; 791 } 792 793 @XmlAttribute(required = false) 794 public String getNormalizedName() { 795 return normalizedName; 796 } 797 798 public void setNormalizedName(String normalizedName) { 799 this.normalizedName = normalizedName; 800 } 801 802 @XmlAttribute(required = false) 803 public String getTemplatedParts() { 804 return templatedParts; 805 } 806 807 public void setTemplatedParts(String templatedParts) { 808 this.templatedParts = templatedParts; 809 } 810 811 @Override 812 public Object clone() throws CloneNotSupportedException { 813 return super.clone(); 814 } 815}