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.sqlenv.ESQLDataObjectType;
007import gudusoft.gsqlparser.sqlenv.TSQLEnv;
008import gudusoft.gsqlparser.util.SQLUtil;
009
010import javax.xml.bind.annotation.XmlAttribute;
011import javax.xml.bind.annotation.XmlElement;
012import javax.xml.bind.annotation.XmlTransient;
013import javax.xml.bind.annotation.XmlType;
014import java.util.ArrayList;
015import java.util.LinkedHashSet;
016import java.util.List;
017
018@XmlType(
019        propOrder = {"id", "server", "userName", "database", "schema", "name", "displayName", "alias", "uri", "type", "subType",
020                "processIds", "procedureId", "fileType", "fileFormat", "location", "namespace", "isTarget", "coordinate", "columns", "parent", "more", "fromDDL", "candidateTables",
021                "endpointKind", "endpointIntroduction", "createdInSql", "normalizedName", "templatedParts"}
022)
023public class table implements Cloneable {
024
025    private String id;
026
027    private String server;
028
029    private String userName;
030
031    private String database;
032
033    private String schema;
034
035    private String name;
036
037    private String displayName;
038
039    private String alias;
040
041    private String type;
042
043    private String subType;
044
045    private String uri;
046
047    private List<String> processIds;
048
049    private String procedureId;
050
051    private String isTarget;
052
053    private StringBuffer coordinate = new StringBuffer();
054
055    private List<column> columns;
056
057    private String parent;
058
059    private String fileType;
060
061    private String fileFormat;
062
063    private String location;
064
065    private String namespace;
066
067    @XmlTransient
068    private String starStmt;
069
070    private Boolean more;
071    
072    @XmlTransient
073    private String isDetermined;
074
075    private String fromDDL;
076
077    /**
078     * Qualified candidate table names when this one-part reference is ambiguous
079     * across two or more schemas of the default catalog (e.g. {@code testdb.s1.orders},
080     * {@code testdb.s2.orders}) and no object of that name exists in the default
081     * schema. Empty/absent for normally-resolved references. See
082     * docs/tmp/unqualified-name-default-schema-masks-cross-schema-ambiguity.md.
083     */
084    private List<String> candidateTables;
085
086    /**
087     * Authoritative endpoint classification (additive). Carried as Strings so a null
088     * value is omitted from XML, keeping existing output byte-identical for endpoints
089     * that aren't classified. Populated from the internal model only when non-default.
090     * See docs/tmp/dlineage-authoritative-endpoint-classification.md.
091     */
092    private String endpointKind;
093
094    private String endpointIntroduction;
095
096    private String createdInSql;
097
098    private String normalizedName;
099
100    /**
101     * Which identifier segments of {@link #getName()} a dynamic-SQL fold assembled at
102     * runtime, pipe-joined outermost-first ({@code "db"}, {@code "db|name"}, ...). Emitted
103     * only for {@code endpointKind} endpoints the analyzer marked inside a fold context;
104     * absent otherwise, so existing output stays byte-identical. See
105     * docs/tmp/dynamic-template-endpoint-provenance.md.
106     */
107    private String templatedParts;
108
109    @XmlTransient
110    private LinkedHashSet<String> coordinateItems = new LinkedHashSet<String>();
111
112    @XmlAttribute(required = false)
113    public String getAlias() {
114        return alias;
115    }
116
117    public void setAlias(String alias) {
118        this.alias = alias;
119    }
120
121    @XmlElement(name = "column", required = false)
122    public List<column> getColumns() {
123        if (this.columns == null) {
124            this.columns = new ArrayList<column>();
125        }
126        return columns;
127    }
128
129    public void setColumns(List<column> columns) {
130        this.columns = columns;
131    }
132
133    @XmlAttribute(required = false)
134    public String getCoordinate() {
135        String result = coordinate.toString();
136        if (SQLUtil.isEmpty(result))
137            return null;
138        return result;
139    }
140
141    public void appendCoordinate(String coordinate) {
142        if (!coordinateItems.contains(coordinate)) {
143            coordinateItems.add(coordinate);
144            rebuildCoordinate();
145        }
146    }
147    
148    private void rebuildCoordinate() {
149        this.coordinate.setLength(0);
150        
151        List<String> itemsList = new ArrayList<>(coordinateItems);
152        boolean hasMultiplePairs = itemsList.size() > 1;
153        
154        String separator = "";
155        for (String coordPair : itemsList) {
156            // 只有多对坐标时才过滤无效对
157            if (!hasMultiplePairs || !isBothCoordsInvalid(coordPair)) {
158                this.coordinate.append(separator).append(coordPair);
159                separator = ",";
160            }
161        }
162    }
163    
164    /**
165     * 判断一对坐标是否两个都是无效坐标([-1,-1,x] 格式)
166     */
167    private static boolean isBothCoordsInvalid(String coordPair) {
168        return coordPair != null 
169            && coordPair.contains("[-1,-1,") 
170            && countOccurrences(coordPair, "[-1,-1,") >= 2;
171    }
172    
173    private static int countOccurrences(String str, String target) {
174        if (str == null || target == null || target.isEmpty()) {
175            return 0;
176        }
177        int count = 0;
178        int idx = 0;
179        while ((idx = str.indexOf(target, idx)) != -1) {
180            count++;
181            idx += target.length();
182        }
183        return count;
184    }
185
186    public void setCoordinate(String coordinate) {
187        if (SQLUtil.isEmpty(coordinate)) {
188                return;
189        }
190        this.coordinate.setLength(0);
191        this.coordinateItems.clear();
192        
193        // 先分割出所有坐标 [line,col,fileIdx]
194        List<String> allCoords = new ArrayList<>();
195        int start = 0;
196        while (start < coordinate.length()) {
197            int open = coordinate.indexOf('[', start);
198            if (open < 0) break;
199            int close = coordinate.indexOf(']', open);
200            if (close < 0) break;
201            allCoords.add(coordinate.substring(open, close + 1));
202            start = close + 1;
203        }
204        
205        // 计算坐标对数量(每两个坐标组成一对)
206        int pairCount = (allCoords.size() + 1) / 2;
207        
208        // 如果只有一对坐标,直接保留
209        if (pairCount <= 1) {
210            this.coordinate.append(coordinate);
211            this.coordinateItems.add(coordinate);
212            return;
213        }
214        
215        // 有多对坐标时,过滤掉无效的坐标对(两个坐标都是 [-1,-1,x] 格式)
216        StringBuilder sb = new StringBuilder();
217        for (int i = 0; i < allCoords.size(); i += 2) {
218            String coord1 = allCoords.get(i);
219            String coord2 = (i + 1 < allCoords.size()) ? allCoords.get(i + 1) : "";
220            
221            // 检查这一对是否都是无效坐标
222            boolean pairIsInvalid = isInvalidCoord(coord1) && isInvalidCoord(coord2);
223            
224            if (!pairIsInvalid) {
225                if (sb.length() > 0) {
226                    sb.append(",");
227                }
228                sb.append(coord1);
229                this.coordinateItems.add(coord1);
230                if (!coord2.isEmpty()) {
231                    sb.append(",").append(coord2);
232                    this.coordinateItems.add(coord2);
233                }
234            }
235        }
236        this.coordinate.append(sb.toString());
237    }
238    
239    private static boolean isInvalidCoord(String coord) {
240        if (coord == null || coord.isEmpty()) {
241            return false;
242        }
243        String trimmed = coord.trim();
244        if (trimmed.indexOf("-1") != -1 && trimmed.startsWith("[") && trimmed.endsWith("]")) {
245            String inner = trimmed.substring(1, trimmed.length() - 1);
246            String[] coords = inner.split(",");
247            if (coords.length >= 2 && "-1".equals(coords[0].trim()) && "-1".equals(coords[1].trim())) {
248                return true;
249            }
250        }
251        return false;
252    }
253    
254    public void clearCoordinate() {
255        this.coordinate = new StringBuffer();
256    }
257
258    @XmlAttribute(required = false)
259    public String getUserName() {
260        return userName;
261    }
262
263    public void setUserName(String userName) {
264        this.userName = userName;
265    }
266
267    @XmlAttribute(required = false)
268    public String getServer() {
269        return server;
270    }
271
272    public void setServer(String server) {
273        this.server = server;
274    }
275
276    @XmlAttribute(required = false)
277    public String getName() {
278        return name;
279    }
280
281    public void setName(String name) {
282        this.name = name;
283    }
284
285    @XmlAttribute(required = false)
286    public String getDisplayName() {
287        return displayName;
288    }
289
290    public void setDisplayName(String displayName) {
291        this.displayName = displayName;
292    }
293
294    @XmlAttribute(required = false)
295    public String getId() {
296        return id;
297    }
298
299    public void setId(String id) {
300        this.id = id;
301    }
302
303    @XmlAttribute(required = false)
304    public List<String> getProcessIds() {
305        return processIds;
306    }
307
308    public void setProcessIds(List<String> processIds) {
309        this.processIds = processIds;
310    }
311
312    @XmlAttribute(required = false)
313    public String getProcedureId() {
314        return procedureId;
315    }
316
317    public void setProcedureId(String procedureId) {
318        this.procedureId = procedureId;
319    }
320
321    @XmlElement(name = "candidate", required = false)
322    public List<String> getCandidateTables() {
323        return candidateTables;
324    }
325
326    public void setCandidateTables(List<String> candidateTables) {
327        this.candidateTables = candidateTables;
328    }
329
330    @XmlAttribute(required = false)
331    public String getType() {
332        return type;
333    }
334
335    public void setType(String type) {
336        this.type = type;
337    }
338
339    @XmlAttribute(required = false)
340    public String getUri() {
341        return uri;
342    }
343
344    public void setUri(String uri) {
345        this.uri = uri;
346    }
347
348    @XmlAttribute(required = false)
349    public String getFileType() {
350        return fileType;
351    }
352
353    public void setFileType(String fileType) {
354        this.fileType = fileType;
355    }
356
357    @XmlAttribute(required = false)
358    public String getFileFormat() {
359        return fileFormat;
360    }
361
362    public void setFileFormat(String fileFormat) {
363        this.fileFormat = fileFormat;
364    }
365
366    @XmlAttribute(required = false)
367    public String getLocation() {
368        return location;
369    }
370
371    public void setLocation(String location) {
372        this.location = location;
373    }
374
375    @XmlAttribute(required = false)
376    public String getNamespace() {
377        return namespace;
378    }
379
380    public void setNamespace(String namespace) {
381        this.namespace = namespace;
382    }
383
384    @XmlTransient
385    public String getStarStmt() {
386        return starStmt;
387    }
388
389    public void setStarStmt(String starStmt) {
390        this.starStmt = starStmt;
391    }
392
393    @XmlTransient
394    public String getIsDetermined() {
395                return isDetermined;
396        }
397
398        public void setIsDetermined(String isDetermined) {
399                this.isDetermined = isDetermined;
400        }
401
402        public boolean isFunction() {
403        return "function".equals(type) || "function".equals(subType);
404    }
405
406    public boolean isView() {
407        return "view".equals(type);
408    }
409
410    public boolean isDatabaseType() {
411        return "database".equals(type);
412    }
413
414    public boolean isSchemaType() {
415        return "schema".equals(type);
416    }
417
418    public boolean isSequence() {
419        return "sequence".equals(type);
420    }
421
422    public boolean isStage() {
423        return "stage".equals(type);
424    }
425
426    public boolean isDataSource() {
427        return "dataSource".equals(type);
428    }
429
430    public boolean isStream() {
431        return "stream".equals(type);
432    }
433
434    public boolean isVariable() {
435        return "variable".equals(type);
436    }
437
438    public boolean isCursor() {
439        return "cursor".equals(type);
440    }
441
442    public boolean isFile() {
443        return "file".equals(type) || "path".equals(type);
444    }
445
446    public boolean isTable() {
447        return "table".equals(type) || "pseudoTable".equals(type) || "constantTable".equals(type);
448    }
449
450    public boolean isPseudoTable() {
451        return "pseudoTable".equals(type);
452    }
453
454    public boolean isConstantTable() {
455        return "pseudoTable".equals(type);
456    }
457
458    public boolean isResultSet() {
459        return type != null && !isView() && !isCursor() && !isTable() && !isStage() && !isSequence() && !isDataSource() && !isDatabaseType() && !isSchemaType() && !isStream() && !isVariable() && !isFile();
460    }
461
462    @XmlAttribute(name = "isTarget", required = false)
463    public String getIsTarget() {
464        return isTarget;
465    }
466
467    public boolean isTarget() {
468        return "true".equals(isTarget);
469    }
470
471    @XmlAttribute(required = false)
472    public String getParent() {
473        return parent;
474    }
475
476    public void setParent(String parent) {
477        this.parent = parent;
478    }
479
480    @XmlAttribute(required = false)
481    public String getDatabase() {
482        return database;
483    }
484
485    public void setDatabase(String database) {
486        if (SQLUtil.parseNames(database).size() > 1) {
487            database = "\"" + database + "\"";
488        }
489        this.database = database;
490    }
491
492    @XmlAttribute(required = false)
493    public String getSchema() {
494        return schema;
495    }
496
497    public void setSchema(String schema) {
498        if (SQLUtil.parseNames(schema).size() > 1) {
499            schema = "\"" + schema + "\"";
500        }
501        this.schema = schema;
502    }
503
504    @XmlAttribute(required = false)
505    public String getSubType() {
506        return subType;
507    }
508
509    public void setSubType(String subType) {
510        this.subType = subType;
511    }
512
513    public String getFullName() {
514        if (isDatabaseType()) {
515            return database;
516        }
517        StringBuilder fullName = new StringBuilder();
518        if (!SQLUtil.isEmpty(database)) {
519            fullName.append(database).append(".");
520        }
521        if (!SQLUtil.isEmpty(schema)) {
522            fullName.append(schema).append(".");
523        }
524        if (fullName.length() > 0) {
525            fullName.append(getTableNameOnly());
526        } else {
527            fullName.append(name);
528        }
529        return fullName.toString();
530    }
531
532    public String getFullSchemaName() {
533        StringBuilder fullName = new StringBuilder();
534        if (!SQLUtil.isEmpty(database)) {
535            if(ModelBindingManager.getGlobalVendor()!=null) {
536                fullName.append(DlineageUtil.getIdentifierNormalName(database, ESQLDataObjectType.dotCatalog)).append(".");
537            }
538            else{
539                fullName.append(database).append(".");
540            }
541        }
542        if (!SQLUtil.isEmpty(schema)) {
543            if(ModelBindingManager.getGlobalVendor()!=null) {
544                fullName.append(DlineageUtil.getIdentifierNormalName(schema, ESQLDataObjectType.dotSchema));
545            }
546            else {
547                fullName.append(schema).append(".");
548            }
549        }
550        String fullSchemaName = fullName.toString();
551        if (fullSchemaName.endsWith(".")) {
552            fullSchemaName = fullSchemaName.substring(0, fullSchemaName.length() - 1);
553        }
554        if (fullSchemaName.length() == 0) {
555            fullSchemaName = TSQLEnv.DEFAULT_SCHEMA_NAME;
556        }
557        return fullSchemaName;
558    }
559
560    public String getTableNameOnly() {
561        if (name.indexOf("@") != -1 && SQLUtil.trimColumnStringQuote(name.substring(name.lastIndexOf("@") + 1).trim()).equals(SQLUtil.trimColumnStringQuote(database))) {
562            List<String> segments = SQLUtil.parseNames(name.substring(0, name.lastIndexOf("@")).trim());
563            if (segments.size() > 2) {
564                return SQLUtil.mergeSegments(segments, 2);
565            }
566            return segments.get(segments.size() - 1);
567        } else {
568            List<String> segments = SQLUtil.parseNames(name);
569            if (segments.size() > 2) {
570                return SQLUtil.mergeSegments(segments, 2);
571            }
572            return segments.get(segments.size() - 1);
573        }
574    }
575
576    public void setIsTarget(String isTarget) {
577        this.isTarget = isTarget;
578    }
579
580    public int getOccurrencesNumber() {
581        return PositionUtil.getOccurrencesNumber(coordinate.toString());
582    }
583
584    public Coordinate getStartPos(int index) {
585        return PositionUtil.getStartPos(coordinate.toString(), index);
586    }
587
588    public Coordinate getEndPos(int index) {
589        return PositionUtil.getEndPos(coordinate.toString(), index);
590    }
591
592    public Boolean getMore() {
593        return more;
594    }
595
596    public void setMore(Boolean more) {
597        this.more = more;
598    }
599
600    @XmlAttribute(required = false)
601    public String getFromDDL() {
602        return fromDDL;
603    }
604
605    public void setFromDDL(String fromDDL) {
606        this.fromDDL = fromDDL;
607    }
608
609    @XmlAttribute(required = false)
610    public String getEndpointKind() {
611        return endpointKind;
612    }
613
614    public void setEndpointKind(String endpointKind) {
615        this.endpointKind = endpointKind;
616    }
617
618    @XmlAttribute(required = false)
619    public String getEndpointIntroduction() {
620        return endpointIntroduction;
621    }
622
623    public void setEndpointIntroduction(String endpointIntroduction) {
624        this.endpointIntroduction = endpointIntroduction;
625    }
626
627    @XmlAttribute(required = false)
628    public String getCreatedInSql() {
629        return createdInSql;
630    }
631
632    public void setCreatedInSql(String createdInSql) {
633        this.createdInSql = createdInSql;
634    }
635
636    @XmlAttribute(required = false)
637    public String getNormalizedName() {
638        return normalizedName;
639    }
640
641    public void setNormalizedName(String normalizedName) {
642        this.normalizedName = normalizedName;
643    }
644
645    @XmlAttribute(required = false)
646    public String getTemplatedParts() {
647        return templatedParts;
648    }
649
650    public void setTemplatedParts(String templatedParts) {
651        this.templatedParts = templatedParts;
652    }
653
654    @Override
655    public Object clone() throws CloneNotSupportedException {
656        return super.clone();
657    }
658}