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    /**
194     * Constructor for MDX and other non-SQL columns where only a name is available.
195     */
196    public TableColumn(Table table, String columnName) {
197        if (table == null || columnName == null)
198            throw new IllegalArgumentException("TableColumn arguments can't be null.");
199
200        id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
201
202        this.startPosition = new Pair3<Long, Long, String>(-1L, -1L, ModelBindingManager.getGlobalHash());
203        this.endPosition = new Pair3<Long, Long, String>(-1L, -1L, ModelBindingManager.getGlobalHash());
204
205        this.name = columnName;
206        this.table = table;
207        table.addColumn(this);
208    }
209
210    public TableColumn(Table table, TConstant columnObject, int columnIndex) {
211        if (table == null || columnObject == null)
212            throw new IllegalArgumentException("TableColumn arguments can't be null.");
213
214        id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
215
216        TSourceToken startToken = columnObject.getStartToken();
217        TSourceToken endToken = columnObject.getEndToken();
218        this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo,
219                startToken.columnNo, ModelBindingManager.getGlobalHash());
220        this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo,
221                endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash());
222
223        this.name = columnObject.toString();
224        
225        this.table = table;
226        table.addColumn(this);
227    }
228    
229    public TableColumn(Table table, TConstant columnObject) {
230        if (table == null || columnObject == null)
231            throw new IllegalArgumentException("TableColumn arguments can't be null.");
232
233        id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
234
235        TSourceToken startToken = columnObject.getStartToken();
236        TSourceToken endToken = columnObject.getEndToken();
237        this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo,
238                startToken.columnNo, ModelBindingManager.getGlobalHash());
239        this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo,
240                endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash());
241
242        this.name = columnObject.toString();        
243        this.table = table;
244        table.addColumn(this);
245    }
246    
247    public TableColumn(Table table, TFunctionCall columnObject) {
248        if (table == null || columnObject == null)
249            throw new IllegalArgumentException("TableColumn arguments can't be null.");
250
251        id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
252
253        TSourceToken startToken = columnObject.getStartToken();
254        TSourceToken endToken = columnObject.getEndToken();
255        this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo,
256                startToken.columnNo, ModelBindingManager.getGlobalHash());
257        this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo,
258                endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash());
259
260        this.name = columnObject.toString();        
261        this.table = table;
262        table.addColumn(this);
263        }
264
265    
266    public TableColumn(Table table, TExpression columnObject, int columnIndex) {
267        if (table == null || columnObject == null)
268            throw new IllegalArgumentException("TableColumn arguments can't be null.");
269
270        id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
271
272        TSourceToken startToken = columnObject.getStartToken();
273        TSourceToken endToken = columnObject.getEndToken();
274        this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo,
275                startToken.columnNo, ModelBindingManager.getGlobalHash());
276        this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo,
277                endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash());
278
279                this.name = getColumnName(columnObject);
280        
281        this.table = table;
282        table.addColumn(this);
283    }
284    
285    protected String getColumnName(TExpression expr) {
286                if (expr.getExpressionType() == EExpressionType.simple_constant_t) {
287                        return expr.toString();
288                } else if (expr.getExpressionType() == EExpressionType.sqlserver_proprietary_column_alias_t) {
289                        return expr.getLeftOperand().toString();
290                } else if (expr.getExpressionType() == EExpressionType.function_t) {
291                        return expr.getFunctionCall().toString();
292                } else if (expr.getExpressionType() == EExpressionType.typecast_t) {
293                        return expr.getLeftOperand().toString();
294                } else if (expr.getExpressionType() == EExpressionType.parenthesis_t) {
295                        TExpression inner = expr.getLeftOperand();
296                        while (inner != null && inner.getExpressionType() == EExpressionType.parenthesis_t) {
297                                inner = inner.getLeftOperand();
298                        }
299                        return getColumnName(inner);
300                } else if (expr.getExpressionType() == EExpressionType.simple_object_name_t) {
301                        TObjectName columnName = expr.getObjectOperand();
302                        if (columnName.getPropertyToken() != null) {
303                                return columnName.getPropertyToken().getAstext();
304                        } else {
305                                return columnName.getColumnNameOnly();
306                        }
307                } else if (expr.getExpressionType() == EExpressionType.subquery_t) {
308                        TResultColumnList resultset = expr.getSubQuery().getResultColumnList();
309                        if (resultset != null && resultset.size() == 1) {
310                                TResultColumn resultColumn = resultset.getResultColumn(0);
311                                if (!SQLUtil.isEmpty(resultColumn.getColumnAlias())) {
312                                        return resultColumn.getColumnAlias();
313                                } else if (resultColumn.getExpr() != null) {
314                                        return getColumnName(resultColumn.getExpr());
315                                } else if (resultColumn.getColumnNameOnly() != null
316                                                && !"".equals(resultColumn.getColumnNameOnly())) {
317                                        if (resultColumn.getExpr().getObjectOperand() != null
318                                                        && resultColumn.getExpr().getObjectOperand().getPropertyToken() != null) {
319                                                this.name = resultColumn.getExpr().getObjectOperand().getPropertyToken().getAstext();
320                                        } else {
321                                                this.name = resultColumn.getColumnNameOnly();
322                                        }
323                                } else {
324                                        this.name = resultColumn.toString();
325                                }
326                        }
327                }
328                return quote(expr.toString());
329        }
330    
331 
332        private String quote(String column) {
333        EDbVendor vendor = ModelBindingManager.getGlobalVendor();
334        String delimitedChar = TSQLEnv.delimitedChar(vendor);
335        if (!DlineageUtil.isQuote(column)){
336                if(vendor == EDbVendor.dbvmssql || vendor == EDbVendor.dbvazuresql) {
337                        return "[" + column + "]";
338                }
339                else {
340                                return delimitedChar + column + delimitedChar;
341                        }
342        }
343        else return column;
344    }
345
346    public Table getTable() {
347        return table;
348    }
349
350    public long getId() {
351        return id;
352    }
353
354    public String getName() {
355        return name;
356    }
357
358    public Pair3<Long, Long, String> getStartPosition() {
359        return startPosition;
360    }
361
362    public Pair3<Long, Long, String> getEndPosition() {
363        return endPosition;
364    }
365
366    public TObjectName getColumnObject() {
367        return columnObject;
368    }
369
370    public void bindStarLinkColumns(Map<String, Set<TObjectName>> starLinkColumns) {
371        if (starLinkColumns != null && !starLinkColumns.isEmpty()) {
372            this.starLinkColumns.putAll(starLinkColumns);
373        }
374    }
375
376    public Map<String, Set<TObjectName>> getStarLinkColumns() {
377        return starLinkColumns;
378    }
379    
380        public boolean hasStarLinkColumn(){
381                return (starLinkColumns!=null && !starLinkColumns.isEmpty());
382        }
383    
384        public boolean bindStarLinkColumn(TObjectName objectName) {
385                if (objectName == null) {
386                        return false;
387                }
388
389                String columnName = DlineageUtil.getColumnName(objectName);
390
391                if ("*".equals(columnName)) {
392                        return false;
393                }
394                
395                boolean flag = false;
396                if (!starLinkColumns.containsKey(columnName)) {
397                        starLinkColumns.put(columnName, new LinkedHashSet<TObjectName>());
398                        flag = true;
399                }
400
401                starLinkColumns.get(columnName).add(objectName);
402                
403                
404                if(this.getStarSourceColumns()!=null) {
405                        for(Column column: this.getStarSourceColumns()) {
406                                boolean bind = true;
407                                if(column instanceof ResultColumn) {
408                                        ResultSet parent = ((ResultColumn) column).getResultSet();
409                                        for(ResultColumn item: parent.getColumns()) {
410                                                if(DlineageUtil.compareColumnIdentifier(DlineageUtil.getColumnName(item.getName()), DlineageUtil.getColumnName(objectName))) {
411                                                        bind = false;
412                                                        break;
413                                                }
414                                        }
415                                }
416                                if(column instanceof TableColumn) {
417                                        Table parent = ((TableColumn) column).getTable();
418                                        for(TableColumn item: parent.getColumns()) {
419                                                if(DlineageUtil.compareColumnIdentifier(DlineageUtil.getColumnName(item.getName()), DlineageUtil.getColumnName(objectName))) {
420                                                        bind = false;
421                                                        break;
422                                                }
423                                        }
424                                }
425                                if (bind) {
426                                        column.bindStarLinkColumn(objectName);
427                                }
428                        }
429                }
430                
431                return flag;
432        }
433    
434        public List<TObjectName> getStarLinkColumnList() {
435                List<TObjectName> columns = new ArrayList<TObjectName>();
436                if (starLinkColumns != null) {
437                        for (Set<TObjectName> columnSet : starLinkColumns.values()) {
438                                columns.addAll(columnSet);
439                        }
440                }
441                return columns;
442        }
443        
444        public List<String> getStarLinkColumnNames() {
445                List<String> columns = new ArrayList<>();
446                if (starLinkColumns != null) {
447                        columns.addAll(starLinkColumns.keySet());
448                }
449                return columns;
450        }
451
452        public boolean isShowStar() {
453                return showStar;
454        }
455
456        public void setShowStar(boolean showStar) {
457                this.showStar = showStar;
458        }
459        
460        public Table getView() {
461                return getTable();
462        }
463
464        public Integer getColumnIndex() {
465                return columnIndex;
466        }
467
468        public boolean isVariant() {
469                return isVariant;
470        }
471
472        public void setVariant(boolean isVariant) {
473                this.isVariant = isVariant;
474        }
475
476        public String getDisplayName() {
477                return displayName;
478        }
479
480        public void setDisplayName(String displayName) {
481                this.displayName = displayName;
482        }
483
484        public void setExpandStar(boolean expandStar) {
485                this.expandStar = expandStar;
486        }
487
488        public boolean isExpandStar() {
489                return expandStar;
490        }
491
492        public boolean isPseduo() {
493                return isPseduo;
494        }
495
496        public void setPseduo(boolean isPseduo) {
497                this.isPseduo = isPseduo;
498        }
499
500        public void notBindStarLinkColumn(boolean notBindStarColumn) {
501                this.notBindStarColumn = notBindStarColumn;
502        }
503        
504        public boolean isNotBindStarLinkColumn() {
505                return notBindStarColumn;
506        }
507
508    public Boolean getPrimaryKey() {
509        return primaryKey;
510    }
511
512    public void setPrimaryKey(Boolean primaryKey) {
513        this.primaryKey = primaryKey;
514    }
515
516    public Boolean getUnqiueKey() {
517        return unqiueKey;
518    }
519
520    public void setUnqiueKey(Boolean unqiueKey) {
521        this.unqiueKey = unqiueKey;
522    }
523
524    public Boolean getIndexKey() {
525        return indexKey;
526    }
527
528    public void setIndexKey(Boolean indexKey) {
529        this.indexKey = indexKey;
530    }
531
532    public Boolean getForeignKey() {
533        return foreignKey;
534    }
535
536    public void setForeignKey(Boolean foreignKey) {
537        this.foreignKey = foreignKey;
538    }
539
540    public String getDataType() {
541        return dataType;
542    }
543
544    public void setDataType(String dataType) {
545        this.dataType = dataType;
546    }
547    
548    @Override
549    public String toString() {
550        return this.name;
551    }
552
553        public void setColumnIndex(int columnIndex) {
554                this.columnIndex = columnIndex;
555        }
556
557        public boolean isStruct() {
558                return isStruct;
559        }
560
561        public void setStruct(boolean isStruct) {
562                this.isStruct = isStruct;
563        }
564
565        public List<Transform> getTransforms() {
566                return transforms;
567        }
568
569        public void setTransform(Transform transform) {
570                if(transform==null) {
571                        return;
572                }
573                if(transforms == null) {
574                        transforms = new ArrayList<Transform>();
575                }
576                if(!transforms.contains(transform)) {
577                        transforms.add(transform);
578                }
579        }
580
581        public List<Object> getCandidateParents() {
582                return candidateParents;
583        }
584
585        public void setCandidateParents(List<Object> candidateParents) {
586                if (this.candidateParents == null) {
587                        this.candidateParents = candidateParents;
588                }
589                else {
590                        this.candidateParents.addAll(candidateParents);
591                }
592        }
593
594        public TableColumn getSourceColumn() {
595                return sourceColumn;
596        }
597
598        public void setSourceColumn(TableColumn sourceColumn) {
599                this.sourceColumn = sourceColumn;
600        }
601}