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}