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}